Skip to content
This repository was archived by the owner on Oct 17, 2021. It is now read-only.

Commit fea394f

Browse files
author
Faris Huskovic
authored
Merge pull request #12 from cdr/subcommand-aliases
Support subcommand aliases
2 parents 4869291 + b4ff21d commit fea394f

File tree

7 files changed

+401
-14
lines changed

7 files changed

+401
-14
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ func (c *subcmd) Spec() cli.CommandSpec {
9595
return cli.CommandSpec{
9696
Name: "sub",
9797
Usage: "",
98+
Aliases: []string{"s"},
9899
Desc: `This is a simple subcommand.`,
99100
}
100101
}
@@ -134,5 +135,5 @@ Usage: subcommand [flags]
134135
This is a simple example of subcommands.
135136
136137
Commands:
137-
sub This is a simple subcommand.
138+
s,sub - This is a simple subcommand.
138139
```

command.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,23 @@ type CommandSpec struct {
2323
// RawArgs indicates that flags should not be parsed, and they should be deferred
2424
// to the command.
2525
RawArgs bool
26-
2726
// Hidden indicates that this command should not show up in it's parent's
2827
// subcommand help.
2928
Hidden bool
29+
// Aliases contains a list of alternative names that can be used for a particular command.
30+
Aliases []string
3031
}
3132

3233
// ShortDesc returns the first line of Desc.
3334
func (c CommandSpec) ShortDesc() string {
3435
return strings.Split(c.Desc, "\n")[0]
3536
}
3637

38+
// HasAliases evaluates whether particular command has any alternative names.
39+
func (c CommandSpec) HasAliases() bool {
40+
return len(c.Aliases) > 0
41+
}
42+
3743
// Command describes a command or subcommand.
3844
type Command interface {
3945
// Spec returns metadata about the command.

command_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package cli
2+
3+
import (
4+
"bytes"
5+
"os"
6+
"testing"
7+
8+
"cdr.dev/slog/sloggers/slogtest/assert"
9+
"github.com/spf13/pflag"
10+
)
11+
12+
const (
13+
success = "test successful"
14+
15+
expectedParentCmdHelpOutput = `Usage: mockParentCmd Mock parent command usage.
16+
17+
Mock parent command description.
18+
19+
Commands:
20+
s,sc,sub,mockSubCmd - A simple mock subcommand with aliases.
21+
`
22+
)
23+
24+
var subCmd = &mockSubCmd{buf: new(bytes.Buffer)}
25+
26+
type (
27+
mockParentCmd struct{}
28+
mockSubCmd struct{ buf *bytes.Buffer }
29+
)
30+
31+
func (c *mockParentCmd) Run(fl *pflag.FlagSet) {}
32+
33+
func (c *mockParentCmd) Subcommands() []Command { return []Command{subCmd} }
34+
35+
func (c *mockParentCmd) Spec() CommandSpec {
36+
return CommandSpec{
37+
Name: "mockParentCmd",
38+
Usage: "Mock parent command usage.",
39+
Desc: "Mock parent command description.",
40+
}
41+
}
42+
43+
func (c *mockSubCmd) Run(fl *pflag.FlagSet) {
44+
c.buf = new(bytes.Buffer)
45+
if _, err := c.Write([]byte(success)); err != nil {
46+
fl.Usage()
47+
}
48+
}
49+
50+
func (c *mockSubCmd) Write(b []byte) (int, error) { return c.buf.Write(b) }
51+
52+
func (c *mockSubCmd) Spec() CommandSpec {
53+
return CommandSpec{
54+
Name: "mockSubCmd",
55+
Usage: "Test a subcommand.",
56+
Aliases: []string{"s", "sc", "sub"},
57+
Desc: "A simple mock subcommand with aliases.",
58+
}
59+
}
60+
61+
func TestSubCmdAliases(t *testing.T) {
62+
for _, test := range []struct {
63+
name, expected string
64+
}{
65+
{
66+
name: "s",
67+
expected: success,
68+
},
69+
{
70+
name: "sc",
71+
expected: success,
72+
},
73+
{
74+
name: "sub",
75+
expected: success,
76+
},
77+
} {
78+
t.Run(test.name, func(t *testing.T) {
79+
// Since the alias is the name of the test
80+
// we can just pass it as the alias arg.
81+
os.Args = []string{"mockParentCmd", test.name}
82+
// Based on os.Args, when splitArgs is invoked,
83+
// it should be able to deduce the subcommand we want
84+
// based on the new alias map it's being passed.
85+
RunRoot(&mockParentCmd{})
86+
// The success const is never written into the buffer
87+
// if the subcommand fails to be invoked by alias.
88+
got := string(subCmd.buf.Bytes())
89+
assert.Equal(t, test.name, test.expected, got)
90+
})
91+
}
92+
}
93+
94+
func TestSubcmdAliasesInParentCmdHelpOutput(t *testing.T) {
95+
buf := new(bytes.Buffer)
96+
cmd := &mockParentCmd{}
97+
name := cmd.Spec().Name
98+
fl := pflag.NewFlagSet(name, pflag.ExitOnError)
99+
// If the help output is not written to the buffer
100+
// in the format we expect then the test will fail.
101+
renderHelp(name, cmd, fl, buf)
102+
got := string(buf.Bytes())
103+
expected := expectedParentCmdHelpOutput
104+
assert.Equal(t, "display_subcmd_aliases_in_parentcmd_help_output", expected, got)
105+
}

go.mod

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,8 @@ module go.coder.com/cli
22

33
go 1.12
44

5-
require github.com/spf13/pflag v1.0.3
5+
require (
6+
cdr.dev/slog v1.3.0
7+
github.com/spf13/pflag v1.0.3
8+
github.com/stretchr/testify v1.6.1 // indirect
9+
)

0 commit comments

Comments
 (0)