Skip to content

Commit

Permalink
Merge pull request #1368 from urfave/michaeljs1990-add-flag-category-…
Browse files Browse the repository at this point in the history
…support

Add flag category support (#796)
  • Loading branch information
meatballhat authored May 22, 2022
2 parents 939ab7f + 4bca72c commit 9e65b4d
Show file tree
Hide file tree
Showing 25 changed files with 466 additions and 23 deletions.
19 changes: 19 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
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 @@ -183,6 +185,8 @@ func (a *App) Setup() {
if c.HelpName == "" {
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
}

c.flagCategories = newFlagCategoriesFromFlags(c.Flags)
newCommands = append(newCommands, c)
}
a.Commands = newCommands
Expand All @@ -207,6 +211,13 @@ func (a *App) Setup() {
}
sort.Sort(a.categories.(*commandCategories))

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

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

// VisibleFlagCategories returns a slice containing all the categories with the flags they contain
func (a *App) VisibleFlagCategories() []VisibleFlagCategory {
if a.flagCategories == nil {
return []VisibleFlagCategory{}
}
return a.flagCategories.VisibleCategories()
}

// VisibleFlags returns a slice of the Flags with Hidden=false
func (a *App) VisibleFlags() []Flag {
return visibleFlags(a.Flags)
Expand Down
10 changes: 9 additions & 1 deletion app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ func ExampleApp_Run_appHelp() {
// help, h Shows a list of commands or help for one command
//
// GLOBAL OPTIONS:
// --name value a name to say (default: "bob")
// --help, -h show help (default: false)
// --name value a name to say (default: "bob")
// --version, -v print the version (default: false)
}

Expand Down Expand Up @@ -1927,6 +1927,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
94 changes: 93 additions & 1 deletion category.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package cli

import "sort"

// CommandCategories interface allows for category manipulation
type CommandCategories interface {
// AddCommand adds a command to a category, creating a new category if necessary.
AddCommand(category string, command *Command)
// categories returns a copy of the category slice
// Categories returns a slice of categories sorted by name
Categories() []CommandCategory
}

Expand Down Expand Up @@ -77,3 +79,93 @@ func (c *commandCategory) VisibleCommands() []*Command {
}
return ret
}

// FlagCategories interface allows for category manipulation
type FlagCategories interface {
// AddFlags adds a flag to a category, creating a new category if necessary.
AddFlag(category string, fl Flag)
// VisibleCategories returns a slice of visible flag categories sorted by name
VisibleCategories() []VisibleFlagCategory
}

type defaultFlagCategories struct {
m map[string]*defaultVisibleFlagCategory
}

func newFlagCategories() FlagCategories {
return &defaultFlagCategories{
m: map[string]*defaultVisibleFlagCategory{},
}
}

func newFlagCategoriesFromFlags(fs []Flag) FlagCategories {
fc := newFlagCategories()
for _, fl := range fs {
if cf, ok := fl.(CategorizableFlag); ok {
fc.AddFlag(cf.GetCategory(), cf)
}
}

return fc
}

func (f *defaultFlagCategories) AddFlag(category string, fl Flag) {
if _, ok := f.m[category]; !ok {
f.m[category] = &defaultVisibleFlagCategory{name: category, m: map[string]Flag{}}
}

f.m[category].m[fl.String()] = fl
}

func (f *defaultFlagCategories) VisibleCategories() []VisibleFlagCategory {
catNames := []string{}
for name := range f.m {
catNames = append(catNames, name)
}

sort.Strings(catNames)

ret := make([]VisibleFlagCategory, len(catNames))
for i, name := range catNames {
ret[i] = f.m[name]
}

return ret
}

// VisibleFlagCategory is a category containing flags.
type VisibleFlagCategory interface {
// Name returns the category name string
Name() string
// Flags returns a slice of VisibleFlag sorted by name
Flags() []VisibleFlag
}

type defaultVisibleFlagCategory struct {
name string
m map[string]Flag
}

func (fc *defaultVisibleFlagCategory) Name() string {
return fc.name
}

func (fc *defaultVisibleFlagCategory) Flags() []VisibleFlag {
vfNames := []string{}
for flName, fl := range fc.m {
if vf, ok := fl.(VisibleFlag); ok {
if vf.IsVisible() {
vfNames = append(vfNames, flName)
}
}
}

sort.Strings(vfNames)

ret := make([]VisibleFlag, len(vfNames))
for i, flName := range vfNames {
ret[i] = fc.m[flName].(VisibleFlag)
}

return ret
}
11 changes: 10 additions & 1 deletion command.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ type Command struct {
// List of child commands
Subcommands []*Command
// List of flags to parse
Flags []Flag
Flags []Flag
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 @@ -286,6 +287,14 @@ func (c *Command) startApp(ctx *Context) error {
return app.RunAsSubcommand(ctx)
}

// VisibleFlagCategories returns a slice containing all the visible flag categories with the flags they contain
func (c *Command) VisibleFlagCategories() []VisibleFlagCategory {
if c.flagCategories == nil {
return []VisibleFlagCategory{}
}
return c.flagCategories.VisibleCategories()
}

// 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
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,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
}

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

Expand Down
5 changes: 5 additions & 0 deletions flag_bool.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,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
5 changes: 5 additions & 0 deletions flag_duration.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,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
5 changes: 5 additions & 0 deletions flag_float64.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,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
5 changes: 5 additions & 0 deletions flag_float64_slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,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
5 changes: 5 additions & 0 deletions flag_generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,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
5 changes: 5 additions & 0 deletions flag_int.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,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
5 changes: 5 additions & 0 deletions flag_int64.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ func (f *Int64Flag) GetUsage() string {
return f.Usage
}

// GetCategory returns the category for the flag
func (f *Int64Flag) 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 *Int64Flag) GetValue() string {
Expand Down
7 changes: 6 additions & 1 deletion flag_int64_slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,15 @@ func (f *Int64SliceFlag) TakesValue() bool {
}

// GetUsage returns the usage string for the flag
func (f Int64SliceFlag) GetUsage() string {
func (f *Int64SliceFlag) GetUsage() string {
return f.Usage
}

// GetCategory returns the category for the flag
func (f *Int64SliceFlag) 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 *Int64SliceFlag) GetValue() string {
Expand Down
7 changes: 6 additions & 1 deletion flag_int_slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,15 @@ func (f *IntSliceFlag) TakesValue() bool {
}

// GetUsage returns the usage string for the flag
func (f IntSliceFlag) GetUsage() string {
func (f *IntSliceFlag) GetUsage() string {
return f.Usage
}

// GetCategory returns the category for the flag
func (f *IntSliceFlag) 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 *IntSliceFlag) GetValue() string {
Expand Down
5 changes: 5 additions & 0 deletions flag_path.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ func (f *PathFlag) GetUsage() string {
return f.Usage
}

// GetCategory returns the category for the flag
func (f *PathFlag) 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 *PathFlag) GetValue() string {
Expand Down
5 changes: 5 additions & 0 deletions flag_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ func (f *StringFlag) GetUsage() string {
return f.Usage
}

// GetCategory returns the category for the flag
func (f *StringFlag) 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 *StringFlag) GetValue() string {
Expand Down
5 changes: 5 additions & 0 deletions flag_string_slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ func (f *StringSliceFlag) GetUsage() string {
return f.Usage
}

// GetCategory returns the category for the flag
func (f *StringSliceFlag) 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 *StringSliceFlag) GetValue() string {
Expand Down
5 changes: 5 additions & 0 deletions flag_timestamp.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ func (f *TimestampFlag) GetUsage() string {
return f.Usage
}

// GetCategory returns the category for the flag
func (f *TimestampFlag) 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 *TimestampFlag) GetValue() string {
Expand Down
5 changes: 5 additions & 0 deletions flag_uint.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ func (f *UintFlag) GetUsage() string {
return f.Usage
}

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

// Apply populates the flag given the flag set and environment
func (f *UintFlag) Apply(set *flag.FlagSet) error {
if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
Expand Down
Loading

0 comments on commit 9e65b4d

Please sign in to comment.