diff --git a/bash_completions.go b/bash_completions.go index 54933b0..fd8060f 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -123,6 +123,10 @@ __handle_reply() fi COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") ) + if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then + COMPREPLY=( $(compgen -W "${noun_aliases[*]}" -- "$cur") ) + fi + if [[ ${#COMPREPLY[@]} -eq 0 ]]; then declare -F __custom_func >/dev/null && __custom_func fi @@ -189,7 +193,7 @@ __handle_noun() if __contains_word "${words[c]}" "${must_have_one_noun[@]}"; then must_have_one_noun=() - elif __contains_word "${words[c]%s}" "${must_have_one_noun[@]}"; then + elif __contains_word "${words[c]}" "${noun_aliases[@]}"; then must_have_one_noun=() fi @@ -460,7 +464,7 @@ func writeRequiredFlag(cmd *Command, w io.Writer) error { return visitErr } -func writeRequiredNoun(cmd *Command, w io.Writer) error { +func writeRequiredNouns(cmd *Command, w io.Writer) error { if _, err := fmt.Fprintf(w, " must_have_one_noun=()\n"); err != nil { return err } @@ -473,6 +477,19 @@ func writeRequiredNoun(cmd *Command, w io.Writer) error { return nil } +func writeArgAliases(cmd *Command, w io.Writer) error { + if _, err := fmt.Fprintf(w, " noun_aliases=()\n"); err != nil { + return err + } + sort.Sort(sort.StringSlice(cmd.ArgAliases)) + for _, value := range cmd.ArgAliases { + if _, err := fmt.Fprintf(w, " noun_aliases+=(%q)\n", value); err != nil { + return err + } + } + return nil +} + func gen(cmd *Command, w io.Writer) error { for _, c := range cmd.Commands() { if !c.IsAvailableCommand() || c == cmd.helpCommand { @@ -500,7 +517,10 @@ func gen(cmd *Command, w io.Writer) error { if err := writeRequiredFlag(cmd, w); err != nil { return err } - if err := writeRequiredNoun(cmd, w); err != nil { + if err := writeRequiredNouns(cmd, w); err != nil { + return err + } + if err := writeArgAliases(cmd, w); err != nil { return err } if _, err := fmt.Fprintf(w, "}\n\n"); err != nil { diff --git a/bash_completions.md b/bash_completions.md index 691b1b7..84d5b01 100644 --- a/bash_completions.md +++ b/bash_completions.md @@ -80,7 +80,7 @@ The `BashCompletionFunction` option is really only valid/useful on the root comm In the above example "pod" was assumed to already be typed. But if you want `kubectl get [tab][tab]` to show a list of valid "nouns" you have to set them. Simplified code from `kubectl get` looks like: ```go -validArgs []string = { "pods", "nodes", "services", "replicationControllers" } +validArgs []string = { "pod", "node", "service", "replicationcontroller" } cmd := &cobra.Command{ Use: "get [(-o|--output=)json|yaml|template|...] (RESOURCE [NAME] | RESOURCE/NAME ...)", @@ -99,9 +99,34 @@ Notice we put the "ValidArgs" on the "get" subcommand. Doing so will give result ```bash # kubectl get [tab][tab] -nodes pods replicationControllers services +node pod replicationcontroller service ``` +## Plural form and shortcuts for nouns + +If your nouns have a number of aliases, you can define them alongside `ValidArgs` using `ArgAliases`: + +```go` +argAliases []string = { "pods", "nodes", "services", "svc", "replicationcontrollers", "rc" } + +cmd := &cobra.Command{ + ... + ValidArgs: validArgs, + ArgAliases: argAliases +} +``` + +The aliases are not shown to the user on tab completion, but they are accepted as valid nouns by +the completion aglorithm if entered manually, e.g. in: + +```bash +# kubectl get rc [tab][tab] +backend frontend database +``` + +Note that without declaring `rc` as an alias, the completion algorithm would show the list of nouns +in this example again instead of the replication controllers. + ## Mark flags as required Most of the time completions will only show subcommands. But if a flag is required to make a subcommand work, you probably want it to show up when the user types [tab][tab]. Marking a flag as 'Required' is incredibly easy. diff --git a/bash_completions_test.go b/bash_completions_test.go index cf2661c..fd0958c 100644 --- a/bash_completions_test.go +++ b/bash_completions_test.go @@ -43,9 +43,13 @@ func TestBashCompletions(t *testing.T) { c.MarkFlagRequired("introot") // valid nouns - validArgs := []string{"pods", "nodes", "services", "replicationControllers"} + validArgs := []string{"pod", "node", "service", "replicationcontroller"} c.ValidArgs = validArgs + // noun aliases + argAliases := []string{"pods", "nodes", "services", "replicationcontrollers", "po", "no", "svc", "rc"} + c.ArgAliases = argAliases + // filename var flagval string c.Flags().StringVar(&flagval, "filename", "", "Enter a filename") @@ -88,7 +92,11 @@ func TestBashCompletions(t *testing.T) { // check for custom completion function check(t, str, `COMPREPLY=( "hello" )`) // check for required nouns - check(t, str, `must_have_one_noun+=("pods")`) + check(t, str, `must_have_one_noun+=("pod")`) + // check for noun aliases + check(t, str, `noun_aliases+=("pods")`) + check(t, str, `noun_aliases+=("rc")`) + checkOmit(t, str, `must_have_one_noun+=("pods")`) // check for filename extension flags check(t, str, `flags_completion+=("_filedir")`) // check for filename extension flags diff --git a/command.go b/command.go index 7671ce5..3be395e 100644 --- a/command.go +++ b/command.go @@ -45,8 +45,11 @@ type Command struct { Long string // Examples of how to use the command Example string - // List of all valid non-flag arguments, used for bash completions *TODO* actually validate these + // List of all valid non-flag arguments that are accepted in bash completions ValidArgs []string + // List of aliases for ValidArgs. These are not suggested to the user in the bash + // completion, but accepted if entered manually. + ArgAliases []string // Custom functions used by the bash autocompletion generator BashCompletionFunction string // Is this command deprecated and should print this string when used?