mirror of
https://github.com/spf13/cobra
synced 2025-04-07 07:19:16 +00:00
Merge branch 'spf13:main' into patch-1
This commit is contained in:
commit
8d0e71e82e
18 changed files with 935 additions and 241 deletions
1
.github/workflows/test.yml
vendored
1
.github/workflows/test.yml
vendored
|
@ -68,6 +68,7 @@ jobs:
|
||||||
- 21
|
- 21
|
||||||
- 22
|
- 22
|
||||||
- 23
|
- 23
|
||||||
|
- 24
|
||||||
name: '${{ matrix.platform }} | 1.${{ matrix.go }}.x'
|
name: '${{ matrix.platform }} | 1.${{ matrix.go }}.x'
|
||||||
runs-on: ${{ matrix.platform }}-latest
|
runs-on: ${{ matrix.platform }}-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||

|
|
||||||
|

|
||||||
|
|
||||||
Cobra is a library for creating powerful modern CLI applications.
|
Cobra is a library for creating powerful modern CLI applications.
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ const (
|
||||||
// This function can be called multiple times before and/or after completions are added to
|
// This function can be called multiple times before and/or after completions are added to
|
||||||
// the array. Each time this function is called with the same array, the new
|
// the array. Each time this function is called with the same array, the new
|
||||||
// ActiveHelp line will be shown below the previous ones when completion is triggered.
|
// ActiveHelp line will be shown below the previous ones when completion is triggered.
|
||||||
func AppendActiveHelp(compArray []string, activeHelpStr string) []string {
|
func AppendActiveHelp(compArray []Completion, activeHelpStr string) []Completion {
|
||||||
return append(compArray, fmt.Sprintf("%s%s", activeHelpMarker, activeHelpStr))
|
return append(compArray, fmt.Sprintf("%s%s", activeHelpMarker, activeHelpStr))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -146,7 +146,7 @@ __%[1]s_process_completion_results() {
|
||||||
|
|
||||||
if (((directive & shellCompDirectiveFilterFileExt) != 0)); then
|
if (((directive & shellCompDirectiveFilterFileExt) != 0)); then
|
||||||
# File extension filtering
|
# File extension filtering
|
||||||
local fullFilter filter filteringCmd
|
local fullFilter="" filter filteringCmd
|
||||||
|
|
||||||
# Do not use quotes around the $completions variable or else newline
|
# Do not use quotes around the $completions variable or else newline
|
||||||
# characters will be kept.
|
# characters will be kept.
|
||||||
|
@ -177,20 +177,71 @@ __%[1]s_process_completion_results() {
|
||||||
__%[1]s_handle_special_char "$cur" =
|
__%[1]s_handle_special_char "$cur" =
|
||||||
|
|
||||||
# Print the activeHelp statements before we finish
|
# Print the activeHelp statements before we finish
|
||||||
if ((${#activeHelp[*]} != 0)); then
|
__%[1]s_handle_activeHelp
|
||||||
printf "\n";
|
}
|
||||||
printf "%%s\n" "${activeHelp[@]}"
|
|
||||||
printf "\n"
|
|
||||||
|
|
||||||
# The prompt format is only available from bash 4.4.
|
__%[1]s_handle_activeHelp() {
|
||||||
# We test if it is available before using it.
|
# Print the activeHelp statements
|
||||||
if (x=${PS1@P}) 2> /dev/null; then
|
if ((${#activeHelp[*]} != 0)); then
|
||||||
printf "%%s" "${PS1@P}${COMP_LINE[@]}"
|
if [ -z $COMP_TYPE ]; then
|
||||||
else
|
# Bash v3 does not set the COMP_TYPE variable.
|
||||||
# Can't print the prompt. Just print the
|
printf "\n";
|
||||||
# text the user had typed, it is workable enough.
|
printf "%%s\n" "${activeHelp[@]}"
|
||||||
printf "%%s" "${COMP_LINE[@]}"
|
printf "\n"
|
||||||
|
__%[1]s_reprint_commandLine
|
||||||
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Only print ActiveHelp on the second TAB press
|
||||||
|
if [ $COMP_TYPE -eq 63 ]; then
|
||||||
|
printf "\n"
|
||||||
|
printf "%%s\n" "${activeHelp[@]}"
|
||||||
|
|
||||||
|
if ((${#COMPREPLY[*]} == 0)); then
|
||||||
|
# When there are no completion choices from the program, file completion
|
||||||
|
# may kick in if the program has not disabled it; in such a case, we want
|
||||||
|
# to know if any files will match what the user typed, so that we know if
|
||||||
|
# there will be completions presented, so that we know how to handle ActiveHelp.
|
||||||
|
# To find out, we actually trigger the file completion ourselves;
|
||||||
|
# the call to _filedir will fill COMPREPLY if files match.
|
||||||
|
if (((directive & shellCompDirectiveNoFileComp) == 0)); then
|
||||||
|
__%[1]s_debug "Listing files"
|
||||||
|
_filedir
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ((${#COMPREPLY[*]} != 0)); then
|
||||||
|
# If there are completion choices to be shown, print a delimiter.
|
||||||
|
# Re-printing the command-line will automatically be done
|
||||||
|
# by the shell when it prints the completion choices.
|
||||||
|
printf -- "--"
|
||||||
|
else
|
||||||
|
# When there are no completion choices at all, we need
|
||||||
|
# to re-print the command-line since the shell will
|
||||||
|
# not be doing it itself.
|
||||||
|
__%[1]s_reprint_commandLine
|
||||||
|
fi
|
||||||
|
elif [ $COMP_TYPE -eq 37 ] || [ $COMP_TYPE -eq 42 ]; then
|
||||||
|
# For completion type: menu-complete/menu-complete-backward and insert-completions
|
||||||
|
# the completions are immediately inserted into the command-line, so we first
|
||||||
|
# print the activeHelp message and reprint the command-line since the shell won't.
|
||||||
|
printf "\n"
|
||||||
|
printf "%%s\n" "${activeHelp[@]}"
|
||||||
|
|
||||||
|
__%[1]s_reprint_commandLine
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
__%[1]s_reprint_commandLine() {
|
||||||
|
# The prompt format is only available from bash 4.4.
|
||||||
|
# We test if it is available before using it.
|
||||||
|
if (x=${PS1@P}) 2> /dev/null; then
|
||||||
|
printf "%%s" "${PS1@P}${COMP_LINE[@]}"
|
||||||
|
else
|
||||||
|
# Can't print the prompt. Just print the
|
||||||
|
# text the user had typed, it is workable enough.
|
||||||
|
printf "%%s" "${COMP_LINE[@]}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,6 +252,8 @@ __%[1]s_extract_activeHelp() {
|
||||||
local endIndex=${#activeHelpMarker}
|
local endIndex=${#activeHelpMarker}
|
||||||
|
|
||||||
while IFS='' read -r comp; do
|
while IFS='' read -r comp; do
|
||||||
|
[[ -z $comp ]] && continue
|
||||||
|
|
||||||
if [[ ${comp:0:endIndex} == $activeHelpMarker ]]; then
|
if [[ ${comp:0:endIndex} == $activeHelpMarker ]]; then
|
||||||
comp=${comp:endIndex}
|
comp=${comp:endIndex}
|
||||||
__%[1]s_debug "ActiveHelp found: $comp"
|
__%[1]s_debug "ActiveHelp found: $comp"
|
||||||
|
@ -223,16 +276,21 @@ __%[1]s_handle_completion_types() {
|
||||||
# If the user requested inserting one completion at a time, or all
|
# If the user requested inserting one completion at a time, or all
|
||||||
# completions at once on the command-line we must remove the descriptions.
|
# completions at once on the command-line we must remove the descriptions.
|
||||||
# https://github.com/spf13/cobra/issues/1508
|
# https://github.com/spf13/cobra/issues/1508
|
||||||
local tab=$'\t' comp
|
|
||||||
while IFS='' read -r comp; do
|
# If there are no completions, we don't need to do anything
|
||||||
[[ -z $comp ]] && continue
|
(( ${#completions[@]} == 0 )) && return 0
|
||||||
# Strip any description
|
|
||||||
comp=${comp%%%%$tab*}
|
local tab=$'\t'
|
||||||
# Only consider the completions that match
|
|
||||||
if [[ $comp == "$cur"* ]]; then
|
# Strip any description and escape the completion to handled special characters
|
||||||
COMPREPLY+=("$comp")
|
IFS=$'\n' read -ra completions -d '' < <(printf "%%q\n" "${completions[@]%%%%$tab*}")
|
||||||
fi
|
|
||||||
done < <(printf "%%s\n" "${completions[@]}")
|
# Only consider the completions that match
|
||||||
|
IFS=$'\n' read -ra COMPREPLY -d '' < <(IFS=$'\n'; compgen -W "${completions[*]}" -- "${cur}")
|
||||||
|
|
||||||
|
# compgen looses the escaping so we need to escape all completions again since they will
|
||||||
|
# all be inserted on the command-line.
|
||||||
|
IFS=$'\n' read -ra COMPREPLY -d '' < <(printf "%%q\n" "${COMPREPLY[@]}")
|
||||||
;;
|
;;
|
||||||
|
|
||||||
*)
|
*)
|
||||||
|
@ -243,11 +301,25 @@ __%[1]s_handle_completion_types() {
|
||||||
}
|
}
|
||||||
|
|
||||||
__%[1]s_handle_standard_completion_case() {
|
__%[1]s_handle_standard_completion_case() {
|
||||||
local tab=$'\t' comp
|
local tab=$'\t'
|
||||||
|
|
||||||
|
# If there are no completions, we don't need to do anything
|
||||||
|
(( ${#completions[@]} == 0 )) && return 0
|
||||||
|
|
||||||
# Short circuit to optimize if we don't have descriptions
|
# Short circuit to optimize if we don't have descriptions
|
||||||
if [[ "${completions[*]}" != *$tab* ]]; then
|
if [[ "${completions[*]}" != *$tab* ]]; then
|
||||||
IFS=$'\n' read -ra COMPREPLY -d '' < <(compgen -W "${completions[*]}" -- "$cur")
|
# First, escape the completions to handle special characters
|
||||||
|
IFS=$'\n' read -ra completions -d '' < <(printf "%%q\n" "${completions[@]}")
|
||||||
|
# Only consider the completions that match what the user typed
|
||||||
|
IFS=$'\n' read -ra COMPREPLY -d '' < <(IFS=$'\n'; compgen -W "${completions[*]}" -- "${cur}")
|
||||||
|
|
||||||
|
# compgen looses the escaping so, if there is only a single completion, we need to
|
||||||
|
# escape it again because it will be inserted on the command-line. If there are multiple
|
||||||
|
# completions, we don't want to escape them because they will be printed in a list
|
||||||
|
# and we don't want to show escape characters in that list.
|
||||||
|
if (( ${#COMPREPLY[@]} == 1 )); then
|
||||||
|
COMPREPLY[0]=$(printf "%%q" "${COMPREPLY[0]}")
|
||||||
|
fi
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -256,23 +328,39 @@ __%[1]s_handle_standard_completion_case() {
|
||||||
# Look for the longest completion so that we can format things nicely
|
# Look for the longest completion so that we can format things nicely
|
||||||
while IFS='' read -r compline; do
|
while IFS='' read -r compline; do
|
||||||
[[ -z $compline ]] && continue
|
[[ -z $compline ]] && continue
|
||||||
# Strip any description before checking the length
|
|
||||||
comp=${compline%%%%$tab*}
|
# Before checking if the completion matches what the user typed,
|
||||||
|
# we need to strip any description and escape the completion to handle special
|
||||||
|
# characters because those escape characters are part of what the user typed.
|
||||||
|
# Don't call "printf" in a sub-shell because it will be much slower
|
||||||
|
# since we are in a loop.
|
||||||
|
printf -v comp "%%q" "${compline%%%%$tab*}" &>/dev/null || comp=$(printf "%%q" "${compline%%%%$tab*}")
|
||||||
|
|
||||||
# Only consider the completions that match
|
# Only consider the completions that match
|
||||||
[[ $comp == "$cur"* ]] || continue
|
[[ $comp == "$cur"* ]] || continue
|
||||||
|
|
||||||
|
# The completions matches. Add it to the list of full completions including
|
||||||
|
# its description. We don't escape the completion because it may get printed
|
||||||
|
# in a list if there are more than one and we don't want show escape characters
|
||||||
|
# in that list.
|
||||||
COMPREPLY+=("$compline")
|
COMPREPLY+=("$compline")
|
||||||
|
|
||||||
|
# Strip any description before checking the length, and again, don't escape
|
||||||
|
# the completion because this length is only used when printing the completions
|
||||||
|
# in a list and we don't want show escape characters in that list.
|
||||||
|
comp=${compline%%%%$tab*}
|
||||||
if ((${#comp}>longest)); then
|
if ((${#comp}>longest)); then
|
||||||
longest=${#comp}
|
longest=${#comp}
|
||||||
fi
|
fi
|
||||||
done < <(printf "%%s\n" "${completions[@]}")
|
done < <(printf "%%s\n" "${completions[@]}")
|
||||||
|
|
||||||
# If there is a single completion left, remove the description text
|
# If there is a single completion left, remove the description text and escape any special characters
|
||||||
if ((${#COMPREPLY[*]} == 1)); then
|
if ((${#COMPREPLY[*]} == 1)); then
|
||||||
__%[1]s_debug "COMPREPLY[0]: ${COMPREPLY[0]}"
|
__%[1]s_debug "COMPREPLY[0]: ${COMPREPLY[0]}"
|
||||||
comp="${COMPREPLY[0]%%%%$tab*}"
|
COMPREPLY[0]=$(printf "%%q" "${COMPREPLY[0]%%%%$tab*}")
|
||||||
__%[1]s_debug "Removed description from single completion, which is now: ${comp}"
|
__%[1]s_debug "Removed description from single completion, which is now: ${COMPREPLY[0]}"
|
||||||
COMPREPLY[0]=$comp
|
else
|
||||||
else # Format the descriptions
|
# Format the descriptions
|
||||||
__%[1]s_format_comp_descriptions $longest
|
__%[1]s_format_comp_descriptions $longest
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
16
cobra.go
16
cobra.go
|
@ -176,12 +176,16 @@ func rpad(s string, padding int) string {
|
||||||
return fmt.Sprintf(formattedString, s)
|
return fmt.Sprintf(formattedString, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// tmpl executes the given template text on data, writing the result to w.
|
func tmpl(text string) *tmplFunc {
|
||||||
func tmpl(w io.Writer, text string, data interface{}) error {
|
return &tmplFunc{
|
||||||
t := template.New("top")
|
tmpl: text,
|
||||||
t.Funcs(templateFuncs)
|
fn: func(w io.Writer, data interface{}) error {
|
||||||
template.Must(t.Parse(text))
|
t := template.New("top")
|
||||||
return t.Execute(w, data)
|
t.Funcs(templateFuncs)
|
||||||
|
template.Must(t.Parse(text))
|
||||||
|
return t.Execute(w, data)
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ld compares two strings and returns the levenshtein distance between them.
|
// ld compares two strings and returns the levenshtein distance between them.
|
||||||
|
|
|
@ -15,6 +15,11 @@
|
||||||
package cobra
|
package cobra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
|
@ -222,3 +227,75 @@ func TestRpad(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestDeadcodeElimination checks that a simple program using cobra in its
|
||||||
|
// default configuration is linked taking full advantage of the linker's
|
||||||
|
// deadcode elimination step.
|
||||||
|
//
|
||||||
|
// If reflect.Value.MethodByName/reflect.Value.Method are reachable the
|
||||||
|
// linker will not always be able to prove that exported methods are
|
||||||
|
// unreachable, making deadcode elimination less effective. Using
|
||||||
|
// text/template and html/template makes reflect.Value.MethodByName
|
||||||
|
// reachable.
|
||||||
|
// Since cobra can use text/template templates this test checks that in its
|
||||||
|
// default configuration that code path can be proven to be unreachable by
|
||||||
|
// the linker.
|
||||||
|
//
|
||||||
|
// See also: https://github.com/spf13/cobra/pull/1956
|
||||||
|
func TestDeadcodeElimination(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("go tool nm fails on windows")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that a simple program using cobra in its default configuration is
|
||||||
|
// linked with deadcode elimination enabled.
|
||||||
|
const (
|
||||||
|
dirname = "test_deadcode"
|
||||||
|
progname = "test_deadcode_elimination"
|
||||||
|
)
|
||||||
|
_ = os.Mkdir(dirname, 0770)
|
||||||
|
defer os.RemoveAll(dirname)
|
||||||
|
filename := filepath.Join(dirname, progname+".go")
|
||||||
|
err := os.WriteFile(filename, []byte(`package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Version: "1.0",
|
||||||
|
Use: "example_program",
|
||||||
|
Short: "example_program - test fixture to check that deadcode elimination is allowed",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
fmt.Println("hello world")
|
||||||
|
},
|
||||||
|
Aliases: []string{"alias1", "alias2"},
|
||||||
|
Example: "stringer --help",
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Whoops. There was an error while executing your CLI '%s'", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`), 0600)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not write test program: %v", err)
|
||||||
|
}
|
||||||
|
buf, err := exec.Command("go", "build", filename).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not compile test program: %s", string(buf))
|
||||||
|
}
|
||||||
|
defer os.Remove(progname)
|
||||||
|
buf, err = exec.Command("go", "tool", "nm", progname).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not run go tool nm: %v", err)
|
||||||
|
}
|
||||||
|
if strings.Contains(string(buf), "MethodByName") {
|
||||||
|
t.Error("compiled programs contains MethodByName symbol")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
314
command.go
314
command.go
|
@ -23,6 +23,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -32,6 +33,9 @@ import (
|
||||||
const (
|
const (
|
||||||
FlagSetByCobraAnnotation = "cobra_annotation_flag_set_by_cobra"
|
FlagSetByCobraAnnotation = "cobra_annotation_flag_set_by_cobra"
|
||||||
CommandDisplayNameAnnotation = "cobra_annotation_command_display_name"
|
CommandDisplayNameAnnotation = "cobra_annotation_command_display_name"
|
||||||
|
|
||||||
|
helpFlagName = "help"
|
||||||
|
helpCommandName = "help"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FParseErrWhitelist configures Flag parse errors to be ignored
|
// FParseErrWhitelist configures Flag parse errors to be ignored
|
||||||
|
@ -79,11 +83,11 @@ type Command struct {
|
||||||
Example string
|
Example string
|
||||||
|
|
||||||
// ValidArgs is list of all valid non-flag arguments that are accepted in shell completions
|
// ValidArgs is list of all valid non-flag arguments that are accepted in shell completions
|
||||||
ValidArgs []string
|
ValidArgs []Completion
|
||||||
// ValidArgsFunction is an optional function that provides valid non-flag arguments for shell completion.
|
// ValidArgsFunction is an optional function that provides valid non-flag arguments for shell completion.
|
||||||
// It is a dynamic version of using ValidArgs.
|
// It is a dynamic version of using ValidArgs.
|
||||||
// Only one of ValidArgs and ValidArgsFunction can be used for a command.
|
// Only one of ValidArgs and ValidArgsFunction can be used for a command.
|
||||||
ValidArgsFunction func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
|
ValidArgsFunction CompletionFunc
|
||||||
|
|
||||||
// Expected arguments
|
// Expected arguments
|
||||||
Args PositionalArgs
|
Args PositionalArgs
|
||||||
|
@ -167,12 +171,12 @@ type Command struct {
|
||||||
// usageFunc is usage func defined by user.
|
// usageFunc is usage func defined by user.
|
||||||
usageFunc func(*Command) error
|
usageFunc func(*Command) error
|
||||||
// usageTemplate is usage template defined by user.
|
// usageTemplate is usage template defined by user.
|
||||||
usageTemplate string
|
usageTemplate *tmplFunc
|
||||||
// flagErrorFunc is func defined by user and it's called when the parsing of
|
// flagErrorFunc is func defined by user and it's called when the parsing of
|
||||||
// flags returns an error.
|
// flags returns an error.
|
||||||
flagErrorFunc func(*Command, error) error
|
flagErrorFunc func(*Command, error) error
|
||||||
// helpTemplate is help template defined by user.
|
// helpTemplate is help template defined by user.
|
||||||
helpTemplate string
|
helpTemplate *tmplFunc
|
||||||
// helpFunc is help func defined by user.
|
// helpFunc is help func defined by user.
|
||||||
helpFunc func(*Command, []string)
|
helpFunc func(*Command, []string)
|
||||||
// helpCommand is command with usage 'help'. If it's not defined by user,
|
// helpCommand is command with usage 'help'. If it's not defined by user,
|
||||||
|
@ -185,7 +189,7 @@ type Command struct {
|
||||||
completionCommandGroupID string
|
completionCommandGroupID string
|
||||||
|
|
||||||
// versionTemplate is the version template defined by user.
|
// versionTemplate is the version template defined by user.
|
||||||
versionTemplate string
|
versionTemplate *tmplFunc
|
||||||
|
|
||||||
// errPrefix is the error message prefix defined by user.
|
// errPrefix is the error message prefix defined by user.
|
||||||
errPrefix string
|
errPrefix string
|
||||||
|
@ -312,7 +316,11 @@ func (c *Command) SetUsageFunc(f func(*Command) error) {
|
||||||
|
|
||||||
// SetUsageTemplate sets usage template. Can be defined by Application.
|
// SetUsageTemplate sets usage template. Can be defined by Application.
|
||||||
func (c *Command) SetUsageTemplate(s string) {
|
func (c *Command) SetUsageTemplate(s string) {
|
||||||
c.usageTemplate = s
|
if s == "" {
|
||||||
|
c.usageTemplate = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.usageTemplate = tmpl(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetFlagErrorFunc sets a function to generate an error when flag parsing
|
// SetFlagErrorFunc sets a function to generate an error when flag parsing
|
||||||
|
@ -348,12 +356,20 @@ func (c *Command) SetCompletionCommandGroupID(groupID string) {
|
||||||
|
|
||||||
// SetHelpTemplate sets help template to be used. Application can use it to set custom template.
|
// SetHelpTemplate sets help template to be used. Application can use it to set custom template.
|
||||||
func (c *Command) SetHelpTemplate(s string) {
|
func (c *Command) SetHelpTemplate(s string) {
|
||||||
c.helpTemplate = s
|
if s == "" {
|
||||||
|
c.helpTemplate = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.helpTemplate = tmpl(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetVersionTemplate sets version template to be used. Application can use it to set custom template.
|
// SetVersionTemplate sets version template to be used. Application can use it to set custom template.
|
||||||
func (c *Command) SetVersionTemplate(s string) {
|
func (c *Command) SetVersionTemplate(s string) {
|
||||||
c.versionTemplate = s
|
if s == "" {
|
||||||
|
c.versionTemplate = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.versionTemplate = tmpl(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetErrPrefix sets error message prefix to be used. Application can use it to set custom prefix.
|
// SetErrPrefix sets error message prefix to be used. Application can use it to set custom prefix.
|
||||||
|
@ -434,7 +450,8 @@ func (c *Command) UsageFunc() (f func(*Command) error) {
|
||||||
}
|
}
|
||||||
return func(c *Command) error {
|
return func(c *Command) error {
|
||||||
c.mergePersistentFlags()
|
c.mergePersistentFlags()
|
||||||
err := tmpl(c.OutOrStderr(), c.UsageTemplate(), c)
|
fn := c.getUsageTemplateFunc()
|
||||||
|
err := fn(c.OutOrStderr(), c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.PrintErrln(err)
|
c.PrintErrln(err)
|
||||||
}
|
}
|
||||||
|
@ -442,6 +459,19 @@ func (c *Command) UsageFunc() (f func(*Command) error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getUsageTemplateFunc returns the usage template function for the command
|
||||||
|
// going up the command tree if necessary.
|
||||||
|
func (c *Command) getUsageTemplateFunc() func(w io.Writer, data interface{}) error {
|
||||||
|
if c.usageTemplate != nil {
|
||||||
|
return c.usageTemplate.fn
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.HasParent() {
|
||||||
|
return c.parent.getUsageTemplateFunc()
|
||||||
|
}
|
||||||
|
return defaultUsageFunc
|
||||||
|
}
|
||||||
|
|
||||||
// Usage puts out the usage for the command.
|
// Usage puts out the usage for the command.
|
||||||
// Used when a user provides invalid input.
|
// Used when a user provides invalid input.
|
||||||
// Can be defined by user by overriding UsageFunc.
|
// Can be defined by user by overriding UsageFunc.
|
||||||
|
@ -460,15 +490,30 @@ func (c *Command) HelpFunc() func(*Command, []string) {
|
||||||
}
|
}
|
||||||
return func(c *Command, a []string) {
|
return func(c *Command, a []string) {
|
||||||
c.mergePersistentFlags()
|
c.mergePersistentFlags()
|
||||||
|
fn := c.getHelpTemplateFunc()
|
||||||
// The help should be sent to stdout
|
// The help should be sent to stdout
|
||||||
// See https://github.com/spf13/cobra/issues/1002
|
// See https://github.com/spf13/cobra/issues/1002
|
||||||
err := tmpl(c.OutOrStdout(), c.HelpTemplate(), c)
|
err := fn(c.OutOrStdout(), c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.PrintErrln(err)
|
c.PrintErrln(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getHelpTemplateFunc returns the help template function for the command
|
||||||
|
// going up the command tree if necessary.
|
||||||
|
func (c *Command) getHelpTemplateFunc() func(w io.Writer, data interface{}) error {
|
||||||
|
if c.helpTemplate != nil {
|
||||||
|
return c.helpTemplate.fn
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.HasParent() {
|
||||||
|
return c.parent.getHelpTemplateFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultHelpFunc
|
||||||
|
}
|
||||||
|
|
||||||
// Help puts out the help for the command.
|
// Help puts out the help for the command.
|
||||||
// Used when a user calls help [command].
|
// Used when a user calls help [command].
|
||||||
// Can be defined by user by overriding HelpFunc.
|
// Can be defined by user by overriding HelpFunc.
|
||||||
|
@ -543,71 +588,55 @@ func (c *Command) NamePadding() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UsageTemplate returns usage template for the command.
|
// UsageTemplate returns usage template for the command.
|
||||||
|
// This function is kept for backwards-compatibility reasons.
|
||||||
func (c *Command) UsageTemplate() string {
|
func (c *Command) UsageTemplate() string {
|
||||||
if c.usageTemplate != "" {
|
if c.usageTemplate != nil {
|
||||||
return c.usageTemplate
|
return c.usageTemplate.tmpl
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.HasParent() {
|
if c.HasParent() {
|
||||||
return c.parent.UsageTemplate()
|
return c.parent.UsageTemplate()
|
||||||
}
|
}
|
||||||
return `Usage:{{if .Runnable}}
|
return defaultUsageTemplate
|
||||||
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
|
|
||||||
{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
|
|
||||||
|
|
||||||
Aliases:
|
|
||||||
{{.NameAndAliases}}{{end}}{{if .HasExample}}
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}{{$cmds := .Commands}}{{if eq (len .Groups) 0}}
|
|
||||||
|
|
||||||
Available Commands:{{range $cmds}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
|
|
||||||
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{else}}{{range $group := .Groups}}
|
|
||||||
|
|
||||||
{{.Title}}{{range $cmds}}{{if (and (eq .GroupID $group.ID) (or .IsAvailableCommand (eq .Name "help")))}}
|
|
||||||
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if not .AllChildCommandsHaveGroup}}
|
|
||||||
|
|
||||||
Additional Commands:{{range $cmds}}{{if (and (eq .GroupID "") (or .IsAvailableCommand (eq .Name "help")))}}
|
|
||||||
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
|
|
||||||
|
|
||||||
Flags:
|
|
||||||
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
|
|
||||||
|
|
||||||
Global Flags:
|
|
||||||
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}
|
|
||||||
|
|
||||||
Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
|
|
||||||
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}
|
|
||||||
|
|
||||||
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
|
|
||||||
`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HelpTemplate return help template for the command.
|
// HelpTemplate return help template for the command.
|
||||||
|
// This function is kept for backwards-compatibility reasons.
|
||||||
func (c *Command) HelpTemplate() string {
|
func (c *Command) HelpTemplate() string {
|
||||||
if c.helpTemplate != "" {
|
if c.helpTemplate != nil {
|
||||||
return c.helpTemplate
|
return c.helpTemplate.tmpl
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.HasParent() {
|
if c.HasParent() {
|
||||||
return c.parent.HelpTemplate()
|
return c.parent.HelpTemplate()
|
||||||
}
|
}
|
||||||
return `{{with (or .Long .Short)}}{{. | trimTrailingWhitespaces}}
|
return defaultHelpTemplate
|
||||||
|
|
||||||
{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// VersionTemplate return version template for the command.
|
// VersionTemplate return version template for the command.
|
||||||
|
// This function is kept for backwards-compatibility reasons.
|
||||||
func (c *Command) VersionTemplate() string {
|
func (c *Command) VersionTemplate() string {
|
||||||
if c.versionTemplate != "" {
|
if c.versionTemplate != nil {
|
||||||
return c.versionTemplate
|
return c.versionTemplate.tmpl
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.HasParent() {
|
if c.HasParent() {
|
||||||
return c.parent.VersionTemplate()
|
return c.parent.VersionTemplate()
|
||||||
}
|
}
|
||||||
return `{{with .DisplayName}}{{printf "%s " .}}{{end}}{{printf "version %s" .Version}}
|
return defaultVersionTemplate
|
||||||
`
|
}
|
||||||
|
|
||||||
|
// getVersionTemplateFunc returns the version template function for the command
|
||||||
|
// going up the command tree if necessary.
|
||||||
|
func (c *Command) getVersionTemplateFunc() func(w io.Writer, data interface{}) error {
|
||||||
|
if c.versionTemplate != nil {
|
||||||
|
return c.versionTemplate.fn
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.HasParent() {
|
||||||
|
return c.parent.getVersionTemplateFunc()
|
||||||
|
}
|
||||||
|
return defaultVersionFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrPrefix return error message prefix for the command
|
// ErrPrefix return error message prefix for the command
|
||||||
|
@ -894,7 +923,7 @@ func (c *Command) execute(a []string) (err error) {
|
||||||
|
|
||||||
// If help is called, regardless of other flags, return we want help.
|
// If help is called, regardless of other flags, return we want help.
|
||||||
// Also say we need help if the command isn't runnable.
|
// Also say we need help if the command isn't runnable.
|
||||||
helpVal, err := c.Flags().GetBool("help")
|
helpVal, err := c.Flags().GetBool(helpFlagName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// should be impossible to get here as we always declare a help
|
// should be impossible to get here as we always declare a help
|
||||||
// flag in InitDefaultHelpFlag()
|
// flag in InitDefaultHelpFlag()
|
||||||
|
@ -914,7 +943,8 @@ func (c *Command) execute(a []string) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if versionVal {
|
if versionVal {
|
||||||
err := tmpl(c.OutOrStdout(), c.VersionTemplate(), c)
|
fn := c.getVersionTemplateFunc()
|
||||||
|
err := fn(c.OutOrStdout(), c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Println(err)
|
c.Println(err)
|
||||||
}
|
}
|
||||||
|
@ -1068,26 +1098,24 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
|
||||||
|
|
||||||
// initialize help at the last point to allow for user overriding
|
// initialize help at the last point to allow for user overriding
|
||||||
c.InitDefaultHelpCmd()
|
c.InitDefaultHelpCmd()
|
||||||
// initialize completion at the last point to allow for user overriding
|
|
||||||
c.InitDefaultCompletionCmd()
|
args := c.args
|
||||||
|
|
||||||
|
// Workaround FAIL with "go test -v" or "cobra.test -test.v", see #155
|
||||||
|
if c.args == nil && filepath.Base(os.Args[0]) != "cobra.test" {
|
||||||
|
args = os.Args[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize the __complete command to be used for shell completion
|
||||||
|
c.initCompleteCmd(args)
|
||||||
|
|
||||||
|
// initialize the default completion command
|
||||||
|
c.InitDefaultCompletionCmd(args...)
|
||||||
|
|
||||||
// Now that all commands have been created, let's make sure all groups
|
// Now that all commands have been created, let's make sure all groups
|
||||||
// are properly created also
|
// are properly created also
|
||||||
c.checkCommandGroups()
|
c.checkCommandGroups()
|
||||||
|
|
||||||
args := c.args
|
|
||||||
|
|
||||||
// If running unit tests, we don't want to take the os.Args, see #155 and #2173.
|
|
||||||
// For example, the following would fail:
|
|
||||||
// go test -c -o foo.test
|
|
||||||
// ./foo.test -test.run TestNoArgs
|
|
||||||
if c.args == nil && !isTesting() {
|
|
||||||
args = os.Args[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize the hidden command to be used for shell completion
|
|
||||||
c.initCompleteCmd(args)
|
|
||||||
|
|
||||||
var flags []string
|
var flags []string
|
||||||
if c.TraverseChildren {
|
if c.TraverseChildren {
|
||||||
cmd, flags, err = c.Traverse(args)
|
cmd, flags, err = c.Traverse(args)
|
||||||
|
@ -1190,7 +1218,7 @@ func (c *Command) checkCommandGroups() {
|
||||||
// If c already has help flag, it will do nothing.
|
// If c already has help flag, it will do nothing.
|
||||||
func (c *Command) InitDefaultHelpFlag() {
|
func (c *Command) InitDefaultHelpFlag() {
|
||||||
c.mergePersistentFlags()
|
c.mergePersistentFlags()
|
||||||
if c.Flags().Lookup("help") == nil {
|
if c.Flags().Lookup(helpFlagName) == nil {
|
||||||
usage := "help for "
|
usage := "help for "
|
||||||
name := c.DisplayName()
|
name := c.DisplayName()
|
||||||
if name == "" {
|
if name == "" {
|
||||||
|
@ -1198,8 +1226,8 @@ func (c *Command) InitDefaultHelpFlag() {
|
||||||
} else {
|
} else {
|
||||||
usage += name
|
usage += name
|
||||||
}
|
}
|
||||||
c.Flags().BoolP("help", "h", false, usage)
|
c.Flags().BoolP(helpFlagName, "h", false, usage)
|
||||||
_ = c.Flags().SetAnnotation("help", FlagSetByCobraAnnotation, []string{"true"})
|
_ = c.Flags().SetAnnotation(helpFlagName, FlagSetByCobraAnnotation, []string{"true"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1243,8 +1271,8 @@ func (c *Command) InitDefaultHelpCmd() {
|
||||||
Short: "Help about any command",
|
Short: "Help about any command",
|
||||||
Long: `Help provides help for any command in the application.
|
Long: `Help provides help for any command in the application.
|
||||||
Simply type ` + c.DisplayName() + ` help [path to command] for full details.`,
|
Simply type ` + c.DisplayName() + ` help [path to command] for full details.`,
|
||||||
ValidArgsFunction: func(c *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
ValidArgsFunction: func(c *Command, args []string, toComplete string) ([]Completion, ShellCompDirective) {
|
||||||
var completions []string
|
var completions []Completion
|
||||||
cmd, _, e := c.Root().Find(args)
|
cmd, _, e := c.Root().Find(args)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return nil, ShellCompDirectiveNoFileComp
|
return nil, ShellCompDirectiveNoFileComp
|
||||||
|
@ -1256,7 +1284,7 @@ Simply type ` + c.DisplayName() + ` help [path to command] for full details.`,
|
||||||
for _, subCmd := range cmd.Commands() {
|
for _, subCmd := range cmd.Commands() {
|
||||||
if subCmd.IsAvailableCommand() || subCmd == cmd.helpCommand {
|
if subCmd.IsAvailableCommand() || subCmd == cmd.helpCommand {
|
||||||
if strings.HasPrefix(subCmd.Name(), toComplete) {
|
if strings.HasPrefix(subCmd.Name(), toComplete) {
|
||||||
completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short))
|
completions = append(completions, CompletionWithDesc(subCmd.Name(), subCmd.Short))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1899,3 +1927,141 @@ func commandNameMatches(s string, t string) bool {
|
||||||
|
|
||||||
return s == t
|
return s == t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tmplFunc holds a template and a function that will execute said template.
|
||||||
|
type tmplFunc struct {
|
||||||
|
tmpl string
|
||||||
|
fn func(io.Writer, interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultUsageTemplate = `Usage:{{if .Runnable}}
|
||||||
|
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
|
||||||
|
{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
|
||||||
|
|
||||||
|
Aliases:
|
||||||
|
{{.NameAndAliases}}{{end}}{{if .HasExample}}
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}{{$cmds := .Commands}}{{if eq (len .Groups) 0}}
|
||||||
|
|
||||||
|
Available Commands:{{range $cmds}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
|
||||||
|
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{else}}{{range $group := .Groups}}
|
||||||
|
|
||||||
|
{{.Title}}{{range $cmds}}{{if (and (eq .GroupID $group.ID) (or .IsAvailableCommand (eq .Name "help")))}}
|
||||||
|
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if not .AllChildCommandsHaveGroup}}
|
||||||
|
|
||||||
|
Additional Commands:{{range $cmds}}{{if (and (eq .GroupID "") (or .IsAvailableCommand (eq .Name "help")))}}
|
||||||
|
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}
|
||||||
|
|
||||||
|
Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
|
||||||
|
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}
|
||||||
|
|
||||||
|
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
|
||||||
|
`
|
||||||
|
|
||||||
|
// defaultUsageFunc is equivalent to executing defaultUsageTemplate. The two should be changed in sync.
|
||||||
|
func defaultUsageFunc(w io.Writer, in interface{}) error {
|
||||||
|
c := in.(*Command)
|
||||||
|
fmt.Fprint(w, "Usage:")
|
||||||
|
if c.Runnable() {
|
||||||
|
fmt.Fprintf(w, "\n %s", c.UseLine())
|
||||||
|
}
|
||||||
|
if c.HasAvailableSubCommands() {
|
||||||
|
fmt.Fprintf(w, "\n %s [command]", c.CommandPath())
|
||||||
|
}
|
||||||
|
if len(c.Aliases) > 0 {
|
||||||
|
fmt.Fprintf(w, "\n\nAliases:\n")
|
||||||
|
fmt.Fprintf(w, " %s", c.NameAndAliases())
|
||||||
|
}
|
||||||
|
if c.HasExample() {
|
||||||
|
fmt.Fprintf(w, "\n\nExamples:\n")
|
||||||
|
fmt.Fprintf(w, "%s", c.Example)
|
||||||
|
}
|
||||||
|
if c.HasAvailableSubCommands() {
|
||||||
|
cmds := c.Commands()
|
||||||
|
if len(c.Groups()) == 0 {
|
||||||
|
fmt.Fprintf(w, "\n\nAvailable Commands:")
|
||||||
|
for _, subcmd := range cmds {
|
||||||
|
if subcmd.IsAvailableCommand() || subcmd.Name() == helpCommandName {
|
||||||
|
fmt.Fprintf(w, "\n %s %s", rpad(subcmd.Name(), subcmd.NamePadding()), subcmd.Short)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, group := range c.Groups() {
|
||||||
|
fmt.Fprintf(w, "\n\n%s", group.Title)
|
||||||
|
for _, subcmd := range cmds {
|
||||||
|
if subcmd.GroupID == group.ID && (subcmd.IsAvailableCommand() || subcmd.Name() == helpCommandName) {
|
||||||
|
fmt.Fprintf(w, "\n %s %s", rpad(subcmd.Name(), subcmd.NamePadding()), subcmd.Short)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !c.AllChildCommandsHaveGroup() {
|
||||||
|
fmt.Fprintf(w, "\n\nAdditional Commands:")
|
||||||
|
for _, subcmd := range cmds {
|
||||||
|
if subcmd.GroupID == "" && (subcmd.IsAvailableCommand() || subcmd.Name() == helpCommandName) {
|
||||||
|
fmt.Fprintf(w, "\n %s %s", rpad(subcmd.Name(), subcmd.NamePadding()), subcmd.Short)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.HasAvailableLocalFlags() {
|
||||||
|
fmt.Fprintf(w, "\n\nFlags:\n")
|
||||||
|
fmt.Fprint(w, trimRightSpace(c.LocalFlags().FlagUsages()))
|
||||||
|
}
|
||||||
|
if c.HasAvailableInheritedFlags() {
|
||||||
|
fmt.Fprintf(w, "\n\nGlobal Flags:\n")
|
||||||
|
fmt.Fprint(w, trimRightSpace(c.InheritedFlags().FlagUsages()))
|
||||||
|
}
|
||||||
|
if c.HasHelpSubCommands() {
|
||||||
|
fmt.Fprintf(w, "\n\nAdditional help topcis:")
|
||||||
|
for _, subcmd := range c.Commands() {
|
||||||
|
if subcmd.IsAdditionalHelpTopicCommand() {
|
||||||
|
fmt.Fprintf(w, "\n %s %s", rpad(subcmd.CommandPath(), subcmd.CommandPathPadding()), subcmd.Short)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.HasAvailableSubCommands() {
|
||||||
|
fmt.Fprintf(w, "\n\nUse \"%s [command] --help\" for more information about a command.", c.CommandPath())
|
||||||
|
}
|
||||||
|
fmt.Fprintln(w)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultHelpTemplate = `{{with (or .Long .Short)}}{{. | trimTrailingWhitespaces}}
|
||||||
|
|
||||||
|
{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
|
||||||
|
|
||||||
|
// defaultHelpFunc is equivalent to executing defaultHelpTemplate. The two should be changed in sync.
|
||||||
|
func defaultHelpFunc(w io.Writer, in interface{}) error {
|
||||||
|
c := in.(*Command)
|
||||||
|
usage := c.Long
|
||||||
|
if usage == "" {
|
||||||
|
usage = c.Short
|
||||||
|
}
|
||||||
|
usage = trimRightSpace(usage)
|
||||||
|
if usage != "" {
|
||||||
|
fmt.Fprintln(w, usage)
|
||||||
|
fmt.Fprintln(w)
|
||||||
|
}
|
||||||
|
if c.Runnable() || c.HasSubCommands() {
|
||||||
|
fmt.Fprint(w, c.UsageString())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultVersionTemplate = `{{with .DisplayName}}{{printf "%s " .}}{{end}}{{printf "version %s" .Version}}
|
||||||
|
`
|
||||||
|
|
||||||
|
// defaultVersionFunc is equivalent to executing defaultVersionTemplate. The two should be changed in sync.
|
||||||
|
func defaultVersionFunc(w io.Writer, in interface{}) error {
|
||||||
|
c := in.(*Command)
|
||||||
|
_, err := fmt.Fprintf(w, "%s version %s\n", c.DisplayName(), c.Version)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
// Copyright 2013-2024 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.
|
|
||||||
|
|
||||||
//go:build !go1.21
|
|
||||||
// +build !go1.21
|
|
||||||
|
|
||||||
package cobra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// based on golang.org/x/mod/internal/lazyregexp: https://cs.opensource.google/go/x/mod/+/refs/tags/v0.19.0:internal/lazyregexp/lazyre.go;l=66
|
|
||||||
// For a non-go-test program which still has a name ending with ".test[.exe]", it will need to either:
|
|
||||||
// 1- Use go >= 1.21, or
|
|
||||||
// 2- call "rootCmd.SetArgs(os.Args[1:])" before calling "rootCmd.Execute()"
|
|
||||||
var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test")
|
|
||||||
|
|
||||||
func isTesting() bool {
|
|
||||||
return inTest
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
// Copyright 2013-2024 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.
|
|
||||||
|
|
||||||
//go:build go1.21
|
|
||||||
// +build go1.21
|
|
||||||
|
|
||||||
package cobra
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func isTesting() bool {
|
|
||||||
// Only available starting with go 1.21
|
|
||||||
return testing.Testing()
|
|
||||||
}
|
|
|
@ -1018,6 +1018,49 @@ func TestSetHelpCommand(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetHelpTemplate(t *testing.T) {
|
||||||
|
rootCmd := &Command{Use: "root", Run: emptyRun}
|
||||||
|
childCmd := &Command{Use: "child", Run: emptyRun}
|
||||||
|
rootCmd.AddCommand(childCmd)
|
||||||
|
|
||||||
|
rootCmd.SetHelpTemplate("WORKS {{.UseLine}}")
|
||||||
|
|
||||||
|
// Call the help on the root command and check the new template is used
|
||||||
|
got, err := executeCommand(rootCmd, "--help")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "WORKS " + rootCmd.UseLine()
|
||||||
|
if got != expected {
|
||||||
|
t.Errorf("Expected %q, got %q", expected, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the help on the child command and check
|
||||||
|
// the new template is inherited from the parent
|
||||||
|
got, err = executeCommand(rootCmd, "child", "--help")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = "WORKS " + childCmd.UseLine()
|
||||||
|
if got != expected {
|
||||||
|
t.Errorf("Expected %q, got %q", expected, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the root command help template and make sure
|
||||||
|
// it falls back to the default
|
||||||
|
rootCmd.SetHelpTemplate("")
|
||||||
|
got, err = executeCommand(rootCmd, "--help")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(got, "Usage:") {
|
||||||
|
t.Errorf("Expected to contain %q, got %q", "Usage:", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHelpFlagExecuted(t *testing.T) {
|
func TestHelpFlagExecuted(t *testing.T) {
|
||||||
rootCmd := &Command{Use: "root", Long: "Long description", Run: emptyRun}
|
rootCmd := &Command{Use: "root", Long: "Long description", Run: emptyRun}
|
||||||
|
|
||||||
|
@ -1083,6 +1126,45 @@ func TestHelpExecutedOnNonRunnableChild(t *testing.T) {
|
||||||
checkStringContains(t, output, childCmd.Long)
|
checkStringContains(t, output, childCmd.Long)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetUsageTemplate(t *testing.T) {
|
||||||
|
rootCmd := &Command{Use: "root", Run: emptyRun}
|
||||||
|
childCmd := &Command{Use: "child", Run: emptyRun}
|
||||||
|
rootCmd.AddCommand(childCmd)
|
||||||
|
|
||||||
|
rootCmd.SetUsageTemplate("WORKS {{.UseLine}}")
|
||||||
|
|
||||||
|
// Trigger the usage on the root command and check the new template is used
|
||||||
|
got, err := executeCommand(rootCmd, "--invalid")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected error but did not get one")
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "WORKS " + rootCmd.UseLine()
|
||||||
|
checkStringContains(t, got, expected)
|
||||||
|
|
||||||
|
// Trigger the usage on the child command and check
|
||||||
|
// the new template is inherited from the parent
|
||||||
|
got, err = executeCommand(rootCmd, "child", "--invalid")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected error but did not get one")
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = "WORKS " + childCmd.UseLine()
|
||||||
|
checkStringContains(t, got, expected)
|
||||||
|
|
||||||
|
// Reset the root command usage template and make sure
|
||||||
|
// it falls back to the default
|
||||||
|
rootCmd.SetUsageTemplate("")
|
||||||
|
got, err = executeCommand(rootCmd, "--invalid")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected error but did not get one")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(got, "Usage:") {
|
||||||
|
t.Errorf("Expected to contain %q, got %q", "Usage:", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestVersionFlagExecuted(t *testing.T) {
|
func TestVersionFlagExecuted(t *testing.T) {
|
||||||
rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun}
|
rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun}
|
||||||
|
|
||||||
|
@ -2839,16 +2921,3 @@ func TestUnknownFlagShouldReturnSameErrorRegardlessOfArgPosition(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This tests verifies that when running unit tests, os.Args are not used.
|
|
||||||
// This is because we don't want to process any arguments that are provided
|
|
||||||
// by "go test"; instead, unit tests must set the arguments they need using
|
|
||||||
// rootCmd.SetArgs().
|
|
||||||
func TestNoOSArgsWhenTesting(t *testing.T) {
|
|
||||||
root := &Command{Use: "root", Run: emptyRun}
|
|
||||||
os.Args = append(os.Args, "--unknown")
|
|
||||||
|
|
||||||
if _, err := root.ExecuteC(); err != nil {
|
|
||||||
t.Errorf("error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
108
completions.go
108
completions.go
|
@ -35,7 +35,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Global map of flag completion functions. Make sure to use flagCompletionMutex before you try to read and write from it.
|
// Global map of flag completion functions. Make sure to use flagCompletionMutex before you try to read and write from it.
|
||||||
var flagCompletionFunctions = map[*pflag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective){}
|
var flagCompletionFunctions = map[*pflag.Flag]CompletionFunc{}
|
||||||
|
|
||||||
// lock for reading and writing from flagCompletionFunctions
|
// lock for reading and writing from flagCompletionFunctions
|
||||||
var flagCompletionMutex = &sync.RWMutex{}
|
var flagCompletionMutex = &sync.RWMutex{}
|
||||||
|
@ -117,22 +117,50 @@ type CompletionOptions struct {
|
||||||
HiddenDefaultCmd bool
|
HiddenDefaultCmd bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Completion is a string that can be used for completions
|
||||||
|
//
|
||||||
|
// two formats are supported:
|
||||||
|
// - the completion choice
|
||||||
|
// - the completion choice with a textual description (separated by a TAB).
|
||||||
|
//
|
||||||
|
// [CompletionWithDesc] can be used to create a completion string with a textual description.
|
||||||
|
//
|
||||||
|
// Note: Go type alias is used to provide a more descriptive name in the documentation, but any string can be used.
|
||||||
|
type Completion = string
|
||||||
|
|
||||||
|
// CompletionFunc is a function that provides completion results.
|
||||||
|
type CompletionFunc = func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective)
|
||||||
|
|
||||||
|
// CompletionWithDesc returns a [Completion] with a description by using the TAB delimited format.
|
||||||
|
func CompletionWithDesc(choice string, description string) Completion {
|
||||||
|
return choice + "\t" + description
|
||||||
|
}
|
||||||
|
|
||||||
// NoFileCompletions can be used to disable file completion for commands that should
|
// NoFileCompletions can be used to disable file completion for commands that should
|
||||||
// not trigger file completions.
|
// not trigger file completions.
|
||||||
func NoFileCompletions(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
//
|
||||||
|
// This method satisfies [CompletionFunc].
|
||||||
|
// It can be used with [Command.RegisterFlagCompletionFunc] and for [Command.ValidArgsFunction].
|
||||||
|
func NoFileCompletions(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective) {
|
||||||
return nil, ShellCompDirectiveNoFileComp
|
return nil, ShellCompDirectiveNoFileComp
|
||||||
}
|
}
|
||||||
|
|
||||||
// FixedCompletions can be used to create a completion function which always
|
// FixedCompletions can be used to create a completion function which always
|
||||||
// returns the same results.
|
// returns the same results.
|
||||||
func FixedCompletions(choices []string, directive ShellCompDirective) func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
//
|
||||||
return func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
// This method returns a function that satisfies [CompletionFunc]
|
||||||
|
// It can be used with [Command.RegisterFlagCompletionFunc] and for [Command.ValidArgsFunction].
|
||||||
|
func FixedCompletions(choices []Completion, directive ShellCompDirective) CompletionFunc {
|
||||||
|
return func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective) {
|
||||||
return choices, directive
|
return choices, directive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterFlagCompletionFunc should be called to register a function to provide completion for a flag.
|
// RegisterFlagCompletionFunc should be called to register a function to provide completion for a flag.
|
||||||
func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)) error {
|
//
|
||||||
|
// You can use pre-defined completion functions such as [FixedCompletions] or [NoFileCompletions],
|
||||||
|
// or you can define your own.
|
||||||
|
func (c *Command) RegisterFlagCompletionFunc(flagName string, f CompletionFunc) error {
|
||||||
flag := c.Flag(flagName)
|
flag := c.Flag(flagName)
|
||||||
if flag == nil {
|
if flag == nil {
|
||||||
return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' does not exist", flagName)
|
return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' does not exist", flagName)
|
||||||
|
@ -148,7 +176,7 @@ func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Comman
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFlagCompletionFunc returns the completion function for the given flag of the command, if available.
|
// GetFlagCompletionFunc returns the completion function for the given flag of the command, if available.
|
||||||
func (c *Command) GetFlagCompletionFunc(flagName string) (func(*Command, []string, string) ([]string, ShellCompDirective), bool) {
|
func (c *Command) GetFlagCompletionFunc(flagName string) (CompletionFunc, bool) {
|
||||||
flag := c.Flag(flagName)
|
flag := c.Flag(flagName)
|
||||||
if flag == nil {
|
if flag == nil {
|
||||||
return nil, false
|
return nil, false
|
||||||
|
@ -278,7 +306,7 @@ type SliceValue interface {
|
||||||
GetSlice() []string
|
GetSlice() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDirective, error) {
|
func (c *Command) getCompletions(args []string) (*Command, []Completion, ShellCompDirective, error) {
|
||||||
// The last argument, which is not completely typed by the user,
|
// The last argument, which is not completely typed by the user,
|
||||||
// should not be part of the list of arguments
|
// should not be part of the list of arguments
|
||||||
toComplete := args[len(args)-1]
|
toComplete := args[len(args)-1]
|
||||||
|
@ -306,7 +334,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Unable to find the real command. E.g., <program> someInvalidCmd <TAB>
|
// Unable to find the real command. E.g., <program> someInvalidCmd <TAB>
|
||||||
return c, []string{}, ShellCompDirectiveDefault, fmt.Errorf("unable to find a command for arguments: %v", trimmedArgs)
|
return c, []Completion{}, ShellCompDirectiveDefault, fmt.Errorf("unable to find a command for arguments: %v", trimmedArgs)
|
||||||
}
|
}
|
||||||
finalCmd.ctx = c.ctx
|
finalCmd.ctx = c.ctx
|
||||||
|
|
||||||
|
@ -336,7 +364,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
|
||||||
|
|
||||||
// Parse the flags early so we can check if required flags are set
|
// Parse the flags early so we can check if required flags are set
|
||||||
if err = finalCmd.ParseFlags(finalArgs); err != nil {
|
if err = finalCmd.ParseFlags(finalArgs); err != nil {
|
||||||
return finalCmd, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error())
|
return finalCmd, []Completion{}, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
realArgCount := finalCmd.Flags().NArg()
|
realArgCount := finalCmd.Flags().NArg()
|
||||||
|
@ -348,14 +376,14 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
|
||||||
if flagErr != nil {
|
if flagErr != nil {
|
||||||
// If error type is flagCompError and we don't want flagCompletion we should ignore the error
|
// If error type is flagCompError and we don't want flagCompletion we should ignore the error
|
||||||
if _, ok := flagErr.(*flagCompError); !(ok && !flagCompletion) {
|
if _, ok := flagErr.(*flagCompError); !(ok && !flagCompletion) {
|
||||||
return finalCmd, []string{}, ShellCompDirectiveDefault, flagErr
|
return finalCmd, []Completion{}, ShellCompDirectiveDefault, flagErr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for the --help or --version flags. If they are present,
|
// Look for the --help or --version flags. If they are present,
|
||||||
// there should be no further completions.
|
// there should be no further completions.
|
||||||
if helpOrVersionFlagPresent(finalCmd) {
|
if helpOrVersionFlagPresent(finalCmd) {
|
||||||
return finalCmd, []string{}, ShellCompDirectiveNoFileComp, nil
|
return finalCmd, []Completion{}, ShellCompDirectiveNoFileComp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// We only remove the flags from the arguments if DisableFlagParsing is not set.
|
// We only remove the flags from the arguments if DisableFlagParsing is not set.
|
||||||
|
@ -384,11 +412,11 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
|
||||||
return finalCmd, subDir, ShellCompDirectiveFilterDirs, nil
|
return finalCmd, subDir, ShellCompDirectiveFilterDirs, nil
|
||||||
}
|
}
|
||||||
// Directory completion
|
// Directory completion
|
||||||
return finalCmd, []string{}, ShellCompDirectiveFilterDirs, nil
|
return finalCmd, []Completion{}, ShellCompDirectiveFilterDirs, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var completions []string
|
var completions []Completion
|
||||||
var directive ShellCompDirective
|
var directive ShellCompDirective
|
||||||
|
|
||||||
// Enforce flag groups before doing flag completions
|
// Enforce flag groups before doing flag completions
|
||||||
|
@ -474,7 +502,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
|
||||||
for _, subCmd := range finalCmd.Commands() {
|
for _, subCmd := range finalCmd.Commands() {
|
||||||
if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand {
|
if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand {
|
||||||
if strings.HasPrefix(subCmd.Name(), toComplete) {
|
if strings.HasPrefix(subCmd.Name(), toComplete) {
|
||||||
completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short))
|
completions = append(completions, CompletionWithDesc(subCmd.Name(), subCmd.Short))
|
||||||
}
|
}
|
||||||
directive = ShellCompDirectiveNoFileComp
|
directive = ShellCompDirectiveNoFileComp
|
||||||
}
|
}
|
||||||
|
@ -519,7 +547,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the completion function for the flag or command
|
// Find the completion function for the flag or command
|
||||||
var completionFn func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
|
var completionFn CompletionFunc
|
||||||
if flag != nil && flagCompletion {
|
if flag != nil && flagCompletion {
|
||||||
flagCompletionMutex.RLock()
|
flagCompletionMutex.RLock()
|
||||||
completionFn = flagCompletionFunctions[flag]
|
completionFn = flagCompletionFunctions[flag]
|
||||||
|
@ -530,7 +558,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
|
||||||
if completionFn != nil {
|
if completionFn != nil {
|
||||||
// Go custom completion defined for this flag or command.
|
// Go custom completion defined for this flag or command.
|
||||||
// Call the registered completion function to get the completions.
|
// Call the registered completion function to get the completions.
|
||||||
var comps []string
|
var comps []Completion
|
||||||
comps, directive = completionFn(finalCmd, finalArgs, toComplete)
|
comps, directive = completionFn(finalCmd, finalArgs, toComplete)
|
||||||
completions = append(completions, comps...)
|
completions = append(completions, comps...)
|
||||||
}
|
}
|
||||||
|
@ -543,23 +571,23 @@ func helpOrVersionFlagPresent(cmd *Command) bool {
|
||||||
len(versionFlag.Annotations[FlagSetByCobraAnnotation]) > 0 && versionFlag.Changed {
|
len(versionFlag.Annotations[FlagSetByCobraAnnotation]) > 0 && versionFlag.Changed {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if helpFlag := cmd.Flags().Lookup("help"); helpFlag != nil &&
|
if helpFlag := cmd.Flags().Lookup(helpFlagName); helpFlag != nil &&
|
||||||
len(helpFlag.Annotations[FlagSetByCobraAnnotation]) > 0 && helpFlag.Changed {
|
len(helpFlag.Annotations[FlagSetByCobraAnnotation]) > 0 && helpFlag.Changed {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string {
|
func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []Completion {
|
||||||
if nonCompletableFlag(flag) {
|
if nonCompletableFlag(flag) {
|
||||||
return []string{}
|
return []Completion{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var completions []string
|
var completions []Completion
|
||||||
flagName := "--" + flag.Name
|
flagName := "--" + flag.Name
|
||||||
if strings.HasPrefix(flagName, toComplete) {
|
if strings.HasPrefix(flagName, toComplete) {
|
||||||
// Flag without the =
|
// Flag without the =
|
||||||
completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
|
completions = append(completions, CompletionWithDesc(flagName, flag.Usage))
|
||||||
|
|
||||||
// Why suggest both long forms: --flag and --flag= ?
|
// Why suggest both long forms: --flag and --flag= ?
|
||||||
// This forces the user to *always* have to type either an = or a space after the flag name.
|
// This forces the user to *always* have to type either an = or a space after the flag name.
|
||||||
|
@ -571,20 +599,20 @@ func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string {
|
||||||
// if len(flag.NoOptDefVal) == 0 {
|
// if len(flag.NoOptDefVal) == 0 {
|
||||||
// // Flag requires a value, so it can be suffixed with =
|
// // Flag requires a value, so it can be suffixed with =
|
||||||
// flagName += "="
|
// flagName += "="
|
||||||
// completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
|
// completions = append(completions, CompletionWithDesc(flagName, flag.Usage))
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
flagName = "-" + flag.Shorthand
|
flagName = "-" + flag.Shorthand
|
||||||
if len(flag.Shorthand) > 0 && strings.HasPrefix(flagName, toComplete) {
|
if len(flag.Shorthand) > 0 && strings.HasPrefix(flagName, toComplete) {
|
||||||
completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
|
completions = append(completions, CompletionWithDesc(flagName, flag.Usage))
|
||||||
}
|
}
|
||||||
|
|
||||||
return completions
|
return completions
|
||||||
}
|
}
|
||||||
|
|
||||||
func completeRequireFlags(finalCmd *Command, toComplete string) []string {
|
func completeRequireFlags(finalCmd *Command, toComplete string) []Completion {
|
||||||
var completions []string
|
var completions []Completion
|
||||||
|
|
||||||
doCompleteRequiredFlags := func(flag *pflag.Flag) {
|
doCompleteRequiredFlags := func(flag *pflag.Flag) {
|
||||||
if _, present := flag.Annotations[BashCompOneRequiredFlag]; present {
|
if _, present := flag.Annotations[BashCompOneRequiredFlag]; present {
|
||||||
|
@ -699,8 +727,8 @@ func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*p
|
||||||
// 1- the feature has been explicitly disabled by the program,
|
// 1- the feature has been explicitly disabled by the program,
|
||||||
// 2- c has no subcommands (to avoid creating one),
|
// 2- c has no subcommands (to avoid creating one),
|
||||||
// 3- c already has a 'completion' command provided by the program.
|
// 3- c already has a 'completion' command provided by the program.
|
||||||
func (c *Command) InitDefaultCompletionCmd() {
|
func (c *Command) InitDefaultCompletionCmd(args ...string) {
|
||||||
if c.CompletionOptions.DisableDefaultCmd || !c.HasSubCommands() {
|
if c.CompletionOptions.DisableDefaultCmd {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -713,6 +741,16 @@ func (c *Command) InitDefaultCompletionCmd() {
|
||||||
|
|
||||||
haveNoDescFlag := !c.CompletionOptions.DisableNoDescFlag && !c.CompletionOptions.DisableDescriptions
|
haveNoDescFlag := !c.CompletionOptions.DisableNoDescFlag && !c.CompletionOptions.DisableDescriptions
|
||||||
|
|
||||||
|
// Special case to know if there are sub-commands or not.
|
||||||
|
hasSubCommands := false
|
||||||
|
for _, cmd := range c.commands {
|
||||||
|
if cmd.Name() != ShellCompRequestCmd && cmd.Name() != helpCommandName {
|
||||||
|
// We found a real sub-command (not 'help' or '__complete')
|
||||||
|
hasSubCommands = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
completionCmd := &Command{
|
completionCmd := &Command{
|
||||||
Use: compCmdName,
|
Use: compCmdName,
|
||||||
Short: "Generate the autocompletion script for the specified shell",
|
Short: "Generate the autocompletion script for the specified shell",
|
||||||
|
@ -726,6 +764,22 @@ See each sub-command's help for details on how to use the generated script.
|
||||||
}
|
}
|
||||||
c.AddCommand(completionCmd)
|
c.AddCommand(completionCmd)
|
||||||
|
|
||||||
|
if !hasSubCommands {
|
||||||
|
// If the 'completion' command will be the only sub-command,
|
||||||
|
// we only create it if it is actually being called.
|
||||||
|
// This avoids breaking programs that would suddenly find themselves with
|
||||||
|
// a subcommand, which would prevent them from accepting arguments.
|
||||||
|
// We also create the 'completion' command if the user is triggering
|
||||||
|
// shell completion for it (prog __complete completion '')
|
||||||
|
subCmd, cmdArgs, err := c.Find(args)
|
||||||
|
if err != nil || subCmd.Name() != compCmdName &&
|
||||||
|
!(subCmd.Name() == ShellCompRequestCmd && len(cmdArgs) > 1 && cmdArgs[0] == compCmdName) {
|
||||||
|
// The completion command is not being called or being completed so we remove it.
|
||||||
|
c.RemoveCommand(completionCmd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
out := c.OutOrStdout()
|
out := c.OutOrStdout()
|
||||||
noDesc := c.CompletionOptions.DisableDescriptions
|
noDesc := c.CompletionOptions.DisableDescriptions
|
||||||
shortDesc := "Generate the autocompletion script for %s"
|
shortDesc := "Generate the autocompletion script for %s"
|
||||||
|
|
|
@ -2465,7 +2465,7 @@ func TestDefaultCompletionCmd(t *testing.T) {
|
||||||
Run: emptyRun,
|
Run: emptyRun,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that no completion command is created if there are not other sub-commands
|
// Test that when there are no sub-commands, the completion command is not created if it is not called directly.
|
||||||
assertNoErr(t, rootCmd.Execute())
|
assertNoErr(t, rootCmd.Execute())
|
||||||
for _, cmd := range rootCmd.commands {
|
for _, cmd := range rootCmd.commands {
|
||||||
if cmd.Name() == compCmdName {
|
if cmd.Name() == compCmdName {
|
||||||
|
@ -2474,6 +2474,17 @@ func TestDefaultCompletionCmd(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test that when there are no sub-commands, the completion command is created when it is called directly.
|
||||||
|
_, err := executeCommand(rootCmd, compCmdName)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
// Reset the arguments
|
||||||
|
rootCmd.args = nil
|
||||||
|
// Remove completion command for the next test
|
||||||
|
removeCompCmd(rootCmd)
|
||||||
|
|
||||||
|
// Add a sub-command
|
||||||
subCmd := &Command{
|
subCmd := &Command{
|
||||||
Use: "sub",
|
Use: "sub",
|
||||||
Run: emptyRun,
|
Run: emptyRun,
|
||||||
|
@ -2595,6 +2606,42 @@ func TestDefaultCompletionCmd(t *testing.T) {
|
||||||
|
|
||||||
func TestCompleteCompletion(t *testing.T) {
|
func TestCompleteCompletion(t *testing.T) {
|
||||||
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
||||||
|
|
||||||
|
// Test that when there are no sub-commands, the 'completion' command is not completed
|
||||||
|
// (because it is not created).
|
||||||
|
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "completion")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := strings.Join([]string{
|
||||||
|
":0",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that when there are no sub-commands, completion can be triggered for the default
|
||||||
|
// 'completion' command
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "completion", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
"bash",
|
||||||
|
"fish",
|
||||||
|
"powershell",
|
||||||
|
"zsh",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a sub-command
|
||||||
subCmd := &Command{
|
subCmd := &Command{
|
||||||
Use: "sub",
|
Use: "sub",
|
||||||
Run: emptyRun,
|
Run: emptyRun,
|
||||||
|
@ -2602,12 +2649,12 @@ func TestCompleteCompletion(t *testing.T) {
|
||||||
rootCmd.AddCommand(subCmd)
|
rootCmd.AddCommand(subCmd)
|
||||||
|
|
||||||
// Test sub-commands of the completion command
|
// Test sub-commands of the completion command
|
||||||
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "completion", "")
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "completion", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error: %v", err)
|
t.Errorf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := strings.Join([]string{
|
expected = strings.Join([]string{
|
||||||
"bash",
|
"bash",
|
||||||
"fish",
|
"fish",
|
||||||
"powershell",
|
"powershell",
|
||||||
|
@ -2838,13 +2885,99 @@ func TestCompleteWithRootAndLegacyArgs(t *testing.T) {
|
||||||
"arg1",
|
"arg1",
|
||||||
"arg2",
|
"arg2",
|
||||||
":4",
|
":4",
|
||||||
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", "",
|
||||||
|
}, "\n")
|
||||||
|
|
||||||
if output != expected {
|
if output != expected {
|
||||||
t.Errorf("expected: %q, got: %q", expected, output)
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCompletionFuncCompatibility(t *testing.T) {
|
||||||
|
t.Run("validate signature", func(t *testing.T) {
|
||||||
|
t.Run("format with []string", func(t *testing.T) {
|
||||||
|
var userComp func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
|
||||||
|
|
||||||
|
// check against new signature
|
||||||
|
var _ CompletionFunc = userComp
|
||||||
|
|
||||||
|
// check Command accepts
|
||||||
|
cmd := Command{
|
||||||
|
ValidArgsFunction: userComp,
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("format with []Completion", func(t *testing.T) {
|
||||||
|
var userComp func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective)
|
||||||
|
|
||||||
|
// check against new signature
|
||||||
|
var _ CompletionFunc = userComp
|
||||||
|
|
||||||
|
// check Command accepts
|
||||||
|
cmd := Command{
|
||||||
|
ValidArgsFunction: userComp,
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("format with CompletionFunc", func(t *testing.T) {
|
||||||
|
var userComp CompletionFunc
|
||||||
|
|
||||||
|
// check helper against old signature
|
||||||
|
var _ func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) = userComp
|
||||||
|
var _ func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective) = userComp
|
||||||
|
|
||||||
|
// check Command accepts
|
||||||
|
cmd := Command{
|
||||||
|
ValidArgsFunction: userComp,
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("user defined completion helper", func(t *testing.T) {
|
||||||
|
t.Run("type helper", func(t *testing.T) {
|
||||||
|
// This is a type that may have been defined by the user of the library
|
||||||
|
// This replicates the issue https://github.com/docker/cli/issues/5827
|
||||||
|
// https://github.com/docker/cli/blob/b6e7eba4470ecdca460e4b63270fba8179674ad6/cli/command/completion/functions.go#L18
|
||||||
|
type UserCompletionTypeHelper func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
|
||||||
|
|
||||||
|
var userComp UserCompletionTypeHelper
|
||||||
|
|
||||||
|
// Here we are validating the existing type validates the CompletionFunc type
|
||||||
|
var _ CompletionFunc = userComp
|
||||||
|
|
||||||
|
cmd := Command{
|
||||||
|
ValidArgsFunction: userComp,
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("type alias helper", func(t *testing.T) {
|
||||||
|
// This is a type that may have been defined by the user of the library
|
||||||
|
// This replicates the possible fix that was tried here https://github.com/docker/cli/pull/5828
|
||||||
|
// https://github.com/docker/cli/blob/ae3d4db9f658259dace9dee515718be7c1b1f517/cli/command/completion/functions.go#L18
|
||||||
|
type UserCompletionTypeAliasHelper = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
|
||||||
|
|
||||||
|
var userComp UserCompletionTypeAliasHelper
|
||||||
|
|
||||||
|
// Here we are validating the existing type validates the CompletionFunc type
|
||||||
|
var _ CompletionFunc = userComp
|
||||||
|
|
||||||
|
cmd := Command{
|
||||||
|
ValidArgsFunction: userComp,
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestFixedCompletions(t *testing.T) {
|
func TestFixedCompletions(t *testing.T) {
|
||||||
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
||||||
choices := []string{"apple", "banana", "orange"}
|
choices := []string{"apple", "banana", "orange"}
|
||||||
|
@ -2872,6 +3005,56 @@ func TestFixedCompletions(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFixedCompletionsWithCompletionHelpers(t *testing.T) {
|
||||||
|
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
|
||||||
|
// here we are mixing string, [Completion] and [CompletionWithDesc]
|
||||||
|
choices := []string{"apple", Completion("banana"), CompletionWithDesc("orange", "orange are orange")}
|
||||||
|
childCmd := &Command{
|
||||||
|
Use: "child",
|
||||||
|
ValidArgsFunction: FixedCompletions(choices, ShellCompDirectiveNoFileComp),
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(childCmd)
|
||||||
|
|
||||||
|
t.Run("completion with description", func(t *testing.T) {
|
||||||
|
output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child", "a")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := strings.Join([]string{
|
||||||
|
"apple",
|
||||||
|
"banana",
|
||||||
|
"orange\torange are orange", // this one has the description as expected with [ShellCompRequestCmd] flag
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", "",
|
||||||
|
}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("completion with no description", func(t *testing.T) {
|
||||||
|
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child", "a")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := strings.Join([]string{
|
||||||
|
"apple",
|
||||||
|
"banana",
|
||||||
|
"orange", // the description is absent as expected with [ShellCompNoDescRequestCmd] flag
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", "",
|
||||||
|
}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestCompletionForGroupedFlags(t *testing.T) {
|
func TestCompletionForGroupedFlags(t *testing.T) {
|
||||||
getCmd := func() *Command {
|
getCmd := func() *Command {
|
||||||
rootCmd := &Command{
|
rootCmd := &Command{
|
||||||
|
@ -3742,3 +3925,94 @@ func TestDisableDescriptions(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A test to make sure the InitDefaultCompletionCmd function works as expected
|
||||||
|
// in case a project calls it directly.
|
||||||
|
func TestInitDefaultCompletionCmd(t *testing.T) {
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
hasChildCmd bool
|
||||||
|
args []string
|
||||||
|
expectCompCmd bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "no child command and not calling the completion command",
|
||||||
|
hasChildCmd: false,
|
||||||
|
args: []string{"somearg"},
|
||||||
|
expectCompCmd: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no child command but calling the completion command",
|
||||||
|
hasChildCmd: false,
|
||||||
|
args: []string{"completion"},
|
||||||
|
expectCompCmd: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no child command but calling __complete on the root command",
|
||||||
|
hasChildCmd: false,
|
||||||
|
args: []string{"__complete", ""},
|
||||||
|
expectCompCmd: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no child command but calling __complete on the completion command",
|
||||||
|
hasChildCmd: false,
|
||||||
|
args: []string{"__complete", "completion", ""},
|
||||||
|
expectCompCmd: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "with child command",
|
||||||
|
hasChildCmd: true,
|
||||||
|
args: []string{"child"},
|
||||||
|
expectCompCmd: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no child command not passing args",
|
||||||
|
hasChildCmd: false,
|
||||||
|
args: nil,
|
||||||
|
expectCompCmd: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "with child command not passing args",
|
||||||
|
hasChildCmd: true,
|
||||||
|
args: nil,
|
||||||
|
expectCompCmd: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
rootCmd := &Command{Use: "root", Run: emptyRun}
|
||||||
|
childCmd := &Command{Use: "child", Run: emptyRun}
|
||||||
|
|
||||||
|
expectedNumSubCommands := 0
|
||||||
|
if tc.hasChildCmd {
|
||||||
|
rootCmd.AddCommand(childCmd)
|
||||||
|
expectedNumSubCommands++
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectCompCmd {
|
||||||
|
expectedNumSubCommands++
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tc.args) > 0 && tc.args[0] == "__complete" {
|
||||||
|
expectedNumSubCommands++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the __complete command to mimic real world scenarios
|
||||||
|
rootCmd.initCompleteCmd(tc.args)
|
||||||
|
|
||||||
|
// Call the InitDefaultCompletionCmd function directly
|
||||||
|
if tc.args == nil {
|
||||||
|
rootCmd.InitDefaultCompletionCmd()
|
||||||
|
} else {
|
||||||
|
rootCmd.InitDefaultCompletionCmd(tc.args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the completion command was added
|
||||||
|
if len(rootCmd.Commands()) != expectedNumSubCommands {
|
||||||
|
t.Errorf("Expected %d subcommands, got %d", expectedNumSubCommands, len(rootCmd.Commands()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -5,6 +5,6 @@ go 1.15
|
||||||
require (
|
require (
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6
|
github.com/cpuguy83/go-md2man/v2 v2.0.6
|
||||||
github.com/inconshreveable/mousetrap v1.1.0
|
github.com/inconshreveable/mousetrap v1.1.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.6
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -4,8 +4,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|
|
@ -41,8 +41,8 @@ cmd := &cobra.Command{
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return addRepo(args)
|
return addRepo(args)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
var comps []string
|
var comps []cobra.Completion
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
comps = cobra.AppendActiveHelp(comps, "You must choose a name for the repo you are adding")
|
comps = cobra.AppendActiveHelp(comps, "You must choose a name for the repo you are adding")
|
||||||
} else if len(args) == 1 {
|
} else if len(args) == 1 {
|
||||||
|
@ -75,7 +75,7 @@ This command does not take any more arguments
|
||||||
Providing Active Help for flags is done in the same fashion as for nouns, but using the completion function registered for the flag. For example:
|
Providing Active Help for flags is done in the same fashion as for nouns, but using the completion function registered for the flag. For example:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
_ = cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
_ = cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
if len(args) != 2 {
|
if len(args) != 2 {
|
||||||
return cobra.AppendActiveHelp(nil, "You must first specify the chart to install before the --version flag can be completed"), cobra.ShellCompDirectiveNoFileComp
|
return cobra.AppendActiveHelp(nil, "You must first specify the chart to install before the --version flag can be completed"), cobra.ShellCompDirectiveNoFileComp
|
||||||
}
|
}
|
||||||
|
@ -112,10 +112,10 @@ should or should not be added (instead of reading the environment variable direc
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
activeHelpLevel := cobra.GetActiveHelpConfig(cmd)
|
activeHelpLevel := cobra.GetActiveHelpConfig(cmd)
|
||||||
|
|
||||||
var comps []string
|
var comps []cobra.Completion
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
if activeHelpLevel != "off" {
|
if activeHelpLevel != "off" {
|
||||||
comps = cobra.AppendActiveHelp(comps, "You must choose a name for the repo you are adding")
|
comps = cobra.AppendActiveHelp(comps, "You must choose a name for the repo you are adding")
|
||||||
|
|
|
@ -8,7 +8,8 @@ The currently supported shells are:
|
||||||
- PowerShell
|
- PowerShell
|
||||||
|
|
||||||
Cobra will automatically provide your program with a fully functional `completion` command,
|
Cobra will automatically provide your program with a fully functional `completion` command,
|
||||||
similarly to how it provides the `help` command.
|
similarly to how it provides the `help` command. If there are no other subcommands, the
|
||||||
|
default `completion` command will be hidden, but still functional.
|
||||||
|
|
||||||
## Creating your own completion command
|
## Creating your own completion command
|
||||||
|
|
||||||
|
@ -177,7 +178,7 @@ cmd := &cobra.Command{
|
||||||
RunE: func(cmd *cobra.Command, args []string) {
|
RunE: func(cmd *cobra.Command, args []string) {
|
||||||
RunGet(args[0])
|
RunGet(args[0])
|
||||||
},
|
},
|
||||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
if len(args) != 0 {
|
if len(args) != 0 {
|
||||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||||
}
|
}
|
||||||
|
@ -211,7 +212,7 @@ ShellCompDirectiveNoFileComp
|
||||||
|
|
||||||
// Indicates that the returned completions should be used as file extension filters.
|
// Indicates that the returned completions should be used as file extension filters.
|
||||||
// For example, to complete only files of the form *.json or *.yaml:
|
// For example, to complete only files of the form *.json or *.yaml:
|
||||||
// return []string{"yaml", "json"}, ShellCompDirectiveFilterFileExt
|
// return []cobra.Completion{"yaml", "json"}, cobra.ShellCompDirectiveFilterFileExt
|
||||||
// For flags, using MarkFlagFilename() and MarkPersistentFlagFilename()
|
// For flags, using MarkFlagFilename() and MarkPersistentFlagFilename()
|
||||||
// is a shortcut to using this directive explicitly.
|
// is a shortcut to using this directive explicitly.
|
||||||
//
|
//
|
||||||
|
@ -219,13 +220,13 @@ ShellCompDirectiveFilterFileExt
|
||||||
|
|
||||||
// Indicates that only directory names should be provided in file completion.
|
// Indicates that only directory names should be provided in file completion.
|
||||||
// For example:
|
// For example:
|
||||||
// return nil, ShellCompDirectiveFilterDirs
|
// return nil, cobra.ShellCompDirectiveFilterDirs
|
||||||
// For flags, using MarkFlagDirname() is a shortcut to using this directive explicitly.
|
// For flags, using MarkFlagDirname() is a shortcut to using this directive explicitly.
|
||||||
//
|
//
|
||||||
// To request directory names within another directory, the returned completions
|
// To request directory names within another directory, the returned completions
|
||||||
// should specify a single directory name within which to search. For example,
|
// should specify a single directory name within which to search. For example,
|
||||||
// to complete directories within "themes/":
|
// to complete directories within "themes/":
|
||||||
// return []string{"themes"}, ShellCompDirectiveFilterDirs
|
// return []cobra.Completion{"themes"}, cobra.ShellCompDirectiveFilterDirs
|
||||||
//
|
//
|
||||||
ShellCompDirectiveFilterDirs
|
ShellCompDirectiveFilterDirs
|
||||||
|
|
||||||
|
@ -259,7 +260,7 @@ Calling the `__complete` command directly allows you to run the Go debugger to t
|
||||||
```go
|
```go
|
||||||
// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE
|
// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE
|
||||||
// is set to a file path) and optionally prints to stderr.
|
// is set to a file path) and optionally prints to stderr.
|
||||||
cobra.CompDebug(msg string, printToStdErr bool) {
|
cobra.CompDebug(msg string, printToStdErr bool)
|
||||||
cobra.CompDebugln(msg string, printToStdErr bool)
|
cobra.CompDebugln(msg string, printToStdErr bool)
|
||||||
|
|
||||||
// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE
|
// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE
|
||||||
|
@ -293,8 +294,8 @@ As for nouns, Cobra provides a way of defining dynamic completion of flags. To
|
||||||
|
|
||||||
```go
|
```go
|
||||||
flagName := "output"
|
flagName := "output"
|
||||||
cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
return []string{"json", "table", "yaml"}, cobra.ShellCompDirectiveDefault
|
return []cobra.Completion{"json", "table", "yaml"}, cobra.ShellCompDirectiveDefault
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
Notice that calling `RegisterFlagCompletionFunc()` is done through the `command` with which the flag is associated. In our example this dynamic completion will give results like so:
|
Notice that calling `RegisterFlagCompletionFunc()` is done through the `command` with which the flag is associated. In our example this dynamic completion will give results like so:
|
||||||
|
@ -327,8 +328,8 @@ cmd.MarkFlagFilename(flagName, "yaml", "json")
|
||||||
or
|
or
|
||||||
```go
|
```go
|
||||||
flagName := "output"
|
flagName := "output"
|
||||||
cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
return []string{"yaml", "json"}, ShellCompDirectiveFilterFileExt})
|
return []cobra.Completion{"yaml", "json"}, cobra.ShellCompDirectiveFilterFileExt})
|
||||||
```
|
```
|
||||||
|
|
||||||
### Limit flag completions to directory names
|
### Limit flag completions to directory names
|
||||||
|
@ -341,15 +342,15 @@ cmd.MarkFlagDirname(flagName)
|
||||||
or
|
or
|
||||||
```go
|
```go
|
||||||
flagName := "output"
|
flagName := "output"
|
||||||
cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
return nil, cobra.ShellCompDirectiveFilterDirs
|
return nil, cobra.ShellCompDirectiveFilterDirs
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
To limit completions of flag values to directory names *within another directory* you can use a combination of `RegisterFlagCompletionFunc()` and `ShellCompDirectiveFilterDirs` like so:
|
To limit completions of flag values to directory names *within another directory* you can use a combination of `RegisterFlagCompletionFunc()` and `ShellCompDirectiveFilterDirs` like so:
|
||||||
```go
|
```go
|
||||||
flagName := "output"
|
flagName := "output"
|
||||||
cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
return []string{"themes"}, cobra.ShellCompDirectiveFilterDirs
|
return []cobra.Completion{"themes"}, cobra.ShellCompDirectiveFilterDirs
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
### Descriptions for completions
|
### Descriptions for completions
|
||||||
|
@ -370,15 +371,21 @@ $ helm s[tab]
|
||||||
search (search for a keyword in charts) show (show information of a chart) status (displays the status of the named release)
|
search (search for a keyword in charts) show (show information of a chart) status (displays the status of the named release)
|
||||||
```
|
```
|
||||||
|
|
||||||
Cobra allows you to add descriptions to your own completions. Simply add the description text after each completion, following a `\t` separator. This technique applies to completions returned by `ValidArgs`, `ValidArgsFunction` and `RegisterFlagCompletionFunc()`. For example:
|
Cobra allows you to add descriptions to your own completions. Simply add the description text after each completion, following a `\t` separator. Cobra provides the helper function `CompletionWithDesc(string, string)` to create a completion with a description. This technique applies to completions returned by `ValidArgs`, `ValidArgsFunction` and `RegisterFlagCompletionFunc()`. For example:
|
||||||
```go
|
```go
|
||||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
return []string{"harbor\tAn image registry", "thanos\tLong-term metrics"}, cobra.ShellCompDirectiveNoFileComp
|
return []cobra.Completion{
|
||||||
|
cobra.CompletionWithDesc("harbor", "An image registry"),
|
||||||
|
cobra.CompletionWithDesc("thanos", "Long-term metrics")
|
||||||
|
}, cobra.ShellCompDirectiveNoFileComp
|
||||||
}}
|
}}
|
||||||
```
|
```
|
||||||
or
|
or
|
||||||
```go
|
```go
|
||||||
ValidArgs: []string{"bash\tCompletions for bash", "zsh\tCompletions for zsh"}
|
ValidArgs: []cobra.Completion{
|
||||||
|
cobra.CompletionWithDesc("bash", "Completions for bash"),
|
||||||
|
cobra.CompletionWithDesc("zsh", "Completions for zsh")
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
If you don't want to show descriptions in the completions, you can add `--no-descriptions` to the default `completion` command to disable them, like:
|
If you don't want to show descriptions in the completions, you can add `--no-descriptions` to the default `completion` command to disable them, like:
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
- [Cilium](https://cilium.io/)
|
- [Cilium](https://cilium.io/)
|
||||||
- [CloudQuery](https://github.com/cloudquery/cloudquery)
|
- [CloudQuery](https://github.com/cloudquery/cloudquery)
|
||||||
- [CockroachDB](https://www.cockroachlabs.com/)
|
- [CockroachDB](https://www.cockroachlabs.com/)
|
||||||
|
- [Conduit](https://github.com/conduitio/conduit)
|
||||||
- [Constellation](https://github.com/edgelesssys/constellation)
|
- [Constellation](https://github.com/edgelesssys/constellation)
|
||||||
- [Cosmos SDK](https://github.com/cosmos/cosmos-sdk)
|
- [Cosmos SDK](https://github.com/cosmos/cosmos-sdk)
|
||||||
- [Datree](https://github.com/datreeio/datree)
|
- [Datree](https://github.com/datreeio/datree)
|
||||||
|
|
|
@ -554,6 +554,9 @@ cmd.SetHelpTemplate(s string)
|
||||||
|
|
||||||
The latter two will also apply to any children commands.
|
The latter two will also apply to any children commands.
|
||||||
|
|
||||||
|
Note that templates specified with `SetHelpTemplate` are evaluated using
|
||||||
|
`text/template` which can increase the size of the compiled executable.
|
||||||
|
|
||||||
## Usage Message
|
## Usage Message
|
||||||
|
|
||||||
When the user provides an invalid flag or invalid command, Cobra responds by
|
When the user provides an invalid flag or invalid command, Cobra responds by
|
||||||
|
@ -586,6 +589,7 @@ Use "cobra [command] --help" for more information about a command.
|
||||||
```
|
```
|
||||||
|
|
||||||
### Defining your own usage
|
### Defining your own usage
|
||||||
|
|
||||||
You can provide your own usage function or template for Cobra to use.
|
You can provide your own usage function or template for Cobra to use.
|
||||||
Like help, the function and template are overridable through public methods:
|
Like help, the function and template are overridable through public methods:
|
||||||
|
|
||||||
|
@ -594,6 +598,9 @@ cmd.SetUsageFunc(f func(*Command) error)
|
||||||
cmd.SetUsageTemplate(s string)
|
cmd.SetUsageTemplate(s string)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that templates specified with `SetUsageTemplate` are evaluated using
|
||||||
|
`text/template` which can increase the size of the compiled executable.
|
||||||
|
|
||||||
## Version Flag
|
## Version Flag
|
||||||
|
|
||||||
Cobra adds a top-level '--version' flag if the Version field is set on the root command.
|
Cobra adds a top-level '--version' flag if the Version field is set on the root command.
|
||||||
|
@ -601,6 +608,9 @@ Running an application with the '--version' flag will print the version to stdou
|
||||||
the version template. The template can be customized using the
|
the version template. The template can be customized using the
|
||||||
`cmd.SetVersionTemplate(s string)` function.
|
`cmd.SetVersionTemplate(s string)` function.
|
||||||
|
|
||||||
|
Note that templates specified with `SetVersionTemplate` are evaluated using
|
||||||
|
`text/template` which can increase the size of the compiled executable.
|
||||||
|
|
||||||
## Error Message Prefix
|
## Error Message Prefix
|
||||||
|
|
||||||
Cobra prints an error message when receiving a non-nil error value.
|
Cobra prints an error message when receiving a non-nil error value.
|
||||||
|
|
Loading…
Reference in a new issue