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

Closed
Closed
Show file tree
Hide file tree
Changes from all 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
15 changes: 15 additions & 0 deletions app.go
Expand Up @@ -51,6 +51,8 @@ type App struct {
HideVersion bool
// Populate on app startup, only gettable through method Categories()
categories CommandCategories
// Populate on app startup, only gettable through method Categories()
flagCategories FlagCategories
// An action to execute when the bash-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 @@ -143,6 +145,14 @@ func (a *App) Setup() {
if c.HelpName == "" {
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
}

fc := FlagCategories{}
for _, flag := range c.Flags {
fc = fc.AddFlag(flag.GetCategory(), flag)
}

sort.Sort(fc)
Copy link

Choose a reason for hiding this comment

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

Hello,

It is possible to add an option to be able to disable default sorting? I would like to sort categories by appearance :

...
OPTIONS:
  scope
   --scope1 value     object_code
   --scope2 value   object test

  mandatory
   --john value  JJ
   --doe value       DD
Flags: []cli.Flag{
			cli.StringFlag{
				Name:  "scope1",
				Category: "scope",
			},
			cli.StringFlag{
				Name:  "scope2",
				Category: "scope",
			},
			cli.StringFlag{
				Name:  "john",
				Category: "mandatory",
			},
			cli.StringFlag{
				Name:  "doe",
				Category: "mandatory",
			},

Thanks !

c.FlagCategories = fc
newCmds = append(newCmds, c)
}
a.Commands = newCmds
Expand Down Expand Up @@ -437,6 +447,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 @@ -1381,20 +1381,23 @@ func TestApp_VisibleCategories(t *testing.T) {
app.HideHelp = true
app.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 All @@ -1421,21 +1424,24 @@ func TestApp_VisibleCategories(t *testing.T) {
app.HideHelp = true
app.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 @@ -1456,22 +1462,25 @@ func TestApp_VisibleCategories(t *testing.T) {
app.HideHelp = true
app.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 Down Expand Up @@ -1603,6 +1612,14 @@ func (c *customBoolFlag) GetName() string {
return c.Nombre
}

func (c *customBoolFlag) GetHidden() bool {
return false
}

func (c *customBoolFlag) GetCategory() string {
return ""
}

func (c *customBoolFlag) Apply(set *flag.FlagSet) {
set.String(c.Nombre, c.Nombre, "")
}
Expand Down
43 changes: 43 additions & 0 deletions category.go
Expand Up @@ -42,3 +42,46 @@ func (c *CommandCategory) VisibleCommands() []Command {
}
return ret
}

// FlagCategories is a slice of *FlagCategory.
type FlagCategories []*FlagCategory

// FlagCategory is a category containing commands.
type FlagCategory struct {
Name string
Flags Flags
}

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() []Flag {
ret := []Flag{}
for _, flag := range c.Flags {
if !flag.GetHidden() {
ret = append(ret, flag)
}
}
return ret
}
9 changes: 8 additions & 1 deletion command.go
Expand Up @@ -45,6 +45,8 @@ type Command struct {
Subcommands Commands
// List of flags to parse
Flags []Flag
// List of all flag categories
FlagCategories FlagCategories
// Treat all flags as normal arguments if true
SkipFlagParsing bool
// Skip argument reordering which attempts to move flags before arguments,
Expand Down Expand Up @@ -266,7 +268,7 @@ func reorderArgs(args []string) []string {
}

func translateShortOptions(set *flag.FlagSet, flagArgs Args) []string {
allCharsFlags := func (s string) bool {
allCharsFlags := func(s string) bool {
for i := range s {
f := set.Lookup(string(s[i]))
if f == nil {
Expand Down Expand Up @@ -377,6 +379,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
22 changes: 11 additions & 11 deletions command_test.go
Expand Up @@ -59,9 +59,9 @@ func TestCommandFlagParsing(t *testing.T) {

func TestParseAndRunShortOpts(t *testing.T) {
cases := []struct {
testArgs []string
expectedErr error
expectedArgs []string
testArgs []string
expectedErr error
expectedArgs []string
}{
{[]string{"foo", "test", "-a"}, nil, []string{}},
{[]string{"foo", "test", "-c", "arg1", "arg2"}, nil, []string{"arg1", "arg2"}},
Expand All @@ -71,18 +71,18 @@ func TestParseAndRunShortOpts(t *testing.T) {
{[]string{"foo", "test", "-cf"}, nil, []string{}},
{[]string{"foo", "test", "-acf"}, nil, []string{}},
{[]string{"foo", "test", "-invalid"}, errors.New("flag provided but not defined: -invalid"), []string{}},
{[]string{"foo", "test", "-acf", "arg1", "-invalid"}, nil, []string{"arg1" ,"-invalid"}},
{[]string{"foo", "test", "-acf", "arg1", "-invalid"}, nil, []string{"arg1", "-invalid"}},
}

var args []string
cmd := Command{
Name: "test",
Usage: "this is for testing",
Description: "testing",
Action: func(c *Context) error {
args = c.Args()
return nil
},
Name: "test",
Usage: "this is for testing",
Description: "testing",
Action: func(c *Context) error {
args = c.Args()
return nil
},
SkipArgReorder: true,
UseShortOptionHandling: true,
Flags: []Flag{
Expand Down
4 changes: 4 additions & 0 deletions flag.go
Expand Up @@ -73,8 +73,12 @@ type Flag interface {
// Apply Flag settings to the given flag set
Apply(*flag.FlagSet)
GetName() string
GetCategory() string
GetHidden() bool
}

type Flags []Flag

// errorableFlag is an interface that allows us to return errors during apply
// it allows flags defined in this library to return errors in a fashion backwards compatible
// TODO remove in v2 and modify the existing Flag interface to return errors
Expand Down