diff --git a/command.go b/command.go index fde2e67e65..d871365ff9 100644 --- a/command.go +++ b/command.go @@ -282,8 +282,13 @@ func (cmd *Command) setupDefaults(osArgs []string) { tracef("sorting command categories (cmd=%[1]q)", cmd.Name) sort.Sort(cmd.categories.(*commandCategories)) + tracef("setting category on mutually exclusive flags (cmd=%[1]q)", cmd.Name) + for _, grp := range cmd.MutuallyExclusiveFlags { + grp.propagateCategory() + } + tracef("setting flag categories (cmd=%[1]q)", cmd.Name) - cmd.flagCategories = newFlagCategoriesFromFlags(cmd.Flags) + cmd.flagCategories = newFlagCategoriesFromFlags(cmd.allFlags()) if cmd.Metadata == nil { tracef("setting default Metadata (cmd=%[1]q)", cmd.Name) @@ -324,8 +329,13 @@ func (cmd *Command) setupSubcommand() { tracef("sorting command categories (cmd=%[1]q)", cmd.Name) sort.Sort(cmd.categories.(*commandCategories)) + tracef("setting category on mutually exclusive flags (cmd=%[1]q)", cmd.Name) + for _, grp := range cmd.MutuallyExclusiveFlags { + grp.propagateCategory() + } + tracef("setting flag categories (cmd=%[1]q)", cmd.Name) - cmd.flagCategories = newFlagCategoriesFromFlags(cmd.Flags) + cmd.flagCategories = newFlagCategoriesFromFlags(cmd.allFlags()) } func (cmd *Command) ensureHelp() { @@ -848,14 +858,14 @@ func (cmd *Command) VisibleCommands() []*Command { // VisibleFlagCategories returns a slice containing all the visible flag categories with the flags they contain func (cmd *Command) VisibleFlagCategories() []VisibleFlagCategory { if cmd.flagCategories == nil { - cmd.flagCategories = newFlagCategoriesFromFlags(cmd.Flags) + cmd.flagCategories = newFlagCategoriesFromFlags(cmd.allFlags()) } return cmd.flagCategories.VisibleCategories() } // VisibleFlags returns a slice of the Flags with Hidden=false func (cmd *Command) VisibleFlags() []Flag { - return visibleFlags(cmd.Flags) + return visibleFlags(cmd.allFlags()) } func (cmd *Command) appendFlag(fl Flag) { diff --git a/command_test.go b/command_test.go index bcff5f681a..9a69e2f02b 100644 --- a/command_test.go +++ b/command_test.go @@ -524,19 +524,33 @@ func TestCommand_VisibleFlagCategories(t *testing.T) { Category: "cat1", }, }, + MutuallyExclusiveFlags: []MutuallyExclusiveFlags{{ + Category: "cat2", + Flags: [][]Flag{ + { + &StringFlag{ + Name: "mutex", + }, + }, + }, + }}, } + cmd.MutuallyExclusiveFlags[0].propagateCategory() + vfc := cmd.VisibleFlagCategories() - require.Len(t, vfc, 2) + require.Len(t, vfc, 3) assert.Equal(t, vfc[0].Name(), "", "expected category name to be empty") + assert.Equal(t, vfc[0].Flags()[0].Names(), []string{"strd"}) assert.Equal(t, vfc[1].Name(), "cat1", "expected category name cat1") + require.Len(t, vfc[1].Flags(), 1, "expected flag category to have one flag") + assert.Equal(t, vfc[1].Flags()[0].Names(), []string{"intd", "altd1", "altd2"}) - require.Len(t, vfc[1].Flags(), 1, "expected flag category to have just one flag") - - fl := vfc[1].Flags()[0] - assert.Equal(t, fl.Names(), []string{"intd", "altd1", "altd2"}) + assert.Equal(t, vfc[2].Name(), "cat2", "expected category name cat2") + require.Len(t, vfc[2].Flags(), 1, "expected flag category to have one flag") + assert.Equal(t, vfc[2].Flags()[0].Names(), []string{"mutex"}) } func TestCommand_RunSubcommandWithDefault(t *testing.T) { diff --git a/flag.go b/flag.go index 5332b22201..d7f771b063 100644 --- a/flag.go +++ b/flag.go @@ -162,6 +162,9 @@ type VisibleFlag interface { type CategorizableFlag interface { // Returns the category of the flag GetCategory() string + + // Sets the category of the flag + SetCategory(string) } // PersistentFlag is an interface to enable detection of flags which are persistent diff --git a/flag_impl.go b/flag_impl.go index dd21f3beb9..3e9fdbc901 100644 --- a/flag_impl.go +++ b/flag_impl.go @@ -221,6 +221,10 @@ func (f *FlagBase[T, C, V]) GetCategory() string { return f.Category } +func (f *FlagBase[T, C, V]) SetCategory(c string) { + f.Category = c +} + // GetUsage returns the usage string for the flag func (f *FlagBase[T, C, V]) GetUsage() string { return f.Usage diff --git a/flag_mutex.go b/flag_mutex.go index 2ca38e71ce..e03de8da9b 100644 --- a/flag_mutex.go +++ b/flag_mutex.go @@ -11,6 +11,9 @@ type MutuallyExclusiveFlags struct { // whether this group is required Required bool + + // Category to apply to all flags within group + Category string } func (grp MutuallyExclusiveFlags) check(cmd *Command) error { @@ -41,3 +44,13 @@ func (grp MutuallyExclusiveFlags) check(cmd *Command) error { } return nil } + +func (grp MutuallyExclusiveFlags) propagateCategory() { + for _, grpf := range grp.Flags { + for _, f := range grpf { + if cf, ok := f.(CategorizableFlag); ok { + cf.SetCategory(grp.Category) + } + } + } +} diff --git a/godoc-current.txt b/godoc-current.txt index 92162465a4..e30dbe2bb2 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -289,6 +289,9 @@ func (s *BoolWithInverseFlag) Value() bool type CategorizableFlag interface { // Returns the category of the flag GetCategory() string + + // Sets the category of the flag + SetCategory(string) } CategorizableFlag is an interface that allows us to potentially use a flag in a categorized representation. @@ -702,6 +705,8 @@ func (f *FlagBase[T, C, V]) Names() []string func (f *FlagBase[T, C, V]) RunAction(ctx context.Context, cmd *Command) error RunAction executes flag action if set +func (f *FlagBase[T, C, V]) SetCategory(c string) + func (f *FlagBase[T, C, V]) String() string String returns a readable representation of this value (for usage defaults) @@ -821,6 +826,9 @@ type MutuallyExclusiveFlags struct { // whether this group is required Required bool + + // Category to apply to all flags within group + Category string } MutuallyExclusiveFlags defines a mutually exclusive flag group Multiple option paths can be provided out of which only one can be defined on cmdline diff --git a/help_test.go b/help_test.go index 1026e5afa5..44d715a55a 100644 --- a/help_test.go +++ b/help_test.go @@ -1176,6 +1176,28 @@ func TestDefaultCompleteWithFlags(t *testing.T) { } } +func TestMutuallyExclusiveFlags(t *testing.T) { + writer := &bytes.Buffer{} + cmd := &Command{ + Name: "cmd", + Writer: writer, + MutuallyExclusiveFlags: []MutuallyExclusiveFlags{ + { + Flags: [][]Flag{ + { + &StringFlag{ + Name: "s1", + }, + }, + }}, + }, + } + + _ = ShowAppHelp(cmd) + + assert.Contains(t, writer.String(), "--s1", "written help does not include mutex flag") +} + func TestWrap(t *testing.T) { emptywrap := wrap("", 4, 16) assert.Empty(t, emptywrap, "Wrapping empty line should return empty line") @@ -1504,6 +1526,29 @@ func TestCategorizedHelp(t *testing.T) { Category: "cat1", }, }, + MutuallyExclusiveFlags: []MutuallyExclusiveFlags{ + { + Category: "cat1", + Flags: [][]Flag{ + { + &StringFlag{ + Name: "m1", + Category: "overridden", + }, + }, + }, + }, + { + Flags: [][]Flag{ + { + &StringFlag{ + Name: "m2", + Category: "ignored", + }, + }, + }, + }, + }, } HelpPrinter = func(w io.Writer, templ string, data interface{}) { @@ -1533,11 +1578,13 @@ COMMANDS: GLOBAL OPTIONS: --help, -h show help (default: false) + --m2 value --strd value cat1 --intd value, --altd1 value, --altd2 value (default: 0) + --m1 value `, output.String()) } diff --git a/testdata/godoc-v3.x.txt b/testdata/godoc-v3.x.txt index 92162465a4..e30dbe2bb2 100644 --- a/testdata/godoc-v3.x.txt +++ b/testdata/godoc-v3.x.txt @@ -289,6 +289,9 @@ func (s *BoolWithInverseFlag) Value() bool type CategorizableFlag interface { // Returns the category of the flag GetCategory() string + + // Sets the category of the flag + SetCategory(string) } CategorizableFlag is an interface that allows us to potentially use a flag in a categorized representation. @@ -702,6 +705,8 @@ func (f *FlagBase[T, C, V]) Names() []string func (f *FlagBase[T, C, V]) RunAction(ctx context.Context, cmd *Command) error RunAction executes flag action if set +func (f *FlagBase[T, C, V]) SetCategory(c string) + func (f *FlagBase[T, C, V]) String() string String returns a readable representation of this value (for usage defaults) @@ -821,6 +826,9 @@ type MutuallyExclusiveFlags struct { // whether this group is required Required bool + + // Category to apply to all flags within group + Category string } MutuallyExclusiveFlags defines a mutually exclusive flag group Multiple option paths can be provided out of which only one can be defined on cmdline