diff --git a/cmd/slog-view/main.go b/cmd/slog-view/main.go index 94d4ddb..c94acc3 100644 --- a/cmd/slog-view/main.go +++ b/cmd/slog-view/main.go @@ -33,7 +33,7 @@ func main() { ctrl := controllers.NewLogFileController(service, flag.Arg(0)) cmdController := commandctrl.NewCommandController() - //cmdController.AddCommands(&commandctrl.CommandContext{ + //cmdController.AddCommands(&commandctrl.CommandList{ // Commands: map[string]commandctrl.Command{ // "cd": func(args []string) tea.Cmd { // return ctrl.ChangePrefix(args[0]) diff --git a/cmd/ssm-browse/main.go b/cmd/ssm-browse/main.go index c5e46c9..e1dab01 100644 --- a/cmd/ssm-browse/main.go +++ b/cmd/ssm-browse/main.go @@ -48,7 +48,7 @@ func main() { ctrl := controllers.New(service) cmdController := commandctrl.NewCommandController() - cmdController.AddCommands(&commandctrl.CommandContext{ + cmdController.AddCommands(&commandctrl.CommandList{ Commands: map[string]commandctrl.Command{ "cd": func(args []string) tea.Msg { return ctrl.ChangePrefix(args[0]) diff --git a/internal/common/ui/commandctrl/commandctrl.go b/internal/common/ui/commandctrl/commandctrl.go index f26ab6c..65cc0ae 100644 --- a/internal/common/ui/commandctrl/commandctrl.go +++ b/internal/common/ui/commandctrl/commandctrl.go @@ -1,9 +1,13 @@ package commandctrl import ( + "bufio" + "bytes" tea "github.com/charmbracelet/bubbletea" "github.com/pkg/errors" "log" + "os" + "path/filepath" "strings" "github.com/lmika/audax/internal/common/ui/events" @@ -11,7 +15,7 @@ import ( ) type CommandController struct { - commandList *CommandContext + commandList *CommandList } func NewCommandController() *CommandController { @@ -20,7 +24,7 @@ func NewCommandController() *CommandController { } } -func (c *CommandController) AddCommands(ctx *CommandContext) { +func (c *CommandController) AddCommands(ctx *CommandList) { ctx.parent = c.commandList c.commandList = ctx } @@ -35,6 +39,10 @@ func (c *CommandController) Prompt() tea.Msg { } func (c *CommandController) Execute(commandInput string) tea.Msg { + return c.execute(ExecContext{FromFile: false}, commandInput) +} + +func (c *CommandController) execute(ctx ExecContext, commandInput string) tea.Msg { input := strings.TrimSpace(commandInput) if input == "" { return nil @@ -43,31 +51,65 @@ func (c *CommandController) Execute(commandInput string) tea.Msg { tokens := shellwords.Split(input) command := c.lookupCommand(tokens[0]) if command == nil { - log.Println("No such command: ", tokens) return events.Error(errors.New("no such command: " + tokens[0])) } - return command(tokens[1:]) + return command(ctx, tokens[1:]) } func (c *CommandController) Alias(commandName string) Command { - return func(args []string) tea.Msg { + return func(ctx ExecContext, args []string) tea.Msg { command := c.lookupCommand(commandName) if command == nil { - log.Println("No such command: ", commandName) return events.Error(errors.New("no such command: " + commandName)) } - return command(args) + return command(ctx, args) } } func (c *CommandController) lookupCommand(name string) Command { for ctx := c.commandList; ctx != nil; ctx = ctx.parent { - log.Printf("Looking in command list: %v", c.commandList) if cmd, ok := ctx.Commands[name]; ok { return cmd } } return nil } + +func (c *CommandController) ExecuteFile(filename string) error { + baseFilename := filepath.Base(filename) + + if rcFile, err := os.ReadFile(filename); err == nil { + if err := c.executeFile(rcFile, baseFilename); err != nil { + return errors.Wrapf(err, "error executing %v", filename) + } + } else { + return errors.Wrapf(err, "error loading %v", filename) + } + return nil +} + +func (c *CommandController) executeFile(file []byte, filename string) error { + scnr := bufio.NewScanner(bytes.NewReader(file)) + + lineNo := 0 + for scnr.Scan() { + lineNo++ + line := strings.TrimSpace(scnr.Text()) + if line == "" { + continue + } else if line[0] == '#' { + continue + } + + msg := c.execute(ExecContext{FromFile: true}, line) + switch m := msg.(type) { + case events.ErrorMsg: + log.Printf("%v:%v: error - %v", filename, lineNo, m.Error()) + case events.StatusMsg: + log.Printf("%v:%v: %v", filename, lineNo, string(m)) + } + } + return scnr.Err() +} diff --git a/internal/common/ui/commandctrl/context.go b/internal/common/ui/commandctrl/context.go index 5417c97..1ba126a 100644 --- a/internal/common/ui/commandctrl/context.go +++ b/internal/common/ui/commandctrl/context.go @@ -1,16 +1,6 @@ package commandctrl -import "context" - -type commandArgContextKeyType struct{} - -var commandArgContextKey = commandArgContextKeyType{} - -func WithCommandArgs(ctx context.Context, args []string) context.Context { - return context.WithValue(ctx, commandArgContextKey, args) -} - -func CommandArgs(ctx context.Context) []string { - args, _ := ctx.Value(commandArgContextKey).([]string) - return args +type ExecContext struct { + // FromFile is true if the command is executed as part of a command + FromFile bool } diff --git a/internal/common/ui/commandctrl/types.go b/internal/common/ui/commandctrl/types.go index c5f6f74..79c4828 100644 --- a/internal/common/ui/commandctrl/types.go +++ b/internal/common/ui/commandctrl/types.go @@ -2,16 +2,16 @@ package commandctrl import tea "github.com/charmbracelet/bubbletea" -type Command func(args []string) tea.Msg +type Command func(ctx ExecContext, args []string) tea.Msg func NoArgCommand(cmd tea.Cmd) Command { - return func(args []string) tea.Msg { + return func(ctx ExecContext, args []string) tea.Msg { return cmd() } } -type CommandContext struct { +type CommandList struct { Commands map[string]Command - parent *CommandContext + parent *CommandList } diff --git a/internal/dynamo-browse/controllers/keybinding.go b/internal/dynamo-browse/controllers/keybinding.go index 79aa578..6d1ec8e 100644 --- a/internal/dynamo-browse/controllers/keybinding.go +++ b/internal/dynamo-browse/controllers/keybinding.go @@ -16,10 +16,12 @@ func NewKeyBindingController(service *keybindings.Service) *KeyBindingController return &KeyBindingController{service: service} } -func (kb *KeyBindingController) Rebind(bindingName string, newKey string) tea.Msg { - err := kb.service.Rebind(bindingName, newKey, false) +func (kb *KeyBindingController) Rebind(bindingName string, newKey string, force bool) tea.Msg { + err := kb.service.Rebind(bindingName, newKey, force) if err == nil { return events.SetStatus(fmt.Sprintf("Binding '%v' now bound to '%v'", bindingName, newKey)) + } else if force { + return events.Error(errors.Wrapf(err, "cannot bind '%v' to '%v'", bindingName, newKey)) } var keyAlreadyBoundErr keybindings.KeyAlreadyBoundError diff --git a/internal/dynamo-browse/ui/model.go b/internal/dynamo-browse/ui/model.go index ac056ee..9f03df1 100644 --- a/internal/dynamo-browse/ui/model.go +++ b/internal/dynamo-browse/ui/model.go @@ -20,6 +20,7 @@ import ( "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/utils" "github.com/pkg/errors" "log" + "os" "strings" ) @@ -31,6 +32,8 @@ const ( ViewModeTableOnly = 4 ViewModeCount = 5 + + initRCFilename = "$HOME/.config/audax/dynamo-browse/init.rc" ) type Model struct { @@ -69,17 +72,17 @@ func NewModel( dialogPrompt := dialogprompt.New(statusAndPrompt) tableSelect := tableselect.New(dialogPrompt, uiStyles) - cc.AddCommands(&commandctrl.CommandContext{ + cc.AddCommands(&commandctrl.CommandList{ Commands: map[string]commandctrl.Command{ "quit": commandctrl.NoArgCommand(tea.Quit), - "table": func(args []string) tea.Msg { + "table": func(ctx commandctrl.ExecContext, args []string) tea.Msg { if len(args) == 0 { return rc.ListTables() } else { return rc.ScanTable(args[0]) } }, - "export": func(args []string) tea.Msg { + "export": func(ctx commandctrl.ExecContext, args []string) tea.Msg { if len(args) == 0 { return events.Error(errors.New("expected filename")) } @@ -90,7 +93,7 @@ func NewModel( // TEMP "new-item": commandctrl.NoArgCommand(wc.NewItem), - "set-attr": func(args []string) tea.Msg { + "set-attr": func(ctx commandctrl.ExecContext, args []string) tea.Msg { if len(args) == 0 { return events.Error(errors.New("expected field")) } @@ -114,28 +117,35 @@ func NewModel( return wc.SetAttributeValue(dtv.SelectedItemIndex(), itemType, args[0]) }, - "del-attr": func(args []string) tea.Msg { + "del-attr": func(ctx commandctrl.ExecContext, args []string) tea.Msg { if len(args) == 0 { return events.Error(errors.New("expected field")) } return wc.DeleteAttribute(dtv.SelectedItemIndex(), args[0]) }, - "put": func(args []string) tea.Msg { + "put": func(ctx commandctrl.ExecContext, args []string) tea.Msg { return wc.PutItems() }, - "touch": func(args []string) tea.Msg { + "touch": func(ctx commandctrl.ExecContext, args []string) tea.Msg { return wc.TouchItem(dtv.SelectedItemIndex()) }, - "noisy-touch": func(args []string) tea.Msg { + "noisy-touch": func(ctx commandctrl.ExecContext, args []string) tea.Msg { return wc.NoisyTouchItem(dtv.SelectedItemIndex()) }, - "rebind": func(args []string) tea.Msg { - if len(args) != 2 { - return events.Error(errors.New("expected: name newKey")) + "echo": func(ctx commandctrl.ExecContext, args []string) tea.Msg { + s := new(strings.Builder) + for _, arg := range args { + s.WriteString(arg) } - return keyBindingController.Rebind(args[0], args[1]) + return events.SetStatus(s.String()) + }, + "rebind": func(ctx commandctrl.ExecContext, args []string) tea.Msg { + if len(args) != 2 { + return events.Error(errors.New("expected: bindingName newKey")) + } + return keyBindingController.Rebind(args[0], args[1], ctx.FromFile) }, // Aliases @@ -164,6 +174,12 @@ func NewModel( } func (m Model) Init() tea.Cmd { + // TODO: this should probably be moved somewhere else + rcFilename := os.ExpandEnv(initRCFilename) + if err := m.commandController.ExecuteFile(rcFilename); err != nil { + log.Println(err) + } + return m.tableReadController.Init } diff --git a/internal/ssm-browse/ui/model.go b/internal/ssm-browse/ui/model.go index 985b694..2feeaf5 100644 --- a/internal/ssm-browse/ui/model.go +++ b/internal/ssm-browse/ui/model.go @@ -30,7 +30,7 @@ func NewModel(controller *controllers.SSMController, cmdController *commandctrl. statusAndPrompt := statusandprompt.New( layout.NewVBox(layout.LastChildFixedAt(17), ssmList, ssmdDetails), "", defaultStyles.StatusAndPrompt) - cmdController.AddCommands(&commandctrl.CommandContext{ + cmdController.AddCommands(&commandctrl.CommandList{ Commands: map[string]commandctrl.Command{ "clone": func(args []string) tea.Msg { if currentParam := ssmList.CurrentParameter(); currentParam != nil {