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
// 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
}
}

View File

@ -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)
}
}