Skip to content

Commit

Permalink
Merge pull request #1863 from joshfrench/mut-exclusive-help
Browse files Browse the repository at this point in the history
Include mutually exclusive flags in help text
  • Loading branch information
dearchap authored Feb 12, 2024
2 parents e1d1334 + 83b8287 commit 204d34f
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 9 deletions.
18 changes: 14 additions & 4 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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) {
Expand Down
24 changes: 19 additions & 5 deletions command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
3 changes: 3 additions & 0 deletions flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions flag_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions flag_mutex.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
}
}
}
8 changes: 8 additions & 0 deletions godoc-current.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down
47 changes: 47 additions & 0 deletions help_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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{}) {
Expand Down Expand Up @@ -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())
}
8 changes: 8 additions & 0 deletions testdata/godoc-v3.x.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 204d34f

Please sign in to comment.