Skip to content

Commit

Permalink
Add CalledAs method to cobra.Command (w/ tests) (#567)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
tep authored and eparis committed Feb 4, 2018
1 parent 9979838 commit eb58983
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 0 deletions.
23 changes: 23 additions & 0 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -567,6 +573,7 @@ func (c *Command) findNext(next string) *Command {
if len(matches) == 1 {
return matches[0]
}

return nil
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
}
Expand Down
63 changes: 63 additions & 0 deletions command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

0 comments on commit eb58983

Please sign in to comment.