XOR groups for flags. (#35)

This commit is contained in:
gak
2019-07-20 20:31:04 +10:00
committed by Alec Thomas
parent 0548c6b1af
commit f830198fcd
6 changed files with 59 additions and 0 deletions
+1
View File
@@ -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.
+1
View File
@@ -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
View File
@@ -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...)
+32
View File
@@ -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")
}
+1
View File
@@ -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
+2
View File
@@ -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 {