From 3afa4db2c62a1ff4befba4660b6a3b057ebcdb8a Mon Sep 17 00:00:00 2001 From: Fabiano Franz Date: Fri, 25 Sep 2015 11:41:47 -0300 Subject: [PATCH 1/2] Improve suggestions - prefix will match --- cobra_test.go | 36 +++++++++++++++++++++++------------- command.go | 34 +++++++++++++++++++--------------- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/cobra_test.go b/cobra_test.go index c9eef5d..6e0e5e3 100644 --- a/cobra_test.go +++ b/cobra_test.go @@ -816,22 +816,32 @@ func TestRootSuggestions(t *testing.T) { cmd.AddCommand(cmdTimes) tests := map[string]string{ - "time": "times", - "tiems": "times", - "timeS": "times", - "rimes": "times", + "time": "times", + "tiems": "times", + "tims": "times", + "timeS": "times", + "rimes": "times", + "ti": "times", + "t": "times", + "timely": "times", + "ri": "", + "timezone": "", + "foo": "", } for typo, suggestion := range tests { - cmd.DisableSuggestions = false - result := simpleTester(cmd, typo) - if expected := fmt.Sprintf(outputWithSuggestions, typo, suggestion); result.Output != expected { - t.Errorf("Unexpected response.\nExpecting to be:\n %q\nGot:\n %q\n", expected, result.Output) - } - cmd.DisableSuggestions = true - result = simpleTester(cmd, typo) - if expected := fmt.Sprintf(outputWithoutSuggestions, typo); result.Output != expected { - t.Errorf("Unexpected response.\nExpecting to be:\n %q\nGot:\n %q\n", expected, result.Output) + for _, suggestionsDisabled := range []bool{false, true} { + cmd.DisableSuggestions = suggestionsDisabled + result := simpleTester(cmd, typo) + expected := "" + if len(suggestion) == 0 || suggestionsDisabled { + expected = fmt.Sprintf(outputWithoutSuggestions, typo) + } else { + expected = fmt.Sprintf(outputWithSuggestions, typo, suggestion) + } + if result.Output != expected { + t.Errorf("Unexpected response.\nExpecting to be:\n %q\nGot:\n %q\n", expected, result.Output) + } } } } diff --git a/command.go b/command.go index 0cc0b72..44e962e 100644 --- a/command.go +++ b/command.go @@ -429,33 +429,37 @@ func (c *Command) Find(args []string) (*Command, []string, error) { // root command with subcommands, do subcommand checking if commandFound == c && len(argsWOflags) > 0 { - suggestions := "" + suggestionsString := "" if !c.DisableSuggestions { if c.SuggestionsMinimumDistance <= 0 { c.SuggestionsMinimumDistance = 2 } - similar := []string{} - for _, cmd := range c.commands { - if cmd.IsAvailableCommand() { - levenshtein := ld(argsWOflags[0], cmd.Name(), true) - if levenshtein <= c.SuggestionsMinimumDistance { - similar = append(similar, cmd.Name()) - } - } - } - if len(similar) > 0 { - suggestions += "\n\nDid you mean this?\n" - for _, s := range similar { - suggestions += fmt.Sprintf("\t%v\n", s) + if suggestions := c.SuggestionsFor(argsWOflags[0]); len(suggestions) > 0 { + suggestionsString += "\n\nDid you mean this?\n" + for _, s := range suggestions { + suggestionsString += fmt.Sprintf("\t%v\n", s) } } } - return commandFound, a, fmt.Errorf("unknown command %q for %q%s", argsWOflags[0], commandFound.CommandPath(), suggestions) + return commandFound, a, fmt.Errorf("unknown command %q for %q%s", argsWOflags[0], commandFound.CommandPath(), suggestionsString) } return commandFound, a, nil } +func (c *Command) SuggestionsFor(cmdName string) []string { + suggestions := []string{} + for _, cmd := range c.commands { + if cmd.IsAvailableCommand() { + levenshtein := ld(cmdName, cmd.Name(), true) + if levenshtein <= c.SuggestionsMinimumDistance || strings.HasPrefix(strings.ToLower(cmd.Name()), strings.ToLower(cmdName)) { + suggestions = append(suggestions, cmd.Name()) + } + } + } + return suggestions +} + func (c *Command) Root() *Command { var findRoot func(*Command) *Command From a83a1721222706babd18127413004bc400304cf3 Mon Sep 17 00:00:00 2001 From: Fabiano Franz Date: Fri, 25 Sep 2015 12:04:28 -0300 Subject: [PATCH 2/2] Improve suggestions - add explicit SuggestFor attribute --- README.md | 12 ++++++++++++ cobra_test.go | 8 +++++--- command.go | 15 ++++++++++++--- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8869ab9..0887e46 100644 --- a/README.md +++ b/README.md @@ -442,6 +442,18 @@ or command.SuggestionsMinimumDistance = 1 +You can also explicitly set names for which a given command will be suggested using the `SuggestFor` attribute. This allows suggestions for strings that are not close in terms of string distance, but makes sense in your set of commands and for some which you don't want aliases. Example: + +``` +$ hugo delete +unknown command "delete" for "hugo" + +Did you mean this? + remove + +Run 'hugo --help' for usage. +``` + ## Generating markdown formatted documentation for your command Cobra can generate a markdown formatted document based on the subcommands, flags, etc. A simple example of how to do this for your command can be found in [Markdown Docs](md_docs.md) diff --git a/cobra_test.go b/cobra_test.go index 6e0e5e3..f4814ea 100644 --- a/cobra_test.go +++ b/cobra_test.go @@ -82,9 +82,10 @@ var cmdDeprecated = &Command{ } var cmdTimes = &Command{ - Use: "times [# times] [string to echo]", - Short: "Echo anything to the screen more times", - Long: `a slightly useless command for testing.`, + Use: "times [# times] [string to echo]", + SuggestFor: []string{"counts"}, + Short: "Echo anything to the screen more times", + Long: `a slightly useless command for testing.`, PersistentPreRun: func(cmd *Command, args []string) { timesPersPre = args }, @@ -827,6 +828,7 @@ func TestRootSuggestions(t *testing.T) { "ri": "", "timezone": "", "foo": "", + "counts": "times", } for typo, suggestion := range tests { diff --git a/command.go b/command.go index 44e962e..b67e025 100644 --- a/command.go +++ b/command.go @@ -39,6 +39,8 @@ type Command struct { Use string // An array of aliases that can be used instead of the first word in Use. Aliases []string + // An array of command names for which this command will be suggested - similar to aliases but only suggests. + SuggestFor []string // The short description shown in the 'help' output. Short string // The long message shown in the 'help ' output. @@ -447,14 +449,21 @@ func (c *Command) Find(args []string) (*Command, []string, error) { return commandFound, a, nil } -func (c *Command) SuggestionsFor(cmdName string) []string { +func (c *Command) SuggestionsFor(typedName string) []string { suggestions := []string{} for _, cmd := range c.commands { if cmd.IsAvailableCommand() { - levenshtein := ld(cmdName, cmd.Name(), true) - if levenshtein <= c.SuggestionsMinimumDistance || strings.HasPrefix(strings.ToLower(cmd.Name()), strings.ToLower(cmdName)) { + levenshteinDistance := ld(typedName, cmd.Name(), true) + suggestByLevenshtein := levenshteinDistance <= c.SuggestionsMinimumDistance + suggestByPrefix := strings.HasPrefix(strings.ToLower(cmd.Name()), strings.ToLower(typedName)) + if suggestByLevenshtein || suggestByPrefix { suggestions = append(suggestions, cmd.Name()) } + for _, explicitSuggestion := range cmd.SuggestFor { + if strings.EqualFold(typedName, explicitSuggestion) { + suggestions = append(suggestions, cmd.Name()) + } + } } } return suggestions