vendor/github.com/spf13/cobra/bash_completions.go
changeset 256 6d9efbef00a9
parent 251 1c52a0eeb952
child 260 445e01aede7e
--- a/vendor/github.com/spf13/cobra/bash_completions.go	Mon Jun 07 20:58:18 2021 +0200
+++ b/vendor/github.com/spf13/cobra/bash_completions.go	Sun Jul 11 10:35:56 2021 +0200
@@ -19,9 +19,9 @@
 	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(`
+func writePreamble(buf io.StringWriter, name string) {
+	WriteStringAndCheck(buf, fmt.Sprintf("# bash completion for %-36s -*- shell-script -*-\n", name))
+	WriteStringAndCheck(buf, fmt.Sprintf(`
 __%[1]s_debug()
 {
     if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then
@@ -58,9 +58,103 @@
     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 to handle aliases
+    args=("${words[@]:1}")
+    requestComp="${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[0]}")
+        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
@@ -72,7 +166,9 @@
             else
                 allflags=("${flags[*]} ${two_word_flags[*]}")
             fi
-            COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") )
+            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
@@ -117,15 +213,22 @@
     local completions
     completions=("${commands[@]}")
     if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then
-        completions=("${must_have_one_noun[@]}")
+        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
-    COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") )
+    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
-        COMPREPLY=( $(compgen -W "${noun_aliases[*]}" -- "$cur") )
+        while IFS='' read -r comp; do
+            COMPREPLY+=("$comp")
+        done < <(compgen -W "${noun_aliases[*]}" -- "$cur")
     fi
 
     if [[ ${#COMPREPLY[@]} -eq 0 ]]; then
@@ -160,7 +263,7 @@
 __%[1]s_handle_subdirs_in_dir_flag()
 {
     local dir="$1"
-    pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1
+    pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return
 }
 
 __%[1]s_handle_flag()
@@ -272,14 +375,16 @@
     __%[1]s_handle_word
 }
 
-`, name))
+`, name, ShellCompNoDescRequestCmd,
+		ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
+		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
 }
 
-func writePostscript(buf *bytes.Buffer, name string) {
+func writePostscript(buf io.StringWriter, name string) {
 	name = strings.Replace(name, ":", "__", -1)
-	buf.WriteString(fmt.Sprintf("__start_%s()\n", name))
-	buf.WriteString(fmt.Sprintf(`{
-    local cur prev words cword
+	WriteStringAndCheck(buf, fmt.Sprintf("__start_%s()\n", name))
+	WriteStringAndCheck(buf, 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
@@ -295,42 +400,45 @@
     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
 }
 
 `, name))
-	buf.WriteString(fmt.Sprintf(`if [[ $(type -t compopt) = "builtin" ]]; then
+	WriteStringAndCheck(buf, fmt.Sprintf(`if [[ $(type -t compopt) = "builtin" ]]; then
     complete -o default -F __start_%s %s
 else
     complete -o default -o nospace -F __start_%s %s
 fi
 
 `, name, name, name, name))
-	buf.WriteString("# ex: ts=4 sw=4 et filetype=sh\n")
+	WriteStringAndCheck(buf, "# ex: ts=4 sw=4 et filetype=sh\n")
 }
 
-func writeCommands(buf *bytes.Buffer, cmd *Command) {
-	buf.WriteString("    commands=()\n")
+func writeCommands(buf io.StringWriter, cmd *Command) {
+	WriteStringAndCheck(buf, "    commands=()\n")
 	for _, c := range cmd.Commands() {
-		if !c.IsAvailableCommand() || c == cmd.helpCommand {
+		if !c.IsAvailableCommand() && c != cmd.helpCommand {
 			continue
 		}
-		buf.WriteString(fmt.Sprintf("    commands+=(%q)\n", c.Name()))
+		WriteStringAndCheck(buf, fmt.Sprintf("    commands+=(%q)\n", c.Name()))
 		writeCmdAliases(buf, c)
 	}
-	buf.WriteString("\n")
+	WriteStringAndCheck(buf, "\n")
 }
 
-func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]string, cmd *Command) {
+func writeFlagHandler(buf io.StringWriter, 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))
+			WriteStringAndCheck(buf, fmt.Sprintf("    flags_with_completion+=(%q)\n", name))
 
 			var ext string
 			if len(value) > 0 {
@@ -338,17 +446,18 @@
 			} else {
 				ext = "_filedir"
 			}
-			buf.WriteString(fmt.Sprintf("    flags_completion+=(%q)\n", ext))
+			WriteStringAndCheck(buf, fmt.Sprintf("    flags_completion+=(%q)\n", ext))
 		case BashCompCustom:
-			buf.WriteString(fmt.Sprintf("    flags_with_completion+=(%q)\n", name))
+			WriteStringAndCheck(buf, 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))
+				WriteStringAndCheck(buf, fmt.Sprintf("    flags_completion+=(%q)\n", handlers))
 			} else {
-				buf.WriteString("    flags_completion+=(:)\n")
+				WriteStringAndCheck(buf, "    flags_completion+=(:)\n")
 			}
 		case BashCompSubdirsInDir:
-			buf.WriteString(fmt.Sprintf("    flags_with_completion+=(%q)\n", name))
+			WriteStringAndCheck(buf, fmt.Sprintf("    flags_with_completion+=(%q)\n", name))
 
 			var ext string
 			if len(value) == 1 {
@@ -356,49 +465,70 @@
 			} else {
 				ext = "_filedir -d"
 			}
-			buf.WriteString(fmt.Sprintf("    flags_completion+=(%q)\n", ext))
+			WriteStringAndCheck(buf, fmt.Sprintf("    flags_completion+=(%q)\n", ext))
 		}
 	}
 }
 
-func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) {
+const cbn = "\")\n"
+
+func writeShortFlag(buf io.StringWriter, 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))
+	format += "flags+=(\"-%s" + cbn
+	WriteStringAndCheck(buf, fmt.Sprintf(format, name))
 	writeFlagHandler(buf, "-"+name, flag.Annotations, cmd)
 }
 
-func writeFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) {
+func writeFlag(buf io.StringWriter, 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))
+	format += cbn
+	WriteStringAndCheck(buf, fmt.Sprintf(format, name))
 	if len(flag.NoOptDefVal) == 0 {
-		format = "    two_word_flags+=(\"--%s\")\n"
-		buf.WriteString(fmt.Sprintf(format, name))
+		format = "    two_word_flags+=(\"--%s" + cbn
+		WriteStringAndCheck(buf, fmt.Sprintf(format, name))
 	}
 	writeFlagHandler(buf, "--"+name, flag.Annotations, cmd)
 }
 
-func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) {
+func writeLocalNonPersistentFlag(buf io.StringWriter, flag *pflag.Flag) {
 	name := flag.Name
-	format := "    local_nonpersistent_flags+=(\"--%s"
+	format := "    local_nonpersistent_flags+=(\"--%[1]s" + cbn
 	if len(flag.NoOptDefVal) == 0 {
-		format += "="
+		format += "    local_nonpersistent_flags+=(\"--%[1]s=" + cbn
 	}
-	format += "\")\n"
-	buf.WriteString(fmt.Sprintf(format, name))
+	WriteStringAndCheck(buf, fmt.Sprintf(format, name))
+	if len(flag.Shorthand) > 0 {
+		WriteStringAndCheck(buf, fmt.Sprintf("    local_nonpersistent_flags+=(\"-%s\")\n", flag.Shorthand))
+	}
 }
 
-func writeFlags(buf *bytes.Buffer, cmd *Command) {
-	buf.WriteString(`    flags=()
+// Setup annotations for go completions for registered flags
+func prepareCustomAnnotationsForFlags(cmd *Command) {
+	flagCompletionMutex.RLock()
+	defer flagCompletionMutex.RUnlock()
+	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 io.StringWriter, cmd *Command) {
+	prepareCustomAnnotationsForFlags(cmd)
+	WriteStringAndCheck(buf, `    flags=()
     two_word_flags=()
     local_nonpersistent_flags=()
     flags_with_completion=()
@@ -414,7 +544,9 @@
 		if len(flag.Shorthand) > 0 {
 			writeShortFlag(buf, flag, cmd)
 		}
-		if localNonPersistentFlags.Lookup(flag.Name) != nil {
+		// 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)
 		}
 	})
@@ -428,11 +560,11 @@
 		}
 	})
 
-	buf.WriteString("\n")
+	WriteStringAndCheck(buf, "\n")
 }
 
-func writeRequiredFlag(buf *bytes.Buffer, cmd *Command) {
-	buf.WriteString("    must_have_one_flag=()\n")
+func writeRequiredFlag(buf io.StringWriter, cmd *Command) {
+	WriteStringAndCheck(buf, "    must_have_one_flag=()\n")
 	flags := cmd.NonInheritedFlags()
 	flags.VisitAll(func(flag *pflag.Flag) {
 		if nonCompletableFlag(flag) {
@@ -445,51 +577,57 @@
 				if flag.Value.Type() != "bool" {
 					format += "="
 				}
-				format += "\")\n"
-				buf.WriteString(fmt.Sprintf(format, flag.Name))
+				format += cbn
+				WriteStringAndCheck(buf, fmt.Sprintf(format, flag.Name))
 
 				if len(flag.Shorthand) > 0 {
-					buf.WriteString(fmt.Sprintf("    must_have_one_flag+=(\"-%s\")\n", flag.Shorthand))
+					WriteStringAndCheck(buf, fmt.Sprintf("    must_have_one_flag+=(\"-%s"+cbn, flag.Shorthand))
 				}
 			}
 		}
 	})
 }
 
-func writeRequiredNouns(buf *bytes.Buffer, cmd *Command) {
-	buf.WriteString("    must_have_one_noun=()\n")
-	sort.Sort(sort.StringSlice(cmd.ValidArgs))
+func writeRequiredNouns(buf io.StringWriter, cmd *Command) {
+	WriteStringAndCheck(buf, "    must_have_one_noun=()\n")
+	sort.Strings(cmd.ValidArgs)
 	for _, value := range cmd.ValidArgs {
-		buf.WriteString(fmt.Sprintf("    must_have_one_noun+=(%q)\n", value))
+		// Remove any description that may be included following a tab character.
+		// Descriptions are not supported by bash completion.
+		value = strings.Split(value, "\t")[0]
+		WriteStringAndCheck(buf, fmt.Sprintf("    must_have_one_noun+=(%q)\n", value))
+	}
+	if cmd.ValidArgsFunction != nil {
+		WriteStringAndCheck(buf, "    has_completion_function=1\n")
 	}
 }
 
-func writeCmdAliases(buf *bytes.Buffer, cmd *Command) {
+func writeCmdAliases(buf io.StringWriter, cmd *Command) {
 	if len(cmd.Aliases) == 0 {
 		return
 	}
 
-	sort.Sort(sort.StringSlice(cmd.Aliases))
+	sort.Strings(cmd.Aliases)
 
-	buf.WriteString(fmt.Sprint(`    if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then`, "\n"))
+	WriteStringAndCheck(buf, 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()))
+		WriteStringAndCheck(buf, fmt.Sprintf("        command_aliases+=(%q)\n", value))
+		WriteStringAndCheck(buf, fmt.Sprintf("        aliashash[%q]=%q\n", value, cmd.Name()))
 	}
-	buf.WriteString(`    fi`)
-	buf.WriteString("\n")
+	WriteStringAndCheck(buf, `    fi`)
+	WriteStringAndCheck(buf, "\n")
 }
-func writeArgAliases(buf *bytes.Buffer, cmd *Command) {
-	buf.WriteString("    noun_aliases=()\n")
-	sort.Sort(sort.StringSlice(cmd.ArgAliases))
+func writeArgAliases(buf io.StringWriter, cmd *Command) {
+	WriteStringAndCheck(buf, "    noun_aliases=()\n")
+	sort.Strings(cmd.ArgAliases)
 	for _, value := range cmd.ArgAliases {
-		buf.WriteString(fmt.Sprintf("    noun_aliases+=(%q)\n", value))
+		WriteStringAndCheck(buf, fmt.Sprintf("    noun_aliases+=(%q)\n", value))
 	}
 }
 
-func gen(buf *bytes.Buffer, cmd *Command) {
+func gen(buf io.StringWriter, cmd *Command) {
 	for _, c := range cmd.Commands() {
-		if !c.IsAvailableCommand() || c == cmd.helpCommand {
+		if !c.IsAvailableCommand() && c != cmd.helpCommand {
 			continue
 		}
 		gen(buf, c)
@@ -499,22 +637,22 @@
 	commandName = strings.Replace(commandName, ":", "__", -1)
 
 	if cmd.Root() == cmd {
-		buf.WriteString(fmt.Sprintf("_%s_root_command()\n{\n", commandName))
+		WriteStringAndCheck(buf, fmt.Sprintf("_%s_root_command()\n{\n", commandName))
 	} else {
-		buf.WriteString(fmt.Sprintf("_%s()\n{\n", commandName))
+		WriteStringAndCheck(buf, 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")
+	WriteStringAndCheck(buf, fmt.Sprintf("    last_command=%q\n", commandName))
+	WriteStringAndCheck(buf, "\n")
+	WriteStringAndCheck(buf, "    command_aliases=()\n")
+	WriteStringAndCheck(buf, "\n")
 
 	writeCommands(buf, cmd)
 	writeFlags(buf, cmd)
 	writeRequiredFlag(buf, cmd)
 	writeRequiredNouns(buf, cmd)
 	writeArgAliases(buf, cmd)
-	buf.WriteString("}\n\n")
+	WriteStringAndCheck(buf, "}\n\n")
 }
 
 // GenBashCompletion generates bash completion file and writes to the passed writer.