From 5897ead2466b52f546c0fd1f784f19328f8654d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDan=20V=2E=20Dragan?= Date: Mon, 20 Jan 2025 21:15:57 +0100 Subject: [PATCH] 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 c86b1bb..ed1e70c 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 78abf4e..54d7b68 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 e5fad89..076aa5b 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)