diff --git a/README.md b/README.md index 133665c..be44b82 100644 --- a/README.md +++ b/README.md @@ -395,7 +395,9 @@ Both can coexist with standard Tag parsing. | `hidden` | If present, command or flag is hidden. | | `format:"X"` | Format for parsing input, if supported. | | `sep:"X"` | Separator for sequences (defaults to ","). May be `none` to disable splitting. | -| `enum:"X,Y,..."` | +| `enum:"X,Y,..."` | Set of valid values allowed for this flag. | +| `group:"X"` | Logical group for a flag or command. | +| `prefix:"X"` | Prefix for all sub-flags. | ## Variable interpolation diff --git a/build.go b/build.go index 666025b..829107c 100644 --- a/build.go +++ b/build.go @@ -38,20 +38,30 @@ func dashedString(s string) string { type flattenedField struct { field reflect.StructField value reflect.Value + tag *Tag } func flattenedFields(v reflect.Value) (out []flattenedField) { for i := 0; i < v.NumField(); i++ { ft := v.Type().Field(i) fv := v.Field(i) + tag := parseTag(fv, ft) if ft.Anonymous { - out = append(out, flattenedFields(fv)...) + sub := flattenedFields(fv) + // Assign parent group to children, if they're not otherwise set. + for _, subf := range sub { + if subf.tag.Group == "" { + subf.tag.Group = tag.Group + } + subf.tag.Prefix = tag.Prefix + subf.tag.Prefix + } + out = append(out, sub...) continue } if !fv.CanSet() { continue } - out = append(out, flattenedField{field: ft, value: fv}) + out = append(out, flattenedField{field: ft, value: fv, tag: tag}) } return } @@ -65,11 +75,12 @@ func buildNode(k *Kong, v reflect.Value, typ NodeType, seenFlags map[string]bool ft := field.field fv := field.value - tag := parseTag(fv, ft) - + tag := field.tag name := tag.Name if name == "" { - name = strings.ToLower(dashedString(ft.Name)) + name = tag.Prefix + strings.ToLower(dashedString(ft.Name)) + } else { + name = tag.Prefix + name } // Nested structs are either commands or args, unless they implement the Mapper interface. @@ -108,6 +119,7 @@ func buildChild(k *Kong, node *Node, typ NodeType, v reflect.Value, ft reflect.S child.Parent = node child.Help = tag.Help child.Hidden = tag.Hidden + child.Group = tag.Group if fv.Type().Implements(helpProviderType) { child.Detail = fv.Interface().(HelpProvider).Help() @@ -176,6 +188,7 @@ func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv Short: tag.Short, PlaceHolder: tag.PlaceHolder, Env: tag.Env, + Group: tag.Group, } value.Flag = flag node.Flags = append(node.Flags, flag) diff --git a/kong_test.go b/kong_test.go index ad0d988..d77b11e 100644 --- a/kong_test.go +++ b/kong_test.go @@ -577,3 +577,16 @@ func TestBoolOverride(t *testing.T) { _, err = p.Parse([]string{"--flag", "false"}) require.Error(t, err) } + +func TestAnonymousPrefix(t *testing.T) { + type Anonymous struct { + Flag string + } + var cli struct { + Anonymous `prefix:"anon-"` + } + p := mustNew(t, &cli) + _, err := p.Parse([]string{"--anon-flag=moo"}) + require.NoError(t, err) + require.Equal(t, "moo", cli.Flag) +} diff --git a/model.go b/model.go index df009ac..db0fa58 100644 --- a/model.go +++ b/model.go @@ -37,6 +37,7 @@ type Node struct { Name string Help string // Short help displayed in summaries. Detail string // Detailed help displayed when describing command/arg alone. + Group string Hidden bool Flags []*Flag Positional []*Positional @@ -284,6 +285,7 @@ type Positional = Value // A Flag represents a command-line flag. type Flag struct { *Value + Group string // Logical grouping when displaying. May also be used by configuration loaders to group options logically. PlaceHolder string Env string Short rune diff --git a/tag.go b/tag.go index e871f16..6669194 100644 --- a/tag.go +++ b/tag.go @@ -25,6 +25,8 @@ type Tag struct { Hidden bool Sep rune Enum string + Group string + Prefix string // Optional prefix on anonymous structs. All sub-flags will have this prefix. // Storage for all tag keys for arbitrary lookups. items map[string]string @@ -137,6 +139,8 @@ func parseTag(fv reflect.Value, ft reflect.StructField) *Tag { t.Hidden = t.Has("hidden") t.Format = t.Get("format") t.Sep, _ = t.GetRune("sep") + t.Group = t.Get("group") + t.Prefix = t.Get("prefix") if t.Sep == 0 { if t.Get("sep") == "none" { t.Sep = -1