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..71a11c0 100644 --- a/completions_bash.go +++ b/completions_bash.go @@ -9,11 +9,49 @@ 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(` - +// 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 -}} +{{- $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() +{ + COMPREPLY=() + + 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 + + # Global Options + COMPREPLY+=($(compgen -W "{{ join .GlobalOpts.Names " " }}" -- $curr)) +} + +complete -F _{{$varName}}_completions {{$progName}} `)) diff --git a/completions_fish.go b/completions_fish.go index 8e0228d..8125f2d 100644 --- a/completions_fish.go +++ b/completions_fish.go @@ -14,11 +14,12 @@ 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 -}} {{- $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) @@ -74,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..834f3a4 100644 --- a/tpl_funcs.go +++ b/tpl_funcs.go @@ -8,9 +8,17 @@ import ( var tplFuncs = template.FuncMap{ "map": tplMap, + "cat": tplCat, "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) { @@ -25,10 +33,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 +52,31 @@ func tplVarPrefix(s string) string { return tplUnder(s) + "_" } + +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 +}