diff --git a/app.go b/app.go index 63f9bbe3b6..6986b6ee72 100644 --- a/app.go +++ b/app.go @@ -107,6 +107,10 @@ type App struct { SkipFlagParsing bool // Flag exclusion group MutuallyExclusiveFlags []MutuallyExclusiveFlags + // Use longest prefix match for commands + PrefixMatchCommands bool + // Custom suggest command for matching + SuggestCommandFunc SuggestCommandFunc didSetup bool @@ -201,6 +205,11 @@ func (a *App) Setup() { a.appendFlag(VersionFlag) } + if a.PrefixMatchCommands { + if a.SuggestCommandFunc == nil { + a.SuggestCommandFunc = suggestCommand + } + } if a.EnableShellCompletion { if a.ShellCompletionCommandName != "" { completionCommand.Name = a.ShellCompletionCommandName @@ -258,6 +267,7 @@ func (a *App) newRootCommand() *Command { SkipFlagParsing: a.SkipFlagParsing, isRoot: true, MutuallyExclusiveFlags: a.MutuallyExclusiveFlags, + PrefixMatchCommands: a.PrefixMatchCommands, } } diff --git a/app_test.go b/app_test.go index ba423d5326..66e0f662b5 100644 --- a/app_test.go +++ b/app_test.go @@ -3295,3 +3295,89 @@ func TestFlagDuplicates(t *testing.T) { }) } } + +func TestShorthandCommand(t *testing.T) { + + af := func(p *int) ActionFunc { + return func(ctx *Context) error { + *p = *p + 1 + return nil + } + } + + var cmd1, cmd2 int + + a := &App{ + PrefixMatchCommands: true, + Commands: []*Command{ + { + Name: "cthdisd", + Aliases: []string{"cth"}, + Action: af(&cmd1), + }, + { + Name: "cthertoop", + Aliases: []string{"cer"}, + Action: af(&cmd2), + }, + }, + } + + err := a.Run([]string{"foo", "cth"}) + if err != nil { + t.Error(err) + } + + if cmd1 != 1 && cmd2 != 0 { + t.Errorf("Expected command1 to be trigerred once but didnt %d %d", cmd1, cmd2) + } + + cmd1 = 0 + cmd2 = 0 + + err = a.Run([]string{"foo", "cthd"}) + if err != nil { + t.Error(err) + } + + if cmd1 != 1 && cmd2 != 0 { + t.Errorf("Expected command1 to be trigerred once but didnt %d %d", cmd1, cmd2) + } + + cmd1 = 0 + cmd2 = 0 + + err = a.Run([]string{"foo", "cthe"}) + if err != nil { + t.Error(err) + } + + if cmd1 != 1 && cmd2 != 0 { + t.Errorf("Expected command1 to be trigerred once but didnt %d %d", cmd1, cmd2) + } + + cmd1 = 0 + cmd2 = 0 + + err = a.Run([]string{"foo", "cthert"}) + if err != nil { + t.Error(err) + } + + if cmd1 != 0 && cmd2 != 1 { + t.Errorf("Expected command1 to be trigerred once but didnt %d %d", cmd1, cmd2) + } + + cmd1 = 0 + cmd2 = 0 + + err = a.Run([]string{"foo", "cthet"}) + if err != nil { + t.Error(err) + } + + if cmd1 != 0 && cmd2 != 1 { + t.Errorf("Expected command1 to be trigerred once but didnt %d %d", cmd1, cmd2) + } + +} diff --git a/command.go b/command.go index 2d1cb36039..5aeac37c0e 100644 --- a/command.go +++ b/command.go @@ -64,6 +64,9 @@ type Command struct { // render custom help text by setting this variable. CustomHelpTemplate string + // Use longest prefix match for commands + PrefixMatchCommands bool + // categories contains the categorized commands and is populated on app startup categories CommandCategories @@ -236,6 +239,9 @@ func (c *Command) Run(cCtx *Context, arguments ...string) (err error) { args := cCtx.Args() if args.Present() { name := args.First() + if cCtx.App.SuggestCommandFunc != nil { + name = cCtx.App.SuggestCommandFunc(c.Commands, name) + } cmd = c.Command(name) if cmd == nil { hasDefault := cCtx.App.DefaultCommand != "" diff --git a/godoc-current.txt b/godoc-current.txt index 1aebcd6822..9048b9a05a 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -332,6 +332,10 @@ type App struct { SkipFlagParsing bool // Flag exclusion group MutuallyExclusiveFlags []MutuallyExclusiveFlags + // Use longest prefix match for commands + PrefixMatchCommands bool + // Custom suggest command for matching + SuggestCommandFunc SuggestCommandFunc // Has unexported fields. } @@ -475,6 +479,9 @@ type Command struct { // render custom help text by setting this variable. CustomHelpTemplate string + // Use longest prefix match for commands + PrefixMatchCommands bool + // Flag exclusion group MutuallyExclusiveFlags []MutuallyExclusiveFlags // Has unexported fields. diff --git a/suggestions.go b/suggestions.go index 1ee7b7a564..b078d8781a 100644 --- a/suggestions.go +++ b/suggestions.go @@ -1,8 +1,6 @@ package cli import ( - "fmt" - "github.com/xrash/smetrics" ) @@ -68,5 +66,5 @@ func suggestCommand(commands []*Command, provided string) (suggestion string) { } } - return fmt.Sprintf(SuggestDidYouMeanTemplate, suggestion) + return suggestion } diff --git a/suggestions_test.go b/suggestions_test.go index 94d8983033..9032b347c2 100644 --- a/suggestions_test.go +++ b/suggestions_test.go @@ -111,13 +111,15 @@ func TestSuggestCommand(t *testing.T) { {"conf", "config"}, {"i", "i"}, {"information", "info"}, + {"inf", "info"}, + {"con", "config"}, {"not-existing", "info"}, } { // When res := suggestCommand(app.Commands, testCase.provided) // Then - expect(t, res, fmt.Sprintf(SuggestDidYouMeanTemplate, testCase.expected)) + expect(t, res, testCase.expected) } } diff --git a/testdata/godoc-v3.x.txt b/testdata/godoc-v3.x.txt index 1aebcd6822..9048b9a05a 100644 --- a/testdata/godoc-v3.x.txt +++ b/testdata/godoc-v3.x.txt @@ -332,6 +332,10 @@ type App struct { SkipFlagParsing bool // Flag exclusion group MutuallyExclusiveFlags []MutuallyExclusiveFlags + // Use longest prefix match for commands + PrefixMatchCommands bool + // Custom suggest command for matching + SuggestCommandFunc SuggestCommandFunc // Has unexported fields. } @@ -475,6 +479,9 @@ type Command struct { // render custom help text by setting this variable. CustomHelpTemplate string + // Use longest prefix match for commands + PrefixMatchCommands bool + // Flag exclusion group MutuallyExclusiveFlags []MutuallyExclusiveFlags // Has unexported fields.