From b336a5c9e19adba31bec1da51e093a11e09a8ead Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Tue, 19 Sep 2023 22:51:49 +0200 Subject: commands: pass raw command line down to template evaluation Some commands need to invoke others and/or run shell commands. For this, we need the raw command line as entered by the user. Pass it down the call chain just before it is split to invoke the command Execute method. Remove unit tests for the template expand() test which does have any added value now that it is performed on a single string without any quote juggling. Update all code to handle a single string instead of a list of arguments. Introduce a new dependency on git.sr.ht/~rjarry/go-opt to deal with shell splitting. This is in preparation for using opt.ArgsToStruct to parse arguments for all aerc commands. There should be no functional change after this patch. Signed-off-by: Robin Jarry Reviewed-by: Koni Marti Tested-by: Moritz Poldrack Tested-by: Inwit --- commands/choose.go | 3 +- commands/commands.go | 58 ++++++++--------------- commands/commands_test.go | 114 ---------------------------------------------- commands/prompt.go | 6 ++- 4 files changed, 23 insertions(+), 158 deletions(-) delete mode 100644 commands/commands_test.go (limited to 'commands') diff --git a/commands/choose.go b/commands/choose.go index 3f1410cf..6810ed1f 100644 --- a/commands/choose.go +++ b/commands/choose.go @@ -2,7 +2,6 @@ package commands import ( "fmt" - "strings" "git.sr.ht/~rjarry/aerc/app" ) @@ -34,7 +33,7 @@ func (Choose) Execute(args []string) error { choices = append(choices, app.Choice{ Key: args[i+2], Text: args[i+3], - Command: strings.Split(args[i+4], " "), + Command: args[i+4], }) } diff --git a/commands/commands.go b/commands/commands.go index 73550a35..0ca8dc36 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -7,6 +7,7 @@ import ( "strings" "unicode" + "git.sr.ht/~rjarry/go-opt" "github.com/google/shlex" "git.sr.ht/~rjarry/aerc/app" @@ -111,68 +112,45 @@ func templateData( } func (cmds *Commands) ExecuteCommand( - origArgs []string, + cmdline string, account *config.AccountConfig, msg *models.MessageInfo, ) error { - if len(origArgs) == 0 { - return errors.New("Expected a command.") - } data := templateData(account, msg) - args, err := expand(data, origArgs) + cmdline, err := expand(data, cmdline) if err != nil { return err } - if len(args) == 0 { + args := opt.LexArgs(cmdline) + name, err := args.ArgSafe(0) + if err != nil { return errors.New("Expected a command after template evaluation.") } - if cmd, ok := cmds.dict()[args[0]]; ok { - log.Tracef("executing command %v", args) - return cmd.Execute(args) + if cmd, ok := cmds.dict()[name]; ok { + log.Tracef("executing command %s", args.String()) + return cmd.Execute(args.Args()) } - return NoSuchCommand(args[0]) + return NoSuchCommand(name) } -// expand expands template expressions and returns a new slice of arguments -func expand(data models.TemplateData, origArgs []string) ([]string, error) { - args := make([]string, len(origArgs)) - copy(args, origArgs) - - c := strings.Join(origArgs, "") - isTemplate := strings.Contains(c, "{{") || strings.Contains(c, "}}") - - if isTemplate { - for i := range args { - if strings.Contains(args[i], " ") { - q := "\"" - if strings.ContainsAny(args[i], "\"") { - q = "'" - } - args[i] = q + args[i] + q - } - } - - cmdline := strings.Join(args, " ") - log.Tracef("template data found in: %v", cmdline) - - t, err := templates.ParseTemplate("execute", cmdline) +// expand expands template expressions +func expand(data models.TemplateData, s string) (string, error) { + if strings.Contains(s, "{{") && strings.Contains(s, "}}") { + t, err := templates.ParseTemplate("execute", s) if err != nil { - return nil, err + return "", err } var buf bytes.Buffer err = templates.Render(t, &buf, data) if err != nil { - return nil, err + return "", err } - args, err = splitCmd(buf.String()) - if err != nil { - return nil, err - } + s = buf.String() } - return args, nil + return s, nil } func GetTemplateCompletion( diff --git a/commands/commands_test.go b/commands/commands_test.go deleted file mode 100644 index 7edd0228..00000000 --- a/commands/commands_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package commands - -import ( - "reflect" - "testing" - "time" - - "git.sr.ht/~rjarry/aerc/models" - "github.com/emersion/go-message/mail" -) - -func TestExecuteCommand_expand(t *testing.T) { - tests := []struct { - args []string - want []string - }{ - { - args: []string{"prompt", "Really quit? ", "quit"}, - want: []string{"prompt", "Really quit? ", "quit"}, - }, - { - args: []string{"{{", "print", "\"hello\"", "}}"}, - want: []string{"hello"}, - }, - { - args: []string{"prompt", "Really quit ? ", " quit "}, - want: []string{"prompt", "Really quit ? ", " quit "}, - }, - { - args: []string{ - "prompt", "Really quit? ", "{{", - "print", "\"quit\"", "}}", - }, - want: []string{"prompt", "Really quit? ", "quit"}, - }, - { - args: []string{ - "prompt", "Really quit? ", "{{", - "if", "1", "}}", "quit", "{{end}}", - }, - want: []string{"prompt", "Really quit? ", "quit"}, - }, - } - - var data dummyData - - for i, test := range tests { - got, err := expand(&data, test.args) - if err != nil { - t.Errorf("test %d failed with err: %v", i, err) - } else if !reflect.DeepEqual(got, test.want) { - t.Errorf("test %d failed: "+ - "got: %v, but want: %v", i, got, test.want) - } - } -} - -// only for validation -type dummyData struct{} - -var ( - addr1 = mail.Address{Name: "John Foo", Address: "foo@bar.org"} - addr2 = mail.Address{Name: "John Bar", Address: "bar@foo.org"} -) - -func (d *dummyData) Account() string { return "work" } -func (d *dummyData) Folder() string { return "INBOX" } -func (d *dummyData) To() []*mail.Address { return []*mail.Address{&addr1} } -func (d *dummyData) Cc() []*mail.Address { return nil } -func (d *dummyData) Bcc() []*mail.Address { return nil } -func (d *dummyData) From() []*mail.Address { return []*mail.Address{&addr2} } -func (d *dummyData) Peer() []*mail.Address { return d.From() } -func (d *dummyData) ReplyTo() []*mail.Address { return nil } -func (d *dummyData) Date() time.Time { return time.Now() } -func (d *dummyData) DateAutoFormat(time.Time) string { return "" } -func (d *dummyData) Header(string) string { return "" } -func (d *dummyData) ThreadPrefix() string { return "└─>" } -func (d *dummyData) ThreadCount() int { return 0 } -func (d *dummyData) ThreadFolded() bool { return false } -func (d *dummyData) ThreadContext() bool { return false } -func (d *dummyData) Subject() string { return "Re: [PATCH] hey" } -func (d *dummyData) SubjectBase() string { return "[PATCH] hey" } -func (d *dummyData) Number() int { return 0 } -func (d *dummyData) Labels() []string { return nil } -func (d *dummyData) Flags() []string { return nil } -func (d *dummyData) IsReplied() bool { return true } -func (d *dummyData) HasAttachment() bool { return true } -func (d *dummyData) Attach(string) string { return "" } -func (d *dummyData) IsRecent() bool { return false } -func (d *dummyData) IsUnread() bool { return false } -func (d *dummyData) IsFlagged() bool { return false } -func (d *dummyData) IsMarked() bool { return false } -func (d *dummyData) MessageId() string { return "123456789@foo.org" } -func (d *dummyData) Size() int { return 420 } -func (d *dummyData) OriginalText() string { return "Blah blah blah" } -func (d *dummyData) OriginalDate() time.Time { return time.Now() } -func (d *dummyData) OriginalFrom() []*mail.Address { return d.From() } -func (d *dummyData) OriginalMIMEType() string { return "text/plain" } -func (d *dummyData) OriginalHeader(string) string { return "" } -func (d *dummyData) Recent(...string) int { return 1 } -func (d *dummyData) Unread(...string) int { return 3 } -func (d *dummyData) Exists(...string) int { return 14 } -func (d *dummyData) RUE(...string) string { return "1/3/14" } -func (d *dummyData) Connected() bool { return false } -func (d *dummyData) ConnectionInfo() string { return "" } -func (d *dummyData) ContentInfo() string { return "" } -func (d *dummyData) StatusInfo() string { return "" } -func (d *dummyData) TrayInfo() string { return "" } -func (d *dummyData) PendingKeys() string { return "" } -func (d *dummyData) Role() string { return "inbox" } -func (d *dummyData) Style(string, string) string { return "" } -func (d *dummyData) StyleSwitch(string, ...models.Case) string { return "" } - -func (d *dummyData) StyleMap([]string, ...models.Case) []string { return []string{} } diff --git a/commands/prompt.go b/commands/prompt.go index f9f5fcc0..0d10ffa0 100644 --- a/commands/prompt.go +++ b/commands/prompt.go @@ -4,6 +4,8 @@ import ( "fmt" "strings" + "git.sr.ht/~rjarry/go-opt" + "git.sr.ht/~rjarry/aerc/app" ) @@ -75,7 +77,7 @@ func (Prompt) Execute(args []string) error { } prompt := args[1] - cmd := args[2:] - app.RegisterPrompt(prompt, cmd) + cmd := opt.QuoteArgs(args[2:]...) + app.RegisterPrompt(prompt, cmd.String()) return nil } -- cgit