This is a series of patches that introduce a new field `description` that appear alongside arguments during autocompletion. v1->v2: * return Completion struct instead of string to allow applications to use description however they want Bojan Gabric (4): spec: add description field to arguments complete: include `description` in autocompletion output complete_test: update test cases to return `Completion` type complete_test: add tests for `description` field in autocompletion README.md | 4 ++ complete.go | 30 +++++++++----- complete_test.go | 104 ++++++++++++++++++++++++++++------------------- opt.go | 2 +- spec.go | 4 ++ 5 files changed, 90 insertions(+), 54 deletions(-) -- 2.45.2
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~rjarry/public-inbox/patches/54866/mbox | git am -3Learn more about email & git
This field will be used to store descriptions that will be displayed during the autocompletion process. Signed-off-by: Bojan Gabric <bojan@bojangabric.com> --- spec.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec.go b/spec.go index d4960d1..1e07042 100644 --- a/spec.go +++ b/spec.go @@ -56,6 +56,8 @@ type optSpec struct { kind optKind // argument is required required bool + // option/argument description + description string // argument was seen on the command line seen bool // argument value was seen on the command line (only applies to options) @@ -218,6 +220,8 @@ func (spec *optSpec) parseField(struc reflect.Value, t reflect.StructField) { spec.metavar = metavar } + spec.description = t.Tag.Get("description") + spec.defval = t.Tag.Get("default") switch t.Tag.Get("required") { -- 2.45.2
Update the autocompletion logic to return `Completion` struct: type Completion struct { Value string Description string } This will allow application that uses autocompletion to do what it wants with the description. Implements: https://todo.sr.ht/~rjarry/aerc/271 Signed-off-by: Bojan Gabric <bojan@bojangabric.com> --- Not sure if the function that's passed to the "complete" argument should return `[]Completion`? README.md | 4 ++++ complete.go | 30 +++++++++++++++++++----------- opt.go | 2 +- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index e2f5590..e942094 100644 --- a/README.md +++ b/README.md @@ -237,6 +237,10 @@ be a method with a pointer receiver to the struct itself, takes a single `string` argument and may return an `error` to abort parsing. The `action` method is responsible of updating the struct. +### `description:"foobaz"` + +A description that appears alongside arguments during autocompletion. + ### `default:"foobaz"` Default `string` value if not specified by the user. Will be processed by the diff --git a/complete.go b/complete.go index f003e88..578ca97 100644 --- a/complete.go +++ b/complete.go @@ -5,8 +5,13 @@ import ( "strings" ) -func (c *CmdSpec) unseenFlags(arg string) []string { - var flags []string +type Completion struct { + Value string + Description string +} + +func (c *CmdSpec) unseenFlags(arg string) []Completion { + var flags []Completion for i := 0; i < len(c.opts); i++ { spec := &c.opts[i] if !spec.appliesToAlias(c.name) || spec.seen { @@ -15,10 +20,10 @@ func (c *CmdSpec) unseenFlags(arg string) []string { switch spec.kind { case flag, option: if spec.short != "" && strings.HasPrefix(spec.short, arg) { - flags = append(flags, spec.short+" ") + flags = append(flags, Completion{Value: spec.short + " ", Description: spec.description}) } if spec.long != "" && strings.HasPrefix(spec.long, arg) { - flags = append(flags, spec.long+" ") + flags = append(flags, Completion{Value: spec.long + " ", Description: spec.description}) } } } @@ -40,25 +45,29 @@ func (c *CmdSpec) nextPositional() *optSpec { return spec } -func (s *optSpec) getCompletions(arg string) []string { +func (s *optSpec) getCompletions(arg string) []Completion { if s.complete.IsValid() { in := []reflect.Value{reflect.ValueOf(arg)} out := s.complete.Call(in) if res, ok := out[0].Interface().([]string); ok { - return res + var completions []Completion + for _, value := range res { + completions = append(completions, Completion{Value: value}) + } + return completions } } return nil } -func (c *CmdSpec) GetCompletions(args *Args) ([]string, string) { +func (c *CmdSpec) GetCompletions(args *Args) ([]Completion, string) { if args.Count() == 0 || (args.Count() == 1 && args.TrailingSpace() == "") { return nil, "" } - var completions []string + var completions []Completion var prefix string - var flags []string + var flags []Completion var last *seenArg var spec *optSpec @@ -94,8 +103,7 @@ func (c *CmdSpec) GetCompletions(args *Args) ([]string, string) { switch { case (s.kind == flag || s.kind == option) && (s.short == arg || s.long == arg): // Current argument is precisely a flag. - spec = nil - completions = []string{arg + " "} + completions = []Completion{{Value: arg + " ", Description: s.description}} case s.kind == option && f != "=" && strings.HasPrefix(arg, f): // Current argument is a long flag in the format: // --flag=value diff --git a/opt.go b/opt.go index 610c0ba..3d9cd7e 100644 --- a/opt.go +++ b/opt.go @@ -148,7 +148,7 @@ func ArgsToStruct(args *Args, v any) error { return nil } -func GetCompletions(cmdline string, v any) (completions []string, prefix string) { +func GetCompletions(cmdline string, v any) (completions []Completion, prefix string) { args := LexArgs(cmdline) if args.Count() == 0 { return nil, "" -- 2.45.2
Update test cases to ensure that the expected completions are of type `Completion`. Signed-off-by: Bojan Gabric <bojan@bojangabric.com> --- complete_test.go | 86 +++++++++++++++++++++++++++--------------------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/complete_test.go b/complete_test.go index a210efc..d2e58f9 100644 --- a/complete_test.go +++ b/complete_test.go @@ -47,93 +47,105 @@ func (c *CompleteStruct) CompleteTag(arg string) []string { func TestComplete(t *testing.T) { vectors := []struct { cmdline string - completions []string + completions []opt.Completion prefix string }{ { "foo --delay 33..33.3 -n", - []string{"-n "}, + []opt.Completion{{Value: "-n "}}, "foo --delay 33..33.3 ", }, { "foo --delay 33..33.3 -n ", - []string{"leonardo", "michelangelo", "rafaelo", "donatello"}, + []opt.Completion{ + {Value: "leonardo"}, + {Value: "michelangelo"}, + {Value: "rafaelo"}, + {Value: "donatello"}, + }, "foo --delay 33..33.3 -n ", }, { "foo --delay 33..33.3 -n don", - []string{"donatello"}, + []opt.Completion{{Value: "donatello"}}, "foo --delay 33..33.3 -n ", }, { "foo --delay 33..33.3 --name=", - []string{"leonardo", "michelangelo", "rafaelo", "donatello"}, + []opt.Completion{ + {Value: "leonardo"}, + {Value: "michelangelo"}, + {Value: "rafaelo"}, + {Value: "donatello"}, + }, "foo --delay 33..33.3 --name=", }, { "foo --delay 33..33.3 --name=leo", - []string{"leonardo"}, + []opt.Completion{{Value: "leonardo"}}, "foo --delay 33..33.3 --name=", }, { "foo --nam", - []string{ - "--name ", - }, + []opt.Completion{{Value: "--name "}}, "foo ", }, { "foo --delay 33..33.3 --backoff", - []string{ - "--backoff ", - }, + []opt.Completion{{Value: "--backoff "}}, "foo --delay 33..33.3 ", }, { "foo --delay 33..33.3 -", - []string{ - "-unread", - "-sent", - "-important", - "-inbox", - "-trash", - "-n ", - "--name ", - "-z ", - "-B ", - "--backoff ", + []opt.Completion{ + {Value: "-unread"}, + {Value: "-sent"}, + {Value: "-important"}, + {Value: "-inbox"}, + {Value: "-trash"}, + {Value: "-n "}, + {Value: "--name "}, + {Value: "-z "}, + {Value: "-B "}, + {Value: "--backoff "}, }, "foo --delay 33..33.3 ", }, { "foo --delay 33..33.3 ", - []string{ - "unread", - "sent", - "important", - "inbox", - "trash", - "-n ", - "--name ", - "-z ", - "-B ", - "--backoff ", + []opt.Completion{ + {Value: "unread"}, + {Value: "sent"}, + {Value: "important"}, + {Value: "inbox"}, + {Value: "trash"}, + {Value: "-n "}, + {Value: "--name "}, + {Value: "-z "}, + {Value: "-B "}, + {Value: "--backoff "}, }, "foo --delay 33..33.3 ", }, { "foo --delay 33..33.3 -n leonardo i", - []string{"important", "inbox"}, + []opt.Completion{{Value: "important"}, {Value: "inbox"}}, "foo --delay 33..33.3 -n leonardo ", }, { "foo +", - []string{"+unread", "+sent", "+important", "+inbox", "+trash"}, + []opt.Completion{ + {Value: "+unread"}, + {Value: "+sent"}, + {Value: "+important"}, + {Value: "+inbox"}, + {Value: "+trash"}, + }, "foo ", }, { "foo -i", - []string{"-important", "-inbox"}, + []opt.Completion{{Value: "-important"}, {Value: "-inbox"}}, "foo ", }, } -- 2.45.2
Add test cases to verify that the `description` field is correctly integrated into the autocompletion output. Signed-off-by: Bojan Gabric <bojan@bojangabric.com> --- complete_test.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/complete_test.go b/complete_test.go index d2e58f9..3349e9b 100644 --- a/complete_test.go +++ b/complete_test.go @@ -9,11 +9,12 @@ import ( ) type CompleteStruct struct { - Name string `opt:"-n,--name" required:"true" complete:"CompleteName"` - Delay float64 `opt:"--delay"` - Zero bool `opt:"-z"` - Backoff bool `opt:"-B,--backoff"` - Tags []string `opt:"..." complete:"CompleteTag"` + Name string `opt:"-n,--name" required:"true" complete:"CompleteName"` + Delay float64 `opt:"--delay"` + Zero bool `opt:"-z"` + Backoff bool `opt:"-B,--backoff"` + Description string `opt:"-d" description:"Argument description"` + Tags []string `opt:"..." complete:"CompleteTag"` } func (c *CompleteStruct) CompleteName(arg string) []string { @@ -108,6 +109,7 @@ func TestComplete(t *testing.T) { {Value: "-z "}, {Value: "-B "}, {Value: "--backoff "}, + {Value: "-d ", Description: "Argument description"}, }, "foo --delay 33..33.3 ", }, @@ -124,6 +126,7 @@ func TestComplete(t *testing.T) { {Value: "-z "}, {Value: "-B "}, {Value: "--backoff "}, + {Value: "-d ", Description: "Argument description"}, }, "foo --delay 33..33.3 ", }, @@ -148,6 +151,11 @@ func TestComplete(t *testing.T) { []opt.Completion{{Value: "-important"}, {Value: "-inbox"}}, "foo ", }, + { + "foo --delay 33..33.3 -d", + []opt.Completion{{Value: "-d ", Description: "Argument description"}}, + "foo --delay 33..33.3 ", + }, } for _, v := range vectors { -- 2.45.2