Add CalledAs method to cobra.Command (w/ tests) (#567)

* Add `CalledAs` method to Command (w/ tests)

The `CalledAs` method returns the name of the command or alias that
invoked the command -- as long as the command was actually invoked.
Otherwise, it returns the empty string.

The opens up possibilies for commands to behave differently based on
which alias invoked the command (in the same vein as Linux programs
which adjust their behavior based on the value of argv[0]).

* Fixed formatting
This commit is contained in:
Tim Peoples 2018-02-04 08:58:53 -08:00 committed by Eric Paris
parent 9979838ec4
commit eb58983359
2 changed files with 86 additions and 0 deletions

View file

@ -147,6 +147,11 @@ type Command struct {
commandsMaxNameLen int commandsMaxNameLen int
// commandsAreSorted defines, if command slice are sorted or not. // commandsAreSorted defines, if command slice are sorted or not.
commandsAreSorted bool 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 is actual args parsed from flags.
args []string args []string
@ -557,6 +562,7 @@ func (c *Command) findNext(next string) *Command {
matches := make([]*Command, 0) matches := make([]*Command, 0)
for _, cmd := range c.commands { for _, cmd := range c.commands {
if cmd.Name() == next || cmd.HasAlias(next) { if cmd.Name() == next || cmd.HasAlias(next) {
cmd.commandCalledAs.name = next
return cmd return cmd
} }
if EnablePrefixMatching && cmd.hasNameOrAliasPrefix(next) { if EnablePrefixMatching && cmd.hasNameOrAliasPrefix(next) {
@ -567,6 +573,7 @@ func (c *Command) findNext(next string) *Command {
if len(matches) == 1 { if len(matches) == 1 {
return matches[0] return matches[0]
} }
return nil return nil
} }
@ -828,6 +835,11 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
return c, err return c, err
} }
cmd.commandCalledAs.called = true
if cmd.commandCalledAs.name == "" {
cmd.commandCalledAs.name = cmd.Name()
}
err = cmd.execute(flags) err = cmd.execute(flags)
if err != nil { if err != nil {
// Always show help if requested, even if SilenceErrors is in // Always show help if requested, even if SilenceErrors is in
@ -1135,14 +1147,25 @@ func (c *Command) HasAlias(s string) bool {
return false 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 // hasNameOrAliasPrefix returns true if the Name or any of aliases start
// with prefix // with prefix
func (c *Command) hasNameOrAliasPrefix(prefix string) bool { func (c *Command) hasNameOrAliasPrefix(prefix string) bool {
if strings.HasPrefix(c.Name(), prefix) { if strings.HasPrefix(c.Name(), prefix) {
c.commandCalledAs.name = c.Name()
return true return true
} }
for _, alias := range c.Aliases { for _, alias := range c.Aliases {
if strings.HasPrefix(alias, prefix) { if strings.HasPrefix(alias, prefix) {
c.commandCalledAs.name = alias
return true return true
} }
} }

View file

@ -1563,3 +1563,66 @@ func TestUpdateName(t *testing.T) {
t.Error("c.Name() should be updated on changed c.Use") 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)
}
}