diff --git a/.travis.yml b/.travis.yml index cf8d098..8bb0e9c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: go sudo: false dist: trusty osx_image: xcode8.3 -go: 1.8.x +go: 1.11.x os: - linux diff --git a/MAINTAINERS.md b/MAINTAINERS.md index f6bdd99..5b7a6ea 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -1 +1,4 @@ - @meatballhat +- @lynncyrin +- @AudriusButkevicius +- @asahasrabuddhe diff --git a/README.md b/README.md index e2df4ec..756aaaa 100644 --- a/README.md +++ b/README.md @@ -670,7 +670,7 @@ func main() { ``` Note that default values set from file (e.g. `FilePath`) take precedence over -default values set from the enviornment (e.g. `EnvVar`). +default values set from the environment (e.g. `EnvVar`). #### Values from alternate input sources (YAML, TOML, and others) @@ -875,7 +875,7 @@ Will include: ``` COMMANDS: - noop + noop Template actions: add diff --git a/app.go b/app.go index 9add067..9ed492f 100644 --- a/app.go +++ b/app.go @@ -228,6 +228,12 @@ func (a *App) Run(arguments []string) (err error) { return nil } + cerr := checkRequiredFlags(a.Flags, context) + if cerr != nil { + ShowAppHelp(context) + return cerr + } + if a.After != nil { defer func() { if afterErr := a.After(context); afterErr != nil { @@ -352,6 +358,12 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } } + cerr := checkRequiredFlags(a.Flags, context) + if cerr != nil { + ShowSubcommandHelp(context) + return cerr + } + if a.After != nil { defer func() { afterErr := a.After(context) @@ -495,11 +507,12 @@ func (a Author) String() string { // it's an ActionFunc or a func with the legacy signature for Action, the func // is run! func HandleAction(action interface{}, context *Context) (err error) { - if a, ok := action.(ActionFunc); ok { + switch a := action.(type) { + case ActionFunc: return a(context) - } else if a, ok := action.(func(*Context) error); ok { + case func(*Context) error: return a(context) - } else if a, ok := action.(func(*Context)); ok { // deprecated function signature + case func(*Context): // deprecated function signature a(context) return nil } diff --git a/app_test.go b/app_test.go index 629681e..69d1418 100644 --- a/app_test.go +++ b/app_test.go @@ -136,8 +136,8 @@ func ExampleApp_Run_appHelp() { // Oliver Allen // // COMMANDS: - // describeit, d use it to see a description - // help, h Shows a list of commands or help for one command + // describeit, d use it to see a description + // help, h Shows a list of commands or help for one command // // GLOBAL OPTIONS: // --name value a name to say (default: "bob") @@ -190,7 +190,7 @@ func ExampleApp_Run_noAction() { // [global options] command [command options] [arguments...] // // COMMANDS: - // 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: // --help, -h show help @@ -877,6 +877,145 @@ func TestAppNoHelpFlag(t *testing.T) { } } +func TestRequiredFlagAppRunBehavior(t *testing.T) { + tdata := []struct { + testCase string + appFlags []Flag + appRunInput []string + appCommands []Command + expectedAnError bool + }{ + // assertion: empty input, when a required flag is present, errors + { + testCase: "error_case_empty_input_with_required_flag_on_app", + appRunInput: []string{"myCLI"}, + appFlags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, + expectedAnError: true, + }, + { + testCase: "error_case_empty_input_with_required_flag_on_command", + appRunInput: []string{"myCLI", "myCommand"}, + appCommands: []Command{Command{ + Name: "myCommand", + Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, + }}, + expectedAnError: true, + }, + { + testCase: "error_case_empty_input_with_required_flag_on_subcommand", + appRunInput: []string{"myCLI", "myCommand", "mySubCommand"}, + appCommands: []Command{Command{ + Name: "myCommand", + Subcommands: []Command{Command{ + Name: "mySubCommand", + Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, + }}, + }}, + expectedAnError: true, + }, + // assertion: inputing --help, when a required flag is present, does not error + { + testCase: "valid_case_help_input_with_required_flag_on_app", + appRunInput: []string{"myCLI", "--help"}, + appFlags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, + }, + { + testCase: "valid_case_help_input_with_required_flag_on_command", + appRunInput: []string{"myCLI", "myCommand", "--help"}, + appCommands: []Command{Command{ + Name: "myCommand", + Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, + }}, + }, + { + testCase: "valid_case_help_input_with_required_flag_on_subcommand", + appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--help"}, + appCommands: []Command{Command{ + Name: "myCommand", + Subcommands: []Command{Command{ + Name: "mySubCommand", + Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, + }}, + }}, + }, + // assertion: giving optional input, when a required flag is present, errors + { + testCase: "error_case_optional_input_with_required_flag_on_app", + appRunInput: []string{"myCLI", "--optional", "cats"}, + appFlags: []Flag{StringFlag{Name: "requiredFlag", Required: true}, StringFlag{Name: "optional"}}, + expectedAnError: true, + }, + { + testCase: "error_case_optional_input_with_required_flag_on_command", + appRunInput: []string{"myCLI", "myCommand", "--optional", "cats"}, + appCommands: []Command{Command{ + Name: "myCommand", + Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}, StringFlag{Name: "optional"}}, + }}, + expectedAnError: true, + }, + { + testCase: "error_case_optional_input_with_required_flag_on_subcommand", + appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--optional", "cats"}, + appCommands: []Command{Command{ + Name: "myCommand", + Subcommands: []Command{Command{ + Name: "mySubCommand", + Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}, StringFlag{Name: "optional"}}, + }}, + }}, + expectedAnError: true, + }, + // assertion: when a required flag is present, inputting that required flag does not error + { + testCase: "valid_case_required_flag_input_on_app", + appRunInput: []string{"myCLI", "--requiredFlag", "cats"}, + appFlags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, + }, + { + testCase: "valid_case_required_flag_input_on_command", + appRunInput: []string{"myCLI", "myCommand", "--requiredFlag", "cats"}, + appCommands: []Command{Command{ + Name: "myCommand", + Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, + }}, + }, + { + testCase: "valid_case_required_flag_input_on_subcommand", + appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--requiredFlag", "cats"}, + appCommands: []Command{Command{ + Name: "myCommand", + Subcommands: []Command{Command{ + Name: "mySubCommand", + Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, + }}, + }}, + }, + } + for _, test := range tdata { + t.Run(test.testCase, func(t *testing.T) { + // setup + app := NewApp() + app.Flags = test.appFlags + app.Commands = test.appCommands + + // logic under test + err := app.Run(test.appRunInput) + + // assertions + if test.expectedAnError && err == nil { + t.Errorf("expected an error, but there was none") + } + if _, ok := err.(requiredFlagsErr); test.expectedAnError && !ok { + t.Errorf("expected a requiredFlagsErr, but got: %s", err) + } + if !test.expectedAnError && err != nil { + t.Errorf("did not expected an error, but there was one: %s", err) + } + }) + } +} + func TestAppHelpPrinter(t *testing.T) { oldPrinter := HelpPrinter defer func() { diff --git a/command.go b/command.go index 56b633c..f1ca02d 100644 --- a/command.go +++ b/command.go @@ -57,7 +57,7 @@ type Command struct { // Boolean to hide this command from help or completion Hidden bool // Boolean to enable short-option handling so user can combine several - // single-character bool arguements into one + // single-character bool arguments into one // i.e. foobar -o -v -> foobar -ov UseShortOptionHandling bool @@ -135,6 +135,12 @@ func (c Command) Run(ctx *Context) (err error) { return nil } + cerr := checkRequiredFlags(c.Flags, context) + if cerr != nil { + ShowCommandHelp(context, c.Name) + return cerr + } + if c.After != nil { defer func() { afterErr := c.After(context) diff --git a/context.go b/context.go index 552ee74..3e516c8 100644 --- a/context.go +++ b/context.go @@ -3,6 +3,7 @@ package cli import ( "errors" "flag" + "fmt" "os" "reflect" "strings" @@ -285,3 +286,43 @@ func normalizeFlags(flags []Flag, set *flag.FlagSet) error { } return nil } + +type requiredFlagsErr interface { + error + getMissingFlags() []string +} + +type errRequiredFlags struct { + missingFlags []string +} + +func (e *errRequiredFlags) Error() string { + numberOfMissingFlags := len(e.missingFlags) + if numberOfMissingFlags == 1 { + return fmt.Sprintf("Required flag %q not set", e.missingFlags[0]) + } + joinedMissingFlags := strings.Join(e.missingFlags, ", ") + return fmt.Sprintf("Required flags %q not set", joinedMissingFlags) +} + +func (e *errRequiredFlags) getMissingFlags() []string { + return e.missingFlags +} + +func checkRequiredFlags(flags []Flag, context *Context) requiredFlagsErr { + var missingFlags []string + for _, f := range flags { + if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() { + key := strings.Split(f.GetName(), ",")[0] + if !context.IsSet(key) { + missingFlags = append(missingFlags, key) + } + } + } + + if len(missingFlags) != 0 { + return &errRequiredFlags{missingFlags: missingFlags} + } + + return nil +} diff --git a/context_test.go b/context_test.go index 7acca10..f329dc7 100644 --- a/context_test.go +++ b/context_test.go @@ -3,6 +3,7 @@ package cli import ( "flag" "os" + "strings" "testing" "time" ) @@ -253,18 +254,23 @@ func TestContext_GlobalIsSet_fromEnv(t *testing.T) { timeoutIsSet, tIsSet bool noEnvVarIsSet, nIsSet bool passwordIsSet, pIsSet bool + passwordValue string unparsableIsSet, uIsSet bool + overrideIsSet, oIsSet bool + overrideValue string ) clearenv() os.Setenv("APP_TIMEOUT_SECONDS", "15.5") - os.Setenv("APP_PASSWORD", "") + os.Setenv("APP_PASSWORD", "badpass") + os.Setenv("APP_OVERRIDE", "overridden") a := App{ Flags: []Flag{ Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, StringFlag{Name: "password, p", EnvVar: "APP_PASSWORD"}, Float64Flag{Name: "no-env-var, n"}, Float64Flag{Name: "unparsable, u", EnvVar: "APP_UNPARSABLE"}, + StringFlag{Name: "overrides-default, o", Value: "default", EnvVar: "APP_OVERRIDE"}, }, Commands: []Command{ { @@ -274,10 +280,14 @@ func TestContext_GlobalIsSet_fromEnv(t *testing.T) { tIsSet = ctx.GlobalIsSet("t") passwordIsSet = ctx.GlobalIsSet("password") pIsSet = ctx.GlobalIsSet("p") + passwordValue = ctx.GlobalString("password") unparsableIsSet = ctx.GlobalIsSet("unparsable") uIsSet = ctx.GlobalIsSet("u") noEnvVarIsSet = ctx.GlobalIsSet("no-env-var") nIsSet = ctx.GlobalIsSet("n") + overrideIsSet = ctx.GlobalIsSet("overrides-default") + oIsSet = ctx.GlobalIsSet("o") + overrideValue = ctx.GlobalString("overrides-default") return nil }, }, @@ -290,8 +300,13 @@ func TestContext_GlobalIsSet_fromEnv(t *testing.T) { expect(t, tIsSet, true) expect(t, passwordIsSet, true) expect(t, pIsSet, true) + expect(t, passwordValue, "badpass") + expect(t, unparsableIsSet, false) expect(t, noEnvVarIsSet, false) expect(t, nIsSet, false) + expect(t, overrideIsSet, true) + expect(t, oIsSet, true) + expect(t, overrideValue, "overridden") os.Setenv("APP_UNPARSABLE", "foobar") if err := a.Run([]string{"run"}); err != nil { @@ -401,3 +416,139 @@ func TestContext_GlobalSet(t *testing.T) { expect(t, c.GlobalInt("int"), 1) expect(t, c.GlobalIsSet("int"), true) } + +func TestCheckRequiredFlags(t *testing.T) { + tdata := []struct { + testCase string + parseInput []string + envVarInput [2]string + flags []Flag + expectedAnError bool + expectedErrorContents []string + }{ + { + testCase: "empty", + }, + { + testCase: "optional", + flags: []Flag{ + StringFlag{Name: "optionalFlag"}, + }, + }, + { + testCase: "required", + flags: []Flag{ + StringFlag{Name: "requiredFlag", Required: true}, + }, + expectedAnError: true, + expectedErrorContents: []string{"requiredFlag"}, + }, + { + testCase: "required_and_present", + flags: []Flag{ + StringFlag{Name: "requiredFlag", Required: true}, + }, + parseInput: []string{"--requiredFlag", "myinput"}, + }, + { + testCase: "required_and_present_via_env_var", + flags: []Flag{ + StringFlag{Name: "requiredFlag", Required: true, EnvVar: "REQUIRED_FLAG"}, + }, + envVarInput: [2]string{"REQUIRED_FLAG", "true"}, + }, + { + testCase: "required_and_optional", + flags: []Flag{ + StringFlag{Name: "requiredFlag", Required: true}, + StringFlag{Name: "optionalFlag"}, + }, + expectedAnError: true, + }, + { + testCase: "required_and_optional_and_optional_present", + flags: []Flag{ + StringFlag{Name: "requiredFlag", Required: true}, + StringFlag{Name: "optionalFlag"}, + }, + parseInput: []string{"--optionalFlag", "myinput"}, + expectedAnError: true, + }, + { + testCase: "required_and_optional_and_optional_present_via_env_var", + flags: []Flag{ + StringFlag{Name: "requiredFlag", Required: true}, + StringFlag{Name: "optionalFlag", EnvVar: "OPTIONAL_FLAG"}, + }, + envVarInput: [2]string{"OPTIONAL_FLAG", "true"}, + expectedAnError: true, + }, + { + testCase: "required_and_optional_and_required_present", + flags: []Flag{ + StringFlag{Name: "requiredFlag", Required: true}, + StringFlag{Name: "optionalFlag"}, + }, + parseInput: []string{"--requiredFlag", "myinput"}, + }, + { + testCase: "two_required", + flags: []Flag{ + StringFlag{Name: "requiredFlagOne", Required: true}, + StringFlag{Name: "requiredFlagTwo", Required: true}, + }, + expectedAnError: true, + expectedErrorContents: []string{"requiredFlagOne", "requiredFlagTwo"}, + }, + { + testCase: "two_required_and_one_present", + flags: []Flag{ + StringFlag{Name: "requiredFlag", Required: true}, + StringFlag{Name: "requiredFlagTwo", Required: true}, + }, + parseInput: []string{"--requiredFlag", "myinput"}, + expectedAnError: true, + }, + { + testCase: "two_required_and_both_present", + flags: []Flag{ + StringFlag{Name: "requiredFlag", Required: true}, + StringFlag{Name: "requiredFlagTwo", Required: true}, + }, + parseInput: []string{"--requiredFlag", "myinput", "--requiredFlagTwo", "myinput"}, + }, + } + for _, test := range tdata { + t.Run(test.testCase, func(t *testing.T) { + // setup + set := flag.NewFlagSet("test", 0) + for _, flags := range test.flags { + flags.Apply(set) + } + set.Parse(test.parseInput) + if test.envVarInput[0] != "" { + os.Clearenv() + os.Setenv(test.envVarInput[0], test.envVarInput[1]) + } + ctx := &Context{} + context := NewContext(ctx.App, set, ctx) + context.Command.Flags = test.flags + + // logic under test + err := checkRequiredFlags(test.flags, context) + + // assertions + if test.expectedAnError && err == nil { + t.Errorf("expected an error, but there was none") + } + if !test.expectedAnError && err != nil { + t.Errorf("did not expected an error, but there was one: %s", err) + } + for _, errString := range test.expectedErrorContents { + if !strings.Contains(err.Error(), errString) { + t.Errorf("expected error %q to contain %q, but it didn't!", err.Error(), errString) + } + } + }) + } +} diff --git a/flag.go b/flag.go index b0cffc0..d98c808 100644 --- a/flag.go +++ b/flag.go @@ -75,6 +75,14 @@ type Flag interface { GetName() string } +// RequiredFlag is an interface that allows us to mark flags as required +// it allows flags required flags to be backwards compatible with the Flag interface +type RequiredFlag interface { + Flag + + IsRequired() bool +} + // 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 001576c..a3e4d6e 100644 --- a/flag_generated.go +++ b/flag_generated.go @@ -14,6 +14,7 @@ type BoolFlag struct { Usage string EnvVar string FilePath string + Required bool Hidden bool Destination *bool } @@ -29,6 +30,11 @@ func (f BoolFlag) GetName() string { return f.Name } +// IsRequired returns the whether or not the flag is required +func (f BoolFlag) IsRequired() bool { + return f.Required +} + // Bool looks up the value of a local BoolFlag, returns // false if not found func (c *Context) Bool(name string) bool { @@ -62,6 +68,7 @@ type BoolTFlag struct { Usage string EnvVar string FilePath string + Required bool Hidden bool Destination *bool } @@ -77,6 +84,11 @@ func (f BoolTFlag) GetName() string { return f.Name } +// IsRequired returns the whether or not the flag is required +func (f BoolTFlag) IsRequired() bool { + return f.Required +} + // BoolT looks up the value of a local BoolTFlag, returns // false if not found func (c *Context) BoolT(name string) bool { @@ -110,6 +122,7 @@ type DurationFlag struct { Usage string EnvVar string FilePath string + Required bool Hidden bool Value time.Duration Destination *time.Duration @@ -126,6 +139,11 @@ func (f DurationFlag) GetName() string { return f.Name } +// IsRequired returns the whether or not the flag is required +func (f DurationFlag) IsRequired() bool { + return f.Required +} + // Duration looks up the value of a local DurationFlag, returns // 0 if not found func (c *Context) Duration(name string) time.Duration { @@ -159,6 +177,7 @@ type Float64Flag struct { Usage string EnvVar string FilePath string + Required bool Hidden bool Value float64 Destination *float64 @@ -175,6 +194,11 @@ func (f Float64Flag) GetName() string { return f.Name } +// IsRequired returns the whether or not the flag is required +func (f Float64Flag) IsRequired() bool { + return f.Required +} + // Float64 looks up the value of a local Float64Flag, returns // 0 if not found func (c *Context) Float64(name string) float64 { @@ -208,6 +232,7 @@ type GenericFlag struct { Usage string EnvVar string FilePath string + Required bool Hidden bool Value Generic } @@ -223,6 +248,11 @@ func (f GenericFlag) GetName() string { return f.Name } +// IsRequired returns the whether or not the flag is required +func (f GenericFlag) IsRequired() bool { + return f.Required +} + // Generic looks up the value of a local GenericFlag, returns // nil if not found func (c *Context) Generic(name string) interface{} { @@ -256,6 +286,7 @@ type Int64Flag struct { Usage string EnvVar string FilePath string + Required bool Hidden bool Value int64 Destination *int64 @@ -272,6 +303,11 @@ func (f Int64Flag) GetName() string { return f.Name } +// IsRequired returns the whether or not the flag is required +func (f Int64Flag) IsRequired() bool { + return f.Required +} + // Int64 looks up the value of a local Int64Flag, returns // 0 if not found func (c *Context) Int64(name string) int64 { @@ -305,6 +341,7 @@ type IntFlag struct { Usage string EnvVar string FilePath string + Required bool Hidden bool Value int Destination *int @@ -321,6 +358,11 @@ func (f IntFlag) GetName() string { return f.Name } +// IsRequired returns the whether or not the flag is required +func (f IntFlag) IsRequired() bool { + return f.Required +} + // Int looks up the value of a local IntFlag, returns // 0 if not found func (c *Context) Int(name string) int { @@ -354,6 +396,7 @@ type IntSliceFlag struct { Usage string EnvVar string FilePath string + Required bool Hidden bool Value *IntSlice } @@ -369,6 +412,11 @@ func (f IntSliceFlag) GetName() string { return f.Name } +// IsRequired returns the whether or not the flag is required +func (f IntSliceFlag) IsRequired() bool { + return f.Required +} + // IntSlice looks up the value of a local IntSliceFlag, returns // nil if not found func (c *Context) IntSlice(name string) []int { @@ -402,6 +450,7 @@ type Int64SliceFlag struct { Usage string EnvVar string FilePath string + Required bool Hidden bool Value *Int64Slice } @@ -417,6 +466,11 @@ func (f Int64SliceFlag) GetName() string { return f.Name } +// IsRequired returns the whether or not the flag is required +func (f Int64SliceFlag) IsRequired() bool { + return f.Required +} + // Int64Slice looks up the value of a local Int64SliceFlag, returns // nil if not found func (c *Context) Int64Slice(name string) []int64 { @@ -450,6 +504,7 @@ type StringFlag struct { Usage string EnvVar string FilePath string + Required bool Hidden bool Value string Destination *string @@ -466,6 +521,11 @@ func (f StringFlag) GetName() string { return f.Name } +// IsRequired returns the whether or not the flag is required +func (f StringFlag) IsRequired() bool { + return f.Required +} + // String looks up the value of a local StringFlag, returns // "" if not found func (c *Context) String(name string) string { @@ -499,6 +559,7 @@ type StringSliceFlag struct { Usage string EnvVar string FilePath string + Required bool Hidden bool Value *StringSlice } @@ -514,6 +575,11 @@ func (f StringSliceFlag) GetName() string { return f.Name } +// IsRequired returns the whether or not the flag is required +func (f StringSliceFlag) IsRequired() bool { + return f.Required +} + // StringSlice looks up the value of a local StringSliceFlag, returns // nil if not found func (c *Context) StringSlice(name string) []string { @@ -547,6 +613,7 @@ type Uint64Flag struct { Usage string EnvVar string FilePath string + Required bool Hidden bool Value uint64 Destination *uint64 @@ -563,6 +630,11 @@ func (f Uint64Flag) GetName() string { return f.Name } +// IsRequired returns the whether or not the flag is required +func (f Uint64Flag) IsRequired() bool { + return f.Required +} + // Uint64 looks up the value of a local Uint64Flag, returns // 0 if not found func (c *Context) Uint64(name string) uint64 { @@ -596,6 +668,7 @@ type UintFlag struct { Usage string EnvVar string FilePath string + Required bool Hidden bool Value uint Destination *uint @@ -612,6 +685,11 @@ func (f UintFlag) GetName() string { return f.Name } +// IsRequired returns the whether or not the flag is required +func (f UintFlag) IsRequired() bool { + return f.Required +} + // Uint looks up the value of a local UintFlag, returns // 0 if not found func (c *Context) Uint(name string) uint { diff --git a/generate-flag-types b/generate-flag-types index 1358857..e0b5aa1 100755 --- a/generate-flag-types +++ b/generate-flag-types @@ -143,6 +143,7 @@ def _write_cli_flag_types(outfile, types): Usage string EnvVar string FilePath string + Required bool Hidden bool """.format(**typedef)) @@ -170,6 +171,11 @@ def _write_cli_flag_types(outfile, types): return f.Name }} + // IsRequired returns the whether or not the flag is required + func (f {name}Flag) IsRequired() bool {{ + return f.Required + }} + // {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/help.go b/help.go index 65874fa..d611971 100644 --- a/help.go +++ b/help.go @@ -30,8 +30,9 @@ AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{end}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + {{.Name}}:{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} GLOBAL OPTIONS: {{range $index, $option := .VisibleFlags}}{{if $index}} @@ -71,9 +72,11 @@ USAGE: {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{end}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}} -{{end}}{{if .VisibleFlags}} + + {{.Name}}:{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + OPTIONS: {{range .VisibleFlags}}{{.}} {{end}}{{end}} @@ -237,10 +240,8 @@ func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc m funcMap := template.FuncMap{ "join": strings.Join, } - if customFunc != nil { - for key, value := range customFunc { - funcMap[key] = value - } + for key, value := range customFunc { + funcMap[key] = value } w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0)