From f4c075f8f8fe5b6cf731fa59f1e982a0ac73b4a5 Mon Sep 17 00:00:00 2001 From: tummychow Date: Wed, 26 Mar 2014 04:53:00 -0400 Subject: [PATCH 1/4] Add command name prefix matching A command can now be invoked with a prefix of its own name, assuming that prefix is unambiguous (ie it isn't also a prefix of any sibling command's name). --- command.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/command.go b/command.go index 73d009f7..61f2104a 100644 --- a/command.go +++ b/command.go @@ -239,11 +239,19 @@ func (c *Command) Find(arrs []string) (*Command, []string, error) { innerfind = func(c *Command, args []string) (*Command, []string) { if len(args) > 0 && c.HasSubCommands() { + matches := make([]*Command, 0) for _, cmd := range c.commands { - if cmd.Name() == args[0] { + if cmd.Name() == args[0] { // exact name match return innerfind(cmd, args[1:]) + } else if strings.HasPrefix(cmd.Name(), args[0]) { // prefix match + matches = append(matches, cmd) } } + + // only accept a single prefix match - multiple matches would be ambiguous + if len(matches) == 1 { + return innerfind(matches[0], args[1:]) + } } return c, args From 96d543cf2ccfa99a2d4589fa21a6b6ce939dd4de Mon Sep 17 00:00:00 2001 From: tummychow Date: Wed, 26 Mar 2014 04:54:47 -0400 Subject: [PATCH 2/4] Reset root command lists in testing This fixes some issues that appear when testing prefix invocations. Since the root command lists weren't being cleared, the list would persist between tests, so there would be multiple instances of each command. Then, if you tried to match a prefix of one of those commands, you'd get two matches (one for each instance) and the command would fail. Resetting the root command lists prevents them from persisting between tests, resolving this issue. --- cobra_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cobra_test.go b/cobra_test.go index 96fffb48..b412ee2b 100644 --- a/cobra_test.go +++ b/cobra_test.go @@ -81,6 +81,8 @@ func commandInit() { cmdEcho.ResetCommands() cmdPrint.ResetCommands() cmdTimes.ResetCommands() + cmdRootNoRun.ResetCommands() + cmdRootWithRun.ResetCommands() } func initialize() *Command { From 4c29b190e047b4c0639dd14820ab0a7487de7d7f Mon Sep 17 00:00:00 2001 From: tummychow Date: Wed, 26 Mar 2014 04:56:46 -0400 Subject: [PATCH 3/4] Add basic test for prefix matching --- cobra_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cobra_test.go b/cobra_test.go index b412ee2b..b23ac2fd 100644 --- a/cobra_test.go +++ b/cobra_test.go @@ -138,6 +138,24 @@ func TestChildCommand(t *testing.T) { } } +func TestChildCommandPrefix(t *testing.T) { + c := initialize() + cmdEcho.AddCommand(cmdTimes) + c.AddCommand(cmdPrint, cmdEcho) + c.SetArgs(strings.Split("ech tim one two", " ")) + c.Execute() + + if te != nil || tp != nil { + t.Error("Wrong command called") + } + if tt == nil { + t.Error("Wrong command called") + } + if strings.Join(tt, " ") != "one two" { + t.Error("Command didn't parse correctly") + } +} + func TestFlagLong(t *testing.T) { c := initialize() c.AddCommand(cmdPrint, cmdEcho, cmdTimes) From 667c348dbdf8cab38b985fc9effbe4a66c0f199f Mon Sep 17 00:00:00 2001 From: tummychow Date: Wed, 26 Mar 2014 16:19:34 -0400 Subject: [PATCH 4/4] Test behavior for subcommand with same name as root command If, for some reason, you have an application with some name "foo", and your app has a subcommand "foo", cobra should behave properly when you call "foo foo", and it should also behave if you call "foo f". These changes verify both of these cases and ensure cobra responds properly. --- cobra_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ command.go | 2 +- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/cobra_test.go b/cobra_test.go index b23ac2fd..fb4a7c4d 100644 --- a/cobra_test.go +++ b/cobra_test.go @@ -47,6 +47,12 @@ var cmdRootNoRun = &Command{ Long: "The root description for help", } +var cmdRootSameName = &Command{ + Use: "print", + Short: "Root with the same name as a subcommand", + Long: "The root description for help", +} + var cmdRootWithRun = &Command{ Use: "cobra-test", Short: "The root can run it's own function", @@ -65,6 +71,7 @@ func flagInit() { cmdPrint.ResetFlags() cmdTimes.ResetFlags() cmdRootNoRun.ResetFlags() + cmdRootSameName.ResetFlags() cmdRootWithRun.ResetFlags() cmdEcho.Flags().IntVarP(&flagi1, "intone", "i", 123, "help message for flag intone") cmdTimes.Flags().IntVarP(&flagi2, "inttwo", "j", 234, "help message for flag inttwo") @@ -82,6 +89,7 @@ func commandInit() { cmdPrint.ResetCommands() cmdTimes.ResetCommands() cmdRootNoRun.ResetCommands() + cmdRootSameName.ResetCommands() cmdRootWithRun.ResetCommands() } @@ -93,6 +101,14 @@ func initialize() *Command { return c } +func initializeWithSameName() *Command { + tt, tp, te = nil, nil, nil + var c = cmdRootSameName + flagInit() + commandInit() + return c +} + func initializeWithRootCmd() *Command { cmdRootWithRun.ResetCommands() tt, tp, te, rootcalled = nil, nil, nil, false @@ -156,6 +172,40 @@ func TestChildCommandPrefix(t *testing.T) { } } +func TestChildSameName(t *testing.T) { + c := initializeWithSameName() + c.AddCommand(cmdPrint, cmdEcho) + c.SetArgs(strings.Split("print one two", " ")) + c.Execute() + + if te != nil || tt != nil { + t.Error("Wrong command called") + } + if tp == nil { + t.Error("Wrong command called") + } + if strings.Join(tp, " ") != "one two" { + t.Error("Command didn't parse correctly") + } +} + +func TestChildSameNamePrefix(t *testing.T) { + c := initializeWithSameName() + c.AddCommand(cmdPrint, cmdEcho) + c.SetArgs(strings.Split("pr one two", " ")) + c.Execute() + + if te != nil || tt != nil { + t.Error("Wrong command called") + } + if tp == nil { + t.Error("Wrong command called") + } + if strings.Join(tp, " ") != "one two" { + t.Error("Command didn't parse correctly") + } +} + func TestFlagLong(t *testing.T) { c := initialize() c.AddCommand(cmdPrint, cmdEcho, cmdTimes) diff --git a/command.go b/command.go index 61f2104a..663681a8 100644 --- a/command.go +++ b/command.go @@ -260,7 +260,7 @@ func (c *Command) Find(arrs []string) (*Command, []string, error) { commandFound, a := innerfind(c, arrs) // if commander returned and not appropriately matched return nil & error - if commandFound.Name() == c.Name() && commandFound.Name() != arrs[0] { + if commandFound.Name() == c.Name() && !strings.HasPrefix(commandFound.Name(), arrs[0]) { return nil, a, fmt.Errorf("unknown command %q\nRun 'help' for usage.\n", a[0]) }