Fix search for parent's suggest function and improve tests.

This commit is contained in:
Žan V. Dragan 2025-01-19 00:36:37 +01:00
parent 84b14d4e50
commit 90335672a9
2 changed files with 131 additions and 34 deletions

View file

@ -485,12 +485,31 @@ func (c *Command) Help() error {
// SuggestFunc returns suggestions for the provided typedName using either
// the function set by SetSuggestFunc for this command, parent's or a default one.
// When searching for a parent's function, it recursively checks towards the root
// and returns the first one found. If none found, uses direct parent's default.
func (c *Command) SuggestFunc(typedName string) string {
if c.suggestFunc != nil && !c.DisableSuggestions {
if c.DisableSuggestions {
return ""
}
if c.suggestFunc != nil {
return c.suggestFunc(typedName)
}
if c.HasParent() {
return c.Parent().SuggestFunc(typedName)
var getParentFunc func(*Command) func(string) string
getParentFunc = func(parent *Command) func(string) string {
if parent.suggestFunc != nil {
return parent.suggestFunc
}
if parent.HasParent() {
return getParentFunc(parent.Parent())
}
return nil
}
parentFunc := getParentFunc(c.Parent())
if parentFunc != nil {
return parentFunc(typedName)
}
return c.Parent().findSuggestions(typedName)
}
return c.findSuggestions(typedName)
}

View file

@ -1394,44 +1394,122 @@ func TestSuggestions(t *testing.T) {
}
func TestCustomSuggestions(t *testing.T) {
templateWithCustomSuggestions := "Error: unknown command \"%s\" for \"root\"\nSome custom suggestion.\n\nRun 'root --help' for usage.\n"
templateWithDefaultSuggestions := "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"
rootCmd := &Command{Use: "root", Run: emptyRun}
timesCmd := &Command{Use: "times", Run: emptyRun}
rootCmd.AddCommand(timesCmd)
for typo, suggestion := range map[string]string{"time": "times", "timse": "times"} {
for _, suggestionsDisabled := range []bool{true, false} {
for _, setCustomSuggest := range []bool{true, false} {
rootCmd := &Command{Use: "root", Run: emptyRun}
timesCmd := &Command{
Use: "times",
Run: emptyRun,
}
rootCmd.AddCommand(timesCmd)
var expected, output string
rootCmd.DisableSuggestions = suggestionsDisabled
expected = ""
output, _ = executeCommand(rootCmd, "times")
if output != expected {
t.Errorf("\nExpected:\n %q\nGot:\n %q", expected, output)
}
if setCustomSuggest {
rootCmd.SetSuggestFunc(func(a string) string {
return "\nSome custom suggestion.\n"
})
}
expected = fmt.Sprintf("Error: unknown command \"%s\" for \"root\"\n\nDid you mean this?\n\t%s\n\nRun 'root --help' for usage.\n", "time", "times")
output, _ = executeCommand(rootCmd, "time")
if output != expected {
t.Errorf("\nExpected:\n %q\nGot:\n %q", expected, output)
}
var expected string
if suggestionsDisabled {
expected = fmt.Sprintf(templateWithoutSuggestions, typo)
} else if setCustomSuggest {
expected = fmt.Sprintf(templateWithCustomSuggestions, typo)
} else {
expected = fmt.Sprintf(templateWithDefaultSuggestions, typo, suggestion)
}
rootCmd.DisableSuggestions = true
output, _ := executeCommand(rootCmd, typo)
expected = fmt.Sprintf("Error: unknown command \"%s\" for \"root\"\nRun 'root --help' for usage.\n", "time")
output, _ = executeCommand(rootCmd, "time")
if output != expected {
t.Errorf("\nExpected:\n %q\nGot:\n %q", expected, output)
}
if output != expected {
t.Errorf("Unexpected response.\nExpected:\n %q\nGot:\n %q\n", expected, output)
}
}
}
rootCmd.DisableSuggestions = false
rootCmd.SetSuggestFunc(func(typedName string) string {
return "\nSome custom suggestion.\n"
})
expected = fmt.Sprintf("Error: unknown command \"%s\" for \"root\"\nSome custom suggestion.\n\nRun 'root --help' for usage.\n", "time")
output, _ = executeCommand(rootCmd, "time")
if output != expected {
t.Errorf("\nExpected:\n %q\nGot:\n %q", expected, output)
}
}
func TestCustomSuggestions_OnlyValidArgs(t *testing.T) {
validArgs := []string{"a"}
rootCmd := &Command{Use: "root", Args: OnlyValidArgs, Run: emptyRun, ValidArgs: validArgs}
grandparentCmd := &Command{Use: "grandparent", Args: OnlyValidArgs, Run: emptyRun, ValidArgs: validArgs}
parentCmd := &Command{Use: "parent", Args: OnlyValidArgs, Run: emptyRun, ValidArgs: validArgs}
timesCmd := &Command{Use: "times", Run: emptyRun}
parentCmd.AddCommand(timesCmd)
grandparentCmd.AddCommand(parentCmd)
rootCmd.AddCommand(grandparentCmd)
var expected, output string
// No typos.
expected = ""
output, _ = executeCommand(rootCmd, "grandparent")
if output != expected {
t.Errorf("\nExpected:\n %q\nGot:\n %q", expected, output)
}
expected = ""
output, _ = executeCommand(rootCmd, "grandparent", "parent")
if output != expected {
t.Errorf("\nExpected:\n %q\nGot:\n %q", expected, output)
}
expected = ""
output, _ = executeCommand(rootCmd, "grandparent", "parent", "times")
if output != expected {
t.Errorf("\nExpected:\n %q\nGot:\n %q", expected, output)
}
// 1st level typo.
expected = "Error: invalid argument \"grandparen\" for \"root\"\n\nDid you mean this?\n\tgrandparent\n\nUsage:\n root [flags]\n root [command]\n\nAvailable Commands:\n completion Generate the autocompletion script for the specified shell\n grandparent \n help Help about any command\n\nFlags:\n -h, --help help for root\n\nUse \"root [command] --help\" for more information about a command.\n\n"
output, _ = executeCommand(rootCmd, "grandparen")
if output != expected {
t.Errorf("\nExpected:\n %q\nGot:\n %q", expected, output)
}
// 2nd level typo.
expected = "Error: invalid argument \"paren\" for \"root grandparent\"\nUsage:\n root grandparent [flags]\n root grandparent [command]\n\nAvailable Commands:\n parent \n\nFlags:\n -h, --help help for grandparent\n\nUse \"root grandparent [command] --help\" for more information about a command.\n\n"
output, _ = executeCommand(rootCmd, "grandparent", "paren")
if output != expected {
t.Errorf("\nExpected:\n %q\nGot:\n %q", expected, output)
}
// 3rd level typo.
expected = "Error: invalid argument \"time\" for \"root grandparent parent\"\nUsage:\n root grandparent parent [flags]\n root grandparent parent [command]\n\nAvailable Commands:\n times \n\nFlags:\n -h, --help help for parent\n\nUse \"root grandparent parent [command] --help\" for more information about a command.\n\n"
output, _ = executeCommand(rootCmd, "grandparent", "parent", "time")
if output != expected {
t.Errorf("\nExpected:\n %q\nGot:\n %q", expected, output)
}
// Custom suggestion on root function.
rootCmd.SetSuggestFunc(func(typedName string) string {
return "\nRoot custom suggestion.\n"
})
expected = "Error: invalid argument \"grandparen\" for \"root\"\nRoot custom suggestion.\n\nUsage:\n root [flags]\n root [command]\n\nAvailable Commands:\n completion Generate the autocompletion script for the specified shell\n grandparent \n help Help about any command\n\nFlags:\n -h, --help help for root\n\nUse \"root [command] --help\" for more information about a command.\n\n"
output, _ = executeCommand(rootCmd, "grandparen")
if output != expected {
t.Errorf("\nExpected:\n %q\nGot:\n %q", expected, output)
}
expected = "Error: invalid argument \"time\" for \"root grandparent parent\"\nRoot custom suggestion.\n\nUsage:\n root grandparent parent [flags]\n root grandparent parent [command]\n\nAvailable Commands:\n times \n\nFlags:\n -h, --help help for parent\n\nUse \"root grandparent parent [command] --help\" for more information about a command.\n\n"
output, _ = executeCommand(rootCmd, "grandparent", "parent", "time")
if output != expected {
t.Errorf("\nExpected:\n %q\nGot:\n %q", expected, output)
}
// Custom suggestion on parent function (kept root's to make sure this one is prioritised).
parentCmd.SetSuggestFunc(func(typedName string) string {
return "\nParent custom suggestion.\n"
})
expected = "Error: invalid argument \"time\" for \"root grandparent parent\"\nParent custom suggestion.\n\nUsage:\n root grandparent parent [flags]\n root grandparent parent [command]\n\nAvailable Commands:\n times \n\nFlags:\n -h, --help help for parent\n\nUse \"root grandparent parent [command] --help\" for more information about a command.\n\n"
output, _ = executeCommand(rootCmd, "grandparent", "parent", "time")
if output != expected {
t.Errorf("\nExpected:\n %q\nGot:\n %q", expected, output)
}
}