Skip to content

Commit 18bfd45

Browse files
committed
feat: support xor/and groups for positional arguments
Fixes #255 Extend checkMissingFlags, checkXorDuplicates, checkAndMissing, and checkOverlappingXorAnd to include positional arguments in xor/and group validation. Error messages now use ShortSummary() to correctly display both flag (--flag) and positional arg names.
1 parent bf9cf79 commit 18bfd45

2 files changed

Lines changed: 47 additions & 15 deletions

File tree

context.go

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ func (c *Context) Validate() error { //nolint: gocyclo
260260
return err
261261
}
262262
}
263-
if err := checkMissingFlags(path.Flags); err != nil {
263+
if err := checkMissingFlags(path.Flags, path.Positional); err != nil {
264264
return err
265265
}
266266
}
@@ -891,13 +891,24 @@ func (c *Context) printHelp(options HelpOptions) error {
891891
return c.help(options, c)
892892
}
893893

894-
func checkMissingFlags(flags []*Flag) error {
894+
func checkMissingFlags(flags []*Flag, positional *Value) error {
895895
xorGroupSet := map[string]bool{}
896896
xorGroup := map[string][]string{}
897897
andGroupSet := map[string]bool{}
898898
andGroup := map[string][]string{}
899899
missing := []string{}
900900
andGroupRequired := getRequiredAndGroupMap(flags)
901+
902+
// Check positional arg for xor/and groups.
903+
if positional != nil && positional.Set {
904+
for _, xor := range positional.Tag.Xor {
905+
xorGroupSet[xor] = true
906+
}
907+
for _, and := range positional.Tag.And {
908+
andGroupSet[and] = true
909+
}
910+
}
911+
901912
for _, flag := range flags {
902913
for _, and := range flag.And {
903914
flag.Required = andGroupRequired[and]
@@ -1085,16 +1096,24 @@ func checkXorDuplicatedAndAndMissing(paths []*Path) error {
10851096

10861097
func checkXorDuplicates(paths []*Path) error {
10871098
for _, path := range paths {
1088-
seen := map[string]*Flag{}
1099+
seen := map[string]*Value{}
10891100
for _, flag := range path.Flags {
10901101
if !flag.Set {
10911102
continue
10921103
}
10931104
for _, xor := range flag.Xor {
10941105
if seen[xor] != nil {
1095-
return fmt.Errorf("--%s and --%s can't be used together", seen[xor].Name, flag.Name)
1106+
return fmt.Errorf("%s and %s can't be used together", seen[xor].ShortSummary(), flag.ShortSummary())
1107+
}
1108+
seen[xor] = flag.Value
1109+
}
1110+
}
1111+
if path.Positional != nil && path.Positional.Set {
1112+
for _, xor := range path.Positional.Tag.Xor {
1113+
if seen[xor] != nil {
1114+
return fmt.Errorf("%s and %s can't be used together", seen[xor].ShortSummary(), path.Positional.ShortSummary())
10961115
}
1097-
seen[xor] = flag
1116+
seen[xor] = path.Positional
10981117
}
10991118
}
11001119
}
@@ -1104,26 +1123,31 @@ func checkXorDuplicates(paths []*Path) error {
11041123
func checkAndMissing(paths []*Path) error {
11051124
for _, path := range paths {
11061125
missingMsgs := []string{}
1107-
andGroups := map[string][]*Flag{}
1126+
andGroups := map[string][]*Value{}
11081127
for _, flag := range path.Flags {
11091128
for _, and := range flag.And {
1110-
andGroups[and] = append(andGroups[and], flag)
1129+
andGroups[and] = append(andGroups[and], flag.Value)
1130+
}
1131+
}
1132+
if path.Positional != nil {
1133+
for _, and := range path.Positional.Tag.And {
1134+
andGroups[and] = append(andGroups[and], path.Positional)
11111135
}
11121136
}
1113-
for _, flags := range andGroups {
1137+
for _, values := range andGroups {
11141138
oneSet := false
1115-
notSet := []*Flag{}
1116-
flagNames := []string{}
1117-
for _, flag := range flags {
1118-
flagNames = append(flagNames, flag.Name)
1119-
if flag.Set {
1139+
notSet := []*Value{}
1140+
names := []string{}
1141+
for _, value := range values {
1142+
names = append(names, value.ShortSummary())
1143+
if value.Set {
11201144
oneSet = true
11211145
} else {
1122-
notSet = append(notSet, flag)
1146+
notSet = append(notSet, value)
11231147
}
11241148
}
11251149
if len(notSet) > 0 && oneSet {
1126-
missingMsgs = append(missingMsgs, fmt.Sprintf("--%s must be used together", strings.Join(flagNames, " and --")))
1150+
missingMsgs = append(missingMsgs, fmt.Sprintf("%s must be used together", strings.Join(names, " and ")))
11271151
}
11281152
}
11291153
if len(missingMsgs) > 0 {

kong.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,14 @@ func checkOverlappingXorAnd(k *Kong) error {
189189
andGroups[and] = append(andGroups[and], flag.Name)
190190
}
191191
}
192+
for _, positional := range k.Model.Node.Positional {
193+
for _, xor := range positional.Tag.Xor {
194+
xorGroups[xor] = append(xorGroups[xor], positional.Name)
195+
}
196+
for _, and := range positional.Tag.And {
197+
andGroups[and] = append(andGroups[and], positional.Name)
198+
}
199+
}
192200
for xor, xorSet := range xorGroups {
193201
for and, andSet := range andGroups {
194202
overlappingEntries := []string{}

0 commit comments

Comments
 (0)