Skip to content

Commit 204d34f

Browse files
authored
Merge pull request #1863 from joshfrench/mut-exclusive-help
Include mutually exclusive flags in help text
2 parents e1d1334 + 83b8287 commit 204d34f

File tree

8 files changed

+116
-9
lines changed

8 files changed

+116
-9
lines changed

command.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -282,8 +282,13 @@ func (cmd *Command) setupDefaults(osArgs []string) {
282282
tracef("sorting command categories (cmd=%[1]q)", cmd.Name)
283283
sort.Sort(cmd.categories.(*commandCategories))
284284

285+
tracef("setting category on mutually exclusive flags (cmd=%[1]q)", cmd.Name)
286+
for _, grp := range cmd.MutuallyExclusiveFlags {
287+
grp.propagateCategory()
288+
}
289+
285290
tracef("setting flag categories (cmd=%[1]q)", cmd.Name)
286-
cmd.flagCategories = newFlagCategoriesFromFlags(cmd.Flags)
291+
cmd.flagCategories = newFlagCategoriesFromFlags(cmd.allFlags())
287292

288293
if cmd.Metadata == nil {
289294
tracef("setting default Metadata (cmd=%[1]q)", cmd.Name)
@@ -324,8 +329,13 @@ func (cmd *Command) setupSubcommand() {
324329
tracef("sorting command categories (cmd=%[1]q)", cmd.Name)
325330
sort.Sort(cmd.categories.(*commandCategories))
326331

332+
tracef("setting category on mutually exclusive flags (cmd=%[1]q)", cmd.Name)
333+
for _, grp := range cmd.MutuallyExclusiveFlags {
334+
grp.propagateCategory()
335+
}
336+
327337
tracef("setting flag categories (cmd=%[1]q)", cmd.Name)
328-
cmd.flagCategories = newFlagCategoriesFromFlags(cmd.Flags)
338+
cmd.flagCategories = newFlagCategoriesFromFlags(cmd.allFlags())
329339
}
330340

331341
func (cmd *Command) ensureHelp() {
@@ -848,14 +858,14 @@ func (cmd *Command) VisibleCommands() []*Command {
848858
// VisibleFlagCategories returns a slice containing all the visible flag categories with the flags they contain
849859
func (cmd *Command) VisibleFlagCategories() []VisibleFlagCategory {
850860
if cmd.flagCategories == nil {
851-
cmd.flagCategories = newFlagCategoriesFromFlags(cmd.Flags)
861+
cmd.flagCategories = newFlagCategoriesFromFlags(cmd.allFlags())
852862
}
853863
return cmd.flagCategories.VisibleCategories()
854864
}
855865

856866
// VisibleFlags returns a slice of the Flags with Hidden=false
857867
func (cmd *Command) VisibleFlags() []Flag {
858-
return visibleFlags(cmd.Flags)
868+
return visibleFlags(cmd.allFlags())
859869
}
860870

861871
func (cmd *Command) appendFlag(fl Flag) {

command_test.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -524,19 +524,33 @@ func TestCommand_VisibleFlagCategories(t *testing.T) {
524524
Category: "cat1",
525525
},
526526
},
527+
MutuallyExclusiveFlags: []MutuallyExclusiveFlags{{
528+
Category: "cat2",
529+
Flags: [][]Flag{
530+
{
531+
&StringFlag{
532+
Name: "mutex",
533+
},
534+
},
535+
},
536+
}},
527537
}
528538

539+
cmd.MutuallyExclusiveFlags[0].propagateCategory()
540+
529541
vfc := cmd.VisibleFlagCategories()
530-
require.Len(t, vfc, 2)
542+
require.Len(t, vfc, 3)
531543

532544
assert.Equal(t, vfc[0].Name(), "", "expected category name to be empty")
545+
assert.Equal(t, vfc[0].Flags()[0].Names(), []string{"strd"})
533546

534547
assert.Equal(t, vfc[1].Name(), "cat1", "expected category name cat1")
548+
require.Len(t, vfc[1].Flags(), 1, "expected flag category to have one flag")
549+
assert.Equal(t, vfc[1].Flags()[0].Names(), []string{"intd", "altd1", "altd2"})
535550

536-
require.Len(t, vfc[1].Flags(), 1, "expected flag category to have just one flag")
537-
538-
fl := vfc[1].Flags()[0]
539-
assert.Equal(t, fl.Names(), []string{"intd", "altd1", "altd2"})
551+
assert.Equal(t, vfc[2].Name(), "cat2", "expected category name cat2")
552+
require.Len(t, vfc[2].Flags(), 1, "expected flag category to have one flag")
553+
assert.Equal(t, vfc[2].Flags()[0].Names(), []string{"mutex"})
540554
}
541555

542556
func TestCommand_RunSubcommandWithDefault(t *testing.T) {

flag.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ type VisibleFlag interface {
162162
type CategorizableFlag interface {
163163
// Returns the category of the flag
164164
GetCategory() string
165+
166+
// Sets the category of the flag
167+
SetCategory(string)
165168
}
166169

167170
// PersistentFlag is an interface to enable detection of flags which are persistent

flag_impl.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,10 @@ func (f *FlagBase[T, C, V]) GetCategory() string {
221221
return f.Category
222222
}
223223

224+
func (f *FlagBase[T, C, V]) SetCategory(c string) {
225+
f.Category = c
226+
}
227+
224228
// GetUsage returns the usage string for the flag
225229
func (f *FlagBase[T, C, V]) GetUsage() string {
226230
return f.Usage

flag_mutex.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ type MutuallyExclusiveFlags struct {
1111

1212
// whether this group is required
1313
Required bool
14+
15+
// Category to apply to all flags within group
16+
Category string
1417
}
1518

1619
func (grp MutuallyExclusiveFlags) check(cmd *Command) error {
@@ -41,3 +44,13 @@ func (grp MutuallyExclusiveFlags) check(cmd *Command) error {
4144
}
4245
return nil
4346
}
47+
48+
func (grp MutuallyExclusiveFlags) propagateCategory() {
49+
for _, grpf := range grp.Flags {
50+
for _, f := range grpf {
51+
if cf, ok := f.(CategorizableFlag); ok {
52+
cf.SetCategory(grp.Category)
53+
}
54+
}
55+
}
56+
}

godoc-current.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,9 @@ func (s *BoolWithInverseFlag) Value() bool
289289
type CategorizableFlag interface {
290290
// Returns the category of the flag
291291
GetCategory() string
292+
293+
// Sets the category of the flag
294+
SetCategory(string)
292295
}
293296
CategorizableFlag is an interface that allows us to potentially use a flag
294297
in a categorized representation.
@@ -702,6 +705,8 @@ func (f *FlagBase[T, C, V]) Names() []string
702705
func (f *FlagBase[T, C, V]) RunAction(ctx context.Context, cmd *Command) error
703706
RunAction executes flag action if set
704707

708+
func (f *FlagBase[T, C, V]) SetCategory(c string)
709+
705710
func (f *FlagBase[T, C, V]) String() string
706711
String returns a readable representation of this value (for usage defaults)
707712

@@ -821,6 +826,9 @@ type MutuallyExclusiveFlags struct {
821826

822827
// whether this group is required
823828
Required bool
829+
830+
// Category to apply to all flags within group
831+
Category string
824832
}
825833
MutuallyExclusiveFlags defines a mutually exclusive flag group Multiple
826834
option paths can be provided out of which only one can be defined on cmdline

help_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,28 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
11761176
}
11771177
}
11781178

1179+
func TestMutuallyExclusiveFlags(t *testing.T) {
1180+
writer := &bytes.Buffer{}
1181+
cmd := &Command{
1182+
Name: "cmd",
1183+
Writer: writer,
1184+
MutuallyExclusiveFlags: []MutuallyExclusiveFlags{
1185+
{
1186+
Flags: [][]Flag{
1187+
{
1188+
&StringFlag{
1189+
Name: "s1",
1190+
},
1191+
},
1192+
}},
1193+
},
1194+
}
1195+
1196+
_ = ShowAppHelp(cmd)
1197+
1198+
assert.Contains(t, writer.String(), "--s1", "written help does not include mutex flag")
1199+
}
1200+
11791201
func TestWrap(t *testing.T) {
11801202
emptywrap := wrap("", 4, 16)
11811203
assert.Empty(t, emptywrap, "Wrapping empty line should return empty line")
@@ -1504,6 +1526,29 @@ func TestCategorizedHelp(t *testing.T) {
15041526
Category: "cat1",
15051527
},
15061528
},
1529+
MutuallyExclusiveFlags: []MutuallyExclusiveFlags{
1530+
{
1531+
Category: "cat1",
1532+
Flags: [][]Flag{
1533+
{
1534+
&StringFlag{
1535+
Name: "m1",
1536+
Category: "overridden",
1537+
},
1538+
},
1539+
},
1540+
},
1541+
{
1542+
Flags: [][]Flag{
1543+
{
1544+
&StringFlag{
1545+
Name: "m2",
1546+
Category: "ignored",
1547+
},
1548+
},
1549+
},
1550+
},
1551+
},
15071552
}
15081553

15091554
HelpPrinter = func(w io.Writer, templ string, data interface{}) {
@@ -1533,11 +1578,13 @@ COMMANDS:
15331578
15341579
GLOBAL OPTIONS:
15351580
--help, -h show help (default: false)
1581+
--m2 value
15361582
--strd value
15371583
15381584
cat1
15391585
15401586
--intd value, --altd1 value, --altd2 value (default: 0)
1587+
--m1 value
15411588
15421589
`, output.String())
15431590
}

testdata/godoc-v3.x.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,9 @@ func (s *BoolWithInverseFlag) Value() bool
289289
type CategorizableFlag interface {
290290
// Returns the category of the flag
291291
GetCategory() string
292+
293+
// Sets the category of the flag
294+
SetCategory(string)
292295
}
293296
CategorizableFlag is an interface that allows us to potentially use a flag
294297
in a categorized representation.
@@ -702,6 +705,8 @@ func (f *FlagBase[T, C, V]) Names() []string
702705
func (f *FlagBase[T, C, V]) RunAction(ctx context.Context, cmd *Command) error
703706
RunAction executes flag action if set
704707

708+
func (f *FlagBase[T, C, V]) SetCategory(c string)
709+
705710
func (f *FlagBase[T, C, V]) String() string
706711
String returns a readable representation of this value (for usage defaults)
707712

@@ -821,6 +826,9 @@ type MutuallyExclusiveFlags struct {
821826

822827
// whether this group is required
823828
Required bool
829+
830+
// Category to apply to all flags within group
831+
Category string
824832
}
825833
MutuallyExclusiveFlags defines a mutually exclusive flag group Multiple
826834
option paths can be provided out of which only one can be defined on cmdline

0 commit comments

Comments
 (0)