diff --git a/README.md b/README.md index 0d576c6..994da42 100644 --- a/README.md +++ b/README.md @@ -577,7 +577,7 @@ Both can coexist with standard Tag parsing. | `enum:"X,Y,..."` | Set of valid values allowed for this flag. An enum field must be `required` or have a valid `default`. | | `group:"X"` | Logical group for a flag or command. | | `xor:"X,Y,..."` | Exclusive OR groups for flags. Only one flag in the group can be used which is restricted within the same command. When combined with `required`, at least one of the `xor` group will be required. | -| `and:"X,Y,..."` | Exclusive 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. | +| `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. | | `set:"K=V"` | Set a variable for expansion by child elements. Multiples can occur. | diff --git a/kong.go b/kong.go index 19ff88e..10235d0 100644 --- a/kong.go +++ b/kong.go @@ -167,9 +167,42 @@ func New(grammar interface{}, options ...Option) (*Kong, error) { k.bindings.add(k.vars) + if err = checkOverlappingXorAnd(k); err != nil { + return nil, err + } + return k, nil } +func checkOverlappingXorAnd(k *Kong) error { + xorGroups := map[string][]string{} + andGroups := map[string][]string{} + for _, flag := range k.Model.Node.Flags { + for _, xor := range flag.Xor { + xorGroups[xor] = append(xorGroups[xor], flag.Name) + } + for _, and := range flag.And { + andGroups[and] = append(andGroups[and], flag.Name) + } + } + for xor, xorSet := range xorGroups { + for and, andSet := range andGroups { + overlappingEntries := []string{} + for _, xorTag := range xorSet { + for _, andTag := range andSet { + if xorTag == andTag { + overlappingEntries = append(overlappingEntries, xorTag) + } + } + } + if len(overlappingEntries) > 1 { + return fmt.Errorf("invalid xor and combination, %s and %s overlap with more than one: %s", xor, and, overlappingEntries) + } + } + } + return nil +} + type varStack []Vars func (v *varStack) head() Vars { return (*v)[len(*v)-1] } diff --git a/kong_test.go b/kong_test.go index 2ac9cbd..c148962 100644 --- a/kong_test.go +++ b/kong_test.go @@ -1026,6 +1026,16 @@ func TestXorAnd(t *testing.T) { assert.EqualError(t, err, "--hello and --one can't be used together, --hello and --two must be used together") } +func TestOverLappingXorAnd(t *testing.T) { + var cli struct { + Hello bool `xor:"one" and:"two"` + One bool `xor:"one" and:"two"` + Two string `xor:"one" and:"two"` + } + _, err := kong.New(&cli) + assert.EqualError(t, err, "invalid xor and combination, one and two overlap with more than one: [hello one two]") +} + func TestXorRequired(t *testing.T) { var cli struct { One bool `xor:"one,two" required:""`