XOR groups for flags. (#35)
This commit is contained in:
@@ -403,6 +403,7 @@ Tag | Description
|
||||
`sep:"X"` | Separator for sequences (defaults to ","). May be `none` to disable splitting.
|
||||
`enum:"X,Y,..."` | Set of valid values allowed for this flag.
|
||||
`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.
|
||||
`prefix:"X"` | Prefix for all sub-flags.
|
||||
`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.
|
||||
|
||||
@@ -202,6 +202,7 @@ func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv
|
||||
PlaceHolder: tag.PlaceHolder,
|
||||
Env: tag.Env,
|
||||
Group: tag.Group,
|
||||
Xor: tag.Xor,
|
||||
Hidden: tag.Hidden,
|
||||
}
|
||||
value.Flag = flag
|
||||
|
||||
+22
@@ -179,6 +179,9 @@ func (c *Context) Validate() error {
|
||||
if err := checkMissingPositionals(positionals, node.Positional); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkXorDuplicates(c.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if node.Type == ArgumentNode {
|
||||
value := node.Argument
|
||||
@@ -643,6 +646,25 @@ func checkMissingPositionals(positional int, values []*Value) error {
|
||||
return fmt.Errorf("missing positional arguments %s", strings.Join(missing, " "))
|
||||
}
|
||||
|
||||
func checkXorDuplicates(paths []*Path) error {
|
||||
for _, path := range paths {
|
||||
seen := map[string]*Flag{}
|
||||
for _, flag := range path.Flags {
|
||||
if !flag.Set {
|
||||
continue
|
||||
}
|
||||
if flag.Xor == "" {
|
||||
continue
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func findPotentialCandidates(needle string, haystack []string, format string, args ...interface{}) error {
|
||||
if len(haystack) == 0 {
|
||||
return fmt.Errorf(format, args...)
|
||||
|
||||
@@ -749,3 +749,35 @@ func TestEnvarEnumValidated(t *testing.T) {
|
||||
_, err := p.Parse(nil)
|
||||
require.EqualError(t, err, "--flag must be one of \"valid\" but got \"invalid\"")
|
||||
}
|
||||
|
||||
func TestXor(t *testing.T) {
|
||||
var cli struct {
|
||||
Hello bool `xor:"another"`
|
||||
One bool `xor:"group"`
|
||||
Two string `xor:"group"`
|
||||
}
|
||||
p := mustNew(t, &cli)
|
||||
_, err := p.Parse([]string{"--hello", "--one", "--two=hi"})
|
||||
require.EqualError(t, err, "--one and --two can't be used together")
|
||||
|
||||
p = mustNew(t, &cli)
|
||||
_, err = p.Parse([]string{"--one", "--hello"})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestXorChild(t *testing.T) {
|
||||
var cli struct {
|
||||
One bool `xor:"group"`
|
||||
Cmd struct {
|
||||
Two string `xor:"group"`
|
||||
Three string `xor:"group"`
|
||||
} `cmd`
|
||||
}
|
||||
p := mustNew(t, &cli)
|
||||
_, err := p.Parse([]string{"--one", "cmd", "--two=hi"})
|
||||
require.NoError(t, err)
|
||||
|
||||
p = mustNew(t, &cli)
|
||||
_, err = p.Parse([]string{"--two=hi", "cmd", "--three"})
|
||||
require.Error(t, err, "--two and --three can't be used together")
|
||||
}
|
||||
|
||||
@@ -351,6 +351,7 @@ type Positional = Value
|
||||
type Flag struct {
|
||||
*Value
|
||||
Group string // Logical grouping when displaying. May also be used by configuration loaders to group options logically.
|
||||
Xor string
|
||||
PlaceHolder string
|
||||
Env string
|
||||
Short rune
|
||||
|
||||
@@ -27,6 +27,7 @@ type Tag struct {
|
||||
Sep rune
|
||||
Enum string
|
||||
Group string
|
||||
Xor string
|
||||
Vars Vars
|
||||
Prefix string // Optional prefix on anonymous structs. All sub-flags will have this prefix.
|
||||
Embed bool
|
||||
@@ -151,6 +152,7 @@ func parseTag(fv reflect.Value, ft reflect.StructField) *Tag {
|
||||
t.Format = t.Get("format")
|
||||
t.Sep, _ = t.GetRune("sep")
|
||||
t.Group = t.Get("group")
|
||||
t.Xor = t.Get("xor")
|
||||
t.Prefix = t.Get("prefix")
|
||||
t.Embed = t.Has("embed")
|
||||
if t.Sep == 0 {
|
||||
|
||||
Reference in New Issue
Block a user