Feature: Add check for overlapping xor and and groups (#443)

* Docs: Clean and group description

* Feat: Add check for overlapping xor and and groups

Co-authored-by: inful <jone.marius@vign.es>

* Chore: Rewrite overlap err to avoid duplicated words

---------

Co-authored-by: inful <jone.marius@vign.es>
This commit is contained in:
Camilla
2024-09-10 13:01:12 +02:00
committed by GitHub
parent b297ae97f2
commit 7d84b95294
3 changed files with 44 additions and 1 deletions
+1 -1
View File
@@ -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. |
+33
View File
@@ -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] }
+10
View File
@@ -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:""`