mirror of https://github.com/spf13/cobra.git
Merge pull request #163 from fabianofranz/siblings_improvements
Improve suggestions
This commit is contained in:
commit
046a673252
12
README.md
12
README.md
|
@ -442,6 +442,18 @@ or
|
||||||
|
|
||||||
command.SuggestionsMinimumDistance = 1
|
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
|
## 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)
|
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)
|
||||||
|
|
|
@ -83,6 +83,7 @@ var cmdDeprecated = &Command{
|
||||||
|
|
||||||
var cmdTimes = &Command{
|
var cmdTimes = &Command{
|
||||||
Use: "times [# times] [string to echo]",
|
Use: "times [# times] [string to echo]",
|
||||||
|
SuggestFor: []string{"counts"},
|
||||||
Short: "Echo anything to the screen more times",
|
Short: "Echo anything to the screen more times",
|
||||||
Long: `a slightly useless command for testing.`,
|
Long: `a slightly useless command for testing.`,
|
||||||
PersistentPreRun: func(cmd *Command, args []string) {
|
PersistentPreRun: func(cmd *Command, args []string) {
|
||||||
|
@ -818,20 +819,31 @@ func TestRootSuggestions(t *testing.T) {
|
||||||
tests := map[string]string{
|
tests := map[string]string{
|
||||||
"time": "times",
|
"time": "times",
|
||||||
"tiems": "times",
|
"tiems": "times",
|
||||||
|
"tims": "times",
|
||||||
"timeS": "times",
|
"timeS": "times",
|
||||||
"rimes": "times",
|
"rimes": "times",
|
||||||
|
"ti": "times",
|
||||||
|
"t": "times",
|
||||||
|
"timely": "times",
|
||||||
|
"ri": "",
|
||||||
|
"timezone": "",
|
||||||
|
"foo": "",
|
||||||
|
"counts": "times",
|
||||||
}
|
}
|
||||||
|
|
||||||
for typo, suggestion := range tests {
|
for typo, suggestion := range tests {
|
||||||
cmd.DisableSuggestions = false
|
for _, suggestionsDisabled := range []bool{false, true} {
|
||||||
|
cmd.DisableSuggestions = suggestionsDisabled
|
||||||
result := simpleTester(cmd, typo)
|
result := simpleTester(cmd, typo)
|
||||||
if expected := fmt.Sprintf(outputWithSuggestions, typo, suggestion); result.Output != expected {
|
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)
|
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
43
command.go
43
command.go
|
@ -39,6 +39,8 @@ type Command struct {
|
||||||
Use string
|
Use string
|
||||||
// An array of aliases that can be used instead of the first word in Use.
|
// An array of aliases that can be used instead of the first word in Use.
|
||||||
Aliases []string
|
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.
|
// The short description shown in the 'help' output.
|
||||||
Short string
|
Short string
|
||||||
// The long message shown in the 'help <this-command>' output.
|
// The long message shown in the 'help <this-command>' output.
|
||||||
|
@ -429,33 +431,44 @@ func (c *Command) Find(args []string) (*Command, []string, error) {
|
||||||
|
|
||||||
// root command with subcommands, do subcommand checking
|
// root command with subcommands, do subcommand checking
|
||||||
if commandFound == c && len(argsWOflags) > 0 {
|
if commandFound == c && len(argsWOflags) > 0 {
|
||||||
suggestions := ""
|
suggestionsString := ""
|
||||||
if !c.DisableSuggestions {
|
if !c.DisableSuggestions {
|
||||||
if c.SuggestionsMinimumDistance <= 0 {
|
if c.SuggestionsMinimumDistance <= 0 {
|
||||||
c.SuggestionsMinimumDistance = 2
|
c.SuggestionsMinimumDistance = 2
|
||||||
}
|
}
|
||||||
similar := []string{}
|
if suggestions := c.SuggestionsFor(argsWOflags[0]); len(suggestions) > 0 {
|
||||||
for _, cmd := range c.commands {
|
suggestionsString += "\n\nDid you mean this?\n"
|
||||||
if cmd.IsAvailableCommand() {
|
for _, s := range suggestions {
|
||||||
levenshtein := ld(argsWOflags[0], cmd.Name(), true)
|
suggestionsString += fmt.Sprintf("\t%v\n", s)
|
||||||
if levenshtein <= c.SuggestionsMinimumDistance {
|
|
||||||
similar = append(similar, cmd.Name())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(similar) > 0 {
|
return commandFound, a, fmt.Errorf("unknown command %q for %q%s", argsWOflags[0], commandFound.CommandPath(), suggestionsString)
|
||||||
suggestions += "\n\nDid you mean this?\n"
|
|
||||||
for _, s := range similar {
|
|
||||||
suggestions += 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, nil
|
return commandFound, a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Command) SuggestionsFor(typedName string) []string {
|
||||||
|
suggestions := []string{}
|
||||||
|
for _, cmd := range c.commands {
|
||||||
|
if cmd.IsAvailableCommand() {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Command) Root() *Command {
|
func (c *Command) Root() *Command {
|
||||||
var findRoot func(*Command) *Command
|
var findRoot func(*Command) *Command
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue