// Copyright 2013-2023 The Cobra Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.package cobraimport ()// Annotations for Bash completion.const (BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extensions"BashCompCustom = "cobra_annotation_bash_completion_custom"BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag"BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir")func writePreamble( io.StringWriter, string) {WriteStringAndCheck(, fmt.Sprintf("# bash completion for %-36s -*- shell-script -*-\n", ))WriteStringAndCheck(, fmt.Sprintf(`__%[1]s_debug(){ if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then echo "$*" >> "${BASH_COMP_DEBUG_FILE}" fi}# 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}__%[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}__%[1]s_contains_word(){ local w word=$1; shift for w in "$@"; do [[ $w = "$word" ]] && return done return 1}__%[1]s_handle_go_custom_completion(){ __%[1]s_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}" local shellCompDirectiveError=%[3]d local shellCompDirectiveNoSpace=%[4]d local shellCompDirectiveNoFileComp=%[5]d local shellCompDirectiveFilterFileExt=%[6]d local shellCompDirectiveFilterDirs=%[7]d local out requestComp lastParam lastChar comp directive args # Prepare the command to request completions for the program. # Calling ${words[0]} instead of directly %[1]s allows handling aliases args=("${words[@]:1}") # Disable ActiveHelp which is not supported for bash completion v1 requestComp="%[8]s=0 ${words[0]} %[2]s ${args[*]}" lastParam=${words[$((${#words[@]}-1))]} lastChar=${lastParam:$((${#lastParam}-1)):1} __%[1]s_debug "${FUNCNAME[0]}: 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" requestComp="${requestComp} \"\"" fi __%[1]s_debug "${FUNCNAME[0]}: calling ${requestComp}" # Use eval to handle any environment variables and such out=$(eval "${requestComp}" 2>/dev/null) # Extract the directive integer at the very end of the output following a colon (:) directive=${out##*:} # Remove the directive out=${out%%:*} if [ "${directive}" = "${out}" ]; then # 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}" if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then # Error code. No completion. __%[1]s_debug "${FUNCNAME[0]}: 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" 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" compopt +o default fi fi fi 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 fullFilter+="$filter|" done filteringCmd="_filedir $fullFilter" __%[1]s_debug "File filtering command: $filteringCmd" $filteringCmd elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then # File completion for directories only local subdir # Use printf to strip any trailing newline subdir=$(printf "%%s" "${out}") if [ -n "$subdir" ]; then __%[1]s_debug "Listing directories in $subdir" __%[1]s_handle_subdirs_in_dir_flag "$subdir" else __%[1]s_debug "Listing directories in ." _filedir -d fi else 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 fi # 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 fi if [[ -z "${flag_parsing_disabled}" ]]; then # If flag parsing is enabled, we have completed the flags and can return. # If flag parsing is disabled, we may not know all (or any) of the flags, so we fallthrough # to possibly call handle_go_custom_completion. return 0; fi ;; 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 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 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_noun(){ __%[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=() fi nouns+=("${words[c]}") c=$((c+1))}__%[1]s_handle_command(){ __%[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" else next_command="_${words[c]//:/__}" 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 fi else __%[1]s_handle_noun fi __%[1]s_handle_word}`, , ShellCompNoDescRequestCmd,ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar()))}func writePostscript( io.StringWriter, string) { = strings.ReplaceAll(, ":", "__")WriteStringAndCheck(, fmt.Sprintf("__start_%s()\n", ))WriteStringAndCheck(, fmt.Sprintf(`{ local cur prev words cword split 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 flag_parsing_disabled= local flags=() local two_word_flags=() local local_nonpersistent_flags=() local flags_with_completion=() local flags_completion=() local commands=("%[1]s") local command_aliases=() local must_have_one_flag=() local must_have_one_noun=() local has_completion_function="" local last_command="" local nouns=() local noun_aliases=() __%[1]s_handle_word}`, ))WriteStringAndCheck(, fmt.Sprintf(`if [[ $(type -t compopt) = "builtin" ]]; then complete -o default -F __start_%s %selse complete -o default -o nospace -F __start_%s %sfi`, , , , ))WriteStringAndCheck(, "# ex: ts=4 sw=4 et filetype=sh\n")}func writeCommands( io.StringWriter, *Command) {WriteStringAndCheck(, " commands=()\n")for , := range .Commands() {if !.IsAvailableCommand() && != .helpCommand {continue }WriteStringAndCheck(, fmt.Sprintf(" commands+=(%q)\n", .Name()))writeCmdAliases(, ) }WriteStringAndCheck(, "\n")}func writeFlagHandler( io.StringWriter, string, map[string][]string, *Command) {for , := range {switch {caseBashCompFilenameExt:WriteStringAndCheck(, fmt.Sprintf(" flags_with_completion+=(%q)\n", ))varstringiflen() > 0 { = fmt.Sprintf("__%s_handle_filename_extension_flag ", .Root().Name()) + strings.Join(, "|") } else { = "_filedir" }WriteStringAndCheck(, fmt.Sprintf(" flags_completion+=(%q)\n", ))caseBashCompCustom:WriteStringAndCheck(, fmt.Sprintf(" flags_with_completion+=(%q)\n", ))iflen() > 0 { := strings.Join(, "; ")WriteStringAndCheck(, fmt.Sprintf(" flags_completion+=(%q)\n", )) } else {WriteStringAndCheck(, " flags_completion+=(:)\n") }caseBashCompSubdirsInDir:WriteStringAndCheck(, fmt.Sprintf(" flags_with_completion+=(%q)\n", ))varstringiflen() == 1 { = fmt.Sprintf("__%s_handle_subdirs_in_dir_flag ", .Root().Name()) + [0] } else { = "_filedir -d" }WriteStringAndCheck(, fmt.Sprintf(" flags_completion+=(%q)\n", )) } }}const cbn = "\")\n"func writeShortFlag( io.StringWriter, *pflag.Flag, *Command) { := .Shorthand := " "iflen(.NoOptDefVal) == 0 { += "two_word_" } += "flags+=(\"-%s" + cbnWriteStringAndCheck(, fmt.Sprintf(, ))writeFlagHandler(, "-"+, .Annotations, )}func writeFlag( io.StringWriter, *pflag.Flag, *Command) { := .Name := " flags+=(\"--%s"iflen(.NoOptDefVal) == 0 { += "=" } += cbnWriteStringAndCheck(, fmt.Sprintf(, ))iflen(.NoOptDefVal) == 0 { = " two_word_flags+=(\"--%s" + cbnWriteStringAndCheck(, fmt.Sprintf(, )) }writeFlagHandler(, "--"+, .Annotations, )}func writeLocalNonPersistentFlag( io.StringWriter, *pflag.Flag) { := .Name := " local_nonpersistent_flags+=(\"--%[1]s" + cbniflen(.NoOptDefVal) == 0 { += " local_nonpersistent_flags+=(\"--%[1]s=" + cbn }WriteStringAndCheck(, fmt.Sprintf(, ))iflen(.Shorthand) > 0 {WriteStringAndCheck(, fmt.Sprintf(" local_nonpersistent_flags+=(\"-%s\")\n", .Shorthand)) }}// prepareCustomAnnotationsForFlags setup annotations for go completions for registered flagsfunc prepareCustomAnnotationsForFlags( *Command) {flagCompletionMutex.RLock()deferflagCompletionMutex.RUnlock()for := rangeflagCompletionFunctions {// 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_completionif .Annotations == nil { .Annotations = map[string][]string{} } .Annotations[BashCompCustom] = []string{fmt.Sprintf("__%[1]s_handle_go_custom_completion", .Root().Name())} }}func writeFlags( io.StringWriter, *Command) {prepareCustomAnnotationsForFlags()WriteStringAndCheck(, ` flags=() two_word_flags=() local_nonpersistent_flags=() flags_with_completion=() flags_completion=()`)if .DisableFlagParsing {WriteStringAndCheck(, " flag_parsing_disabled=1\n") } := .LocalNonPersistentFlags() .NonInheritedFlags().VisitAll(func( *pflag.Flag) {ifnonCompletableFlag() {return }writeFlag(, , )iflen(.Shorthand) > 0 {writeShortFlag(, , ) }// localNonPersistentFlags are used to stop the completion of subcommands when one is set // if TraverseChildren is true we should allow to complete subcommandsif .Lookup(.Name) != nil && !.Root().TraverseChildren {writeLocalNonPersistentFlag(, ) } }) .InheritedFlags().VisitAll(func( *pflag.Flag) {ifnonCompletableFlag() {return }writeFlag(, , )iflen(.Shorthand) > 0 {writeShortFlag(, , ) } })WriteStringAndCheck(, "\n")}func writeRequiredFlag( io.StringWriter, *Command) {WriteStringAndCheck(, " must_have_one_flag=()\n") := .NonInheritedFlags() .VisitAll(func( *pflag.Flag) {ifnonCompletableFlag() {return }if , := .Annotations[BashCompOneRequiredFlag]; { := " must_have_one_flag+=(\"--%s"if .Value.Type() != "bool" { += "=" } += cbnWriteStringAndCheck(, fmt.Sprintf(, .Name))iflen(.Shorthand) > 0 {WriteStringAndCheck(, fmt.Sprintf(" must_have_one_flag+=(\"-%s"+cbn, .Shorthand)) } } })}func writeRequiredNouns( io.StringWriter, *Command) {WriteStringAndCheck(, " must_have_one_noun=()\n")sort.Strings(.ValidArgs)for , := range .ValidArgs {// Remove any description that may be included following a tab character. // Descriptions are not supported by bash completion. = strings.SplitN(, "\t", 2)[0]WriteStringAndCheck(, fmt.Sprintf(" must_have_one_noun+=(%q)\n", )) }if .ValidArgsFunction != nil {WriteStringAndCheck(, " has_completion_function=1\n") }}func writeCmdAliases( io.StringWriter, *Command) {iflen(.Aliases) == 0 {return }sort.Strings(.Aliases)WriteStringAndCheck(, fmt.Sprint(` if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then`, "\n"))for , := range .Aliases {WriteStringAndCheck(, fmt.Sprintf(" command_aliases+=(%q)\n", ))WriteStringAndCheck(, fmt.Sprintf(" aliashash[%q]=%q\n", , .Name())) }WriteStringAndCheck(, ` fi`)WriteStringAndCheck(, "\n")}func writeArgAliases( io.StringWriter, *Command) {WriteStringAndCheck(, " noun_aliases=()\n")sort.Strings(.ArgAliases)for , := range .ArgAliases {WriteStringAndCheck(, fmt.Sprintf(" noun_aliases+=(%q)\n", )) }}func gen( io.StringWriter, *Command) {for , := range .Commands() {if !.IsAvailableCommand() && != .helpCommand {continue } (, ) } := .CommandPath() = strings.ReplaceAll(, " ", "_") = strings.ReplaceAll(, ":", "__")if .Root() == {WriteStringAndCheck(, fmt.Sprintf("_%s_root_command()\n{\n", )) } else {WriteStringAndCheck(, fmt.Sprintf("_%s()\n{\n", )) }WriteStringAndCheck(, fmt.Sprintf(" last_command=%q\n", ))WriteStringAndCheck(, "\n")WriteStringAndCheck(, " command_aliases=()\n")WriteStringAndCheck(, "\n")writeCommands(, )writeFlags(, )writeRequiredFlag(, )writeRequiredNouns(, )writeArgAliases(, )WriteStringAndCheck(, "}\n\n")}// GenBashCompletion generates bash completion file and writes to the passed writer.func ( *Command) ( io.Writer) error { := new(bytes.Buffer)writePreamble(, .Name())iflen(.BashCompletionFunction) > 0 { .WriteString(.BashCompletionFunction + "\n") }gen(, )writePostscript(, .Name()) , := .WriteTo()return}func nonCompletableFlag( *pflag.Flag) bool {return .Hidden || len(.Deprecated) > 0}// GenBashCompletionFile generates bash completion file.func ( *Command) ( string) error { , := os.Create()if != nil {return }defer .Close()return .GenBashCompletion()}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.