diff --git a/README.md b/README.md index e5b4ebe..368b098 100644 --- a/README.md +++ b/README.md @@ -446,7 +446,7 @@ Tag | Description `mapsep:"X"` | Separator for maps (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,Y,..."` | Exclusive OR groups 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. When combined with `required`, at least one of the `xor` group will be required. `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. diff --git a/context.go b/context.go index 1231009..8065efd 100644 --- a/context.go +++ b/context.go @@ -743,17 +743,32 @@ func (c *Context) PrintUsage(summary bool) error { } func checkMissingFlags(flags []*Flag) error { + xorGroup := map[string][]string{} missing := []string{} for _, flag := range flags { if !flag.Required || flag.Set { continue } - missing = append(missing, flag.Summary()) + if len(flag.Xor) > 0 { + for _, xor := range flag.Xor { + xorGroup[xor] = append(xorGroup[xor], flag.Summary()) + } + } else { + missing = append(missing, flag.Summary()) + } } + for _, flags := range xorGroup { + if len(flags) > 1 { + missing = append(missing, strings.Join(flags, " or ")) + } + } + if len(missing) == 0 { return nil } + sort.Strings(missing) + return fmt.Errorf("missing flags: %s", strings.Join(missing, ", ")) } diff --git a/kong_test.go b/kong_test.go index d879967..1a8af52 100644 --- a/kong_test.go +++ b/kong_test.go @@ -884,6 +884,26 @@ func TestMultiXor(t *testing.T) { require.EqualError(t, err, "--hello and --two can't be used together") } +func TestXorRequired(t *testing.T) { + var cli struct { + One bool `xor:"one,two" required:""` + Two bool `xor:"one" required:""` + Three bool `xor:"two" required:""` + Four bool `required:""` + } + p := mustNew(t, &cli) + _, err := p.Parse([]string{"--one"}) + require.EqualError(t, err, "missing flags: --four") + + p = mustNew(t, &cli) + _, err = p.Parse([]string{"--two"}) + require.EqualError(t, err, "missing flags: --four, --one or --three") + + p = mustNew(t, &cli) + _, err = p.Parse([]string{}) + require.EqualError(t, err, "missing flags: --four, --one or --three, --one or --two") +} + func TestEnumSequence(t *testing.T) { var cli struct { State []string `enum:"a,b,c" default:"a"`