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