Allow multiple xor:"" groups to be defined.

Fixes #113.
This commit is contained in:
Alec Thomas
2021-06-21 18:58:44 +09:30
parent 8aa52739ea
commit 54558f65e8
5 changed files with 32 additions and 14 deletions
+1 -1
View File
@@ -446,7 +446,7 @@ Tag | Description
`mapsep:"X"` | Separator for maps (defaults to ";"). May be `none` to disable splitting. `mapsep:"X"` | Separator for maps (defaults to ";"). May be `none` to disable splitting.
`enum:"X,Y,..."` | Set of valid values allowed for this flag. `enum:"X,Y,..."` | Set of valid values allowed for this flag.
`group:"X"` | Logical group for a flag or command. `group:"X"` | Logical group for a flag or command.
`xor:"X"` | Exclusive OR group for flags. Only one flag in the group can be used which is restricted within the same 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.
`prefix:"X"` | Prefix for all sub-flags. `prefix:"X"` | Prefix for all sub-flags.
`set:"K=V"` | Set a variable for expansion by child elements. Multiples can occur. `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. `embed:""` | If present, this field's children will be embedded in the parent. Useful for composition.
+5 -6
View File
@@ -871,13 +871,12 @@ func checkXorDuplicates(paths []*Path) error {
if !flag.Set { if !flag.Set {
continue continue
} }
if flag.Xor == "" { for _, xor := range flag.Xor {
continue if seen[xor] != nil {
return fmt.Errorf("--%s and --%s can't be used together", seen[xor].Name, flag.Name)
}
seen[xor] = flag
} }
if seen[flag.Xor] != nil {
return fmt.Errorf("--%s and --%s can't be used together", seen[flag.Xor].Name, flag.Name)
}
seen[flag.Xor] = flag
} }
} }
return nil return nil
+16
View File
@@ -868,6 +868,22 @@ func TestXorChild(t *testing.T) {
require.Error(t, err, "--two and --three can't be used together") require.Error(t, err, "--two and --three can't be used together")
} }
func TestMultiXor(t *testing.T) {
var cli struct {
Hello bool `xor:"one,two"`
One bool `xor:"one"`
Two string `xor:"two"`
}
p := mustNew(t, &cli)
_, err := p.Parse([]string{"--hello", "--one"})
require.EqualError(t, err, "--hello and --one can't be used together")
p = mustNew(t, &cli)
_, err = p.Parse([]string{"--hello", "--two=foo"})
require.EqualError(t, err, "--hello and --two can't be used together")
}
func TestEnumSequence(t *testing.T) { func TestEnumSequence(t *testing.T) {
var cli struct { var cli struct {
State []string `enum:"a,b,c" default:"a"` State []string `enum:"a,b,c" default:"a"`
+1 -1
View File
@@ -377,7 +377,7 @@ type Positional = Value
type Flag struct { type Flag struct {
*Value *Value
Group *Group // Logical grouping when displaying. May also be used by configuration loaders to group options logically. Group *Group // Logical grouping when displaying. May also be used by configuration loaders to group options logically.
Xor string Xor []string
PlaceHolder string PlaceHolder string
Env string Env string
Short rune Short rune
+9 -6
View File
@@ -29,7 +29,7 @@ type Tag struct {
MapSep rune MapSep rune
Enum string Enum string
Group string Group string
Xor string Xor []string
Vars Vars Vars Vars
Prefix string // Optional prefix on anonymous structs. All sub-flags will have this prefix. Prefix string // Optional prefix on anonymous structs. All sub-flags will have this prefix.
Embed bool Embed bool
@@ -125,6 +125,10 @@ func newEmptyTag() *Tag {
return &Tag{items: map[string][]string{}} return &Tag{items: map[string][]string{}}
} }
func tagSplitFn(r rune) bool {
return r == ',' || r == ' '
}
func parseTag(fv reflect.Value, ft reflect.StructField) *Tag { func parseTag(fv reflect.Value, ft reflect.StructField) *Tag {
if ft.Tag.Get("kong") == "-" { if ft.Tag.Get("kong") == "-" {
t := newEmptyTag() t := newEmptyTag()
@@ -164,7 +168,9 @@ func parseTag(fv reflect.Value, ft reflect.StructField) *Tag {
t.Sep, _ = t.GetSep("sep", ',') t.Sep, _ = t.GetSep("sep", ',')
t.MapSep, _ = t.GetSep("mapsep", ';') t.MapSep, _ = t.GetSep("mapsep", ';')
t.Group = t.Get("group") t.Group = t.Get("group")
t.Xor = t.Get("xor") for _, xor := range t.GetAll("xor") {
t.Xor = append(t.Xor, strings.FieldsFunc(xor, tagSplitFn)...)
}
t.Prefix = t.Get("prefix") t.Prefix = t.Get("prefix")
t.Embed = t.Has("embed") t.Embed = t.Has("embed")
negatable := t.Has("negatable") negatable := t.Has("negatable")
@@ -172,12 +178,9 @@ func parseTag(fv reflect.Value, ft reflect.StructField) *Tag {
fail("negatable can only be set on booleans") fail("negatable can only be set on booleans")
} }
t.Negatable = negatable t.Negatable = negatable
splitFn := func(r rune) bool {
return r == ',' || r == ' '
}
aliases := t.Get("aliases") aliases := t.Get("aliases")
if len(aliases) > 0 { if len(aliases) > 0 {
t.Aliases = append(t.Aliases, strings.FieldsFunc(aliases, splitFn)...) t.Aliases = append(t.Aliases, strings.FieldsFunc(aliases, tagSplitFn)...)
} }
t.Vars = Vars{} t.Vars = Vars{}
for _, set := range t.GetAll("set") { for _, set := range t.GetAll("set") {