Repository: alecthomas/kong (go)

Issue:
command aliases (short commands)
Some flag handling packages allow shortcuts for commands.
I can do it my putting commands double in the parent struct like this, but it is kind of ugly:
```
type rootCmd struct {
	LongName    longNameCmd        `cmd:"" help:"Do something."`
	X1                 longNameCmd        `cmd:"" name:"ln"`
}
```

Is there a better way? Should I (try to) make a pull request for an extra tag, or to enable the short tag for commands as well?

Pull request:
Add support for command aliases
This forks and fixes the CI for the original PR opened at https://github.com/alecthomas/kong/pull/95

Closes #66 

Patch:
diff --git a/README.md b/README.md
index 0788541..71fcd00 100644
--- a/README.md
+++ b/README.md
@@ -436,6 +436,7 @@ Tag                    | Description
 `default:"X"`          | Default value.
 `default:"1"`          | On a command, make it the default.
 `short:"X"`            | Short name, if flag.
+`aliases:"X,Y"`        | One or more aliases (for cmd).
 `required`             | If present, flag/arg is required.
 `optional`             | If present, flag/arg is optional.
 `hidden`               | If present, command or flag is hidden.
diff --git a/build.go b/build.go
index 9135b4d..9037512 100644
--- a/build.go
+++ b/build.go
@@ -143,6 +143,7 @@ func buildChild(k *Kong, node *Node, typ NodeType, v reflect.Value, ft reflect.S
 	child.Help = tag.Help
 	child.Hidden = tag.Hidden
 	child.Group = tag.Group
+	child.Aliases = tag.Aliases
 
 	if provider, ok := fv.Addr().Interface().(HelpProvider); ok {
 		child.Detail = provider.Help()
diff --git a/context.go b/context.go
index bd6ebae..4d24161 100644
--- a/context.go
+++ b/context.go
@@ -425,6 +425,24 @@ func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo
 				break
 			}
 
+			// Assign token value to a branch name if tagged as an alias
+			// An alias will be ignored in the case of an existing command
+			cmds := make(map[string]bool)
+			for _, branch := range node.Children {
+				if branch.Type == CommandNode {
+					cmds[branch.Name] = true
+				}
+			}
+			for _, branch := range node.Children {
+				for _, a := range branch.Aliases {
+					_, ok := cmds[a]
+					if token.Value == a && !ok {
+						token.Value = branch.Name
+						break
+					}
+				}
+			}
+
 			// After positional arguments have been consumed, check commands next...
 			for _, branch := range node.Children {
 				if branch.Type == CommandNode && !branch.Hidden {
diff --git a/model.go b/model.go
index bd9eec4..72dba3e 100644
--- a/model.go
+++ b/model.go
@@ -53,6 +53,7 @@ type Node struct {
 	Children   []*Node
 	Target     reflect.Value // Pointer to the value in the grammar that this Node is associated with.
 	Tag        *Tag
+	Aliases    []string
 
 	Argument *Value // Populated when Type is ArgumentNode.
 }
diff --git a/tag.go b/tag.go
index 4c58517..b780000 100644
--- a/tag.go
+++ b/tag.go
@@ -32,6 +32,7 @@ type Tag struct {
 	Vars        Vars
 	Prefix      string // Optional prefix on anonymous structs. All sub-flags will have this prefix.
 	Embed       bool
+	Aliases     []string
 
 	// Storage for all tag keys for arbitrary lookups.
 	items map[string][]string
@@ -157,6 +158,13 @@ func parseTag(fv reflect.Value, ft reflect.StructField) *Tag {
 	t.Xor = t.Get("xor")
 	t.Prefix = t.Get("prefix")
 	t.Embed = t.Has("embed")
+	splitFn := func(r rune) bool {
+		return r == ',' || r == ' '
+	}
+	aliases := t.Get("aliases")
+	if len(aliases) > 0 {
+		t.Aliases = append(t.Aliases, strings.FieldsFunc(aliases, splitFn)...)
+	}
 	t.Vars = Vars{}
 	for _, set := range t.GetAll("set") {
 		parts := strings.SplitN(set, "=", 2)


Test patch:
diff --git a/tag_test.go b/tag_test.go
index 5f155a2..fc512c4 100644
--- a/tag_test.go
+++ b/tag_test.go
@@ -149,3 +149,49 @@ func TestTagSetOnFlag(t *testing.T) {
 	require.NoError(t, err)
 	require.Contains(t, buf.String(), `A key from somewhere.`)
 }
+
+func TestTagAliases(t *testing.T) {
+	type Command struct {
+		Arg string `arg help:"Some arg"`
+	}
+	var cli struct {
+		Cmd Command `cmd aliases:"alias1, alias2"`
+	}
+	p := mustNew(t, &cli)
+	_, err := p.Parse([]string{"alias1", "arg"})
+	require.NoError(t, err)
+	require.Equal(t, "arg", cli.Cmd.Arg)
+	_, err = p.Parse([]string{"alias2", "arg"})
+	require.NoError(t, err)
+	require.Equal(t, "arg", cli.Cmd.Arg)
+}
+
+func TestTagAliasesConflict(t *testing.T) {
+	type Command struct {
+		Arg string `arg help:"Some arg"`
+	}
+	var cli struct {
+		Cmd      Command `cmd hidden aliases:"other-cmd"`
+		OtherCmd Command `cmd`
+	}
+	p := mustNew(t, &cli)
+	_, err := p.Parse([]string{"other-cmd", "arg"})
+	require.NoError(t, err)
+	require.Equal(t, "arg", cli.OtherCmd.Arg)
+}
+
+func TestTagAliasesSub(t *testing.T) {
+	type SubCommand struct {
+		Arg string `arg help:"Some arg"`
+	}
+	type Command struct {
+		SubCmd SubCommand `cmd aliases:"other-sub-cmd"`
+	}
+	var cli struct {
+		Cmd Command `cmd hidden`
+	}
+	p := mustNew(t, &cli)
+	_, err := p.Parse([]string{"cmd", "other-sub-cmd", "arg"})
+	require.NoError(t, err)
+	require.Equal(t, "arg", cli.Cmd.SubCmd.Arg)
+}