diff options
author | Paul Holzinger <paul.holzinger@web.de> | 2020-09-10 22:36:19 +0200 |
---|---|---|
committer | Paul Holzinger <paul.holzinger@web.de> | 2020-11-12 11:32:03 +0100 |
commit | df4bf5c584f264bdefef5cb30d85c036260eff8f (patch) | |
tree | 8d71634cc63feac5020dfdfc745d178457b50805 /vendor/github.com/spf13/cobra/bash_completions.go | |
parent | ea753128952e1a6d4b56cc80d232f6dbfb420ba5 (diff) | |
download | podman-df4bf5c584f264bdefef5cb30d85c036260eff8f.tar.gz podman-df4bf5c584f264bdefef5cb30d85c036260eff8f.tar.bz2 podman-df4bf5c584f264bdefef5cb30d85c036260eff8f.zip |
Vendor in some cobra PRs to improve the completion experience.
This is only temporary until the cobra following PRs are merged:
- PR#1258 Custom completion handle multiple shorhand flags together
- PR#1249 Fix fish handling of "ShellCompDirectiveNoSpace" and file completion
- PR#1213 Fix zsh completion handling of nospace and file completion
- PR#1146 Bash completion V2 with completion descriptions
Signed-off-by: Paul Holzinger <paul.holzinger@web.de>
Diffstat (limited to 'vendor/github.com/spf13/cobra/bash_completions.go')
-rw-r--r-- | vendor/github.com/spf13/cobra/bash_completions.go | 752 |
1 files changed, 207 insertions, 545 deletions
diff --git a/vendor/github.com/spf13/cobra/bash_completions.go b/vendor/github.com/spf13/cobra/bash_completions.go index 846636d75..2ceebc552 100644 --- a/vendor/github.com/spf13/cobra/bash_completions.go +++ b/vendor/github.com/spf13/cobra/bash_completions.go @@ -5,70 +5,95 @@ import ( "fmt" "io" "os" - "sort" - "strings" - - "github.com/spf13/pflag" ) // Annotations for Bash completion. const ( - BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extensions" + BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extensions" + // BashCompCustom should be avoided as it only works for bash. + // Function RegisterFlagCompletionFunc() should be used instead. BashCompCustom = "cobra_annotation_bash_completion_custom" BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag" BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir" ) -func writePreamble(buf *bytes.Buffer, name string) { - buf.WriteString(fmt.Sprintf("# bash completion for %-36s -*- shell-script -*-\n", name)) - buf.WriteString(fmt.Sprintf(` -__%[1]s_debug() -{ - if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then - echo "$*" >> "${BASH_COMP_DEBUG_FILE}" - fi +// GenBashCompletion generates bash completion file and writes to the passed writer. +func (c *Command) GenBashCompletion(w io.Writer) error { + return c.genBashCompletion(w, false) } -# Homebrew on Macs have version 1.3 of bash-completion which doesn't include -# _init_completion. This is a very minimal version of that function. -__%[1]s_init_completion() -{ - COMPREPLY=() - _get_comp_words_by_ref "$@" cur prev words cword +// GenBashCompletionWithDesc generates bash completion file with descriptions and writes to the passed writer. +func (c *Command) GenBashCompletionWithDesc(w io.Writer) error { + return c.genBashCompletion(w, true) } -__%[1]s_index_of_word() -{ - local w word=$1 - shift - index=0 - for w in "$@"; do - [[ $w = "$word" ]] && return - index=$((index+1)) - done - index=-1 +// GenBashCompletionFile generates bash completion file. +func (c *Command) GenBashCompletionFile(filename string) error { + return c.genBashCompletionFile(filename, false) +} + +// GenBashCompletionFileWithDesc generates bash completion file with descriptions. +func (c *Command) GenBashCompletionFileWithDesc(filename string) error { + return c.genBashCompletionFile(filename, true) +} + +func (c *Command) genBashCompletionFile(filename string, includeDesc bool) error { + outFile, err := os.Create(filename) + if err != nil { + return err + } + defer outFile.Close() + + return c.genBashCompletion(outFile, includeDesc) } -__%[1]s_contains_word() +func (c *Command) genBashCompletion(w io.Writer, includeDesc bool) error { + buf := new(bytes.Buffer) + if len(c.BashCompletionFunction) > 0 { + buf.WriteString(c.BashCompletionFunction + "\n") + } + genBashComp(buf, c.Name(), includeDesc) + + _, err := buf.WriteTo(w) + return err +} + +func genBashComp(buf *bytes.Buffer, name string, includeDesc bool) { + compCmd := ShellCompRequestCmd + if !includeDesc { + compCmd = ShellCompNoDescRequestCmd + } + + buf.WriteString(fmt.Sprintf(`# bash completion for %-36[1]s -*- shell-script -*- + +__%[1]s_debug() { - local w word=$1; shift - for w in "$@"; do - [[ $w = "$word" ]] && return - done - return 1 + if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then + echo "$*" >> "${BASH_COMP_DEBUG_FILE}" + fi } -__%[1]s_handle_go_custom_completion() +__%[1]s_perform_completion() { - __%[1]s_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}" + __%[1]s_debug + __%[1]s_debug "========= starting completion logic ==========" + __%[1]s_debug "cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}, cword is $cword" + + # The user could have moved the cursor backwards on the command-line. + # We need to trigger completion from the $cword location, so we need + # to truncate the command-line ($words) up to the $cword location. + words=("${words[@]:0:$cword+1}") + __%[1]s_debug "Truncated words[*]: ${words[*]}," local shellCompDirectiveError=%[3]d local shellCompDirectiveNoSpace=%[4]d local shellCompDirectiveNoFileComp=%[5]d local shellCompDirectiveFilterFileExt=%[6]d local shellCompDirectiveFilterDirs=%[7]d + local shellCompDirectiveLegacyCustomComp=%[8]d + local shellCompDirectiveLegacyCustomArgsComp=%[9]d - local out requestComp lastParam lastChar comp directive args + local out requestComp lastParam lastChar comp directive args flagPrefix # Prepare the command to request completions for the program. # Calling ${words[0]} instead of directly %[1]s allows to handle aliases @@ -77,16 +102,24 @@ __%[1]s_handle_go_custom_completion() lastParam=${words[$((${#words[@]}-1))]} lastChar=${lastParam:$((${#lastParam}-1)):1} - __%[1]s_debug "${FUNCNAME[0]}: lastParam ${lastParam}, lastChar ${lastChar}" + __%[1]s_debug "lastParam ${lastParam}, lastChar ${lastChar}" if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then # If the last parameter is complete (there is a space following it) # We add an extra empty parameter so we can indicate this to the go method. - __%[1]s_debug "${FUNCNAME[0]}: Adding extra empty parameter" + __%[1]s_debug "Adding extra empty parameter" requestComp="${requestComp} \"\"" fi - __%[1]s_debug "${FUNCNAME[0]}: calling ${requestComp}" + # When completing a flag with an = (e.g., %[1]s -n=<TAB>) + # bash focuses on the part after the =, so we need to remove + # the flag part from $cur + if [[ "${cur}" == -*=* ]]; then + flagPrefix="${cur%%%%=*}=" + cur="${cur#*=}" + fi + + __%[1]s_debug "Calling ${requestComp}" # Use eval to handle any environment variables and such out=$(eval "${requestComp}" 2>/dev/null) @@ -98,23 +131,23 @@ __%[1]s_handle_go_custom_completion() # There is not directive specified directive=0 fi - __%[1]s_debug "${FUNCNAME[0]}: the completion directive is: ${directive}" - __%[1]s_debug "${FUNCNAME[0]}: the completions are: ${out[*]}" + __%[1]s_debug "The completion directive is: ${directive}" + __%[1]s_debug "The completions are: ${out[*]}" if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then # Error code. No completion. - __%[1]s_debug "${FUNCNAME[0]}: received error from custom completion go code" + __%[1]s_debug "Received error from custom completion go code" return else if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then if [[ $(type -t compopt) = "builtin" ]]; then - __%[1]s_debug "${FUNCNAME[0]}: activating no space" + __%[1]s_debug "Activating no space" compopt -o nospace fi fi if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then if [[ $(type -t compopt) = "builtin" ]]; then - __%[1]s_debug "${FUNCNAME[0]}: activating no file completion" + __%[1]s_debug "Activating no file completion" compopt +o default fi fi @@ -123,6 +156,7 @@ __%[1]s_handle_go_custom_completion() if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then # File extension filtering local fullFilter filter filteringCmd + # Do not use quotes around the $out variable or else newline # characters will be kept. for filter in ${out[*]}; do @@ -134,545 +168,173 @@ __%[1]s_handle_go_custom_completion() $filteringCmd elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then # File completion for directories only - local subDir + # Use printf to strip any trailing newline + local subdir subdir=$(printf "%%s" "${out[0]}") if [ -n "$subdir" ]; then __%[1]s_debug "Listing directories in $subdir" - __%[1]s_handle_subdirs_in_dir_flag "$subdir" + pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return else __%[1]s_debug "Listing directories in ." _filedir -d fi + elif [ $((directive & shellCompDirectiveLegacyCustomComp)) -ne 0 ]; then + local cmd + __%[1]s_debug "Legacy custom completion. Directive: $directive, cmds: ${out[*]}" + + # The following variables should get their value through the commands + # we have received as completions and are parsing below. + local last_command + local nouns + + # Execute every command received + while IFS='' read -r cmd; do + __%[1]s_debug "About to execute: $cmd" + eval "$cmd" + done < <(printf "%%s\n" "${out[@]}") + + __%[1]s_debug "last_command: $last_command" + __%[1]s_debug "nouns[0]: ${nouns[0]}, nouns[1]: ${nouns[1]}" + + if [ $((directive & shellCompDirectiveLegacyCustomArgsComp)) -ne 0 ]; then + # We should call the global legacy custom completion function, if it is defined + if declare -F __%[1]s_custom_func >/dev/null; then + # Use command name qualified legacy custom func + __%[1]s_debug "About to call: __%[1]s_custom_func" + __%[1]s_custom_func + elif declare -F __custom_func >/dev/null; then + # Otherwise fall back to unqualified legacy custom func for compatibility + __%[1]s_debug "About to call: __custom_func" + __custom_func + fi + fi else + local tab + tab=$(printf '\t') + local longest=0 + # Look for the longest completion so that we can format things nicely while IFS='' read -r comp; do - COMPREPLY+=("$comp") - done < <(compgen -W "${out[*]}" -- "$cur") - fi -} - -__%[1]s_handle_reply() -{ - __%[1]s_debug "${FUNCNAME[0]}" - local comp - case $cur in - -*) - if [[ $(type -t compopt) = "builtin" ]]; then - compopt -o nospace - fi - local allflags - if [ ${#must_have_one_flag[@]} -ne 0 ]; then - allflags=("${must_have_one_flag[@]}") - else - allflags=("${flags[*]} ${two_word_flags[*]}") - fi - while IFS='' read -r comp; do - COMPREPLY+=("$comp") - done < <(compgen -W "${allflags[*]}" -- "$cur") - if [[ $(type -t compopt) = "builtin" ]]; then - [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace + comp=${comp%%%%$tab*} + if ((${#comp}>longest)); then + longest=${#comp} fi + done < <(printf "%%s\n" "${out[@]}") - # complete after --flag=abc - if [[ $cur == *=* ]]; then - if [[ $(type -t compopt) = "builtin" ]]; then - compopt +o nospace - fi - - local index flag - flag="${cur%%=*}" - __%[1]s_index_of_word "${flag}" "${flags_with_completion[@]}" - COMPREPLY=() - if [[ ${index} -ge 0 ]]; then - PREFIX="" - cur="${cur#*=}" - ${flags_completion[${index}]} - if [ -n "${ZSH_VERSION}" ]; then - # zsh completion needs --flag= prefix - eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )" - fi - fi + local completions=() + while IFS='' read -r comp; do + if [ -z "$comp" ]; then + continue fi - return 0; - ;; - esac - - # check if we are handling a flag with special work handling - local index - __%[1]s_index_of_word "${prev}" "${flags_with_completion[@]}" - if [[ ${index} -ge 0 ]]; then - ${flags_completion[${index}]} - return - fi - # we are parsing a flag and don't have a special handler, no completion - if [[ ${cur} != "${words[cword]}" ]]; then - return - fi + __%[1]s_debug "Original comp: $comp" + comp="$(__%[1]s_format_comp_descriptions "$comp" "$longest")" + __%[1]s_debug "Final comp: $comp" + completions+=("$comp") + done < <(printf "%%s\n" "${out[@]}") - local completions - completions=("${commands[@]}") - if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then - completions+=("${must_have_one_noun[@]}") - elif [[ -n "${has_completion_function}" ]]; then - # if a go completion function is provided, defer to that function - __%[1]s_handle_go_custom_completion - fi - if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then - completions+=("${must_have_one_flag[@]}") - fi - while IFS='' read -r comp; do - COMPREPLY+=("$comp") - done < <(compgen -W "${completions[*]}" -- "$cur") - - if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then while IFS='' read -r comp; do + # Although this script should only be used for bash + # there may be programs that still convert the bash + # script into a zsh one. To continue supporting those + # programs, we do this single adaptation for zsh + if [ -n "${ZSH_VERSION}" ]; then + # zsh completion needs --flag= prefix + COMPREPLY+=("$flagPrefix$comp") + else + COMPREPLY+=("$comp") + fi + done < <(compgen -W "${completions[*]}" -- "$cur") + + # If there is a single completion left, remove the description text + if [ ${#COMPREPLY[*]} -eq 1 ]; then + __%[1]s_debug "COMPREPLY[0]: ${COMPREPLY[0]}" + comp="${COMPREPLY[0]%%%% *}" + __%[1]s_debug "Removed description from single completion, which is now: ${comp}" + COMPREPLY=() COMPREPLY+=("$comp") - done < <(compgen -W "${noun_aliases[*]}" -- "$cur") - fi - - if [[ ${#COMPREPLY[@]} -eq 0 ]]; then - if declare -F __%[1]s_custom_func >/dev/null; then - # try command name qualified custom func - __%[1]s_custom_func - else - # otherwise fall back to unqualified for compatibility - declare -F __custom_func >/dev/null && __custom_func - fi - fi - - # available in bash-completion >= 2, not always present on macOS - if declare -F __ltrim_colon_completions >/dev/null; then - __ltrim_colon_completions "$cur" - fi - - # If there is only 1 completion and it is a flag with an = it will be completed - # but we don't want a space after the = - if [[ "${#COMPREPLY[@]}" -eq "1" ]] && [[ $(type -t compopt) = "builtin" ]] && [[ "${COMPREPLY[0]}" == --*= ]]; then - compopt -o nospace - fi -} - -# The arguments should be in the form "ext1|ext2|extn" -__%[1]s_handle_filename_extension_flag() -{ - local ext="$1" - _filedir "@(${ext})" -} - -__%[1]s_handle_subdirs_in_dir_flag() -{ - local dir="$1" - pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return -} - -__%[1]s_handle_flag() -{ - __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" - - # if a command required a flag, and we found it, unset must_have_one_flag() - local flagname=${words[c]} - local flagvalue - # if the word contained an = - if [[ ${words[c]} == *"="* ]]; then - flagvalue=${flagname#*=} # take in as flagvalue after the = - flagname=${flagname%%=*} # strip everything after the = - flagname="${flagname}=" # but put the = back - fi - __%[1]s_debug "${FUNCNAME[0]}: looking for ${flagname}" - if __%[1]s_contains_word "${flagname}" "${must_have_one_flag[@]}"; then - must_have_one_flag=() - fi - - # if you set a flag which only applies to this command, don't show subcommands - if __%[1]s_contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then - commands=() - fi - - # keep flag value with flagname as flaghash - # flaghash variable is an associative array which is only supported in bash > 3. - if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then - if [ -n "${flagvalue}" ] ; then - flaghash[${flagname}]=${flagvalue} - elif [ -n "${words[ $((c+1)) ]}" ] ; then - flaghash[${flagname}]=${words[ $((c+1)) ]} - else - flaghash[${flagname}]="true" # pad "true" for bool flag - fi - fi - - # skip the argument to a two word flag - if [[ ${words[c]} != *"="* ]] && __%[1]s_contains_word "${words[c]}" "${two_word_flags[@]}"; then - __%[1]s_debug "${FUNCNAME[0]}: found a flag ${words[c]}, skip the next argument" - c=$((c+1)) - # if we are looking for a flags value, don't show commands - if [[ $c -eq $cword ]]; then - commands=() fi fi - c=$((c+1)) - + __%[1]s_handle_special_char "$cur" : + __%[1]s_handle_special_char "$cur" = } -__%[1]s_handle_noun() +__%[1]s_handle_special_char() { - __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" - - if __%[1]s_contains_word "${words[c]}" "${must_have_one_noun[@]}"; then - must_have_one_noun=() - elif __%[1]s_contains_word "${words[c]}" "${noun_aliases[@]}"; then - must_have_one_noun=() + local comp="$1" + local char=$2 + if [[ "$comp" == *${char}* && "$COMP_WORDBREAKS" == *${char}* ]]; then + local word=${comp%%"${comp##*${char}}"} + local idx=${#COMPREPLY[*]} + while [[ $((--idx)) -ge 0 ]]; do + COMPREPLY[$idx]=${COMPREPLY[$idx]#"$word"} + done fi - - nouns+=("${words[c]}") - c=$((c+1)) } -__%[1]s_handle_command() +__%[1]s_format_comp_descriptions() { - __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" - - local next_command - if [[ -n ${last_command} ]]; then - next_command="_${last_command}_${words[c]//:/__}" - else - if [[ $c -eq 0 ]]; then - next_command="_%[1]s_root_command" + local tab + tab=$(printf '\t') + local comp="$1" + local longest=$2 + + # Properly format the description string which follows a tab character if there is one + if [[ "$comp" == *$tab* ]]; then + desc=${comp#*$tab} + comp=${comp%%%%$tab*} + + # $COLUMNS stores the current shell width. + # Remove an extra 4 because we add 2 spaces and 2 parentheses. + maxdesclength=$(( COLUMNS - longest - 4 )) + + # Make sure we can fit a description of at least 8 characters + # if we are to align the descriptions. + if [[ $maxdesclength -gt 8 ]]; then + # Add the proper number of spaces to align the descriptions + for ((i = ${#comp} ; i < longest ; i++)); do + comp+=" " + done else - next_command="_${words[c]//:/__}" + # Don't pad the descriptions so we can fit more text after the completion + maxdesclength=$(( COLUMNS - ${#comp} - 4 )) fi - fi - c=$((c+1)) - __%[1]s_debug "${FUNCNAME[0]}: looking for ${next_command}" - declare -F "$next_command" >/dev/null && $next_command -} -__%[1]s_handle_word() -{ - if [[ $c -ge $cword ]]; then - __%[1]s_handle_reply - return - fi - __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" - if [[ "${words[c]}" == -* ]]; then - __%[1]s_handle_flag - elif __%[1]s_contains_word "${words[c]}" "${commands[@]}"; then - __%[1]s_handle_command - elif [[ $c -eq 0 ]]; then - __%[1]s_handle_command - elif __%[1]s_contains_word "${words[c]}" "${command_aliases[@]}"; then - # aliashash variable is an associative array which is only supported in bash > 3. - if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then - words[c]=${aliashash[${words[c]}]} - __%[1]s_handle_command - else - __%[1]s_handle_noun + # If there is enough space for any description text, + # truncate the descriptions that are too long for the shell width + if [ $maxdesclength -gt 0 ]; then + if [ ${#desc} -gt $maxdesclength ]; then + desc=${desc:0:$(( maxdesclength - 1 ))} + desc+="…" + fi + comp+=" ($desc)" fi - else - __%[1]s_handle_noun fi - __%[1]s_handle_word -} -`, name, ShellCompNoDescRequestCmd, - ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, - ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) + # Must use printf to escape all special characters + printf "%%q" "${comp}" } -func writePostscript(buf *bytes.Buffer, name string) { - name = strings.Replace(name, ":", "__", -1) - buf.WriteString(fmt.Sprintf("__start_%s()\n", name)) - buf.WriteString(fmt.Sprintf(`{ +__start_%[1]s() +{ local cur prev words cword - declare -A flaghash 2>/dev/null || : - declare -A aliashash 2>/dev/null || : - if declare -F _init_completion >/dev/null 2>&1; then - _init_completion -s || return - else - __%[1]s_init_completion -n "=" || return - fi - local c=0 - local flags=() - local two_word_flags=() - local local_nonpersistent_flags=() - local flags_with_completion=() - local flags_completion=() - local commands=("%[1]s") - local must_have_one_flag=() - local must_have_one_noun=() - local has_completion_function - local last_command - local nouns=() - - __%[1]s_handle_word + COMPREPLY=() + _get_comp_words_by_ref -n "=:" cur prev words cword + + __%[1]s_perform_completion } -`, name)) - buf.WriteString(fmt.Sprintf(`if [[ $(type -t compopt) = "builtin" ]]; then - complete -o default -F __start_%s %s +if [[ $(type -t compopt) = "builtin" ]]; then + complete -o default -F __start_%[1]s %[1]s else - complete -o default -o nospace -F __start_%s %s + complete -o default -o nospace -F __start_%[1]s %[1]s fi -`, name, name, name, name)) - buf.WriteString("# ex: ts=4 sw=4 et filetype=sh\n") -} - -func writeCommands(buf *bytes.Buffer, cmd *Command) { - buf.WriteString(" commands=()\n") - for _, c := range cmd.Commands() { - if !c.IsAvailableCommand() && c != cmd.helpCommand { - continue - } - buf.WriteString(fmt.Sprintf(" commands+=(%q)\n", c.Name())) - writeCmdAliases(buf, c) - } - buf.WriteString("\n") -} - -func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]string, cmd *Command) { - for key, value := range annotations { - switch key { - case BashCompFilenameExt: - buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) - - var ext string - if len(value) > 0 { - ext = fmt.Sprintf("__%s_handle_filename_extension_flag ", cmd.Root().Name()) + strings.Join(value, "|") - } else { - ext = "_filedir" - } - buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", ext)) - case BashCompCustom: - buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) - if len(value) > 0 { - handlers := strings.Join(value, "; ") - buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", handlers)) - } else { - buf.WriteString(" flags_completion+=(:)\n") - } - case BashCompSubdirsInDir: - buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) - - var ext string - if len(value) == 1 { - ext = fmt.Sprintf("__%s_handle_subdirs_in_dir_flag ", cmd.Root().Name()) + value[0] - } else { - ext = "_filedir -d" - } - buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", ext)) - } - } -} - -func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) { - name := flag.Shorthand - format := " " - if len(flag.NoOptDefVal) == 0 { - format += "two_word_" - } - format += "flags+=(\"-%s\")\n" - buf.WriteString(fmt.Sprintf(format, name)) - writeFlagHandler(buf, "-"+name, flag.Annotations, cmd) -} - -func writeFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) { - name := flag.Name - format := " flags+=(\"--%s" - if len(flag.NoOptDefVal) == 0 { - format += "=" - } - format += "\")\n" - buf.WriteString(fmt.Sprintf(format, name)) - if len(flag.NoOptDefVal) == 0 { - format = " two_word_flags+=(\"--%s\")\n" - buf.WriteString(fmt.Sprintf(format, name)) - } - writeFlagHandler(buf, "--"+name, flag.Annotations, cmd) -} - -func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) { - name := flag.Name - format := " local_nonpersistent_flags+=(\"--%[1]s\")\n" - if len(flag.NoOptDefVal) == 0 { - format += " local_nonpersistent_flags+=(\"--%[1]s=\")\n" - } - buf.WriteString(fmt.Sprintf(format, name)) - if len(flag.Shorthand) > 0 { - buf.WriteString(fmt.Sprintf(" local_nonpersistent_flags+=(\"-%s\")\n", flag.Shorthand)) - } -} - -// Setup annotations for go completions for registered flags -func prepareCustomAnnotationsForFlags(cmd *Command) { - for flag := range flagCompletionFunctions { - // Make sure the completion script calls the __*_go_custom_completion function for - // every registered flag. We need to do this here (and not when the flag was registered - // for completion) so that we can know the root command name for the prefix - // of __<prefix>_go_custom_completion - if flag.Annotations == nil { - flag.Annotations = map[string][]string{} - } - flag.Annotations[BashCompCustom] = []string{fmt.Sprintf("__%[1]s_handle_go_custom_completion", cmd.Root().Name())} - } -} - -func writeFlags(buf *bytes.Buffer, cmd *Command) { - prepareCustomAnnotationsForFlags(cmd) - buf.WriteString(` flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - -`) - localNonPersistentFlags := cmd.LocalNonPersistentFlags() - cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { - if nonCompletableFlag(flag) { - return - } - writeFlag(buf, flag, cmd) - if len(flag.Shorthand) > 0 { - writeShortFlag(buf, flag, cmd) - } - // localNonPersistentFlags are used to stop the completion of subcommands when one is set - // if TraverseChildren is true we should allow to complete subcommands - if localNonPersistentFlags.Lookup(flag.Name) != nil && !cmd.Root().TraverseChildren { - writeLocalNonPersistentFlag(buf, flag) - } - }) - cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) { - if nonCompletableFlag(flag) { - return - } - writeFlag(buf, flag, cmd) - if len(flag.Shorthand) > 0 { - writeShortFlag(buf, flag, cmd) - } - }) - - buf.WriteString("\n") -} - -func writeRequiredFlag(buf *bytes.Buffer, cmd *Command) { - buf.WriteString(" must_have_one_flag=()\n") - flags := cmd.NonInheritedFlags() - flags.VisitAll(func(flag *pflag.Flag) { - if nonCompletableFlag(flag) { - return - } - for key := range flag.Annotations { - switch key { - case BashCompOneRequiredFlag: - format := " must_have_one_flag+=(\"--%s" - if flag.Value.Type() != "bool" { - format += "=" - } - format += "\")\n" - buf.WriteString(fmt.Sprintf(format, flag.Name)) - - if len(flag.Shorthand) > 0 { - buf.WriteString(fmt.Sprintf(" must_have_one_flag+=(\"-%s\")\n", flag.Shorthand)) - } - } - } - }) -} - -func writeRequiredNouns(buf *bytes.Buffer, cmd *Command) { - buf.WriteString(" must_have_one_noun=()\n") - sort.Sort(sort.StringSlice(cmd.ValidArgs)) - for _, value := range cmd.ValidArgs { - // Remove any description that may be included following a tab character. - // Descriptions are not supported by bash completion. - value = strings.Split(value, "\t")[0] - buf.WriteString(fmt.Sprintf(" must_have_one_noun+=(%q)\n", value)) - } - if cmd.ValidArgsFunction != nil { - buf.WriteString(" has_completion_function=1\n") - } -} - -func writeCmdAliases(buf *bytes.Buffer, cmd *Command) { - if len(cmd.Aliases) == 0 { - return - } - - sort.Sort(sort.StringSlice(cmd.Aliases)) - - buf.WriteString(fmt.Sprint(` if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then`, "\n")) - for _, value := range cmd.Aliases { - buf.WriteString(fmt.Sprintf(" command_aliases+=(%q)\n", value)) - buf.WriteString(fmt.Sprintf(" aliashash[%q]=%q\n", value, cmd.Name())) - } - buf.WriteString(` fi`) - buf.WriteString("\n") -} -func writeArgAliases(buf *bytes.Buffer, cmd *Command) { - buf.WriteString(" noun_aliases=()\n") - sort.Sort(sort.StringSlice(cmd.ArgAliases)) - for _, value := range cmd.ArgAliases { - buf.WriteString(fmt.Sprintf(" noun_aliases+=(%q)\n", value)) - } -} - -func gen(buf *bytes.Buffer, cmd *Command) { - for _, c := range cmd.Commands() { - if !c.IsAvailableCommand() && c != cmd.helpCommand { - continue - } - gen(buf, c) - } - commandName := cmd.CommandPath() - commandName = strings.Replace(commandName, " ", "_", -1) - commandName = strings.Replace(commandName, ":", "__", -1) - - if cmd.Root() == cmd { - buf.WriteString(fmt.Sprintf("_%s_root_command()\n{\n", commandName)) - } else { - buf.WriteString(fmt.Sprintf("_%s()\n{\n", commandName)) - } - - buf.WriteString(fmt.Sprintf(" last_command=%q\n", commandName)) - buf.WriteString("\n") - buf.WriteString(" command_aliases=()\n") - buf.WriteString("\n") - - writeCommands(buf, cmd) - writeFlags(buf, cmd) - writeRequiredFlag(buf, cmd) - writeRequiredNouns(buf, cmd) - writeArgAliases(buf, cmd) - buf.WriteString("}\n\n") -} - -// GenBashCompletion generates bash completion file and writes to the passed writer. -func (c *Command) GenBashCompletion(w io.Writer) error { - buf := new(bytes.Buffer) - writePreamble(buf, c.Name()) - if len(c.BashCompletionFunction) > 0 { - buf.WriteString(c.BashCompletionFunction + "\n") - } - gen(buf, c) - writePostscript(buf, c.Name()) - - _, err := buf.WriteTo(w) - return err -} - -func nonCompletableFlag(flag *pflag.Flag) bool { - return flag.Hidden || len(flag.Deprecated) > 0 -} - -// GenBashCompletionFile generates bash completion file. -func (c *Command) GenBashCompletionFile(filename string) error { - outFile, err := os.Create(filename) - if err != nil { - return err - } - defer outFile.Close() - - return c.GenBashCompletion(outFile) +# ex: ts=4 sw=4 et filetype=sh +`, name, compCmd, + ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, + shellCompDirectiveLegacyCustomComp, shellCompDirectiveLegacyCustomArgsComp)) } |