diff --git a/bash_completions.go b/bash_completions.go index eea11ee5..60336f3c 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -12,6 +12,7 @@ import ( const ( BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extentions" + BashCompCustom = "cobra_annotation_bash_completion_custom" BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag" BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir" ) @@ -34,7 +35,7 @@ __debug() __my_init_completion() { COMPREPLY=() - _get_comp_words_by_ref cur prev words cword + _get_comp_words_by_ref "$@" cur prev words cword } __index_of_word() @@ -76,6 +77,25 @@ __handle_reply() if [[ $(type -t compopt) = "builtin" ]]; then [[ $COMPREPLY == *= ]] || compopt +o nospace fi + + # complete after --flag=abc + if [[ $cur == *=* ]]; then + if [[ $(type -t compopt) = "builtin" ]]; then + compopt +o nospace + fi + + local index flag + flag="${cur%%=*}" + __index_of_word "${flag}" "${flags_with_completion[@]}" + if [[ ${index} -ge 0 ]]; then + COMPREPLY=() PREFIX="" cur="${cur#*=}" + ${flags_completion[${index}]} + if [ -n "${ZSH_VERSION}" ]; then + # zfs completion needs --flag= prefix + eval COMPREPLY=( "\${COMPREPLY[@]/#/${flag}=}" ) + fi + fi + fi return 0; ;; esac @@ -169,6 +189,8 @@ __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 + must_have_one_noun=() fi nouns+=("${words[c]}") @@ -229,7 +251,7 @@ func postscript(w io.Writer, name string) error { if declare -F _init_completion >/dev/null 2>&1; then _init_completion -s || return else - __my_init_completion || return + __my_init_completion -n "=" || return fi local c=0 @@ -299,6 +321,20 @@ func writeFlagHandler(name string, annotations map[string][]string, w io.Writer) if err != nil { return err } + case BashCompCustom: + _, err := fmt.Fprintf(w, " flags_with_completion+=(%q)\n", name) + if err != nil { + return err + } + if len(value) > 0 { + handlers := strings.Join(value, "; ") + _, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", handlers) + } else { + _, err = fmt.Fprintf(w, " flags_completion+=(:)\n") + } + if err != nil { + return err + } case BashCompSubdirsInDir: _, err := fmt.Fprintf(w, " flags_with_completion+=(%q)\n", name) @@ -519,6 +555,12 @@ func (cmd *Command) MarkFlagFilename(name string, extensions ...string) error { return MarkFlagFilename(cmd.Flags(), name, extensions...) } +// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists. +// Generated bash autocompletion will call the bash function f for the flag. +func (cmd *Command) MarkFlagCustom(name string, f string) error { + return MarkFlagCustom(cmd.Flags(), name, f) +} + // MarkPersistentFlagFilename adds the BashCompFilenameExt annotation to the named persistent flag, if it exists. // Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided. func (cmd *Command) MarkPersistentFlagFilename(name string, extensions ...string) error { @@ -530,3 +572,9 @@ func (cmd *Command) MarkPersistentFlagFilename(name string, extensions ...string func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error { return flags.SetAnnotation(name, BashCompFilenameExt, extensions) } + +// MarkFlagCustom adds the BashCompCustom annotation to the named flag in the flag set, if it exists. +// Generated bash autocompletion will call the bash function f for the flag. +func MarkFlagCustom(flags *pflag.FlagSet, name string, f string) error { + return flags.SetAnnotation(name, BashCompCustom, []string{f}) +} diff --git a/bash_completions.md b/bash_completions.md index 204704ef..691b1b79 100644 --- a/bash_completions.md +++ b/bash_completions.md @@ -147,3 +147,35 @@ hello.yml test.json ``` So while there are many other files in the CWD it only shows me subdirs and those with valid extensions. + +# Specifiy custom flag completion + +Similar to the filename completion and filtering usingn cobra.BashCompFilenameExt, you can specifiy +a custom flag completion function with cobra.BashCompCustom: + +```go + annotation := make(map[string][]string) + annotation[cobra.BashCompFilenameExt] = []string{"__kubectl_get_namespaces"} + + flag := &pflag.Flag{ + Name: "namespace", + Usage: usage, + Annotations: annotation, + } + cmd.Flags().AddFlag(flag) +``` + +In addition add the `__handle_namespace_flag` implementation in the `BashCompletionFunction` +value, e.g.: + +```bash +__kubectl_get_namespaces() +{ + local template + template="{{ range .items }}{{ .metadata.name }} {{ end }}" + local kubectl_out + if kubectl_out=$(kubectl get -o template --template="${template}" namespace 2>/dev/null); then + COMPREPLY=( $( compgen -W "${kubectl_out}[*]" -- "$cur" ) ) + fi +} +``` diff --git a/bash_completions_test.go b/bash_completions_test.go index 86f3d010..cf2661cf 100644 --- a/bash_completions_test.go +++ b/bash_completions_test.go @@ -62,6 +62,11 @@ func TestBashCompletions(t *testing.T) { c.Flags().StringVar(&flagvalExt, "filename-ext", "", "Enter a filename (extension limited)") c.MarkFlagFilename("filename-ext") + // filename extensions + var flagvalCustom string + c.Flags().StringVar(&flagvalCustom, "custom", "", "Enter a filename (extension limited)") + c.MarkFlagCustom("custom", "__complete_custom") + // subdirectories in a given directory var flagvalTheme string c.Flags().StringVar(&flagvalTheme, "theme", "", "theme to use (located in /themes/THEMENAME/)") @@ -88,6 +93,8 @@ func TestBashCompletions(t *testing.T) { check(t, str, `flags_completion+=("_filedir")`) // check for filename extension flags check(t, str, `flags_completion+=("__handle_filename_extension_flag json|yaml|yml")`) + // check for custom flags + check(t, str, `flags_completion+=("__complete_custom")`) // check for subdirs_in_dir flags check(t, str, `flags_completion+=("__handle_subdirs_in_dir_flag themes")`)