From 4538d3cec1b666f1030bff7d13cd84fd813499e5 Mon Sep 17 00:00:00 2001 From: Joshua Herring Date: Mon, 11 May 2026 10:43:10 -0400 Subject: [PATCH] DebugState and ReadForm --- commands/utils.go | 2 +- go.mod | 33 ++++++++- main.go | 169 ++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 187 insertions(+), 17 deletions(-) diff --git a/commands/utils.go b/commands/utils.go index 4bf3a63..698fc7c 100644 --- a/commands/utils.go +++ b/commands/utils.go @@ -33,7 +33,7 @@ func GetSigner(private_key_file string) ssh.Signer { key_bytes, err := os.ReadFile(private_key_file) if err != nil { - fmt.Fprintf(os.Stderr, "Error reading private key: %v\n", err) + fmt.Fprintf(os.Stderr, "Error reading private key from file %s: %v\n", private_key_file, err) os.Exit(1) } diff --git a/go.mod b/go.mod index 12bc7e3..86bdc5a 100644 --- a/go.mod +++ b/go.mod @@ -4,4 +4,35 @@ go 1.26.1 require golang.org/x/crypto v0.51.0 -require golang.org/x/sys v0.44.0 // indirect +require ( + github.com/atotto/clipboard v0.1.4 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/catppuccin/go v0.3.0 // indirect + github.com/charmbracelet/bubbles v1.0.0 // indirect + github.com/charmbracelet/bubbletea v1.3.10 // indirect + github.com/charmbracelet/colorprofile v0.4.1 // indirect + github.com/charmbracelet/huh v1.0.0 // indirect + github.com/charmbracelet/lipgloss v1.1.0 // indirect + github.com/charmbracelet/x/ansi v0.11.6 // indirect + github.com/charmbracelet/x/cellbuf v0.0.15 // indirect + github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect + github.com/charmbracelet/x/term v0.2.2 // indirect + github.com/clipperhouse/displaywidth v0.9.0 // indirect + github.com/clipperhouse/stringish v0.1.1 // indirect + github.com/clipperhouse/uax29/v2 v2.5.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/lucasb-eyer/go-colorful v1.3.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.19 // indirect + github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/termenv v0.16.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/sahilm/fuzzy v0.1.1 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + golang.org/x/sys v0.44.0 // indirect + golang.org/x/text v0.37.0 // indirect +) diff --git a/main.go b/main.go index 8a6e6b0..406eabe 100644 --- a/main.go +++ b/main.go @@ -4,27 +4,166 @@ import ( "fmt" "os" + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/huh" "periodic/prddeploy/commands" ) -func main() { +type appState int - if len(os.Args) < 2 { - fmt.Fprintf(os.Stderr, "Usage: %s [args...]", os.Args[0]) - os.Exit(1) +const ( + stateList appState = iota + stateForm +) + +type item string + +func (i item) FilterValue() string { return string(i) } +func (i item) Title() string { return string(i) } +func (i item) Description() string { return "" } + +type model struct { + list list.Model + form *huh.Form + state appState + choice int + + RemoteCommand string + PrivateKeyFile string + Username string + Host string + LocalFilepath string + DestinationFilepath string +} + +func (m model) DebugState() { + fmt.Printf("REMOTE COMMAND: %s\n", m.RemoteCommand) + fmt.Printf("PRIVATE KEY FILE: %s\n", m.PrivateKeyFile) + fmt.Printf("USERNAME: %s\n", m.Username) + fmt.Printf("HOST: %s\n", m.Host) + fmt.Printf("LOCAL FILEPATH: %s\n", m.LocalFilepath) + fmt.Printf("DESTINATION FILEPATH: %s\n", m.DestinationFilepath) +} + +func (m *model) ReadForm() { + m.RemoteCommand = m.form.GetString("remote_command") + m.PrivateKeyFile = m.form.GetString("private_key_file") + m.Username = m.form.GetString("username") + m.Host = m.form.GetString("host") + m.LocalFilepath = m.form.GetString("local_filepath") + m.DestinationFilepath = m.form.GetString("destination_filepath") +} + +func newModel() model { + items := []list.Item{ + item("Run Command"), + item("Upload File"), + } + l := list.New(items, list.NewDefaultDelegate(), 80, 20) + l.Title = "prddeploy" + return model{list: l, state: stateList} +} + +func (m model) Init() tea.Cmd { + return nil +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch m.state { + case stateList: + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.list.SetSize(msg.Width, msg.Height) + case tea.KeyMsg: + if msg.String() == "enter" { + m.choice = m.list.Index() + m.state = stateForm + if m.choice == 0 { + m.form = huh.NewForm( + huh.NewGroup( + huh.NewInput().Title("Remote Command").Key("remote_command").Value(&m.RemoteCommand), + huh.NewInput().Title("Private Key File").Key("private_key_file").Value(&m.PrivateKeyFile), + huh.NewInput().Title("Username").Key("username").Value(&m.Username), + huh.NewInput().Title("Host").Key("host").Value(&m.Host), + ), + ) + } else { + m.form = huh.NewForm( + huh.NewGroup( + huh.NewInput().Title("Local Filepath").Key("local_filepath").Value(&m.LocalFilepath), + huh.NewInput().Title("Destination Filepath").Key("destination_filepath").Value(&m.DestinationFilepath), + huh.NewInput().Title("Private Key File").Key("private_key_file").Value(&m.PrivateKeyFile), + huh.NewInput().Title("Username").Key("username").Value(&m.Username), + huh.NewInput().Title("Host").Key("host").Value(&m.Host), + ), + ) + } + return m, m.form.Init() + } + } + var cmd tea.Cmd + m.list, cmd = m.list.Update(msg) + return m, cmd + + case stateForm: + form, cmd := m.form.Update(msg) + if f, ok := form.(*huh.Form); ok { + m.form = f + } + if m.form.State == huh.StateCompleted { + m.ReadForm() + return m, tea.Quit + } + if m.form.State == huh.StateAborted { + m.ReadForm() + return m, tea.Quit + } + return m, cmd } - switch os.Args[1] { - case "upload_file": - ufa := commands.UploadFileArgs{} - ufa.ReadArgs() - commands.UploadFile(ufa) - case "run_command": - rca := commands.RunCommandArgs{} - rca.ReadArgs() - commands.RunCommand(rca) - default: - fmt.Fprintf(os.Stderr, "Command not found: %s\n", os.Args[1]) + return m, nil +} + +func (m model) View() string { + switch m.state { + case stateList: + return m.list.View() + case stateForm: + return m.form.View() + } + return "" +} + +func main() { + p := tea.NewProgram(newModel(), tea.WithAltScreen()) + result, err := p.Run() + if err != nil { + fmt.Fprintf(os.Stderr, "Error running program: %v\n", err) os.Exit(1) } + + m, ok := result.(model) + if !ok || m.form == nil || m.form.State != huh.StateCompleted { + return + } + + m.DebugState() + + if m.choice == 0 { + commands.RunCommand(commands.RunCommandArgs{ + RemoteCommand: m.RemoteCommand, + PrivateKeyFile: m.PrivateKeyFile, + Username: m.Username, + Host: m.Host, + }) + } else { + commands.UploadFile(commands.UploadFileArgs{ + LocalFilepath: m.LocalFilepath, + DestinationFilepath: m.DestinationFilepath, + PrivateKeyFile: m.PrivateKeyFile, + Username: m.Username, + Host: m.Host, + }) + } }