From 31c4264e4f8e21ccdaa92e5f7fb2f20cc022a8fa Mon Sep 17 00:00:00 2001 From: Evan Fiordeliso Date: Mon, 13 Nov 2023 10:12:42 -0500 Subject: [PATCH 1/4] Add todo to fish shell --- completions_fish.go | 1 + 1 file changed, 1 insertion(+) diff --git a/completions_fish.go b/completions_fish.go index 8e0228d..fd6ab09 100644 --- a/completions_fish.go +++ b/completions_fish.go @@ -14,6 +14,7 @@ func WriteFishCompletions(out io.Writer, rootCmd *Command) error { }) } +// TODO: Fix fish infinitely completing last command at the end of the command chain var fishTpl = template.Must(template.New("fish").Funcs(tplFuncs).Parse(` {{- $rootCmd := .RootCmd -}} {{- $progName := $rootCmd.Name -}} From 2c26eda5e2f56d6bbfd28a165f51e76fcd57ee33 Mon Sep 17 00:00:00 2001 From: Evan Fiordeliso Date: Mon, 13 Nov 2023 13:20:43 -0500 Subject: [PATCH 2/4] Add bash implementation of autocomplete and add names helper for command and option sets --- command.go | 15 +++++++-------- completions_bash.go | 43 +++++++++++++++++++++++++++++++++++++++---- completions_fish.go | 4 ++-- opt/set.go | 10 ++++++++++ set.go | 9 +++++++++ tpl_funcs.go | 12 +++++++++++- 6 files changed, 78 insertions(+), 15 deletions(-) diff --git a/command.go b/command.go index 5112b00..e82b256 100644 --- a/command.go +++ b/command.go @@ -71,6 +71,13 @@ func (c *Command) Execute(args []string) { if c.isRoot { args = args[1:] } + if len(args) > 0 { + sc, ok := c.Subcommands.Get(args[0]) + if ok { + sc.Execute(args[1:]) + return + } + } parser := opt.NewParser(args, c.Opts, false) restArgs, err := parser.Parse() @@ -80,14 +87,6 @@ func (c *Command) Execute(args []string) { os.Exit(1) } - if len(restArgs) > 0 { - sc, ok := c.Subcommands.Get(restArgs[0]) - if ok { - sc.Execute(restArgs[1:]) - return - } - } - helpOpt, ok := opt.Globals().GetBool("help") if ok && helpOpt.Value() { c.ShowHelp() diff --git a/completions_bash.go b/completions_bash.go index a790090..b402e91 100644 --- a/completions_bash.go +++ b/completions_bash.go @@ -9,11 +9,46 @@ import ( func WriteBashCompletions(out io.Writer, rootCmd *Command) error { return bashTpl.Execute(out, map[string]any{ - "rootCmd": rootCmd, - "globalOpts": opt.Globals(), + "RootCmd": rootCmd, + "GlobalOpts": opt.Globals(), }) } -var bashTpl = template.Must(template.New("bash").Parse(` - +var bashTpl = template.Must(template.New("bash").Funcs(tplFuncs).Parse(` +{{- $rootCmd := .RootCmd -}} +{{- $progName := $rootCmd.Name -}} +{{- $varName := under $rootCmd.Name -}} + +{{- define "cmd" }} + {{ $currIdx := .Index }} + {{ $nextIdx := inc .Index }} + "{{ .Cmd.Name }}") + case ${COMP_WORDS[{{ $currIdx }}]} in + {{ range .Cmd.Subcommands -}} + {{ template "cmd" (map "Cmd" . "Index" $nextIdx) -}} + {{ end }} + *) + COMPREPLY=($(compgen -W "{{ join .Cmd.Subcommands.Names " " }} {{ join .Cmd.Opts.Names " " }}" -- $curr)) + esac + ;; +{{ end -}} + +_{{$varName}}_completions() +{ + local curr prev + COMPREPLY=() + curr=${COMP_WORDS[COMP_CWORD]} + prev=${COMP_WORDS[COMP_CWORD-1]} + + case ${COMP_WORDS[1]} in + {{ range .RootCmd.Subcommands -}} + {{ template "cmd" (map "Cmd" . "Index" 2) -}} + {{ end }} + + *) + COMPREPLY=($(compgen -W "{{ join .RootCmd.Subcommands.Names " " }} {{ join .RootCmd.Opts.Names " " }}" -- $curr)) + esac +} + +complete -F _{{$varName}}_completions {{$progName}} `)) diff --git a/completions_fish.go b/completions_fish.go index fd6ab09..8125f2d 100644 --- a/completions_fish.go +++ b/completions_fish.go @@ -19,7 +19,7 @@ var fishTpl = template.Must(template.New("fish").Funcs(tplFuncs).Parse(` {{- $rootCmd := .RootCmd -}} {{- $progName := $rootCmd.Name -}} {{- $varName := under $rootCmd.Name -}} -set -l commands {{- range $rootCmd.Subcommands }} {{ .Name }}{{ end }} +set -l commands {{ join $rootCmd.Subcommands.Names " " }} function __fish_{{ $varName }}_needs_command set -l cmd (commandline -opc) @@ -75,7 +75,7 @@ complete -f -c {{ $progName }} -n "__fish_{{ $varName }}_using_command {{.Cmd.Pa {{ end -}} {{ if gt (len .Cmd.Subcommands) 0 }} -set -l {{ $varPrefix }}commands {{- range .Cmd.Subcommands }} {{ .Name }}{{ end -}} +set -l {{ $varPrefix }}commands {{ join .Cmd.Subcommands.Names " " }} {{ $cmdName := .Cmd.Name }} {{- range .Cmd.Subcommands }} {{- template "cmd" (map "Cmd" . "ProgName" $progName "VarName" $varName) -}} diff --git a/opt/set.go b/opt/set.go index 7a76aca..8b43aa9 100644 --- a/opt/set.go +++ b/opt/set.go @@ -33,6 +33,16 @@ func (s Set) Get(name string) (Option, bool) { return nil, false } +func (s Set) Names() []string { + names := []string{} + for _, o := range s { + names = append(names, "--"+o.Name()) + names = append(names, "-"+o.ShortName()) + } + return names + +} + func (s Set) GetByLongName(longName string) (Option, bool) { for _, o := range s { if o.Name() == longName { diff --git a/set.go b/set.go index 2ef3976..8e756ea 100644 --- a/set.go +++ b/set.go @@ -35,6 +35,15 @@ func (s Set) MaxNameWidth() int { return max } +func (s Set) Names() []string { + names := make([]string, 0, len(s)) + for _, c := range s { + names = append(names, c.Name) + names = append(names, c.Aliases...) + } + return names +} + func (s Set) Len() int { return len(s) } diff --git a/tpl_funcs.go b/tpl_funcs.go index dd41bfa..a41e10b 100644 --- a/tpl_funcs.go +++ b/tpl_funcs.go @@ -8,9 +8,11 @@ import ( var tplFuncs = template.FuncMap{ "map": tplMap, + "cat": tplCat, "join": tplJoin, "under": tplUnder, "varPrefix": tplVarPrefix, + "inc": tplInc, } func tplMap(vals ...any) (map[string]any, error) { @@ -25,10 +27,14 @@ func tplMap(vals ...any) (map[string]any, error) { return m, nil } -func tplJoin(strs ...string) string { +func tplCat(strs ...string) string { return strings.Join(strs, "") } +func tplJoin(strs []string, sep string) string { + return strings.Join(strs, sep) +} + func tplUnder(s string) string { return strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(s, " ", "_"), "-", "_")) } @@ -40,3 +46,7 @@ func tplVarPrefix(s string) string { return tplUnder(s) + "_" } + +func tplInc(i int) int { + return i + 1 +} From 71cddc5b20c21fde4ace52e089916cac4066f308 Mon Sep 17 00:00:00 2001 From: Evan Fiordeliso Date: Mon, 13 Nov 2023 13:42:06 -0500 Subject: [PATCH 3/4] Fix spacing in bash implementation --- completions_bash.go | 49 ++++++++++++++++++++++++--------------------- tpl_funcs.go | 34 +++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 25 deletions(-) diff --git a/completions_bash.go b/completions_bash.go index b402e91..4cc2d75 100644 --- a/completions_bash.go +++ b/completions_bash.go @@ -14,40 +14,43 @@ func WriteBashCompletions(out io.Writer, rootCmd *Command) error { }) } +// TODO: Add --option|--other-option...) to return nothing for options that require a value var bashTpl = template.Must(template.New("bash").Funcs(tplFuncs).Parse(` {{- $rootCmd := .RootCmd -}} {{- $progName := $rootCmd.Name -}} {{- $varName := under $rootCmd.Name -}} {{- define "cmd" }} - {{ $currIdx := .Index }} - {{ $nextIdx := inc .Index }} - "{{ .Cmd.Name }}") - case ${COMP_WORDS[{{ $currIdx }}]} in - {{ range .Cmd.Subcommands -}} - {{ template "cmd" (map "Cmd" . "Index" $nextIdx) -}} - {{ end }} - *) - COMPREPLY=($(compgen -W "{{ join .Cmd.Subcommands.Names " " }} {{ join .Cmd.Opts.Names " " }}" -- $curr)) - esac - ;; -{{ end -}} +{{ $currIdx := .Index -}} +{{- $nextIdx := inc .Index -}} +{{- $indentSize := mult $currIdx 2 -}} +{{ indent $indentSize }}"{{ .Cmd.Name }}") +{{ indent $indentSize }} case ${COMP_WORDS[{{ $currIdx }}]} in +{{ indent $indentSize }} {{ range .Cmd.Subcommands -}} +{{ indent $indentSize }} {{ template "cmd" (map "Cmd" . "Index" $nextIdx) -}} +{{ indent $indentSize }} {{ end }} +{{ indent $indentSize }} *) +{{ indent $indentSize }} COMPREPLY=($(compgen -W "{{ join .Cmd.Subcommands.Names " " }} {{ join .Cmd.Opts.Names " " }}" -- $curr)) +{{ indent $indentSize }} ;; +{{ indent $indentSize }} esac +{{ indent $indentSize }};; # {{ .Cmd.Name }} +{{- end -}} _{{$varName}}_completions() { - local curr prev - COMPREPLY=() - curr=${COMP_WORDS[COMP_CWORD]} - prev=${COMP_WORDS[COMP_CWORD-1]} + COMPREPLY=() - case ${COMP_WORDS[1]} in - {{ range .RootCmd.Subcommands -}} - {{ template "cmd" (map "Cmd" . "Index" 2) -}} - {{ end }} + case ${COMP_WORDS[1]} in + {{- range .RootCmd.Subcommands -}} + {{ template "cmd" (map "Cmd" . "Index" 2) -}} + {{ end }} + *) + COMPREPLY=($(compgen -W "{{ join .RootCmd.Subcommands.Names " " }} {{ join .RootCmd.Opts.Names " " }}" -- $curr)) + ;; + esac - *) - COMPREPLY=($(compgen -W "{{ join .RootCmd.Subcommands.Names " " }} {{ join .RootCmd.Opts.Names " " }}" -- $curr)) - esac + # Global Options + +=($(compgen -W "{{ join .GlobalOpts.Names " " }}" -- $curr)) } complete -F _{{$varName}}_completions {{$progName}} diff --git a/tpl_funcs.go b/tpl_funcs.go index a41e10b..834f3a4 100644 --- a/tpl_funcs.go +++ b/tpl_funcs.go @@ -12,7 +12,13 @@ var tplFuncs = template.FuncMap{ "join": tplJoin, "under": tplUnder, "varPrefix": tplVarPrefix, + "repeat": tplRepeat, + "indent": tplIndent, + "add": tplAdd, "inc": tplInc, + "sub": tplSub, + "dec": tplDec, + "mult": tplMult, } func tplMap(vals ...any) (map[string]any, error) { @@ -47,6 +53,30 @@ func tplVarPrefix(s string) string { return tplUnder(s) + "_" } -func tplInc(i int) int { - return i + 1 +func tplRepeat(s string, n int) string { + return strings.Repeat(s, n) +} + +func tplIndent(n int) string { + return tplRepeat(" ", n) +} + +func tplAdd(a, b int) int { + return a + b +} + +func tplInc(i int) int { + return tplAdd(i, 1) +} + +func tplSub(a, b int) int { + return a - b +} + +func tplDec(i int) int { + return tplSub(i, 1) +} + +func tplMult(a, b int) int { + return a * b } From 5979ea58a23415cb1abca72dc610726b0c5d9dcc Mon Sep 17 00:00:00 2001 From: Evan Fiordeliso Date: Mon, 13 Nov 2023 13:42:25 -0500 Subject: [PATCH 4/4] Fix missing var in bash impl --- completions_bash.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/completions_bash.go b/completions_bash.go index 4cc2d75..71a11c0 100644 --- a/completions_bash.go +++ b/completions_bash.go @@ -50,7 +50,7 @@ _{{$varName}}_completions() esac # Global Options - +=($(compgen -W "{{ join .GlobalOpts.Names " " }}" -- $curr)) + COMPREPLY+=($(compgen -W "{{ join .GlobalOpts.Names " " }}" -- $curr)) } complete -F _{{$varName}}_completions {{$progName}}