diff --git a/app.go b/app.go index 9add067b05..94834820cb 100644 --- a/app.go +++ b/app.go @@ -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 @@ -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) + c.FlagCategories = fc newCmds = append(newCmds, c) } a.Commands = newCmds @@ -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) diff --git a/app_test.go b/app_test.go index 629681e68b..f779d69a91 100644 --- a/app_test.go +++ b/app_test.go @@ -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{}, }, } @@ -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{}, }, } @@ -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{}, }, } @@ -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, "") } diff --git a/category.go b/category.go index bf3c73c55e..af478a5648 100644 --- a/category.go +++ b/category.go @@ -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 +} diff --git a/command.go b/command.go index 3d44404f10..1e6ee9f3e7 100644 --- a/command.go +++ b/command.go @@ -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, @@ -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 { @@ -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) diff --git a/command_test.go b/command_test.go index 8c2650e550..6ef920279b 100644 --- a/command_test.go +++ b/command_test.go @@ -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"}}, @@ -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{ diff --git a/flag.go b/flag.go index b0cffc006e..8d01ca9069 100644 --- a/flag.go +++ b/flag.go @@ -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 diff --git a/flag_generated.go b/flag_generated.go index 001576c8b3..b3ebfbce2c 100644 --- a/flag_generated.go +++ b/flag_generated.go @@ -15,6 +15,7 @@ type BoolFlag struct { EnvVar string FilePath string Hidden bool + Category string Destination *bool } @@ -29,6 +30,16 @@ func (f BoolFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f BoolFlag) GetHidden() bool { + return f.Hidden +} + +// GetCategory lets us access the flag category +func (f BoolFlag) GetCategory() string { + return f.Category +} + // Bool looks up the value of a local BoolFlag, returns // false if not found func (c *Context) Bool(name string) bool { @@ -63,6 +74,7 @@ type BoolTFlag struct { EnvVar string FilePath string Hidden bool + Category string Destination *bool } @@ -77,6 +89,16 @@ func (f BoolTFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f BoolTFlag) GetHidden() bool { + return f.Hidden +} + +// GetCategory lets us access the flag category +func (f BoolTFlag) GetCategory() string { + return f.Category +} + // BoolT looks up the value of a local BoolTFlag, returns // false if not found func (c *Context) BoolT(name string) bool { @@ -111,6 +133,7 @@ type DurationFlag struct { EnvVar string FilePath string Hidden bool + Category string Value time.Duration Destination *time.Duration } @@ -126,6 +149,16 @@ func (f DurationFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f DurationFlag) GetHidden() bool { + return f.Hidden +} + +// GetCategory lets us access the flag category +func (f DurationFlag) GetCategory() string { + return f.Category +} + // Duration looks up the value of a local DurationFlag, returns // 0 if not found func (c *Context) Duration(name string) time.Duration { @@ -160,6 +193,7 @@ type Float64Flag struct { EnvVar string FilePath string Hidden bool + Category string Value float64 Destination *float64 } @@ -175,6 +209,16 @@ func (f Float64Flag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f Float64Flag) GetHidden() bool { + return f.Hidden +} + +// GetCategory lets us access the flag category +func (f Float64Flag) GetCategory() string { + return f.Category +} + // Float64 looks up the value of a local Float64Flag, returns // 0 if not found func (c *Context) Float64(name string) float64 { @@ -209,6 +253,7 @@ type GenericFlag struct { EnvVar string FilePath string Hidden bool + Category string Value Generic } @@ -223,6 +268,16 @@ func (f GenericFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f GenericFlag) GetHidden() bool { + return f.Hidden +} + +// GetCategory lets us access the flag category +func (f GenericFlag) GetCategory() string { + return f.Category +} + // Generic looks up the value of a local GenericFlag, returns // nil if not found func (c *Context) Generic(name string) interface{} { @@ -257,6 +312,7 @@ type Int64Flag struct { EnvVar string FilePath string Hidden bool + Category string Value int64 Destination *int64 } @@ -272,6 +328,16 @@ func (f Int64Flag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f Int64Flag) GetHidden() bool { + return f.Hidden +} + +// GetCategory lets us access the flag category +func (f Int64Flag) GetCategory() string { + return f.Category +} + // Int64 looks up the value of a local Int64Flag, returns // 0 if not found func (c *Context) Int64(name string) int64 { @@ -306,6 +372,7 @@ type IntFlag struct { EnvVar string FilePath string Hidden bool + Category string Value int Destination *int } @@ -321,6 +388,16 @@ func (f IntFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f IntFlag) GetHidden() bool { + return f.Hidden +} + +// GetCategory lets us access the flag category +func (f IntFlag) GetCategory() string { + return f.Category +} + // Int looks up the value of a local IntFlag, returns // 0 if not found func (c *Context) Int(name string) int { @@ -355,6 +432,7 @@ type IntSliceFlag struct { EnvVar string FilePath string Hidden bool + Category string Value *IntSlice } @@ -369,6 +447,16 @@ func (f IntSliceFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f IntSliceFlag) GetHidden() bool { + return f.Hidden +} + +// GetCategory lets us access the flag category +func (f IntSliceFlag) GetCategory() string { + return f.Category +} + // IntSlice looks up the value of a local IntSliceFlag, returns // nil if not found func (c *Context) IntSlice(name string) []int { @@ -403,6 +491,7 @@ type Int64SliceFlag struct { EnvVar string FilePath string Hidden bool + Category string Value *Int64Slice } @@ -417,6 +506,16 @@ func (f Int64SliceFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f Int64SliceFlag) GetHidden() bool { + return f.Hidden +} + +// GetCategory lets us access the flag category +func (f Int64SliceFlag) GetCategory() string { + return f.Category +} + // Int64Slice looks up the value of a local Int64SliceFlag, returns // nil if not found func (c *Context) Int64Slice(name string) []int64 { @@ -451,6 +550,7 @@ type StringFlag struct { EnvVar string FilePath string Hidden bool + Category string Value string Destination *string } @@ -466,6 +566,16 @@ func (f StringFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f StringFlag) GetHidden() bool { + return f.Hidden +} + +// GetCategory lets us access the flag category +func (f StringFlag) GetCategory() string { + return f.Category +} + // String looks up the value of a local StringFlag, returns // "" if not found func (c *Context) String(name string) string { @@ -500,6 +610,7 @@ type StringSliceFlag struct { EnvVar string FilePath string Hidden bool + Category string Value *StringSlice } @@ -514,6 +625,16 @@ func (f StringSliceFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f StringSliceFlag) GetHidden() bool { + return f.Hidden +} + +// GetCategory lets us access the flag category +func (f StringSliceFlag) GetCategory() string { + return f.Category +} + // StringSlice looks up the value of a local StringSliceFlag, returns // nil if not found func (c *Context) StringSlice(name string) []string { @@ -548,6 +669,7 @@ type Uint64Flag struct { EnvVar string FilePath string Hidden bool + Category string Value uint64 Destination *uint64 } @@ -563,6 +685,16 @@ func (f Uint64Flag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f Uint64Flag) GetHidden() bool { + return f.Hidden +} + +// GetCategory lets us access the flag category +func (f Uint64Flag) GetCategory() string { + return f.Category +} + // Uint64 looks up the value of a local Uint64Flag, returns // 0 if not found func (c *Context) Uint64(name string) uint64 { @@ -597,6 +729,7 @@ type UintFlag struct { EnvVar string FilePath string Hidden bool + Category string Value uint Destination *uint } @@ -612,6 +745,16 @@ func (f UintFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f UintFlag) GetHidden() bool { + return f.Hidden +} + +// GetCategory lets us access the flag category +func (f UintFlag) GetCategory() string { + return f.Category +} + // Uint looks up the value of a local UintFlag, returns // 0 if not found func (c *Context) Uint(name string) uint { diff --git a/flag_test.go b/flag_test.go index da9fd731a7..ac3e70ce0a 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1052,7 +1052,7 @@ func TestParseBoolShortOptionHandle(t *testing.T) { a := App{ Commands: []Command{ { - Name: "foobar", + Name: "foobar", UseShortOptionHandling: true, Action: func(ctx *Context) error { if ctx.Bool("serve") != true { diff --git a/generate-flag-types b/generate-flag-types index 1358857312..9cea29eb2a 100755 --- a/generate-flag-types +++ b/generate-flag-types @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python2 """ The flag types that ship with the cli library have many things in common, and so we can take advantage of the `go generate` command to create much of the @@ -144,6 +144,7 @@ def _write_cli_flag_types(outfile, types): EnvVar string FilePath string Hidden bool + Category string """.format(**typedef)) if typedef['value']: @@ -170,6 +171,16 @@ def _write_cli_flag_types(outfile, types): return f.Name }} + // GetHidden lets us know if the flag is hidden or not + func (f {name}Flag) GetHidden() bool {{ + return f.Hidden + }} + + // GetCategory lets us access the flag category + func (f {name}Flag) GetCategory() string {{ + return f.Category + }} + // {name} looks up the value of a local {name}Flag, returns // {context_default} if not found func (c *Context) {name}(name string) {context_type} {{ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000000..8899222758 --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module github.com/urfave/cli + +require ( + github.com/BurntSushi/toml v0.3.1 + gopkg.in/urfave/cli.v1 v1.20.0 + gopkg.in/yaml.v2 v2.2.2 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000000..46f9ae29da --- /dev/null +++ b/go.sum @@ -0,0 +1,7 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= +gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/help.go b/help.go index 65874fa2fc..b80bac3ce6 100644 --- a/help.go +++ b/help.go @@ -54,11 +54,11 @@ CATEGORY: {{.Category}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description}}{{end}}{{if .VisibleFlags}} - -OPTIONS: + {{.Description}}{{end}}{{if .VisibleFlagCategories}} +OPTIONS:{{range .VisibleFlagCategories}} + {{.Name}} {{range .VisibleFlags}}{{.}} - {{end}}{{end}} + {{end}}{{end}}{{end}} ` // SubcommandHelpTemplate is the text template for the subcommand help topic. @@ -250,7 +250,10 @@ func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc m // If the writer is closed, t.Execute will fail, and there's nothing // we can do to recover. if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" { - fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err) + // Generic error message + fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR DEBUG: %#v\n", err) + // Helpful error message + fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR DEBUG: %#v\n", err.Error()) } return } diff --git a/runtests b/runtests index ee22bdeed5..92d2081212 100755 --- a/runtests +++ b/runtests @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python2 from __future__ import print_function import argparse