mirror of
https://github.com/spf13/cobra
synced 2025-04-07 07:19:16 +00:00
Merge 84f8ee7fef
into ceb39aba25
This commit is contained in:
commit
492a99bf2d
4 changed files with 389 additions and 1 deletions
17
command.go
17
command.go
|
@ -257,6 +257,11 @@ type Command struct {
|
||||||
// SuggestionsMinimumDistance defines minimum levenshtein distance to display suggestions.
|
// SuggestionsMinimumDistance defines minimum levenshtein distance to display suggestions.
|
||||||
// Must be > 0.
|
// Must be > 0.
|
||||||
SuggestionsMinimumDistance int
|
SuggestionsMinimumDistance int
|
||||||
|
|
||||||
|
// When performing commandline completions specific to handling flags only, override
|
||||||
|
// Cobra's default behavior and allow ValidArgsFunction to be used if it exists, otherwise
|
||||||
|
// use the default behavior
|
||||||
|
AllowCustomFlagCompletions bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context returns underlying command context. If command was executed
|
// Context returns underlying command context. If command was executed
|
||||||
|
@ -1602,6 +1607,18 @@ func (c *Command) HasSubCommands() bool {
|
||||||
return len(c.commands) > 0
|
return len(c.commands) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasCustomFlagCompletion determines if a command has a defined
|
||||||
|
// ValidArgsFunction, if it does, then it returns the value of
|
||||||
|
// the AllowCustomFlagCompletions field indicating if the function
|
||||||
|
// should be used for handling custom flag completions.
|
||||||
|
func (c *Command) HasCustomFlagCompletion() bool {
|
||||||
|
if c.ValidArgsFunction == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.AllowCustomFlagCompletions
|
||||||
|
}
|
||||||
|
|
||||||
// IsAvailableCommand determines if a command is available as a non-help command
|
// IsAvailableCommand determines if a command is available as a non-help command
|
||||||
// (this includes all non deprecated/hidden commands).
|
// (this includes all non deprecated/hidden commands).
|
||||||
func (c *Command) IsAvailableCommand() bool {
|
func (c *Command) IsAvailableCommand() bool {
|
||||||
|
|
|
@ -2952,3 +2952,83 @@ func TestHelpFuncExecuted(t *testing.T) {
|
||||||
|
|
||||||
checkStringContains(t, output, helpText)
|
checkStringContains(t, output, helpText)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHasCustomFlagCompletion(t *testing.T) {
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
cmd *Command
|
||||||
|
expectedResult bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "HasCustomFlagCompletion() AllowCustomFlagCompletions not set",
|
||||||
|
cmd: &Command{
|
||||||
|
Use: "c",
|
||||||
|
Run: emptyRun,
|
||||||
|
},
|
||||||
|
expectedResult: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "HasCustomFlagCompletion() AllowCustomFlagCompletions set true",
|
||||||
|
cmd: &Command{
|
||||||
|
Use: "c",
|
||||||
|
Run: emptyRun,
|
||||||
|
AllowCustomFlagCompletions: true,
|
||||||
|
},
|
||||||
|
expectedResult: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "HasCustomFlagCompletion() AllowCustomFlagCompletions set false",
|
||||||
|
cmd: &Command{
|
||||||
|
Use: "c",
|
||||||
|
Run: emptyRun,
|
||||||
|
AllowCustomFlagCompletions: false,
|
||||||
|
},
|
||||||
|
expectedResult: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "HasCustomFlagCompletion() AllowCustomFlagCompletions not set with Valid",
|
||||||
|
cmd: &Command{
|
||||||
|
Use: "c",
|
||||||
|
Run: emptyRun,
|
||||||
|
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
return []string{}, ShellCompDirectiveNoFileComp
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedResult: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "HasCustomFlagCompletion() AllowCustomFlagCompletions set true",
|
||||||
|
cmd: &Command{
|
||||||
|
Use: "c",
|
||||||
|
Run: emptyRun,
|
||||||
|
AllowCustomFlagCompletions: true,
|
||||||
|
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
return []string{}, ShellCompDirectiveNoFileComp
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "HasCustomFlagCompletion() AllowCustomFlagCompletions set false",
|
||||||
|
cmd: &Command{
|
||||||
|
Use: "c",
|
||||||
|
Run: emptyRun,
|
||||||
|
AllowCustomFlagCompletions: false,
|
||||||
|
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
return []string{}, ShellCompDirectiveNoFileComp
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedResult: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if tc.cmd.HasCustomFlagCompletion() != tc.expectedResult {
|
||||||
|
t.Errorf("did not return expected results")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -358,7 +358,7 @@ func (c *Command) getCompletions(args []string) (*Command, []Completion, ShellCo
|
||||||
// This works by counting the arguments. Normally -- is not counted as arg but
|
// This works by counting the arguments. Normally -- is not counted as arg but
|
||||||
// if -- was already set or interspersed is false and there is already one arg then
|
// if -- was already set or interspersed is false and there is already one arg then
|
||||||
// the extra added -- is counted as arg.
|
// the extra added -- is counted as arg.
|
||||||
flagCompletion := true
|
flagCompletion := !finalCmd.HasCustomFlagCompletion()
|
||||||
_ = finalCmd.ParseFlags(append(finalArgs, "--"))
|
_ = finalCmd.ParseFlags(append(finalArgs, "--"))
|
||||||
newArgCount := finalCmd.Flags().NArg()
|
newArgCount := finalCmd.Flags().NArg()
|
||||||
|
|
||||||
|
|
|
@ -4016,3 +4016,294 @@ func TestInitDefaultCompletionCmd(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFlagNameCompletionAllowWithoutValidFunc(t *testing.T) {
|
||||||
|
rootCmd := &Command{
|
||||||
|
Use: "root",
|
||||||
|
Run: emptyRun,
|
||||||
|
AllowCustomFlagCompletions: true,
|
||||||
|
}
|
||||||
|
childCmd := &Command{
|
||||||
|
Use: "childCmd",
|
||||||
|
Short: "first command",
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(childCmd)
|
||||||
|
|
||||||
|
rootCmd.Flags().IntP("first", "f", -1, "first flag")
|
||||||
|
firstFlag := rootCmd.Flags().Lookup("first")
|
||||||
|
|
||||||
|
rootCmd.Flags().BoolP("second", "s", false, "second flag")
|
||||||
|
secondFlag := rootCmd.Flags().Lookup("second")
|
||||||
|
|
||||||
|
rootCmd.Flags().StringArrayP("array", "a", nil, "array flag")
|
||||||
|
arrayFlag := rootCmd.Flags().Lookup("array")
|
||||||
|
|
||||||
|
rootCmd.Flags().IntSliceP("slice", "l", nil, "slice flag")
|
||||||
|
sliceFlag := rootCmd.Flags().Lookup("slice")
|
||||||
|
|
||||||
|
rootCmd.Flags().BoolSliceP("bslice", "b", nil, "bool slice flag")
|
||||||
|
bsliceFlag := rootCmd.Flags().Lookup("bslice")
|
||||||
|
|
||||||
|
rootCmd.Flags().VarP(&customMultiString{}, "multi", "m", "multi string flag")
|
||||||
|
multiFlag := rootCmd.Flags().Lookup("multi")
|
||||||
|
|
||||||
|
// Test that flag names are not repeated unless they are an array or slice
|
||||||
|
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--first", "1", "--")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
// Reset the flag for the next command
|
||||||
|
firstFlag.Changed = false
|
||||||
|
|
||||||
|
expected := strings.Join([]string{
|
||||||
|
"--array",
|
||||||
|
"--bslice",
|
||||||
|
"--help",
|
||||||
|
"--multi",
|
||||||
|
"--second",
|
||||||
|
"--slice",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that flag names are not repeated unless they are an array or slice
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--first", "1", "--second=false", "--")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
// Reset the flag for the next command
|
||||||
|
firstFlag.Changed = false
|
||||||
|
secondFlag.Changed = false
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
"--array",
|
||||||
|
"--bslice",
|
||||||
|
"--help",
|
||||||
|
"--multi",
|
||||||
|
"--slice",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that flag names are not repeated unless they are an array or slice
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--slice", "1", "--slice=2", "--array", "val", "--bslice", "true", "--multi", "val", "--")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
// Reset the flag for the next command
|
||||||
|
sliceFlag.Changed = false
|
||||||
|
arrayFlag.Changed = false
|
||||||
|
bsliceFlag.Changed = false
|
||||||
|
multiFlag.Changed = false
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
"--array",
|
||||||
|
"--bslice",
|
||||||
|
"--first",
|
||||||
|
"--help",
|
||||||
|
"--multi",
|
||||||
|
"--second",
|
||||||
|
"--slice",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that flag names are not repeated unless they are an array or slice, using shortname
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-l", "1", "-l=2", "-a", "val", "-")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
// Reset the flag for the next command
|
||||||
|
sliceFlag.Changed = false
|
||||||
|
arrayFlag.Changed = false
|
||||||
|
multiFlag.Changed = false
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
"--array",
|
||||||
|
"-a",
|
||||||
|
"--bslice",
|
||||||
|
"-b",
|
||||||
|
"--first",
|
||||||
|
"-f",
|
||||||
|
"--help",
|
||||||
|
"-h",
|
||||||
|
"--multi",
|
||||||
|
"-m",
|
||||||
|
"--second",
|
||||||
|
"-s",
|
||||||
|
"--slice",
|
||||||
|
"-l",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that flag names are not repeated unless they are an array or slice, using shortname with prefix
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-l", "1", "-l=2", "-a", "val", "-a")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
// Reset the flag for the next command
|
||||||
|
sliceFlag.Changed = false
|
||||||
|
arrayFlag.Changed = false
|
||||||
|
multiFlag.Changed = false
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
"-a",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlagNameCompletionAllowWithValidFunc(t *testing.T) {
|
||||||
|
rootCmd := &Command{
|
||||||
|
Use: "root",
|
||||||
|
Run: emptyRun,
|
||||||
|
AllowCustomFlagCompletions: true,
|
||||||
|
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
return []string{"--bogus-flag", "-z", "non-match-value"}, ShellCompDirectiveNoFileComp
|
||||||
|
},
|
||||||
|
}
|
||||||
|
childCmd := &Command{
|
||||||
|
Use: "childCmd",
|
||||||
|
Short: "first command",
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(childCmd)
|
||||||
|
|
||||||
|
rootCmd.Flags().IntP("first", "f", -1, "first flag")
|
||||||
|
firstFlag := rootCmd.Flags().Lookup("first")
|
||||||
|
|
||||||
|
rootCmd.Flags().BoolP("second", "s", false, "second flag")
|
||||||
|
secondFlag := rootCmd.Flags().Lookup("second")
|
||||||
|
|
||||||
|
rootCmd.Flags().StringArrayP("array", "a", nil, "array flag")
|
||||||
|
arrayFlag := rootCmd.Flags().Lookup("array")
|
||||||
|
|
||||||
|
rootCmd.Flags().IntSliceP("slice", "l", nil, "slice flag")
|
||||||
|
sliceFlag := rootCmd.Flags().Lookup("slice")
|
||||||
|
|
||||||
|
rootCmd.Flags().BoolSliceP("bslice", "b", nil, "bool slice flag")
|
||||||
|
bsliceFlag := rootCmd.Flags().Lookup("bslice")
|
||||||
|
|
||||||
|
rootCmd.Flags().VarP(&customMultiString{}, "multi", "m", "multi string flag")
|
||||||
|
multiFlag := rootCmd.Flags().Lookup("multi")
|
||||||
|
|
||||||
|
// Test that flag names are not repeated unless they are an array or slice
|
||||||
|
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--first", "1", "--")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
// Reset the flag for the next command
|
||||||
|
firstFlag.Changed = false
|
||||||
|
|
||||||
|
expected := strings.Join([]string{
|
||||||
|
"--bogus-flag",
|
||||||
|
"-z",
|
||||||
|
"non-match-value",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that flag names are not repeated unless they are an array or slice
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--first", "1", "--second=false", "--")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
// Reset the flag for the next command
|
||||||
|
firstFlag.Changed = false
|
||||||
|
secondFlag.Changed = false
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
"--bogus-flag",
|
||||||
|
"-z",
|
||||||
|
"non-match-value",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that flag names are not repeated unless they are an array or slice
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--slice", "1", "--slice=2", "--array", "val", "--bslice", "true", "--multi", "val", "--")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
// Reset the flag for the next command
|
||||||
|
sliceFlag.Changed = false
|
||||||
|
arrayFlag.Changed = false
|
||||||
|
bsliceFlag.Changed = false
|
||||||
|
multiFlag.Changed = false
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
"--bogus-flag",
|
||||||
|
"-z",
|
||||||
|
"non-match-value",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that flag names are not repeated unless they are an array or slice, using shortname
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-l", "1", "-l=2", "-a", "val", "-")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
// Reset the flag for the next command
|
||||||
|
sliceFlag.Changed = false
|
||||||
|
arrayFlag.Changed = false
|
||||||
|
multiFlag.Changed = false
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
"--bogus-flag",
|
||||||
|
"-z",
|
||||||
|
"non-match-value",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that flag names are not repeated unless they are an array or slice, using shortname with prefix
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-l", "1", "-l=2", "-a", "val", "-a")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
// Reset the flag for the next command
|
||||||
|
sliceFlag.Changed = false
|
||||||
|
arrayFlag.Changed = false
|
||||||
|
multiFlag.Changed = false
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
"--bogus-flag",
|
||||||
|
"-z",
|
||||||
|
"non-match-value",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue