Merge pull request #255 from sttts/sttts-bash-completion-flag-equal

Advanced custom bash completion of flags
This commit is contained in:
Eric Paris 2016-03-21 19:22:24 -05:00
commit d6b481239e
3 changed files with 89 additions and 2 deletions

View File

@ -12,6 +12,7 @@ import (
const ( const (
BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extentions" BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extentions"
BashCompCustom = "cobra_annotation_bash_completion_custom"
BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag" BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag"
BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir" BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir"
) )
@ -34,7 +35,7 @@ __debug()
__my_init_completion() __my_init_completion()
{ {
COMPREPLY=() COMPREPLY=()
_get_comp_words_by_ref cur prev words cword _get_comp_words_by_ref "$@" cur prev words cword
} }
__index_of_word() __index_of_word()
@ -76,6 +77,25 @@ __handle_reply()
if [[ $(type -t compopt) = "builtin" ]]; then if [[ $(type -t compopt) = "builtin" ]]; then
[[ $COMPREPLY == *= ]] || compopt +o nospace [[ $COMPREPLY == *= ]] || compopt +o nospace
fi 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; return 0;
;; ;;
esac esac
@ -169,6 +189,8 @@ __handle_noun()
if __contains_word "${words[c]}" "${must_have_one_noun[@]}"; then if __contains_word "${words[c]}" "${must_have_one_noun[@]}"; then
must_have_one_noun=() must_have_one_noun=()
elif __contains_word "${words[c]%s}" "${must_have_one_noun[@]}"; then
must_have_one_noun=()
fi fi
nouns+=("${words[c]}") 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 if declare -F _init_completion >/dev/null 2>&1; then
_init_completion -s || return _init_completion -s || return
else else
__my_init_completion || return __my_init_completion -n "=" || return
fi fi
local c=0 local c=0
@ -299,6 +321,20 @@ func writeFlagHandler(name string, annotations map[string][]string, w io.Writer)
if err != nil { if err != nil {
return err 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: case BashCompSubdirsInDir:
_, err := fmt.Fprintf(w, " flags_with_completion+=(%q)\n", name) _, 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...) 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. // 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. // Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
func (cmd *Command) MarkPersistentFlagFilename(name string, extensions ...string) error { 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 { func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error {
return flags.SetAnnotation(name, BashCompFilenameExt, extensions) 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})
}

View File

@ -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. 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
}
```

View File

@ -62,6 +62,11 @@ func TestBashCompletions(t *testing.T) {
c.Flags().StringVar(&flagvalExt, "filename-ext", "", "Enter a filename (extension limited)") c.Flags().StringVar(&flagvalExt, "filename-ext", "", "Enter a filename (extension limited)")
c.MarkFlagFilename("filename-ext") 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 // subdirectories in a given directory
var flagvalTheme string var flagvalTheme string
c.Flags().StringVar(&flagvalTheme, "theme", "", "theme to use (located in /themes/THEMENAME/)") 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(t, str, `flags_completion+=("_filedir")`)
// check for filename extension flags // check for filename extension flags
check(t, str, `flags_completion+=("__handle_filename_extension_flag json|yaml|yml")`) 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 for subdirs_in_dir flags
check(t, str, `flags_completion+=("__handle_subdirs_in_dir_flag themes")`) check(t, str, `flags_completion+=("__handle_subdirs_in_dir_flag themes")`)