From e4d96b75c149feac932fcba8869a70d028924ae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDan=20V=2E=20Dragan?= <zan10d@gmail.com> Date: Fri, 17 Jan 2025 22:27:51 +0100 Subject: [PATCH 1/6] Make command suggestion messages configurable. --- args.go | 4 ++-- command.go | 18 ++++++++++++++++++ command_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/args.go b/args.go index ed1e70ce..92e1dde0 100644 --- a/args.go +++ b/args.go @@ -33,7 +33,7 @@ func legacyArgs(cmd *Command, args []string) error { // root command with subcommands, do subcommand checking. if !cmd.HasParent() && len(args) > 0 { - return fmt.Errorf("unknown command %q for %q%s", args[0], cmd.CommandPath(), cmd.findSuggestions(args[0])) + return fmt.Errorf("unknown command %q for %q%s", args[0], cmd.CommandPath(), cmd.SuggestFunc()(args[0])) } return nil } @@ -58,7 +58,7 @@ func OnlyValidArgs(cmd *Command, args []string) error { } for _, v := range args { if !stringInSlice(v, validArgs) { - return fmt.Errorf("invalid argument %q for %q%s", v, cmd.CommandPath(), cmd.findSuggestions(args[0])) + return fmt.Errorf("invalid argument %q for %q%s", v, cmd.CommandPath(), cmd.SuggestFunc()(args[0])) } } } diff --git a/command.go b/command.go index 19602946..abf3f06c 100644 --- a/command.go +++ b/command.go @@ -180,6 +180,8 @@ type Command struct { helpCommand *Command // helpCommandGroupID is the group id for the helpCommand helpCommandGroupID string + // suggestFunc is suggest func defined by the user. + suggestFunc func(string) string // completionCommandGroupID is the group id for the completion command completionCommandGroupID string @@ -340,6 +342,10 @@ func (c *Command) SetHelpCommandGroupID(groupID string) { c.helpCommandGroupID = groupID } +func (c *Command) SetSuggestFunc(f func(string) string) { + c.suggestFunc = f +} + // SetCompletionCommandGroupID sets the group id of the completion command. func (c *Command) SetCompletionCommandGroupID(groupID string) { // completionCommandGroupID is used if no completion command is defined by the user @@ -477,6 +483,18 @@ func (c *Command) Help() error { return nil } +// SuggestFunc returns either the function set by SetSuggestFunc for this command +// or a parent, or it returns a function with default suggestion behavior. +func (c *Command) SuggestFunc() func(string) string { + if c.suggestFunc != nil && !c.DisableSuggestions { + return c.suggestFunc + } + if c.HasParent() { + return c.Parent().SuggestFunc() + } + return c.findSuggestions +} + // UsageString returns usage string. func (c *Command) UsageString() string { // Storing normal writers diff --git a/command_test.go b/command_test.go index 837b6b30..16df3a53 100644 --- a/command_test.go +++ b/command_test.go @@ -1393,6 +1393,48 @@ 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" + + 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) + + rootCmd.DisableSuggestions = suggestionsDisabled + + if setCustomSuggest { + rootCmd.SetSuggestFunc(func(a string) string { + return "\nSome custom suggestion.\n" + }) + } + + 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) + } + + output, _ := executeCommand(rootCmd, typo) + + if output != expected { + t.Errorf("Unexpected response.\nExpected:\n %q\nGot:\n %q\n", expected, output) + } + } + } + } +} + func TestCaseInsensitive(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} childCmd := &Command{Use: "child", Run: emptyRun, Aliases: []string{"alternative"}} From 84b14d4e50375e2f3fd3f03033d8b6e0d851e60e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDan=20V=2E=20Dragan?= <zan10d@gmail.com> Date: Sat, 18 Jan 2025 00:14:46 +0100 Subject: [PATCH 2/6] Simplify calling of the suggestion function. --- args.go | 4 ++-- command.go | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/args.go b/args.go index 92e1dde0..c86b1bb4 100644 --- a/args.go +++ b/args.go @@ -33,7 +33,7 @@ func legacyArgs(cmd *Command, args []string) error { // root command with subcommands, do subcommand checking. if !cmd.HasParent() && len(args) > 0 { - return fmt.Errorf("unknown command %q for %q%s", args[0], cmd.CommandPath(), cmd.SuggestFunc()(args[0])) + return fmt.Errorf("unknown command %q for %q%s", args[0], cmd.CommandPath(), cmd.SuggestFunc(args[0])) } return nil } @@ -58,7 +58,7 @@ func OnlyValidArgs(cmd *Command, args []string) error { } for _, v := range args { if !stringInSlice(v, validArgs) { - return fmt.Errorf("invalid argument %q for %q%s", v, cmd.CommandPath(), cmd.SuggestFunc()(args[0])) + return fmt.Errorf("invalid argument %q for %q%s", v, cmd.CommandPath(), cmd.SuggestFunc(args[0])) } } } diff --git a/command.go b/command.go index abf3f06c..1b3a687b 100644 --- a/command.go +++ b/command.go @@ -483,16 +483,16 @@ func (c *Command) Help() error { return nil } -// SuggestFunc returns either the function set by SetSuggestFunc for this command -// or a parent, or it returns a function with default suggestion behavior. -func (c *Command) SuggestFunc() func(string) string { +// SuggestFunc returns suggestions for the provided typedName using either +// the function set by SetSuggestFunc for this command, parent's or a default one. +func (c *Command) SuggestFunc(typedName string) string { if c.suggestFunc != nil && !c.DisableSuggestions { - return c.suggestFunc + return c.suggestFunc(typedName) } if c.HasParent() { - return c.Parent().SuggestFunc() + return c.Parent().SuggestFunc(typedName) } - return c.findSuggestions + return c.findSuggestions(typedName) } // UsageString returns usage string. From 90335672a922af345d72bf493e1530d761ed0cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDan=20V=2E=20Dragan?= <zan10d@gmail.com> Date: Sun, 19 Jan 2025 00:36:37 +0100 Subject: [PATCH 3/6] Fix search for parent's suggest function and improve tests. --- command.go | 23 +++++++- command_test.go | 142 +++++++++++++++++++++++++++++++++++++----------- 2 files changed, 131 insertions(+), 34 deletions(-) diff --git a/command.go b/command.go index 1b3a687b..2b152058 100644 --- a/command.go +++ b/command.go @@ -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) } diff --git a/command_test.go b/command_test.go index 16df3a53..e5fad89b 100644 --- a/command_test.go +++ b/command_test.go @@ -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) } } From 4d65edfb548d3f28c33a164d374f997c6dfe142d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDan=20V=2E=20Dragan?= <zan10d@gmail.com> Date: Sun, 19 Jan 2025 13:05:58 +0100 Subject: [PATCH 4/6] Switch condition and returns. --- command.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/command.go b/command.go index 2b152058..78abf4e2 100644 --- a/command.go +++ b/command.go @@ -500,10 +500,10 @@ func (c *Command) SuggestFunc(typedName string) string { if parent.suggestFunc != nil { return parent.suggestFunc } - if parent.HasParent() { - return getParentFunc(parent.Parent()) + if !parent.HasParent() { + return nil } - return nil + return getParentFunc(parent.Parent()) } parentFunc := getParentFunc(c.Parent()) if parentFunc != nil { From 5897ead2466b52f546c0fd1f784f19328f8654d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDan=20V=2E=20Dragan?= <zan10d@gmail.com> Date: Mon, 20 Jan 2025 21:15:57 +0100 Subject: [PATCH 5/6] Rework to allow override on suggestion output, only, keeping common search logic. --- args.go | 4 +-- command.go | 69 +++++++++++++++++++++++-------------------------- command_test.go | 24 ++++++++--------- 3 files changed, 46 insertions(+), 51 deletions(-) diff --git a/args.go b/args.go index c86b1bb4..ed1e70ce 100644 --- a/args.go +++ b/args.go @@ -33,7 +33,7 @@ func legacyArgs(cmd *Command, args []string) error { // root command with subcommands, do subcommand checking. if !cmd.HasParent() && len(args) > 0 { - return fmt.Errorf("unknown command %q for %q%s", args[0], cmd.CommandPath(), cmd.SuggestFunc(args[0])) + return fmt.Errorf("unknown command %q for %q%s", args[0], cmd.CommandPath(), cmd.findSuggestions(args[0])) } return nil } @@ -58,7 +58,7 @@ func OnlyValidArgs(cmd *Command, args []string) error { } for _, v := range args { if !stringInSlice(v, validArgs) { - return fmt.Errorf("invalid argument %q for %q%s", v, cmd.CommandPath(), cmd.SuggestFunc(args[0])) + return fmt.Errorf("invalid argument %q for %q%s", v, cmd.CommandPath(), cmd.findSuggestions(args[0])) } } } diff --git a/command.go b/command.go index 78abf4e2..54d7b68d 100644 --- a/command.go +++ b/command.go @@ -180,8 +180,8 @@ type Command struct { helpCommand *Command // helpCommandGroupID is the group id for the helpCommand helpCommandGroupID string - // suggestFunc is suggest func defined by the user. - suggestFunc func(string) string + // suggestOutputFunc is user's override for the suggestion output. + suggestOutputFunc func([]string) string // completionCommandGroupID is the group id for the completion command completionCommandGroupID string @@ -342,8 +342,8 @@ func (c *Command) SetHelpCommandGroupID(groupID string) { c.helpCommandGroupID = groupID } -func (c *Command) SetSuggestFunc(f func(string) string) { - c.suggestFunc = f +func (c *Command) SetSuggestOutputFunc(f func([]string) string) { + c.suggestOutputFunc = f } // SetCompletionCommandGroupID sets the group id of the completion command. @@ -483,37 +483,6 @@ func (c *Command) Help() error { return nil } -// 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.DisableSuggestions { - return "" - } - if c.suggestFunc != nil { - return c.suggestFunc(typedName) - } - if c.HasParent() { - 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 nil - } - return getParentFunc(parent.Parent()) - } - parentFunc := getParentFunc(c.Parent()) - if parentFunc != nil { - return parentFunc(typedName) - } - return c.Parent().findSuggestions(typedName) - } - return c.findSuggestions(typedName) -} - // UsageString returns usage string. func (c *Command) UsageString() string { // Storing normal writers @@ -786,15 +755,41 @@ func (c *Command) Find(args []string) (*Command, []string, error) { return commandFound, a, nil } -func (c *Command) findSuggestions(arg string) string { +// findSuggestions returns suggestions for the provided typedName if suggestions aren't disabled. +// The output building function can be overridden by setting it with the SetSuggestOutputFunc. +// If the output override is, instead, set on a parent, it uses the first one found. +// If none is set, a default is used. +func (c *Command) findSuggestions(typedName string) string { if c.DisableSuggestions { return "" } if c.SuggestionsMinimumDistance <= 0 { c.SuggestionsMinimumDistance = 2 } + + suggestions := c.SuggestionsFor(typedName) + + if c.suggestOutputFunc != nil { + return c.suggestOutputFunc(suggestions) + } + if c.HasParent() { + var getParentFunc func(*Command) func([]string) string + getParentFunc = func(parent *Command) func([]string) string { + if parent.suggestOutputFunc != nil { + return parent.suggestOutputFunc + } + if !parent.HasParent() { + return nil + } + return getParentFunc(parent.Parent()) + } + if parentFunc := getParentFunc(c.Parent()); parentFunc != nil { + return parentFunc(suggestions) + } + } + var sb strings.Builder - if suggestions := c.SuggestionsFor(arg); len(suggestions) > 0 { + if len(suggestions) > 0 { sb.WriteString("\n\nDid you mean this?\n") for _, s := range suggestions { _, _ = fmt.Fprintf(&sb, "\t%v\n", s) diff --git a/command_test.go b/command_test.go index e5fad89b..076aa5b7 100644 --- a/command_test.go +++ b/command_test.go @@ -1421,11 +1421,11 @@ func TestCustomSuggestions(t *testing.T) { } rootCmd.DisableSuggestions = false - rootCmd.SetSuggestFunc(func(typedName string) string { - return "\nSome custom suggestion.\n" + rootCmd.SetSuggestOutputFunc(func(suggestions []string) string { + return fmt.Sprintf("\nSuggestions:\n\t%s\n", strings.Join(suggestions, "\n")) }) - expected = fmt.Sprintf("Error: unknown command \"%s\" for \"root\"\nSome custom suggestion.\n\nRun 'root --help' for usage.\n", "time") + expected = fmt.Sprintf("Error: unknown command \"time\" for \"root\"\nSuggestions:\n\ttimes\n\nRun 'root --help' for usage.\n") output, _ = executeCommand(rootCmd, "time") if output != expected { t.Errorf("\nExpected:\n %q\nGot:\n %q", expected, output) @@ -1471,42 +1471,42 @@ func TestCustomSuggestions_OnlyValidArgs(t *testing.T) { } // 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" + expected = "Error: invalid argument \"paren\" for \"root grandparent\"\n\nDid you mean this?\n\tparent\n\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" + expected = "Error: invalid argument \"time\" for \"root grandparent parent\"\n\nDid you mean this?\n\ttimes\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 root function. - rootCmd.SetSuggestFunc(func(typedName string) string { - return "\nRoot custom suggestion.\n" + rootCmd.SetSuggestOutputFunc(func(suggestions []string) string { + return fmt.Sprintf("\nRoot Suggestions:\n\t%s\n", strings.Join(suggestions, "\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" + expected = "Error: invalid argument \"grandparen\" for \"root\"\nRoot Suggestions:\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) } - 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" + expected = "Error: invalid argument \"time\" for \"root grandparent parent\"\nRoot Suggestions:\n\ttimes\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" + parentCmd.SetSuggestOutputFunc(func(suggestions []string) string { + return fmt.Sprintf("\nParent Suggestions:\n\t%s\n", strings.Join(suggestions, "\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" + expected = "Error: invalid argument \"time\" for \"root grandparent parent\"\nParent Suggestions:\n\ttimes\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) From b81f68ad852c695a67c8d610986929045e829292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDan=20V=2E=20Dragan?= <zan10d@gmail.com> Date: Sat, 1 Feb 2025 21:42:11 +0100 Subject: [PATCH 6/6] Fix unnecessary Sprintf. --- command_test.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/command_test.go b/command_test.go index 076aa5b7..41815144 100644 --- a/command_test.go +++ b/command_test.go @@ -1399,6 +1399,8 @@ func TestCustomSuggestions(t *testing.T) { rootCmd.AddCommand(timesCmd) var expected, output string + suggestion := "times" + typo := "time" expected = "" output, _ = executeCommand(rootCmd, "times") @@ -1406,16 +1408,16 @@ func TestCustomSuggestions(t *testing.T) { t.Errorf("\nExpected:\n %q\nGot:\n %q", expected, output) } - 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") + expected = fmt.Sprintf("Error: unknown command \"%s\" for \"root\"\n\nDid you mean this?\n\t%s\n\nRun 'root --help' for usage.\n", typo, suggestion) + output, _ = executeCommand(rootCmd, typo) if output != expected { t.Errorf("\nExpected:\n %q\nGot:\n %q", expected, output) } rootCmd.DisableSuggestions = true - expected = fmt.Sprintf("Error: unknown command \"%s\" for \"root\"\nRun 'root --help' for usage.\n", "time") - output, _ = executeCommand(rootCmd, "time") + expected = fmt.Sprintf("Error: unknown command \"%s\" for \"root\"\nRun 'root --help' for usage.\n", typo) + output, _ = executeCommand(rootCmd, typo) if output != expected { t.Errorf("\nExpected:\n %q\nGot:\n %q", expected, output) } @@ -1425,8 +1427,8 @@ func TestCustomSuggestions(t *testing.T) { return fmt.Sprintf("\nSuggestions:\n\t%s\n", strings.Join(suggestions, "\n")) }) - expected = fmt.Sprintf("Error: unknown command \"time\" for \"root\"\nSuggestions:\n\ttimes\n\nRun 'root --help' for usage.\n") - output, _ = executeCommand(rootCmd, "time") + expected = fmt.Sprintf("Error: unknown command \"%s\" for \"root\"\nSuggestions:\n\t%s\n\nRun 'root --help' for usage.\n", typo, suggestion) + output, _ = executeCommand(rootCmd, typo) if output != expected { t.Errorf("\nExpected:\n %q\nGot:\n %q", expected, output) }