]> git.christiangoeschel.com Git - repoman-cli.git/commitdiff
Restructuring of module directory trees and source code refactoring
authorchristiangoeschel <cndjomouo@icloud.com>
Tue, 1 Oct 2024 20:16:55 +0000 (16:16 -0400)
committerchristiangoeschel <cndjomouo@icloud.com>
Tue, 1 Oct 2024 20:16:55 +0000 (16:16 -0400)
15 files changed:
cli/main [deleted file]
cli/main.go [deleted file]
go.mod
go.sum
lib/go.mod [new file with mode: 0644]
lib/progaminfo.go [new file with mode: 0644]
main [new file with mode: 0755]
main.go [new file with mode: 0644]
old-main.go [new file with mode: 0644]
screens/apistatus.go [new file with mode: 0644]
screens/go.mod [new file with mode: 0644]
screens/go.sum [new file with mode: 0644]
screens/root.go [new file with mode: 0644]
themes/default-theme.go [new file with mode: 0644]
themes/go.mod [new file with mode: 0644]

diff --git a/cli/main b/cli/main
deleted file mode 100755 (executable)
index ccf0d0b..0000000
Binary files a/cli/main and /dev/null differ
diff --git a/cli/main.go b/cli/main.go
deleted file mode 100644 (file)
index 8469ae3..0000000
+++ /dev/null
@@ -1,299 +0,0 @@
-package main
-
-// An example demonstrating an application with multiple views.
-//
-// Note that this example was produced before the Bubbles progress component
-// was available (github.com/charmbracelet/bubbles/progress) and thus, we're
-// implementing a progress bar from scratch here.
-
-import (
-       "fmt"
-  "net/http"
-  "io/ioutil"
-       // "time"
-  "encoding/json"
-       tea "github.com/charmbracelet/bubbletea"
-       "github.com/charmbracelet/lipgloss"
-)
-
-const (
-       dotChar           = " • "
-  arrowRightChar    = " → "
-  apiUrl            = "https://repoman.christiangoeschel.com:8443"
-)
-
-// General stuff for styling the view
-var (
-  spinnerStyle  = lipgloss.NewStyle().Foreground(lipgloss.Color("69"))
-       keywordStyle  = lipgloss.NewStyle().Foreground(lipgloss.Color("211"))
-       subtleStyle   = lipgloss.NewStyle().Foreground(lipgloss.Color("241"))
-       checkboxStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#03A9F4"))
-  titleStyle    = lipgloss.NewStyle().Background(lipgloss.Color("#448AFF")).Foreground(lipgloss.Color("#FFFFFF"))
-  statusOK      = lipgloss.NewStyle().Foreground(lipgloss.Color("200"))
-       dotStyle      = lipgloss.NewStyle().Foreground(lipgloss.Color("236")).Render(dotChar)
-       mainStyle     = lipgloss.NewStyle().Margin(10)
-
-  mainMenuOptions  = []string{ 
-      "List all repos",
-      "Create a new repository",
-      "Delete a repository",
-      "Manage API Keys",
-      "Check API Status",
-      "Settings",
-      "Quit",
-  }
-
-
-)
-
-func main() {
-  initialModel := model{0, false, false, APIStatusMsg{} }
-       
-  p := tea.NewProgram(initialModel, tea.WithAltScreen())
-       if _, err := p.Run(); err != nil {
-               fmt.Println("could not start program:", err)
-       }
-
-}
-
-type (
-       frameMsg struct{}
-)
-
-
-type model struct {
-       Choice        int
-       Chosen        bool
-       Quitting      bool
-  APIStatus     APIStatusMsg
-}
-
-func (m model) Init() tea.Cmd {
-               
-  return nil
-}
-
-// Main update function.
-func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
-       
-  // Make sure these keys always quit
-       if msg, ok := msg.(tea.KeyMsg); ok {
-               key := msg.String()
-
-               if key == "q" || key == "esc" || key == "ctrl+c" { 
-      m.Quitting = true 
-      return m, tea.Quit
-               } else if key == "backspace" {
-      m.Chosen = false 
-    }
-       }
-
-       // Hand off the message and model to the appropriate update function for the
-       // appropriate view based on the current state.
-       if !m.Chosen {
-               return updateMainMenu(msg, m)
-       }
-
-       return updateChosen(msg, m)
-}
-
-// The main view, which just calls the appropriate sub-view
-func (m model) View() string {
-       var s string
-       if m.Quitting {
-               return "\n  See you soon my friend! \n\n"
-       }
-       if !m.Chosen {
-               s = mainMenuView(m)
-       } else {
-               s = chosenView(m)
-       }
-
-       return mainStyle.Render("\n" + s + "\n\n")
-}
-
-// Sub-update functions
-
-// Update loop for the first view where you're choosing a task.
-func updateMainMenu(msg tea.Msg, m model) (tea.Model, tea.Cmd) {
-       switch msg := msg.(type) {
-       case tea.KeyMsg:
-    var lastChoice int = len(mainMenuOptions) - 1
-
-               switch msg.String() {
-
-    case "j", "down":
-                       m.Choice++
-                       if m.Choice > lastChoice {
-                               m.Choice = 0
-                       }
-               case "k", "up":
-                       m.Choice--
-                       if m.Choice < 0 {
-                               m.Choice = lastChoice 
-                       }
-               case "enter", " ":
-                       m.Chosen = true
-      
-      if mainMenuOptions[m.Choice] == "Quit" {
-        return m, tea.Quit
-      }
-      
-                       return m, nil
-               }
-
-}
-
-       return m, nil
-}
-
-
-// Update loop for the second view after a choice has been made
-func updateChosen(msg tea.Msg, m model) (tea.Model, tea.Cmd) {
-       
-  switch msg := msg.(type) {
-
-  case tea.KeyMsg:
-    switch msg.String() {
-    case "enter":
-      if m.Choice == 4 {
-        m.APIStatus = APIStatusMsg{}
-        return m, handleAPIStatusRequest(apiUrl)
-      } 
-    }
-
-  case APIStatusMsg:
-    m.APIStatus = msg
-    
-    return m, nil
-  }
-  
-       return m, nil
-}
-
-// Sub-views
-
-// The first view, where you're choosing a task
-func mainMenuView(m model) string {
-       c := m.Choice
-
-       tpl := fmt.Sprintf("%s\n\n", titleStyle.Render("[  +-+  Repoman CLI  +-+  ]"))
-  tpl += "Main Menu\n\n"
-       tpl += "%s\n\n"
-       tpl += fmt.Sprintf("%s\n%s\n%s\n%s\n", subtleStyle.Render("j/k, up/down: select"), subtleStyle.Render("enter, spacebar: choose"),
-  subtleStyle.Render("q, esc: quit"), subtleStyle.Render("backspace: main menu"))
-  
-  var choices string
-  for i := 0;i < len(mainMenuOptions);i++{
-      choices += fmt.Sprintf("%s\n", checkbox(mainMenuOptions[i], c == i))
-  }
-
-       return fmt.Sprintf(tpl, choices)
-}
-
-// The second view, after a task has been chosen
-func chosenView(m model) string {
-       var msg string 
-
-       switch m.Choice {
-  
-  case 0:
-               msg = fmt.Sprintf("%s\n\nCool, we'll need %s and %s...", 
-          titleStyle.Render("[  +-+  " + mainMenuOptions[m.Choice] + "  +-+  ]"),
-          keywordStyle.Render("libgarden"), 
-          keywordStyle.Render("vegeutils"))
-       
-  case 1:
-               msg = fmt.Sprintf("%s\n\nOkay, then we should install %s and %s...", 
-          titleStyle.Render("[  +-+  " + mainMenuOptions[m.Choice] + "  +-+  ]"),
-          keywordStyle.Render("marketkit"), 
-          keywordStyle.Render("libshopping"))
-       
-  case 2:
-               msg = fmt.Sprintf("%s\n\nOkay, cool, then we’ll need a library. Yes, an %s.", 
-          titleStyle.Render("[  +-+  " + mainMenuOptions[m.Choice] + "  +-+  ]"),
-          keywordStyle.Render("actual library"))
-  
-  case 3:
-               msg = fmt.Sprintf("%s\n\nOkay, cool, then we’ll need a library. Yes, an %s.", 
-          titleStyle.Render("[  +-+  " + mainMenuOptions[m.Choice] + "  +-+  ]"),
-          keywordStyle.Render("actual library"))
-  
-  case 4:
-
-    statusResponse := fmt.Sprintf("Status:%s\nDescription:%s\nStart:%s\nExpected End:%s\n",
-                      keywordStyle.Render(m.APIStatus.Status), 
-                      keywordStyle.Render(m.APIStatus.Description), 
-                      keywordStyle.Render(m.APIStatus.StartDateTime), 
-                      keywordStyle.Render(m.APIStatus.EstimatedEndTime))
-
-    msg = fmt.Sprintf("%s\n\nRequesting API Server Status from:\n%s\n\nResponse:\n\n%v\n\n%s", 
-          titleStyle.Render("[  +-+  " + mainMenuOptions[m.Choice] + "  +-+  ]"),
-          keywordStyle.Render(apiUrl), 
-          statusResponse,
-          subtleStyle.Render("Press Enter to refresh"))
-  
-  case 5:
-               msg = fmt.Sprintf("%s\n\nOkay, cool, then we’ll need a library. Yes, an %s.", 
-          titleStyle.Render("[  +-+  " + mainMenuOptions[m.Choice] + "  +-+  ]"), 
-          keywordStyle.Render("actual library"))
-       
-  default:
-               msg = fmt.Sprintf("Seems like you are lost.\n\nI'll help you find your way back ... simply press %s", 
-          keywordStyle.Render("backspace"))
-       }
-
-
-       return msg + "\n\n"  
-}
-
-func checkbox(label string, checked bool) string {
-  if checked {
-               return checkboxStyle.Render("+  " + label)
-       }
-       return fmt.Sprintf("- %s", label)
-}
-
-// Utils
-
-// API Status Handle
-func handleAPIStatusRequest(url string) tea.Cmd {
-
-  return func() tea.Msg {
-    url := fmt.Sprintf("%s/status", url)
-    req, err := http.NewRequest("GET", url, nil)
-    if err != nil {
-        fmt.Print(err.Error())
-    }
-
-    res, err := http.DefaultClient.Do(req)
-    if err != nil {
-        
-    }
-    defer res.Body.Close()
-    
-    body, readErr := ioutil.ReadAll(res.Body)
-    if readErr != nil {
-        fmt.Print(err.Error())
-    }
-
-    var apiStatus APIStatusMsg
-    if err := json.Unmarshal(body, &apiStatus); err != nil {  // Parse []byte to the go struct pointer
-        fmt.Println("Can not unmarshal JSON")
-    }
-
-    
-    return apiStatus
-  }
-}
-
-type APIStatus struct {
-       Status           string `json:"Status"`
-       Description      string `json:"Description"`
-       StartDateTime    string `json:"StartDateTime"`
-       EstimatedEndTime string `json:"EstimatedEndTime"`
-}
-
-type APIStatusMsg APIStatus
-
diff --git a/go.mod b/go.mod
index afe36cdaf5cf61a579f6cf9b5e2539e402ecf3d0..d1ae8287f27f5dc56692ff2d239063fc46dcad25 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -3,9 +3,9 @@ module git.christiangoeschel.com/repoman-cli
 go 1.23.0
 
 require (
-       github.com/charmbracelet/bubbles v0.20.0
        github.com/charmbracelet/bubbletea v1.1.1
        github.com/charmbracelet/lipgloss v0.13.0
+       github.com/repoman-cli/screens v0.0.0-00010101000000-000000000000
 )
 
 require (
@@ -16,12 +16,20 @@ require (
        github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
        github.com/mattn/go-isatty v0.0.20 // indirect
        github.com/mattn/go-localereader v0.0.1 // indirect
-       github.com/mattn/go-runewidth v0.0.16 // indirect
+       github.com/mattn/go-runewidth v0.0.15 // indirect
        github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
        github.com/muesli/cancelreader v0.2.2 // indirect
        github.com/muesli/termenv v0.15.2 // indirect
+       github.com/repoman-cli/lib v0.0.0-00010101000000-000000000000 // indirect
+       github.com/repoman-cli/themes v0.0.0-00010101000000-000000000000 // indirect
        github.com/rivo/uniseg v0.4.7 // indirect
        golang.org/x/sync v0.8.0 // indirect
        golang.org/x/sys v0.24.0 // indirect
        golang.org/x/text v0.3.8 // indirect
 )
+
+replace github.com/repoman-cli/lib => /home/cgoesche/repoman-cli/lib
+
+replace github.com/repoman-cli/themes => /home/cgoesche/repoman-cli/themes
+
+replace github.com/repoman-cli/screens => /home/cgoesche/repoman-cli/screens
diff --git a/go.sum b/go.sum
index daac31727c039b56b30ef870018e80b0d7459c73..161a3de608ca6bfaf81fb57bf17f4c258071be19 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,5 @@
 github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
 github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
-github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
-github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
 github.com/charmbracelet/bubbletea v1.1.1 h1:KJ2/DnmpfqFtDNVTvYZ6zpPFL9iRCRr0qqKOCvppbPY=
 github.com/charmbracelet/bubbletea v1.1.1/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4=
 github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
@@ -18,8 +16,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
 github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
-github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
-github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
+github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
 github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
diff --git a/lib/go.mod b/lib/go.mod
new file mode 100644 (file)
index 0000000..b3a80d6
--- /dev/null
@@ -0,0 +1,3 @@
+module git.christiangoeschel.com/repoman-cli/lib
+
+go 1.23.0
diff --git a/lib/progaminfo.go b/lib/progaminfo.go
new file mode 100644 (file)
index 0000000..cb61824
--- /dev/null
@@ -0,0 +1,38 @@
+package lib
+
+type ProgramInfo struct {
+  ProgramName       string  
+  Description       string 
+  Version           string  
+  BuildID           string 
+  CreationDate      string
+  LastModification  string 
+  Maintainers       []string
+  License           string  
+}
+
+func GetProgramInfo() *ProgramInfo{
+  maintainers   := []string{
+    "Christian Goeschel Ndjomouo (cgoesc2@wgu.edu)",
+  }
+
+  pInfo := ProgramInfo {
+    ProgramName:      "Repoman Client",
+    Description:      "A simplistic terminal user interface to manage your self-hosted Repoman Git repository", 
+    Version:          "0.1.0 alpha",  
+    BuildID:          "a89d2e1",
+    CreationDate:     "2024-09-30", 
+    Maintainers:      maintainers,
+    License:          "Copyright 2024 Christian Goeschel - MIT License", 
+  }
+
+  return &pInfo
+}
+
+
+
+  
+
+
+
diff --git a/main b/main
new file mode 100755 (executable)
index 0000000..d69066c
Binary files /dev/null and b/main differ
diff --git a/main.go b/main.go
new file mode 100644 (file)
index 0000000..a3e0e58
--- /dev/null
+++ b/main.go
@@ -0,0 +1,29 @@
+package main
+
+import (
+  "fmt"
+  "os"
+  "github.com/repoman-cli/screens"
+  
+  tea "github.com/charmbracelet/bubbletea"
+)
+
+// Main 
+func main(){
+  
+  p := tea.NewProgram(screens.RootScreen(0), tea.WithAltScreen())
+  if _, err := p.Run(); err != nil {
+        fmt.Println("Error starting program:", err)
+        os.Exit(1)
+  }
+}
+
+
+
+
+
+
+
+
+
+
diff --git a/old-main.go b/old-main.go
new file mode 100644 (file)
index 0000000..892f6c1
--- /dev/null
@@ -0,0 +1,288 @@
+package main
+
+
+import (
+       "fmt"
+  "net/http"
+  "io/ioutil"
+  "encoding/json"
+       tea "github.com/charmbracelet/bubbletea"
+       "github.com/charmbracelet/lipgloss"
+)
+
+const (
+       dotChar           = " • "
+  arrowRightChar    = " → "
+  apiUrl            = "https://repoman.christiangoeschel.com:8443"
+)
+
+var (
+  spinnerStyle  = lipgloss.NewStyle().Foreground(lipgloss.Color("69"))
+       keywordStyle  = lipgloss.NewStyle().Foreground(lipgloss.Color("211"))
+       subtleStyle   = lipgloss.NewStyle().Foreground(lipgloss.Color("241"))
+       checkboxStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#03A9F4"))
+  titleStyle    = lipgloss.NewStyle().Background(lipgloss.Color("#448AFF")).Foreground(lipgloss.Color("#FFFFFF"))
+  statusOK      = lipgloss.NewStyle().Foreground(lipgloss.Color("200"))
+       dotStyle      = lipgloss.NewStyle().Foreground(lipgloss.Color("236")).Render(dotChar)
+       mainStyle     = lipgloss.NewStyle().Margin(15).Padding(10)
+  Altstyle      = lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).BorderForeground(lipgloss.Color("63"))
+
+
+  mainMenuOptions  = []string{ 
+      "List all repos",
+      "Create a new repository",
+      "Delete a repository",
+      "Manage API Keys",
+      "Check API Status",
+      "Settings",
+      "Quit",
+  }
+
+)
+
+func main() {
+
+  initialModel := model{0, false, false, false, APIStatusMsg{} }
+       
+  p := tea.NewProgram(initialModel, tea.WithAltScreen())
+       if _, err := p.Run(); err != nil {
+               fmt.Println("could not start program:", err)
+       }
+
+}
+
+type model struct {
+       Choice        int
+       Chosen        bool
+  Loaded        bool
+       Quitting      bool
+  APIStatus     APIStatusMsg
+}
+
+func (m model) Init() tea.Cmd {
+  return nil
+}
+
+// Main update function.
+func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+       
+  // Make sure these keys always quit
+       if msg, ok := msg.(tea.KeyMsg); ok {
+               key := msg.String()
+
+               if key == "q" || key == "esc" || key == "ctrl+c" { 
+      m.Quitting = true 
+      return m, tea.Quit
+               } else if key == "backspace" {
+      m.Chosen = false 
+    }
+       }
+
+       // Hand off the message and model to the appropriate update function for the
+       // appropriate view based on the current state.
+       if !m.Chosen {
+               return updateMainMenu(msg, m)
+       }
+
+       return updateChosen(msg, m)
+}
+
+// The main view, which just calls the appropriate sub-view
+func (m model) View() string {
+       var s string
+       if m.Quitting {
+               return "\n  See you soon my friend! \n\n"
+       }
+       if !m.Chosen {
+               s = mainMenuView(m)
+       } else {
+               s = chosenView(m)
+       }
+
+       return mainStyle.Render("\n" + s + "\n\n")
+}
+
+// Sub-update functions
+
+// Update loop for the first view where you're choosing a task.
+func updateMainMenu(msg tea.Msg, m model) (tea.Model, tea.Cmd) {
+       switch msg := msg.(type) {
+       case tea.KeyMsg:
+    var lastChoice int = len(mainMenuOptions) - 1
+
+               switch msg.String() {
+
+      case "j", "down":
+                       m.Choice++
+                       if m.Choice > lastChoice {
+                               m.Choice = 0
+                       }
+               case "k", "up":
+                       m.Choice--
+                       if m.Choice < 0 {
+                               m.Choice = lastChoice 
+                       }
+               case "enter", " ":
+                       m.Chosen = true
+      
+      if mainMenuOptions[m.Choice] == "Quit" {
+        return m, tea.Quit
+      }
+      
+                       return m, nil
+               }
+
+    }
+
+       return m, nil
+}
+
+
+// Update loop for the second view after a choice has been made
+func updateChosen(msg tea.Msg, m model) (tea.Model, tea.Cmd) {
+       
+  switch msg := msg.(type) {
+
+  case tea.KeyMsg:
+    switch msg.String() {
+    case "enter":
+      if m.Choice == 4 {
+        m.APIStatus = APIStatusMsg{}
+        return m, handleAPIStatusRequest(apiUrl)
+      } 
+    }
+
+  case APIStatusMsg:
+    m.APIStatus = msg
+    return m, nil
+
+
+  }
+  
+       return m, nil
+}
+
+// Sub-views
+
+// The first view, where you're choosing a task
+func mainMenuView(m model) string {
+       c := m.Choice
+
+       tpl := fmt.Sprintf("%s\n\n", titleStyle.Render("[  +-+  Repoman CLI  +-+  ]"))
+  tpl += "Main Menu\n\n"
+  tpl += "%s\n\n"
+       tpl += fmt.Sprintf("%s\n%s\n%s\n%s\n", subtleStyle.Render("j/k, up/down: select"), subtleStyle.Render("enter, spacebar: choose"),
+  subtleStyle.Render("q, esc: quit"), subtleStyle.Render("backspace: main menu"))
+  
+  var opts string
+  for i := 0;i < len(mainMenuOptions);i++{
+      opts += fmt.Sprintf("%s\n", checkbox(mainMenuOptions[i], c == i))
+  }
+
+       return fmt.Sprintf(tpl, opts) 
+}
+
+// The second view, after a task has been chosen
+func chosenView(m model) string {
+       var msg string 
+
+       switch m.Choice {
+  
+  case 0:
+               msg = fmt.Sprintf("%s\n\nCool, we'll need %s and %s...", 
+          titleStyle.Render("[  +-+  " + mainMenuOptions[m.Choice] + "  +-+  ]"),
+          keywordStyle.Render("libgarden"), 
+          keywordStyle.Render("vegeutils"))
+       
+  case 1:
+               msg = fmt.Sprintf("%s\n\nOkay, then we should install %s and %s...", 
+          titleStyle.Render("[  +-+  " + mainMenuOptions[m.Choice] + "  +-+  ]"),
+          keywordStyle.Render("marketkit"), 
+          keywordStyle.Render("libshopping"))
+       
+  case 2:
+               msg = fmt.Sprintf("%s\n\nOkay, cool, then we’ll need a library. Yes, an %s.", 
+          titleStyle.Render("[  +-+  " + mainMenuOptions[m.Choice] + "  +-+  ]"),
+          keywordStyle.Render("actual library"))
+  
+  case 3:
+               msg = fmt.Sprintf("%s\n\nOkay, cool, then we’ll need a library. Yes, an %s.", 
+          titleStyle.Render("[  +-+  " + mainMenuOptions[m.Choice] + "  +-+  ]"),
+          keywordStyle.Render("actual library"))
+  
+  case 4:
+
+    statusResponse := fmt.Sprintf("Status:%s\nDescription:%s\nStart:%s\nExpected End:%s\n",
+                      keywordStyle.Render(m.APIStatus.Status), 
+                      keywordStyle.Render(m.APIStatus.Description), 
+                      keywordStyle.Render(m.APIStatus.StartDateTime), 
+                      keywordStyle.Render(m.APIStatus.EstimatedEndTime))
+
+    msg = fmt.Sprintf("%s\n\nRequesting API Server Status from:\n%s\n\nResponse:\n\n%v\n\n%s", 
+          titleStyle.Render("[  +-+  " + mainMenuOptions[m.Choice] + "  +-+  ]"),
+          keywordStyle.Render(apiUrl), 
+          statusResponse,
+          subtleStyle.Render("Press Enter to refresh")) 
+  
+  case 5:
+               msg = fmt.Sprintf("%s\n\nOkay, cool, then we’ll need a library. Yes, an %s.", 
+          titleStyle.Render("[  +-+  " + mainMenuOptions[m.Choice] + "  +-+  ]"), 
+          keywordStyle.Render("actual library"))
+       
+  default:
+               msg = fmt.Sprintf("Seems like you are lost.\n\nI'll help you find your way back ... simply press %s", 
+          keywordStyle.Render("backspace"))
+       }
+
+  return msg + "\n\n"  
+}
+
+func checkbox(label string, checked bool) string {
+  if checked {
+               return checkboxStyle.Render("+  " + label)
+       }
+       return fmt.Sprintf("- %s", label) 
+}
+
+// Utils
+
+// API Status Handle
+func handleAPIStatusRequest(url string) tea.Cmd {
+
+  return func() tea.Msg {
+    url := fmt.Sprintf("%s/status", url)
+    req, err := http.NewRequest("GET", url, nil)
+    if err != nil {
+        fmt.Print(err.Error())
+    }
+
+    res, err := http.DefaultClient.Do(req)
+    if err != nil {
+        
+    }
+    defer res.Body.Close()
+    
+    body, readErr := ioutil.ReadAll(res.Body)
+    if readErr != nil {
+        fmt.Print(err.Error())
+    }
+
+    var apiStatus APIStatusMsg
+    if err := json.Unmarshal(body, &apiStatus); err != nil {  // Parse []byte to the go struct pointer
+        fmt.Println("Can not unmarshal JSON")
+    }
+
+    
+    return apiStatus
+  }
+}
+
+type APIStatus struct {
+       Status           string `json:"Status"`
+       Description      string `json:"Description"`
+       StartDateTime    string `json:"StartDateTime"`
+       EstimatedEndTime string `json:"EstimatedEndTime"`
+}
+
+type APIStatusMsg APIStatus
+
diff --git a/screens/apistatus.go b/screens/apistatus.go
new file mode 100644 (file)
index 0000000..388c5fa
--- /dev/null
@@ -0,0 +1,62 @@
+package screens
+
+
+import (
+  //"fmt"
+  tea "github.com/charmbracelet/bubbletea"
+  "github.com/repoman-cli/themes"
+)
+
+
+type APIStatusScreenModel struct {
+  APIStatus     string
+  URL           string
+  Err           error
+  StatusCode    int
+}
+
+
+// --- AltScreenModel functions
+
+// Creates a new mainMenuScreenModel
+func NewAPIStatusScreen() APIStatusScreenModel {
+  return APIStatusScreenModel {
+    APIStatus:    "Normal",
+    URL:          "https://repoman.christiangoeschel.com:8443/status",
+    Err:          nil,
+    StatusCode:   200,
+  }
+}
+
+// Init
+func (m *APIStatusScreenModel) Init() tea.Cmd {
+  return nil
+}
+
+// Update 
+func (m *APIStatusScreenModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+   
+  switch msg := msg.(type) {
+  case tea.KeyMsg:
+    switch msg.String() {
+      case "q":
+        mainMenu := MainMenu()
+        return RootScreen(0).SwitchScreen(&mainMenu)
+        
+    } 
+
+  default: 
+    return m, nil 
+  }
+
+  return m, nil 
+}
+
+// View 
+func (m *APIStatusScreenModel) View() string {
+  msg := themes.KeywordStyle.Render("API Status")
+  
+  return msg + "\n\n"
+}
+
diff --git a/screens/go.mod b/screens/go.mod
new file mode 100644 (file)
index 0000000..62191bb
--- /dev/null
@@ -0,0 +1,30 @@
+module git.christiangoeschel.com/repoman-cli/screens
+
+go 1.23.0
+
+require (
+       github.com/charmbracelet/bubbletea v1.1.1
+       github.com/repoman-cli/lib v0.0.0-00010101000000-000000000000
+)
+
+require (
+       github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
+       github.com/charmbracelet/lipgloss v0.13.0 // indirect
+       github.com/charmbracelet/x/ansi v0.2.3 // indirect
+       github.com/charmbracelet/x/term v0.2.0 // indirect
+       github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
+       github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
+       github.com/mattn/go-isatty v0.0.20 // indirect
+       github.com/mattn/go-localereader v0.0.1 // indirect
+       github.com/mattn/go-runewidth v0.0.15 // indirect
+       github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
+       github.com/muesli/cancelreader v0.2.2 // indirect
+       github.com/muesli/termenv v0.15.2 // indirect
+       github.com/rivo/uniseg v0.4.7 // indirect
+       golang.org/x/sync v0.8.0 // indirect
+       golang.org/x/sys v0.24.0 // indirect
+       golang.org/x/text v0.3.8 // indirect
+)
+
+replace github.com/repoman-cli/themes => /home/cgoesche/repoman-cli/themes
+replace github.com/repoman-cli/lib => /home/cgoesche/repoman-cli/lib
diff --git a/screens/go.sum b/screens/go.sum
new file mode 100644 (file)
index 0000000..161a3de
--- /dev/null
@@ -0,0 +1,37 @@
+github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
+github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
+github.com/charmbracelet/bubbletea v1.1.1 h1:KJ2/DnmpfqFtDNVTvYZ6zpPFL9iRCRr0qqKOCvppbPY=
+github.com/charmbracelet/bubbletea v1.1.1/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4=
+github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
+github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
+github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY=
+github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
+github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
+github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
+github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
+github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
+github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
+github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
+github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
+github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
+github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
+github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
+github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
+github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
+github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
+github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
+golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
+golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
diff --git a/screens/root.go b/screens/root.go
new file mode 100644 (file)
index 0000000..5b7709f
--- /dev/null
@@ -0,0 +1,112 @@
+package screens
+
+import (
+  "fmt"
+  "github.com/repoman-cli/lib"
+  //"github.com/repoman-cli/themes"
+
+  tea "github.com/charmbracelet/bubbletea"
+)
+
+
+  // Root Screen Model
+  type rootScreenModel struct {
+    model   tea.Model   // Contains the current screen model which represents the programs current state
+    theme   int 
+  }
+
+  //Main Menu Screen 
+  type mainMenuScreenModel struct {
+    Title     string
+    Message   string
+  }
+
+
+// Initialize model 
+func (m rootScreenModel) Init() tea.Cmd {
+  return m.model.Init() // Calls current model's Init function
+}
+
+// Update programs state based on received messages
+func (m rootScreenModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+  return m.model.Update(msg) // Calls current model's Update function
+}
+
+// Change the View of current model based on its state
+func (m rootScreenModel) View() string {
+  return m.model.View()
+}
+
+// --- Utils
+
+// Creates a new model based on what RootScreenModel.model Contains
+func RootScreen(theme int) rootScreenModel {
+  var rootModel tea.Model
+
+  // Set the rootModel (current state) to be the mainMenuScreen 
+  mainMenu   :=   MainMenu()
+  rootModel   =    &mainMenu
+
+  return rootScreenModel {
+    model:  rootModel,
+    theme:  theme,
+  }
+}
+
+
+// Screen switcher sets new model and returns 
+func (m rootScreenModel) SwitchScreen(model tea.Model) (tea.Model, tea.Cmd) {
+  m.model = model
+  return m.model, m.model.Init()
+}
+
+
+// --- MainMenuScreenModel functions
+
+// Creates a new mainMenuScreenModel
+func MainMenu() mainMenuScreenModel {
+  return mainMenuScreenModel {
+    Title:    "Repoman CLI",
+    Message:  "Hello my friend",
+  }
+}
+
+// Init
+func (m *mainMenuScreenModel) Init() tea.Cmd {
+  return nil
+}
+
+// Update 
+func (m *mainMenuScreenModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+   
+  switch msg := msg.(type) {
+  case tea.KeyMsg:
+    switch msg.String() {
+      case "q":
+        return m, tea.Quit
+    
+      case "n":
+        apiStatusScreen := NewAPIStatusScreen()
+        return RootScreen(0).SwitchScreen(&apiStatusScreen)
+    }
+  
+
+  default: 
+    return m, nil 
+  }
+
+  return m, nil 
+}
+
+// View 
+func (m *mainMenuScreenModel) View() string {
+  programInfo := lib.GetProgramInfo()
+
+  message := fmt.Sprintf("%s\n\n%s", programInfo.ProgramName, programInfo.BuildID)
+  return message
+}
+
+
diff --git a/themes/default-theme.go b/themes/default-theme.go
new file mode 100644 (file)
index 0000000..9cf35df
--- /dev/null
@@ -0,0 +1,27 @@
+package themes
+
+import (
+
+       "github.com/charmbracelet/lipgloss"
+)
+
+var (
+  spinnerStyle  = lipgloss.NewStyle().Foreground(lipgloss.Color("69"))
+  KeywordStyle  = lipgloss.NewStyle().Foreground(lipgloss.Color("211"))
+  subtleStyle   = lipgloss.NewStyle().Foreground(lipgloss.Color("241"))
+  checkboxStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#03A9F4"))
+  titleStyle    = lipgloss.NewStyle().Background(lipgloss.Color("#448AFF")).Foreground(lipgloss.Color("#FFFFFF"))
+  statusOK      = lipgloss.NewStyle().Foreground(lipgloss.Color("200"))
+  mainStyle     = lipgloss.NewStyle().Margin(15).Padding(10)
+  Altstyle      = lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).BorderForeground(lipgloss.Color("63"))
+)
+
+func ChangeTheme(themeID int) (err error) {
+
+  if themeID == 0 {
+    KeywordStyle  = lipgloss.NewStyle().Foreground(lipgloss.Color("200"))
+    return nil
+  }
+
+  return nil
+}
diff --git a/themes/go.mod b/themes/go.mod
new file mode 100644 (file)
index 0000000..48db9e8
--- /dev/null
@@ -0,0 +1,3 @@
+module git.christiangoeschel.com/repoman-cli/themes
+
+go 1.23.0