diff --git a/command.go b/command.go index 3fd77667..29675b33 100644 --- a/command.go +++ b/command.go @@ -147,6 +147,11 @@ type Command struct { commandsMaxNameLen int // commandsAreSorted defines, if command slice are sorted or not. commandsAreSorted bool + // commandCalledAs is the name or alias value used to call this command. + commandCalledAs struct { + name string + called bool + } // args is actual args parsed from flags. args []string @@ -557,6 +562,7 @@ func (c *Command) findNext(next string) *Command { matches := make([]*Command, 0) for _, cmd := range c.commands { if cmd.Name() == next || cmd.HasAlias(next) { + cmd.commandCalledAs.name = next return cmd } if EnablePrefixMatching && cmd.hasNameOrAliasPrefix(next) { @@ -567,6 +573,7 @@ func (c *Command) findNext(next string) *Command { if len(matches) == 1 { return matches[0] } + return nil } @@ -828,6 +835,11 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { return c, err } + cmd.commandCalledAs.called = true + if cmd.commandCalledAs.name == "" { + cmd.commandCalledAs.name = cmd.Name() + } + err = cmd.execute(flags) if err != nil { // Always show help if requested, even if SilenceErrors is in @@ -1135,14 +1147,25 @@ func (c *Command) HasAlias(s string) bool { return false } +// CalledAs returns the command name or alias that was used to invoke +// this command or an empty string if the command has not been called. +func (c *Command) CalledAs() string { + if c.commandCalledAs.called { + return c.commandCalledAs.name + } + return "" +} + // hasNameOrAliasPrefix returns true if the Name or any of aliases start // with prefix func (c *Command) hasNameOrAliasPrefix(prefix string) bool { if strings.HasPrefix(c.Name(), prefix) { + c.commandCalledAs.name = c.Name() return true } for _, alias := range c.Aliases { if strings.HasPrefix(alias, prefix) { + c.commandCalledAs.name = alias return true } } diff --git a/command_test.go b/command_test.go index d3dde152..d874a9a5 100644 --- a/command_test.go +++ b/command_test.go @@ -1563,3 +1563,66 @@ func TestUpdateName(t *testing.T) { t.Error("c.Name() should be updated on changed c.Use") } } + +type calledAsTestcase struct { + args []string + call string + want string + epm bool + tc bool +} + +func (tc *calledAsTestcase) test(t *testing.T) { + defer func(ov bool) { EnablePrefixMatching = ov }(EnablePrefixMatching) + EnablePrefixMatching = tc.epm + + var called *Command + run := func(c *Command, _ []string) { t.Logf("called: %q", c.Name()); called = c } + + parent := &Command{Use: "parent", Run: run} + child1 := &Command{Use: "child1", Run: run, Aliases: []string{"this"}} + child2 := &Command{Use: "child2", Run: run, Aliases: []string{"that"}} + + parent.AddCommand(child1) + parent.AddCommand(child2) + parent.SetArgs(tc.args) + + output := new(bytes.Buffer) + parent.SetOutput(output) + + parent.Execute() + + if called == nil { + if tc.call != "" { + t.Errorf("missing expected call to command: %s", tc.call) + } + return + } + + if called.Name() != tc.call { + t.Errorf("called command == %q; Wanted %q", called.Name(), tc.call) + } else if got := called.CalledAs(); got != tc.want { + t.Errorf("%s.CalledAs() == %q; Wanted: %q", tc.call, got, tc.want) + } +} + +func TestCalledAs(t *testing.T) { + tests := map[string]calledAsTestcase{ + "find/no-args": {nil, "parent", "parent", false, false}, + "find/real-name": {[]string{"child1"}, "child1", "child1", false, false}, + "find/full-alias": {[]string{"that"}, "child2", "that", false, false}, + "find/part-no-prefix": {[]string{"thi"}, "", "", false, false}, + "find/part-alias": {[]string{"thi"}, "child1", "this", true, false}, + "find/conflict": {[]string{"th"}, "", "", true, false}, + "traverse/no-args": {nil, "parent", "parent", false, true}, + "traverse/real-name": {[]string{"child1"}, "child1", "child1", false, true}, + "traverse/full-alias": {[]string{"that"}, "child2", "that", false, true}, + "traverse/part-no-prefix": {[]string{"thi"}, "", "", false, true}, + "traverse/part-alias": {[]string{"thi"}, "child1", "this", true, true}, + "traverse/conflict": {[]string{"th"}, "", "", true, true}, + } + + for name, tc := range tests { + t.Run(name, tc.test) + } +}