Rework flag categories a bit

with internal maps instead of slices and slightly less public API
surface area
This commit is contained in:
Dan Buch 2022-04-30 08:59:50 -04:00
parent ddac788d85
commit 156eaafb22
Signed by: meatballhat
GPG Key ID: A12F782281063434
5 changed files with 123 additions and 93 deletions

19
app.go
View File

@ -184,15 +184,14 @@ func (a *App) Setup() {
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
} }
fc := FlagCategories{} fc := newFlagCategories()
for _, fl := range c.Flags { for _, fl := range c.Flags {
if cf, ok := fl.(CategorizableFlag); ok { if cf, ok := fl.(CategorizableFlag); ok {
fc = fc.AddFlag(cf.GetCategory(), cf) fc.AddFlag(cf.GetCategory(), cf)
} }
} }
sort.Sort(fc) c.flagCategories = fc
c.FlagCategories = fc
newCommands = append(newCommands, c) newCommands = append(newCommands, c)
} }
a.Commands = newCommands a.Commands = newCommands
@ -217,13 +216,12 @@ func (a *App) Setup() {
} }
sort.Sort(a.categories.(*commandCategories)) sort.Sort(a.categories.(*commandCategories))
a.flagCategories = FlagCategories{} a.flagCategories = newFlagCategories()
for _, fl := range a.Flags { for _, fl := range a.Flags {
if cf, ok := fl.(CategorizableFlag); ok { if cf, ok := fl.(CategorizableFlag); ok {
a.flagCategories.AddFlag(cf.GetCategory(), cf) a.flagCategories.AddFlag(cf.GetCategory(), cf)
} }
} }
sort.Sort(a.flagCategories)
if a.Metadata == nil { if a.Metadata == nil {
a.Metadata = make(map[string]interface{}) a.Metadata = make(map[string]interface{})
@ -501,9 +499,12 @@ func (a *App) VisibleCommands() []*Command {
return ret return ret
} }
// Categories returns a slice containing all the categories with the commands they contain // VisibleFlagCategories returns a slice containing all the categories with the flags they contain
func (a *App) VisibleFlagCategories() FlagCategories { func (a *App) VisibleFlagCategories() []VisibleFlagCategory {
return a.flagCategories if a.flagCategories == nil {
a.flagCategories = newFlagCategories()
}
return a.flagCategories.VisibleCategories()
} }
// VisibleFlags returns a slice of the Flags with Hidden=false // VisibleFlags returns a slice of the Flags with Hidden=false

View File

@ -142,8 +142,8 @@ func ExampleApp_Run_appHelp() {
// help, h Shows a list of commands or help for one command // help, h Shows a list of commands or help for one command
// //
// GLOBAL OPTIONS: // GLOBAL OPTIONS:
// --name value a name to say (default: "bob")
// --help, -h show help (default: false) // --help, -h show help (default: false)
// --name value a name to say (default: "bob")
// --version, -v print the version (default: false) // --version, -v print the version (default: false)
} }
@ -1823,23 +1823,20 @@ func TestApp_VisibleCategories(t *testing.T) {
HideHelp: true, HideHelp: true,
Commands: []*Command{ Commands: []*Command{
{ {
Name: "command1", Name: "command1",
Category: "1", Category: "1",
HelpName: "foo command1", HelpName: "foo command1",
Hidden: true, Hidden: true,
FlagCategories: FlagCategories{},
}, },
{ {
Name: "command2", Name: "command2",
Category: "2", Category: "2",
HelpName: "foo command2", HelpName: "foo command2",
FlagCategories: FlagCategories{},
}, },
{ {
Name: "command3", Name: "command3",
Category: "3", Category: "3",
HelpName: "foo command3", HelpName: "foo command3",
FlagCategories: FlagCategories{},
}, },
}, },
} }
@ -1867,24 +1864,21 @@ func TestApp_VisibleCategories(t *testing.T) {
HideHelp: true, HideHelp: true,
Commands: []*Command{ Commands: []*Command{
{ {
Name: "command1", Name: "command1",
Category: "1", Category: "1",
HelpName: "foo command1", HelpName: "foo command1",
Hidden: true, Hidden: true,
FlagCategories: FlagCategories{},
}, },
{ {
Name: "command2", Name: "command2",
Category: "2", Category: "2",
HelpName: "foo command2", HelpName: "foo command2",
Hidden: true, Hidden: true,
FlagCategories: FlagCategories{},
}, },
{ {
Name: "command3", Name: "command3",
Category: "3", Category: "3",
HelpName: "foo command3", HelpName: "foo command3",
FlagCategories: FlagCategories{},
}, },
}, },
} }
@ -1906,25 +1900,22 @@ func TestApp_VisibleCategories(t *testing.T) {
HideHelp: true, HideHelp: true,
Commands: []*Command{ Commands: []*Command{
{ {
Name: "command1", Name: "command1",
Category: "1", Category: "1",
HelpName: "foo command1", HelpName: "foo command1",
Hidden: true, Hidden: true,
FlagCategories: FlagCategories{},
}, },
{ {
Name: "command2", Name: "command2",
Category: "2", Category: "2",
HelpName: "foo command2", HelpName: "foo command2",
Hidden: true, Hidden: true,
FlagCategories: FlagCategories{},
}, },
{ {
Name: "command3", Name: "command3",
Category: "3", Category: "3",
HelpName: "foo command3", HelpName: "foo command3",
Hidden: true, Hidden: true,
FlagCategories: FlagCategories{},
}, },
}, },
} }

View File

@ -1,10 +1,12 @@
package cli package cli
import "sort"
// CommandCategories interface allows for category manipulation // CommandCategories interface allows for category manipulation
type CommandCategories interface { type CommandCategories interface {
// AddCommand adds a command to a category, creating a new category if necessary. // AddCommand adds a command to a category, creating a new category if necessary.
AddCommand(category string, command *Command) AddCommand(category string, command *Command)
// categories returns a copy of the category slice // Categories returns a slice of categories sorted by name
Categories() []CommandCategory Categories() []CommandCategory
} }
@ -78,47 +80,81 @@ func (c *commandCategory) VisibleCommands() []*Command {
return ret return ret
} }
// FlagCategories is a slice of *FlagCategory. // FlagCategories interface allows for category manipulation
type FlagCategories []*FlagCategory type FlagCategories interface {
// AddFlags adds a flag to a category, creating a new category if necessary.
// FlagCategory is a category containing flags. AddFlag(category string, fl Flag)
type FlagCategory struct { // VisibleCategories returns a slice of visible flag categories sorted by name
Name string VisibleCategories() []VisibleFlagCategory
Flags []Flag
} }
func (f FlagCategories) Less(i, j int) bool { type defaultFlagCategories struct {
return lexicographicLess(f[i].Name, f[j].Name) m map[string]*defaultVisibleFlagCategory
} }
func (f FlagCategories) Len() int { func newFlagCategories() FlagCategories {
return len(f) return &defaultFlagCategories{
} m: map[string]*defaultVisibleFlagCategory{},
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 (f *defaultFlagCategories) AddFlag(category string, fl Flag) {
func (c *FlagCategory) VisibleFlags() []VisibleFlag { if _, ok := f.m[category]; !ok {
ret := []VisibleFlag{} f.m[category] = &defaultVisibleFlagCategory{name: category, m: map[string]Flag{}}
for _, fl := range c.Flags { }
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, ok := fl.(VisibleFlag); ok {
if vf.IsVisible() { if vf.IsVisible() {
ret = append(ret, vf) 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 return ret
} }

View File

@ -38,9 +38,8 @@ type Command struct {
// List of child commands // List of child commands
Subcommands []*Command Subcommands []*Command
// List of flags to parse // List of flags to parse
Flags []Flag Flags []Flag
// List of all flag categories flagCategories FlagCategories
FlagCategories FlagCategories
// Treat all flags as normal arguments if true // Treat all flags as normal arguments if true
SkipFlagParsing bool SkipFlagParsing bool
// Boolean to hide built-in help command and help flag // Boolean to hide built-in help command and help flag
@ -282,9 +281,12 @@ func (c *Command) startApp(ctx *Context) error {
return app.RunAsSubcommand(ctx) return app.RunAsSubcommand(ctx)
} }
// Categories returns a slice containing all the categories with the commands they contain // VisibleFlagCategories returns a slice containing all the visible flag categories with the flags they contain
func (c Command) VisibleFlagCategories() FlagCategories { func (c *Command) VisibleFlagCategories() []VisibleFlagCategory {
return c.FlagCategories if c.flagCategories == nil {
c.flagCategories = newFlagCategories()
}
return c.flagCategories.VisibleCategories()
} }
// VisibleFlags returns a slice of the Flags with Hidden=false // VisibleFlags returns a slice of the Flags with Hidden=false

View File

@ -25,8 +25,8 @@ COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}}
GLOBAL OPTIONS:{{range .VisibleFlagCategories}} GLOBAL OPTIONS:{{range .VisibleFlagCategories}}
{{.Name}} {{if .Name}}{{.Name}}
{{range .VisibleFlags}}{{.}} {{end}}{{range .Flags}}{{.}}
{{end}}{{end}}{{else}}{{if .VisibleFlags}} {{end}}{{end}}{{else}}{{if .VisibleFlags}}
GLOBAL OPTIONS: GLOBAL OPTIONS:
@ -53,8 +53,8 @@ DESCRIPTION:
{{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlagCategories}} {{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlagCategories}}
OPTIONS:{{range .VisibleFlagCategories}} OPTIONS:{{range .VisibleFlagCategories}}
{{.Name}} {{if .Name}}{{.Name}}
{{range .VisibleFlags}}{{.}} {{end}}{{range .Flags}}{{.}}
{{end}}{{end}}{{else}}{{if .VisibleFlags}} {{end}}{{end}}{{else}}{{if .VisibleFlags}}
OPTIONS: OPTIONS: