Skip to content

Commit

Permalink
Add command alias
Browse files Browse the repository at this point in the history
  • Loading branch information
cristaloleg committed Nov 1, 2021
1 parent ba9e95d commit d594866
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 3 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ Popular CLI libraries (or better frameworks) have too large and unclear API, in

* Simple API.
* Dependency-free.
* Easy to integrate.
* Clean and tested code.
* Command aliases.
* Auto suggesting command.
* Builtin `help` and `version` commands.

Expand Down
20 changes: 18 additions & 2 deletions acmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ type Runner struct {

// Command specifies a sub-command for a program's command-line interface.
type Command struct {
// Name is just a one-word.
// Name of the command, ex: `init`
Name string

// Alias is an optional short second name, ex: `i`.
Alias string

// Description of the command.
Description string

Expand Down Expand Up @@ -158,9 +161,15 @@ func validateCommand(cmd Command) error {
case cmd.Name == "help" || cmd.Name == "version":
return fmt.Errorf("command %q is reserved", cmd.Name)

case cmd.Alias == "help" || cmd.Alias == "version":
return fmt.Errorf("command alias %q is reserved", cmd.Alias)

case !cmdNameRE.MatchString(cmd.Name):
return fmt.Errorf("command %q must contains only letters, digits, - and _", cmd.Name)

case cmd.Alias != "" && !cmdNameRE.MatchString(cmd.Alias):
return fmt.Errorf("command alias %q must contains only letters, digits, - and _", cmd.Alias)

case len(cmds) != 0:
sort.Slice(cmds, func(i, j int) bool {
return cmds[i].Name < cmds[j].Name
Expand All @@ -171,7 +180,14 @@ func validateCommand(cmd Command) error {
if _, ok := names[cmd.Name]; ok {
return fmt.Errorf("duplicate command %q", cmd.Name)
}
if _, ok := names[cmd.Alias]; ok {
return fmt.Errorf("duplicate command alias %q", cmd.Alias)
}

names[cmd.Name] = struct{}{}
if cmd.Alias != "" {
names[cmd.Alias] = struct{}{}
}

if err := validateCommand(cmd); err != nil {
return err
Expand All @@ -196,7 +212,7 @@ func run(ctx context.Context, cfg Config, cmds []Command, args []string) error {
selected, params := args[0], args[1:]

for _, c := range cmds {
if c.Name == selected {
if selected == c.Name || selected == c.Alias {
return c.Do(ctx, params)
}
}
Expand Down
28 changes: 28 additions & 0 deletions acmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,18 @@ func TestRunnerInit(t *testing.T) {
cfg Config
wantErrStr string
}{
{
cmds: []Command{{Name: "", Do: nopFunc}},
wantErrStr: `command "" must contains only letters, digits, - and _`,
},
{
cmds: []Command{{Name: "foo%", Do: nopFunc}},
wantErrStr: `command "foo%" must contains only letters, digits, - and _`,
},
{
cmds: []Command{{Name: "foo", Alias: "%", Do: nopFunc}},
wantErrStr: `command alias "%" must contains only letters, digits, - and _`,
},
{
cmds: []Command{{Name: "foo%", Do: nil}},
wantErrStr: `command "foo%" function cannot be nil`,
Expand All @@ -102,10 +110,30 @@ func TestRunnerInit(t *testing.T) {
cmds: []Command{{Name: "version", Do: nopFunc}},
wantErrStr: `command "version" is reserved`,
},
{
cmds: []Command{{Name: "foo", Alias: "help", Do: nopFunc}},
wantErrStr: `command alias "help" is reserved`,
},
{
cmds: []Command{{Name: "foo", Alias: "version", Do: nopFunc}},
wantErrStr: `command alias "version" is reserved`,
},
{
cmds: []Command{{Name: "a", Do: nopFunc}, {Name: "a", Do: nopFunc}},
wantErrStr: `duplicate command "a"`,
},
{
cmds: []Command{{Name: "aaa", Do: nopFunc}, {Name: "b", Alias: "aaa", Do: nopFunc}},
wantErrStr: `duplicate command alias "aaa"`,
},
{
cmds: []Command{{Name: "aaa", Alias: "a", Do: nopFunc}, {Name: "bbb", Alias: "a", Do: nopFunc}},
wantErrStr: `duplicate command alias "a"`,
},
{
cmds: []Command{{Name: "a", Do: nopFunc}, {Name: "b", Alias: "a", Do: nopFunc}},
wantErrStr: `duplicate command alias "a"`,
},
}

for _, tc := range testCases {
Expand Down
38 changes: 38 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,44 @@ func ExampleVersion() {
// Output: acmd-example version: the best v0.x.y
}

func ExampleAlias() {
testOut := os.Stdout
testArgs := []string{"f"}

cmds := []acmd.Command{
{
Name: "foo",
Alias: "f",
Do: func(ctx context.Context, args []string) error {
fmt.Fprint(testOut, "foo")
return nil
},
},
{
Name: "bar",
Alias: "b",
Do: func(ctx context.Context, args []string) error {
fmt.Fprint(testOut, "bar")
return nil
},
},
}

r := acmd.RunnerOf(cmds, acmd.Config{
AppName: "acmd-example",
AppDescription: "Example of acmd package",
Version: "the best v0.x.y",
Output: testOut,
Args: testArgs,
})

if err := r.Run(); err != nil {
panic(err)
}

// Output: foo
}

func ExampleAutosuggestion() {
testOut := os.Stdout
testArgs := []string{"baz"}
Expand Down

0 comments on commit d594866

Please sign in to comment.