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

Add flag category support (#796) #1368

Merged
merged 17 commits into from May 22, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 25 additions & 0 deletions app.go
Expand Up @@ -52,6 +52,8 @@ type App struct {
HideVersion bool
// categories contains the categorized commands and is populated on app startup
categories CommandCategories
// flagCategories contains the categorized flags and is populated on app startup
flagCategories FlagCategories
// An action to execute when the shell completion flag is set
BashComplete BashCompleteFunc
// An action to execute before any subcommands are run, but after the context is ready
Expand Down Expand Up @@ -181,6 +183,16 @@ func (a *App) Setup() {
if c.HelpName == "" {
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
}

fc := FlagCategories{}
for _, fl := range c.Flags {
if cf, ok := fl.(CategorizableFlag); ok {
fc = fc.AddFlag(cf.GetCategory(), cf)
}
}

sort.Sort(fc)
c.FlagCategories = fc
newCommands = append(newCommands, c)
}
a.Commands = newCommands
Expand All @@ -205,6 +217,14 @@ func (a *App) Setup() {
}
sort.Sort(a.categories.(*commandCategories))

a.flagCategories = FlagCategories{}
for _, fl := range a.Flags {
if cf, ok := fl.(CategorizableFlag); ok {
a.flagCategories.AddFlag(cf.GetCategory(), cf)
}
}
sort.Sort(a.flagCategories)

if a.Metadata == nil {
a.Metadata = make(map[string]interface{})
}
Expand Down Expand Up @@ -481,6 +501,11 @@ func (a *App) VisibleCommands() []*Command {
return ret
}

// Categories returns a slice containing all the categories with the commands they contain
func (a *App) VisibleFlagCategories() FlagCategories {
return a.flagCategories
}

// VisibleFlags returns a slice of the Flags with Hidden=false
func (a *App) VisibleFlags() []Flag {
return visibleFlags(a.Flags)
Expand Down
83 changes: 50 additions & 33 deletions app_test.go
Expand Up @@ -1789,20 +1789,23 @@ func TestApp_VisibleCategories(t *testing.T) {
HideHelp: true,
Commands: []*Command{
{
Name: "command1",
Category: "1",
HelpName: "foo command1",
Hidden: true,
Name: "command1",
Category: "1",
HelpName: "foo command1",
Hidden: true,
FlagCategories: FlagCategories{},
},
{
Name: "command2",
Category: "2",
HelpName: "foo command2",
Name: "command2",
Category: "2",
HelpName: "foo command2",
FlagCategories: FlagCategories{},
},
{
Name: "command3",
Category: "3",
HelpName: "foo command3",
Name: "command3",
Category: "3",
HelpName: "foo command3",
FlagCategories: FlagCategories{},
},
},
}
Expand Down Expand Up @@ -1830,21 +1833,24 @@ func TestApp_VisibleCategories(t *testing.T) {
HideHelp: true,
Commands: []*Command{
{
Name: "command1",
Category: "1",
HelpName: "foo command1",
Hidden: true,
Name: "command1",
Category: "1",
HelpName: "foo command1",
Hidden: true,
FlagCategories: FlagCategories{},
},
{
Name: "command2",
Category: "2",
HelpName: "foo command2",
Hidden: true,
Name: "command2",
Category: "2",
HelpName: "foo command2",
Hidden: true,
FlagCategories: FlagCategories{},
},
{
Name: "command3",
Category: "3",
HelpName: "foo command3",
Name: "command3",
Category: "3",
HelpName: "foo command3",
FlagCategories: FlagCategories{},
},
},
}
Expand All @@ -1866,22 +1872,25 @@ func TestApp_VisibleCategories(t *testing.T) {
HideHelp: true,
Commands: []*Command{
{
Name: "command1",
Category: "1",
HelpName: "foo command1",
Hidden: true,
Name: "command1",
Category: "1",
HelpName: "foo command1",
Hidden: true,
FlagCategories: FlagCategories{},
},
{
Name: "command2",
Category: "2",
HelpName: "foo command2",
Hidden: true,
Name: "command2",
Category: "2",
HelpName: "foo command2",
Hidden: true,
FlagCategories: FlagCategories{},
},
{
Name: "command3",
Category: "3",
HelpName: "foo command3",
Hidden: true,
Name: "command3",
Category: "3",
HelpName: "foo command3",
Hidden: true,
FlagCategories: FlagCategories{},
},
},
}
Expand All @@ -1890,6 +1899,14 @@ func TestApp_VisibleCategories(t *testing.T) {
expect(t, []CommandCategory{}, app.VisibleCategories())
}

func TestApp_VisibleFlagCategories(t *testing.T) {
app := &App{}
vfc := app.VisibleFlagCategories()
if len(vfc) != 0 {
t.Errorf("unexpected visible flag categories %+v", vfc)
}
}

func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
app := &App{
Action: func(c *Context) error { return nil },
Expand Down
45 changes: 45 additions & 0 deletions category.go
Expand Up @@ -77,3 +77,48 @@ func (c *commandCategory) VisibleCommands() []*Command {
}
return ret
}

// FlagCategories is a slice of *FlagCategory.
type FlagCategories []*FlagCategory
meatballhat marked this conversation as resolved.
Show resolved Hide resolved

// FlagCategory is a category containing commands.
meatballhat marked this conversation as resolved.
Show resolved Hide resolved
type FlagCategory struct {
Name string
Flags []Flag
}

func (f FlagCategories) Less(i, j int) bool {
return lexicographicLess(f[i].Name, f[j].Name)
}

func (f FlagCategories) Len() int {
return len(f)
}

func (f FlagCategories) Swap(i, j int) {
f[i], f[j] = f[j], f[i]
}

// AddFlags adds a command to a category.
func (f FlagCategories) AddFlag(category string, flag Flag) FlagCategories {
for _, flagCategory := range f {
if flagCategory.Name == category {
flagCategory.Flags = append(flagCategory.Flags, flag)
return f
}
}
return append(f, &FlagCategory{Name: category, Flags: []Flag{flag}})
}

// VisibleFlags returns a slice of the Flags with Hidden=false
func (c *FlagCategory) VisibleFlags() []VisibleFlag {
ret := []VisibleFlag{}
for _, fl := range c.Flags {
if vf, ok := fl.(VisibleFlag); ok {
if vf.IsVisible() {
ret = append(ret, vf)
}
}
}
return ret
}
7 changes: 7 additions & 0 deletions command.go
Expand Up @@ -39,6 +39,8 @@ type Command struct {
Subcommands []*Command
// List of flags to parse
Flags []Flag
// List of all flag categories
FlagCategories FlagCategories
// Treat all flags as normal arguments if true
SkipFlagParsing bool
// Boolean to hide built-in help command and help flag
Expand Down Expand Up @@ -280,6 +282,11 @@ func (c *Command) startApp(ctx *Context) error {
return app.RunAsSubcommand(ctx)
}

// Categories returns a slice containing all the categories with the commands they contain
func (c Command) VisibleFlagCategories() FlagCategories {
return c.FlagCategories
}

// VisibleFlags returns a slice of the Flags with Hidden=false
func (c *Command) VisibleFlags() []Flag {
return visibleFlags(c.Flags)
Expand Down
8 changes: 8 additions & 0 deletions flag.go
Expand Up @@ -133,6 +133,14 @@ type VisibleFlag interface {
IsVisible() bool
}

// CategorizableFlag is an interface that allows us to potentially
// use a flag in a categorized representation.
type CategorizableFlag interface {
VisibleFlag

GetCategory() string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to just Category(). golang doesnt recommend using GetXXX

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the conflict with the field name Category? 🤔 I agree that GetXXX is generally bad style, but this is a language-level wart we're working around.

}

func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
set := flag.NewFlagSet(name, flag.ContinueOnError)

Expand Down
6 changes: 6 additions & 0 deletions flag_bool.go
Expand Up @@ -19,6 +19,7 @@ type BoolFlag struct {
DefaultText string
Destination *bool
HasBeenSet bool
Category string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@meatballhat How would this tie-in with your generator code ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If all the flag types defined in this repo have Category fields, then it'd be part of the struct template. Or did you mean the potential merge conflict? (or something else?)

}

// IsSet returns whether or not the flag has been set through env or file
Expand Down Expand Up @@ -52,6 +53,11 @@ func (f *BoolFlag) GetUsage() string {
return f.Usage
}

// GetCategory returns the category for the flag
func (f *BoolFlag) GetCategory() string {
return f.Category
}

// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *BoolFlag) GetValue() string {
Expand Down
6 changes: 6 additions & 0 deletions flag_duration.go
Expand Up @@ -19,6 +19,7 @@ type DurationFlag struct {
DefaultText string
Destination *time.Duration
HasBeenSet bool
Category string
}

// IsSet returns whether or not the flag has been set through env or file
Expand Down Expand Up @@ -52,6 +53,11 @@ func (f *DurationFlag) GetUsage() string {
return f.Usage
}

// GetCategory returns the category for the flag
func (f *DurationFlag) GetCategory() string {
return f.Category
}

// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *DurationFlag) GetValue() string {
Expand Down
6 changes: 6 additions & 0 deletions flag_float64.go
Expand Up @@ -19,6 +19,7 @@ type Float64Flag struct {
DefaultText string
Destination *float64
HasBeenSet bool
Category string
}

// IsSet returns whether or not the flag has been set through env or file
Expand Down Expand Up @@ -52,6 +53,11 @@ func (f *Float64Flag) GetUsage() string {
return f.Usage
}

// GetCategory returns the category for the flag
func (f *Float64Flag) GetCategory() string {
return f.Category
}

// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *Float64Flag) GetValue() string {
Expand Down
6 changes: 6 additions & 0 deletions flag_float64_slice.go
Expand Up @@ -85,6 +85,7 @@ type Float64SliceFlag struct {
Value *Float64Slice
DefaultText string
HasBeenSet bool
Category string
}

// IsSet returns whether or not the flag has been set through env or file
Expand Down Expand Up @@ -118,6 +119,11 @@ func (f *Float64SliceFlag) GetUsage() string {
return f.Usage
}

// GetCategory returns the category for the flag
func (f *Float64SliceFlag) GetCategory() string {
return f.Category
}

// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *Float64SliceFlag) GetValue() string {
Expand Down
6 changes: 6 additions & 0 deletions flag_generic.go
Expand Up @@ -24,6 +24,7 @@ type GenericFlag struct {
Value Generic
DefaultText string
HasBeenSet bool
Category string
}

// IsSet returns whether or not the flag has been set through env or file
Expand Down Expand Up @@ -57,6 +58,11 @@ func (f *GenericFlag) GetUsage() string {
return f.Usage
}

// GetCategory returns the category for the flag
func (f *GenericFlag) GetCategory() string {
return f.Category
}

// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *GenericFlag) GetValue() string {
Expand Down
6 changes: 6 additions & 0 deletions flag_int.go
Expand Up @@ -19,6 +19,7 @@ type IntFlag struct {
DefaultText string
Destination *int
HasBeenSet bool
Category string
}

// IsSet returns whether or not the flag has been set through env or file
Expand Down Expand Up @@ -52,6 +53,11 @@ func (f *IntFlag) GetUsage() string {
return f.Usage
}

// GetCategory returns the category for the flag
func (f *IntFlag) GetCategory() string {
return f.Category
}

// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *IntFlag) GetValue() string {
Expand Down