Skip to content

Commit

Permalink
Support subcommands checking for suggestions (spf13#1500)
Browse files Browse the repository at this point in the history
Till now, only the root command is looked for suggestions.
Now `findSuggestions` works for nested commands as well.

For example

```
Error: unknown command "rewin" for "root times"

Did you mean this?
	rewind

Run 'root times --help' for usage.
```

Signed-off-by: Yuval Goldberg <yuvigoldi@gmail.com>
  • Loading branch information
YuviGold authored and umarcor committed Nov 15, 2021
1 parent 7815c8b commit ba6c848
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 29 deletions.
8 changes: 4 additions & 4 deletions args.go
Expand Up @@ -27,17 +27,17 @@ func validateArgs(cmd *Command, args []string) error {
}

// Legacy arg validation has the following behaviour:
// - root commands with no subcommands can take arbitrary arguments
// - root commands with subcommands will do subcommand validity checking
// - commands with no subcommands can take arbitrary arguments
// - commands with subcommands will do subcommand validity checking
// - subcommands will always accept arbitrary arguments
func legacyArgs(cmd *Command, args []string) error {
// no subcommand, always take args
if !cmd.HasSubCommands() {
return nil
}

// root command with subcommands, do subcommand checking.
if !cmd.HasParent() && len(args) > 0 {
// do subcommand checking
if len(args) > 0 {
return fmt.Errorf("unknown command %q for %q%s", args[0], cmd.CommandPath(), cmd.findSuggestions(args[0]))
}
return nil
Expand Down
122 changes: 97 additions & 25 deletions command_test.go
Expand Up @@ -1194,39 +1194,111 @@ func TestSuggestions(t *testing.T) {
SuggestFor: []string{"counts"},
Run: emptyRun,
}
rewindCmd := &Command{
Use: "rewind",
SuggestFor: []string{"dejavu"},
Run: emptyRun,
}
timesCmd.AddCommand(rewindCmd)
rootCmd.AddCommand(timesCmd)

templateWithSuggestions := "Error: unknown command \"%s\" for \"root\"\n\nDid you mean this?\n\t%s\n\nRun 'root --help' for usage.\n"
templateWithoutSuggestions := "Error: unknown command \"%s\" for \"root\"\nRun 'root --help' for usage.\n"

tests := map[string]string{
"time": "times",
"tiems": "times",
"tims": "times",
"timeS": "times",
"rimes": "times",
"ti": "times",
"t": "times",
"timely": "times",
"ri": "",
"timezone": "",
"foo": "",
"counts": "times",
}

for typo, suggestion := range tests {
templateSuggestions := "\nDid you mean this?\n\t%s\n\n"

templatePrefix := "Error: unknown command \"%s\" for \"%s\"\n"
templateSuffix := "Run '%s --help' for usage.\n"

tests := []struct {
input string
targetCommand string
expectedSuggestion string
}{
{
input: "time",
expectedSuggestion: "times",
},
{
input: "tiems",
expectedSuggestion: "times",
},
{
input: "tims",
expectedSuggestion: "times",
},
{
input: "timeS",
expectedSuggestion: "times",
},
{
input: "rimes",
expectedSuggestion: "times",
},
{
input: "ti",
expectedSuggestion: "times",
},
{
input: "t",
expectedSuggestion: "times",
},
{
input: "timely",
expectedSuggestion: "times",
},
{
input: "ri",
expectedSuggestion: "",
},
{
input: "timezone",
expectedSuggestion: "",
},
{
input: "foo",
expectedSuggestion: "",
},
{
input: "counts",
expectedSuggestion: "times",
},
{
input: "foo rewind",
expectedSuggestion: "",
},
{
input: "times rewin",
targetCommand: "root times",
expectedSuggestion: "rewind",
},
{
input: "times dejavu",
targetCommand: "root times",
expectedSuggestion: "rewind",
},
}

for index := range tests {
for _, suggestionsDisabled := range []bool{true, false} {
test := tests[index]

timesCmd.DisableSuggestions = suggestionsDisabled
rootCmd.DisableSuggestions = suggestionsDisabled

var expected string
output, _ := executeCommand(rootCmd, typo)
args := strings.Split(test.input, " ")
output, _ := executeCommand(rootCmd, args...)

if suggestion == "" || suggestionsDisabled {
expected = fmt.Sprintf(templateWithoutSuggestions, typo)
} else {
expected = fmt.Sprintf(templateWithSuggestions, typo, suggestion)
unknownArg := args[len(args)-1]
if test.targetCommand == "" {
test.targetCommand = rootCmd.Use
unknownArg = args[0]
}

expected := fmt.Sprintf(templatePrefix, unknownArg, test.targetCommand)
if test.expectedSuggestion != "" && !suggestionsDisabled {
expected += fmt.Sprintf(templateSuggestions, test.expectedSuggestion)
}

expected += fmt.Sprintf(templateSuffix, test.targetCommand)

if output != expected {
t.Errorf("Unexpected response.\nExpected:\n %q\nGot:\n %q\n", expected, output)
}
Expand Down

0 comments on commit ba6c848

Please sign in to comment.