gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

bash_completions.go (23058B)


      1 // Copyright 2013-2023 The Cobra Authors
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //      http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 package cobra
     16 
     17 import (
     18 	"bytes"
     19 	"fmt"
     20 	"io"
     21 	"os"
     22 	"sort"
     23 	"strings"
     24 
     25 	"github.com/spf13/pflag"
     26 )
     27 
     28 // Annotations for Bash completion.
     29 const (
     30 	BashCompFilenameExt     = "cobra_annotation_bash_completion_filename_extensions"
     31 	BashCompCustom          = "cobra_annotation_bash_completion_custom"
     32 	BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag"
     33 	BashCompSubdirsInDir    = "cobra_annotation_bash_completion_subdirs_in_dir"
     34 )
     35 
     36 func writePreamble(buf io.StringWriter, name string) {
     37 	WriteStringAndCheck(buf, fmt.Sprintf("# bash completion for %-36s -*- shell-script -*-\n", name))
     38 	WriteStringAndCheck(buf, fmt.Sprintf(`
     39 __%[1]s_debug()
     40 {
     41     if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then
     42         echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
     43     fi
     44 }
     45 
     46 # Homebrew on Macs have version 1.3 of bash-completion which doesn't include
     47 # _init_completion. This is a very minimal version of that function.
     48 __%[1]s_init_completion()
     49 {
     50     COMPREPLY=()
     51     _get_comp_words_by_ref "$@" cur prev words cword
     52 }
     53 
     54 __%[1]s_index_of_word()
     55 {
     56     local w word=$1
     57     shift
     58     index=0
     59     for w in "$@"; do
     60         [[ $w = "$word" ]] && return
     61         index=$((index+1))
     62     done
     63     index=-1
     64 }
     65 
     66 __%[1]s_contains_word()
     67 {
     68     local w word=$1; shift
     69     for w in "$@"; do
     70         [[ $w = "$word" ]] && return
     71     done
     72     return 1
     73 }
     74 
     75 __%[1]s_handle_go_custom_completion()
     76 {
     77     __%[1]s_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}"
     78 
     79     local shellCompDirectiveError=%[3]d
     80     local shellCompDirectiveNoSpace=%[4]d
     81     local shellCompDirectiveNoFileComp=%[5]d
     82     local shellCompDirectiveFilterFileExt=%[6]d
     83     local shellCompDirectiveFilterDirs=%[7]d
     84 
     85     local out requestComp lastParam lastChar comp directive args
     86 
     87     # Prepare the command to request completions for the program.
     88     # Calling ${words[0]} instead of directly %[1]s allows to handle aliases
     89     args=("${words[@]:1}")
     90     # Disable ActiveHelp which is not supported for bash completion v1
     91     requestComp="%[8]s=0 ${words[0]} %[2]s ${args[*]}"
     92 
     93     lastParam=${words[$((${#words[@]}-1))]}
     94     lastChar=${lastParam:$((${#lastParam}-1)):1}
     95     __%[1]s_debug "${FUNCNAME[0]}: lastParam ${lastParam}, lastChar ${lastChar}"
     96 
     97     if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then
     98         # If the last parameter is complete (there is a space following it)
     99         # We add an extra empty parameter so we can indicate this to the go method.
    100         __%[1]s_debug "${FUNCNAME[0]}: Adding extra empty parameter"
    101         requestComp="${requestComp} \"\""
    102     fi
    103 
    104     __%[1]s_debug "${FUNCNAME[0]}: calling ${requestComp}"
    105     # Use eval to handle any environment variables and such
    106     out=$(eval "${requestComp}" 2>/dev/null)
    107 
    108     # Extract the directive integer at the very end of the output following a colon (:)
    109     directive=${out##*:}
    110     # Remove the directive
    111     out=${out%%:*}
    112     if [ "${directive}" = "${out}" ]; then
    113         # There is not directive specified
    114         directive=0
    115     fi
    116     __%[1]s_debug "${FUNCNAME[0]}: the completion directive is: ${directive}"
    117     __%[1]s_debug "${FUNCNAME[0]}: the completions are: ${out}"
    118 
    119     if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
    120         # Error code.  No completion.
    121         __%[1]s_debug "${FUNCNAME[0]}: received error from custom completion go code"
    122         return
    123     else
    124         if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
    125             if [[ $(type -t compopt) = "builtin" ]]; then
    126                 __%[1]s_debug "${FUNCNAME[0]}: activating no space"
    127                 compopt -o nospace
    128             fi
    129         fi
    130         if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
    131             if [[ $(type -t compopt) = "builtin" ]]; then
    132                 __%[1]s_debug "${FUNCNAME[0]}: activating no file completion"
    133                 compopt +o default
    134             fi
    135         fi
    136     fi
    137 
    138     if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
    139         # File extension filtering
    140         local fullFilter filter filteringCmd
    141         # Do not use quotes around the $out variable or else newline
    142         # characters will be kept.
    143         for filter in ${out}; do
    144             fullFilter+="$filter|"
    145         done
    146 
    147         filteringCmd="_filedir $fullFilter"
    148         __%[1]s_debug "File filtering command: $filteringCmd"
    149         $filteringCmd
    150     elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
    151         # File completion for directories only
    152         local subdir
    153         # Use printf to strip any trailing newline
    154         subdir=$(printf "%%s" "${out}")
    155         if [ -n "$subdir" ]; then
    156             __%[1]s_debug "Listing directories in $subdir"
    157             __%[1]s_handle_subdirs_in_dir_flag "$subdir"
    158         else
    159             __%[1]s_debug "Listing directories in ."
    160             _filedir -d
    161         fi
    162     else
    163         while IFS='' read -r comp; do
    164             COMPREPLY+=("$comp")
    165         done < <(compgen -W "${out}" -- "$cur")
    166     fi
    167 }
    168 
    169 __%[1]s_handle_reply()
    170 {
    171     __%[1]s_debug "${FUNCNAME[0]}"
    172     local comp
    173     case $cur in
    174         -*)
    175             if [[ $(type -t compopt) = "builtin" ]]; then
    176                 compopt -o nospace
    177             fi
    178             local allflags
    179             if [ ${#must_have_one_flag[@]} -ne 0 ]; then
    180                 allflags=("${must_have_one_flag[@]}")
    181             else
    182                 allflags=("${flags[*]} ${two_word_flags[*]}")
    183             fi
    184             while IFS='' read -r comp; do
    185                 COMPREPLY+=("$comp")
    186             done < <(compgen -W "${allflags[*]}" -- "$cur")
    187             if [[ $(type -t compopt) = "builtin" ]]; then
    188                 [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace
    189             fi
    190 
    191             # complete after --flag=abc
    192             if [[ $cur == *=* ]]; then
    193                 if [[ $(type -t compopt) = "builtin" ]]; then
    194                     compopt +o nospace
    195                 fi
    196 
    197                 local index flag
    198                 flag="${cur%%=*}"
    199                 __%[1]s_index_of_word "${flag}" "${flags_with_completion[@]}"
    200                 COMPREPLY=()
    201                 if [[ ${index} -ge 0 ]]; then
    202                     PREFIX=""
    203                     cur="${cur#*=}"
    204                     ${flags_completion[${index}]}
    205                     if [ -n "${ZSH_VERSION:-}" ]; then
    206                         # zsh completion needs --flag= prefix
    207                         eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )"
    208                     fi
    209                 fi
    210             fi
    211 
    212             if [[ -z "${flag_parsing_disabled}" ]]; then
    213                 # If flag parsing is enabled, we have completed the flags and can return.
    214                 # If flag parsing is disabled, we may not know all (or any) of the flags, so we fallthrough
    215                 # to possibly call handle_go_custom_completion.
    216                 return 0;
    217             fi
    218             ;;
    219     esac
    220 
    221     # check if we are handling a flag with special work handling
    222     local index
    223     __%[1]s_index_of_word "${prev}" "${flags_with_completion[@]}"
    224     if [[ ${index} -ge 0 ]]; then
    225         ${flags_completion[${index}]}
    226         return
    227     fi
    228 
    229     # we are parsing a flag and don't have a special handler, no completion
    230     if [[ ${cur} != "${words[cword]}" ]]; then
    231         return
    232     fi
    233 
    234     local completions
    235     completions=("${commands[@]}")
    236     if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then
    237         completions+=("${must_have_one_noun[@]}")
    238     elif [[ -n "${has_completion_function}" ]]; then
    239         # if a go completion function is provided, defer to that function
    240         __%[1]s_handle_go_custom_completion
    241     fi
    242     if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then
    243         completions+=("${must_have_one_flag[@]}")
    244     fi
    245     while IFS='' read -r comp; do
    246         COMPREPLY+=("$comp")
    247     done < <(compgen -W "${completions[*]}" -- "$cur")
    248 
    249     if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then
    250         while IFS='' read -r comp; do
    251             COMPREPLY+=("$comp")
    252         done < <(compgen -W "${noun_aliases[*]}" -- "$cur")
    253     fi
    254 
    255     if [[ ${#COMPREPLY[@]} -eq 0 ]]; then
    256         if declare -F __%[1]s_custom_func >/dev/null; then
    257             # try command name qualified custom func
    258             __%[1]s_custom_func
    259         else
    260             # otherwise fall back to unqualified for compatibility
    261             declare -F __custom_func >/dev/null && __custom_func
    262         fi
    263     fi
    264 
    265     # available in bash-completion >= 2, not always present on macOS
    266     if declare -F __ltrim_colon_completions >/dev/null; then
    267         __ltrim_colon_completions "$cur"
    268     fi
    269 
    270     # If there is only 1 completion and it is a flag with an = it will be completed
    271     # but we don't want a space after the =
    272     if [[ "${#COMPREPLY[@]}" -eq "1" ]] && [[ $(type -t compopt) = "builtin" ]] && [[ "${COMPREPLY[0]}" == --*= ]]; then
    273        compopt -o nospace
    274     fi
    275 }
    276 
    277 # The arguments should be in the form "ext1|ext2|extn"
    278 __%[1]s_handle_filename_extension_flag()
    279 {
    280     local ext="$1"
    281     _filedir "@(${ext})"
    282 }
    283 
    284 __%[1]s_handle_subdirs_in_dir_flag()
    285 {
    286     local dir="$1"
    287     pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return
    288 }
    289 
    290 __%[1]s_handle_flag()
    291 {
    292     __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
    293 
    294     # if a command required a flag, and we found it, unset must_have_one_flag()
    295     local flagname=${words[c]}
    296     local flagvalue=""
    297     # if the word contained an =
    298     if [[ ${words[c]} == *"="* ]]; then
    299         flagvalue=${flagname#*=} # take in as flagvalue after the =
    300         flagname=${flagname%%=*} # strip everything after the =
    301         flagname="${flagname}=" # but put the = back
    302     fi
    303     __%[1]s_debug "${FUNCNAME[0]}: looking for ${flagname}"
    304     if __%[1]s_contains_word "${flagname}" "${must_have_one_flag[@]}"; then
    305         must_have_one_flag=()
    306     fi
    307 
    308     # if you set a flag which only applies to this command, don't show subcommands
    309     if __%[1]s_contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then
    310       commands=()
    311     fi
    312 
    313     # keep flag value with flagname as flaghash
    314     # flaghash variable is an associative array which is only supported in bash > 3.
    315     if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then
    316         if [ -n "${flagvalue}" ] ; then
    317             flaghash[${flagname}]=${flagvalue}
    318         elif [ -n "${words[ $((c+1)) ]}" ] ; then
    319             flaghash[${flagname}]=${words[ $((c+1)) ]}
    320         else
    321             flaghash[${flagname}]="true" # pad "true" for bool flag
    322         fi
    323     fi
    324 
    325     # skip the argument to a two word flag
    326     if [[ ${words[c]} != *"="* ]] && __%[1]s_contains_word "${words[c]}" "${two_word_flags[@]}"; then
    327         __%[1]s_debug "${FUNCNAME[0]}: found a flag ${words[c]}, skip the next argument"
    328         c=$((c+1))
    329         # if we are looking for a flags value, don't show commands
    330         if [[ $c -eq $cword ]]; then
    331             commands=()
    332         fi
    333     fi
    334 
    335     c=$((c+1))
    336 
    337 }
    338 
    339 __%[1]s_handle_noun()
    340 {
    341     __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
    342 
    343     if __%[1]s_contains_word "${words[c]}" "${must_have_one_noun[@]}"; then
    344         must_have_one_noun=()
    345     elif __%[1]s_contains_word "${words[c]}" "${noun_aliases[@]}"; then
    346         must_have_one_noun=()
    347     fi
    348 
    349     nouns+=("${words[c]}")
    350     c=$((c+1))
    351 }
    352 
    353 __%[1]s_handle_command()
    354 {
    355     __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
    356 
    357     local next_command
    358     if [[ -n ${last_command} ]]; then
    359         next_command="_${last_command}_${words[c]//:/__}"
    360     else
    361         if [[ $c -eq 0 ]]; then
    362             next_command="_%[1]s_root_command"
    363         else
    364             next_command="_${words[c]//:/__}"
    365         fi
    366     fi
    367     c=$((c+1))
    368     __%[1]s_debug "${FUNCNAME[0]}: looking for ${next_command}"
    369     declare -F "$next_command" >/dev/null && $next_command
    370 }
    371 
    372 __%[1]s_handle_word()
    373 {
    374     if [[ $c -ge $cword ]]; then
    375         __%[1]s_handle_reply
    376         return
    377     fi
    378     __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
    379     if [[ "${words[c]}" == -* ]]; then
    380         __%[1]s_handle_flag
    381     elif __%[1]s_contains_word "${words[c]}" "${commands[@]}"; then
    382         __%[1]s_handle_command
    383     elif [[ $c -eq 0 ]]; then
    384         __%[1]s_handle_command
    385     elif __%[1]s_contains_word "${words[c]}" "${command_aliases[@]}"; then
    386         # aliashash variable is an associative array which is only supported in bash > 3.
    387         if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then
    388             words[c]=${aliashash[${words[c]}]}
    389             __%[1]s_handle_command
    390         else
    391             __%[1]s_handle_noun
    392         fi
    393     else
    394         __%[1]s_handle_noun
    395     fi
    396     __%[1]s_handle_word
    397 }
    398 
    399 `, name, ShellCompNoDescRequestCmd,
    400 		ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
    401 		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar(name)))
    402 }
    403 
    404 func writePostscript(buf io.StringWriter, name string) {
    405 	name = strings.ReplaceAll(name, ":", "__")
    406 	WriteStringAndCheck(buf, fmt.Sprintf("__start_%s()\n", name))
    407 	WriteStringAndCheck(buf, fmt.Sprintf(`{
    408     local cur prev words cword split
    409     declare -A flaghash 2>/dev/null || :
    410     declare -A aliashash 2>/dev/null || :
    411     if declare -F _init_completion >/dev/null 2>&1; then
    412         _init_completion -s || return
    413     else
    414         __%[1]s_init_completion -n "=" || return
    415     fi
    416 
    417     local c=0
    418     local flag_parsing_disabled=
    419     local flags=()
    420     local two_word_flags=()
    421     local local_nonpersistent_flags=()
    422     local flags_with_completion=()
    423     local flags_completion=()
    424     local commands=("%[1]s")
    425     local command_aliases=()
    426     local must_have_one_flag=()
    427     local must_have_one_noun=()
    428     local has_completion_function=""
    429     local last_command=""
    430     local nouns=()
    431     local noun_aliases=()
    432 
    433     __%[1]s_handle_word
    434 }
    435 
    436 `, name))
    437 	WriteStringAndCheck(buf, fmt.Sprintf(`if [[ $(type -t compopt) = "builtin" ]]; then
    438     complete -o default -F __start_%s %s
    439 else
    440     complete -o default -o nospace -F __start_%s %s
    441 fi
    442 
    443 `, name, name, name, name))
    444 	WriteStringAndCheck(buf, "# ex: ts=4 sw=4 et filetype=sh\n")
    445 }
    446 
    447 func writeCommands(buf io.StringWriter, cmd *Command) {
    448 	WriteStringAndCheck(buf, "    commands=()\n")
    449 	for _, c := range cmd.Commands() {
    450 		if !c.IsAvailableCommand() && c != cmd.helpCommand {
    451 			continue
    452 		}
    453 		WriteStringAndCheck(buf, fmt.Sprintf("    commands+=(%q)\n", c.Name()))
    454 		writeCmdAliases(buf, c)
    455 	}
    456 	WriteStringAndCheck(buf, "\n")
    457 }
    458 
    459 func writeFlagHandler(buf io.StringWriter, name string, annotations map[string][]string, cmd *Command) {
    460 	for key, value := range annotations {
    461 		switch key {
    462 		case BashCompFilenameExt:
    463 			WriteStringAndCheck(buf, fmt.Sprintf("    flags_with_completion+=(%q)\n", name))
    464 
    465 			var ext string
    466 			if len(value) > 0 {
    467 				ext = fmt.Sprintf("__%s_handle_filename_extension_flag ", cmd.Root().Name()) + strings.Join(value, "|")
    468 			} else {
    469 				ext = "_filedir"
    470 			}
    471 			WriteStringAndCheck(buf, fmt.Sprintf("    flags_completion+=(%q)\n", ext))
    472 		case BashCompCustom:
    473 			WriteStringAndCheck(buf, fmt.Sprintf("    flags_with_completion+=(%q)\n", name))
    474 
    475 			if len(value) > 0 {
    476 				handlers := strings.Join(value, "; ")
    477 				WriteStringAndCheck(buf, fmt.Sprintf("    flags_completion+=(%q)\n", handlers))
    478 			} else {
    479 				WriteStringAndCheck(buf, "    flags_completion+=(:)\n")
    480 			}
    481 		case BashCompSubdirsInDir:
    482 			WriteStringAndCheck(buf, fmt.Sprintf("    flags_with_completion+=(%q)\n", name))
    483 
    484 			var ext string
    485 			if len(value) == 1 {
    486 				ext = fmt.Sprintf("__%s_handle_subdirs_in_dir_flag ", cmd.Root().Name()) + value[0]
    487 			} else {
    488 				ext = "_filedir -d"
    489 			}
    490 			WriteStringAndCheck(buf, fmt.Sprintf("    flags_completion+=(%q)\n", ext))
    491 		}
    492 	}
    493 }
    494 
    495 const cbn = "\")\n"
    496 
    497 func writeShortFlag(buf io.StringWriter, flag *pflag.Flag, cmd *Command) {
    498 	name := flag.Shorthand
    499 	format := "    "
    500 	if len(flag.NoOptDefVal) == 0 {
    501 		format += "two_word_"
    502 	}
    503 	format += "flags+=(\"-%s" + cbn
    504 	WriteStringAndCheck(buf, fmt.Sprintf(format, name))
    505 	writeFlagHandler(buf, "-"+name, flag.Annotations, cmd)
    506 }
    507 
    508 func writeFlag(buf io.StringWriter, flag *pflag.Flag, cmd *Command) {
    509 	name := flag.Name
    510 	format := "    flags+=(\"--%s"
    511 	if len(flag.NoOptDefVal) == 0 {
    512 		format += "="
    513 	}
    514 	format += cbn
    515 	WriteStringAndCheck(buf, fmt.Sprintf(format, name))
    516 	if len(flag.NoOptDefVal) == 0 {
    517 		format = "    two_word_flags+=(\"--%s" + cbn
    518 		WriteStringAndCheck(buf, fmt.Sprintf(format, name))
    519 	}
    520 	writeFlagHandler(buf, "--"+name, flag.Annotations, cmd)
    521 }
    522 
    523 func writeLocalNonPersistentFlag(buf io.StringWriter, flag *pflag.Flag) {
    524 	name := flag.Name
    525 	format := "    local_nonpersistent_flags+=(\"--%[1]s" + cbn
    526 	if len(flag.NoOptDefVal) == 0 {
    527 		format += "    local_nonpersistent_flags+=(\"--%[1]s=" + cbn
    528 	}
    529 	WriteStringAndCheck(buf, fmt.Sprintf(format, name))
    530 	if len(flag.Shorthand) > 0 {
    531 		WriteStringAndCheck(buf, fmt.Sprintf("    local_nonpersistent_flags+=(\"-%s\")\n", flag.Shorthand))
    532 	}
    533 }
    534 
    535 // prepareCustomAnnotationsForFlags setup annotations for go completions for registered flags
    536 func prepareCustomAnnotationsForFlags(cmd *Command) {
    537 	flagCompletionMutex.RLock()
    538 	defer flagCompletionMutex.RUnlock()
    539 	for flag := range flagCompletionFunctions {
    540 		// Make sure the completion script calls the __*_go_custom_completion function for
    541 		// every registered flag.  We need to do this here (and not when the flag was registered
    542 		// for completion) so that we can know the root command name for the prefix
    543 		// of __<prefix>_go_custom_completion
    544 		if flag.Annotations == nil {
    545 			flag.Annotations = map[string][]string{}
    546 		}
    547 		flag.Annotations[BashCompCustom] = []string{fmt.Sprintf("__%[1]s_handle_go_custom_completion", cmd.Root().Name())}
    548 	}
    549 }
    550 
    551 func writeFlags(buf io.StringWriter, cmd *Command) {
    552 	prepareCustomAnnotationsForFlags(cmd)
    553 	WriteStringAndCheck(buf, `    flags=()
    554     two_word_flags=()
    555     local_nonpersistent_flags=()
    556     flags_with_completion=()
    557     flags_completion=()
    558 
    559 `)
    560 
    561 	if cmd.DisableFlagParsing {
    562 		WriteStringAndCheck(buf, "    flag_parsing_disabled=1\n")
    563 	}
    564 
    565 	localNonPersistentFlags := cmd.LocalNonPersistentFlags()
    566 	cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
    567 		if nonCompletableFlag(flag) {
    568 			return
    569 		}
    570 		writeFlag(buf, flag, cmd)
    571 		if len(flag.Shorthand) > 0 {
    572 			writeShortFlag(buf, flag, cmd)
    573 		}
    574 		// localNonPersistentFlags are used to stop the completion of subcommands when one is set
    575 		// if TraverseChildren is true we should allow to complete subcommands
    576 		if localNonPersistentFlags.Lookup(flag.Name) != nil && !cmd.Root().TraverseChildren {
    577 			writeLocalNonPersistentFlag(buf, flag)
    578 		}
    579 	})
    580 	cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
    581 		if nonCompletableFlag(flag) {
    582 			return
    583 		}
    584 		writeFlag(buf, flag, cmd)
    585 		if len(flag.Shorthand) > 0 {
    586 			writeShortFlag(buf, flag, cmd)
    587 		}
    588 	})
    589 
    590 	WriteStringAndCheck(buf, "\n")
    591 }
    592 
    593 func writeRequiredFlag(buf io.StringWriter, cmd *Command) {
    594 	WriteStringAndCheck(buf, "    must_have_one_flag=()\n")
    595 	flags := cmd.NonInheritedFlags()
    596 	flags.VisitAll(func(flag *pflag.Flag) {
    597 		if nonCompletableFlag(flag) {
    598 			return
    599 		}
    600 		for key := range flag.Annotations {
    601 			switch key {
    602 			case BashCompOneRequiredFlag:
    603 				format := "    must_have_one_flag+=(\"--%s"
    604 				if flag.Value.Type() != "bool" {
    605 					format += "="
    606 				}
    607 				format += cbn
    608 				WriteStringAndCheck(buf, fmt.Sprintf(format, flag.Name))
    609 
    610 				if len(flag.Shorthand) > 0 {
    611 					WriteStringAndCheck(buf, fmt.Sprintf("    must_have_one_flag+=(\"-%s"+cbn, flag.Shorthand))
    612 				}
    613 			}
    614 		}
    615 	})
    616 }
    617 
    618 func writeRequiredNouns(buf io.StringWriter, cmd *Command) {
    619 	WriteStringAndCheck(buf, "    must_have_one_noun=()\n")
    620 	sort.Strings(cmd.ValidArgs)
    621 	for _, value := range cmd.ValidArgs {
    622 		// Remove any description that may be included following a tab character.
    623 		// Descriptions are not supported by bash completion.
    624 		value = strings.Split(value, "\t")[0]
    625 		WriteStringAndCheck(buf, fmt.Sprintf("    must_have_one_noun+=(%q)\n", value))
    626 	}
    627 	if cmd.ValidArgsFunction != nil {
    628 		WriteStringAndCheck(buf, "    has_completion_function=1\n")
    629 	}
    630 }
    631 
    632 func writeCmdAliases(buf io.StringWriter, cmd *Command) {
    633 	if len(cmd.Aliases) == 0 {
    634 		return
    635 	}
    636 
    637 	sort.Strings(cmd.Aliases)
    638 
    639 	WriteStringAndCheck(buf, fmt.Sprint(`    if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then`, "\n"))
    640 	for _, value := range cmd.Aliases {
    641 		WriteStringAndCheck(buf, fmt.Sprintf("        command_aliases+=(%q)\n", value))
    642 		WriteStringAndCheck(buf, fmt.Sprintf("        aliashash[%q]=%q\n", value, cmd.Name()))
    643 	}
    644 	WriteStringAndCheck(buf, `    fi`)
    645 	WriteStringAndCheck(buf, "\n")
    646 }
    647 func writeArgAliases(buf io.StringWriter, cmd *Command) {
    648 	WriteStringAndCheck(buf, "    noun_aliases=()\n")
    649 	sort.Strings(cmd.ArgAliases)
    650 	for _, value := range cmd.ArgAliases {
    651 		WriteStringAndCheck(buf, fmt.Sprintf("    noun_aliases+=(%q)\n", value))
    652 	}
    653 }
    654 
    655 func gen(buf io.StringWriter, cmd *Command) {
    656 	for _, c := range cmd.Commands() {
    657 		if !c.IsAvailableCommand() && c != cmd.helpCommand {
    658 			continue
    659 		}
    660 		gen(buf, c)
    661 	}
    662 	commandName := cmd.CommandPath()
    663 	commandName = strings.ReplaceAll(commandName, " ", "_")
    664 	commandName = strings.ReplaceAll(commandName, ":", "__")
    665 
    666 	if cmd.Root() == cmd {
    667 		WriteStringAndCheck(buf, fmt.Sprintf("_%s_root_command()\n{\n", commandName))
    668 	} else {
    669 		WriteStringAndCheck(buf, fmt.Sprintf("_%s()\n{\n", commandName))
    670 	}
    671 
    672 	WriteStringAndCheck(buf, fmt.Sprintf("    last_command=%q\n", commandName))
    673 	WriteStringAndCheck(buf, "\n")
    674 	WriteStringAndCheck(buf, "    command_aliases=()\n")
    675 	WriteStringAndCheck(buf, "\n")
    676 
    677 	writeCommands(buf, cmd)
    678 	writeFlags(buf, cmd)
    679 	writeRequiredFlag(buf, cmd)
    680 	writeRequiredNouns(buf, cmd)
    681 	writeArgAliases(buf, cmd)
    682 	WriteStringAndCheck(buf, "}\n\n")
    683 }
    684 
    685 // GenBashCompletion generates bash completion file and writes to the passed writer.
    686 func (c *Command) GenBashCompletion(w io.Writer) error {
    687 	buf := new(bytes.Buffer)
    688 	writePreamble(buf, c.Name())
    689 	if len(c.BashCompletionFunction) > 0 {
    690 		buf.WriteString(c.BashCompletionFunction + "\n")
    691 	}
    692 	gen(buf, c)
    693 	writePostscript(buf, c.Name())
    694 
    695 	_, err := buf.WriteTo(w)
    696 	return err
    697 }
    698 
    699 func nonCompletableFlag(flag *pflag.Flag) bool {
    700 	return flag.Hidden || len(flag.Deprecated) > 0
    701 }
    702 
    703 // GenBashCompletionFile generates bash completion file.
    704 func (c *Command) GenBashCompletionFile(filename string) error {
    705 	outFile, err := os.Create(filename)
    706 	if err != nil {
    707 		return err
    708 	}
    709 	defer outFile.Close()
    710 
    711 	return c.GenBashCompletion(outFile)
    712 }