Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include mutually exclusive flags in help text #1863

Merged
merged 7 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,8 +282,13 @@
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 @@
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()
}

Check warning on line 335 in command.go

View check run for this annotation

Codecov / codecov/patch

command.go#L334-L335

Added lines #L334 - L335 were not covered by tests

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 @@
// 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
Loading