diff --git a/README.md b/README.md index 5595a41..4a86251 100644 --- a/README.md +++ b/README.md @@ -550,6 +550,7 @@ Both can coexist with standard Tag parsing. | `and:"X,Y,..."` | AND groups for flags. All flags in the group must be used in the same command. When combined with `required`, all flags in the group will be required. | | `prefix:"X"` | Prefix for all sub-flags. | | `envprefix:"X"` | Envar prefix for all sub-flags. | +| `xorprefix:"X"` | Prefix for all sub-flags in XOR/AND groups. | | `set:"K=V"` | Set a variable for expansion by child elements. Multiples can occur. | | `embed:""` | If present, this field's children will be embedded in the parent. Useful for composition. | | `passthrough:""`[^1] | If present on a positional argument, it stops flag parsing when encountered, as if `--` was processed before. Useful for external command wrappers, like `exec`. On a command it requires that the command contains only one argument of type `[]string` which is then filled with everything following the command, unparsed. | diff --git a/build.go b/build.go index 166935b..228c0e9 100644 --- a/build.go +++ b/build.go @@ -71,6 +71,7 @@ func flattenedFields(v reflect.Value, ptag *Tag) (out []flattenedField, err erro // Accumulate prefixes. tag.Prefix = ptag.Prefix + tag.Prefix tag.EnvPrefix = ptag.EnvPrefix + tag.EnvPrefix + tag.XorPrefix = ptag.XorPrefix + tag.XorPrefix // Combine parent vars. tag.Vars = ptag.Vars.CloneWith(tag.Vars) // Command and embedded structs can be pointers, so we hydrate them now. @@ -111,7 +112,7 @@ func flattenedFields(v reflect.Value, ptag *Tag) (out []flattenedField, err erro // Build a Node in the Kong data model. // // "v" is the value to create the node from, "typ" is the output Node type. -func buildNode(k *Kong, v reflect.Value, typ NodeType, tag *Tag, seenFlags map[string]bool) (*Node, error) { +func buildNode(k *Kong, v reflect.Value, typ NodeType, tag *Tag, seenFlags map[string]bool) (*Node, error) { //nolint:gocyclo node := &Node{ Type: typ, Target: v, @@ -147,6 +148,12 @@ MAIN: } } + if len(tag.Xor) != 0 { + for i := range tag.Xor { + tag.Xor[i] = tag.XorPrefix + tag.Xor[i] + } + } + // Nested structs are either commands or args, unless they implement the Mapper interface. if field.value.Kind() == reflect.Struct && (tag.Cmd || tag.Arg) && k.registry.ForValue(fv) == nil { typ := CommandNode diff --git a/kong_test.go b/kong_test.go index 66ad847..2f3ab47 100644 --- a/kong_test.go +++ b/kong_test.go @@ -2475,3 +2475,23 @@ func TestCustomTypeNoEllipsis(t *testing.T) { help := w.String() assert.NotContains(t, help, "...") } + +func TestPrefixXorIssue343(t *testing.T) { + type DBConfig struct { + Password string `help:"Password" xor:"password" optional:""` + PasswordFile string `help:"File which content will be used for a password" xor:"password" optional:""` + PasswordCommand string `help:"Command to run to retrieve password" xor:"password" optional:""` + } + + type SourceTargetConfig struct { + Source DBConfig `help:"Database config of source to be copied from" prefix:"source-" xorprefix:"source-" embed:""` + Target DBConfig `help:"Database config of source to be copied from" prefix:"target-" xorprefix:"target-" embed:""` + } + + cli := SourceTargetConfig{} + kctx := mustNew(t, &cli) + _, err := kctx.Parse([]string{"--source-password=foo", "--target-password=bar"}) + assert.NoError(t, err) + _, err = kctx.Parse([]string{"--source-password-file=foo", "--source-password=bar"}) + assert.Error(t, err) +} diff --git a/tag.go b/tag.go index 226171b..a2bc4a9 100644 --- a/tag.go +++ b/tag.go @@ -48,6 +48,7 @@ type Tag struct { Vars Vars Prefix string // Optional prefix on anonymous structs. All sub-flags will have this prefix. EnvPrefix string + XorPrefix string // Optional prefix on XOR/AND groups. Embed bool Aliases []string Negatable string @@ -268,6 +269,7 @@ func hydrateTag(t *Tag, typ reflect.Type) error { //nolint: gocyclo } t.Prefix = t.Get("prefix") t.EnvPrefix = t.Get("envprefix") + t.XorPrefix = t.Get("xorprefix") t.Embed = t.Has("embed") if t.Has("negatable") { if !isBool && !isBoolPtr {