diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2a36d3c..ea84b4d 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -5,6 +5,14 @@ on: push: jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: golangci-lint + uses: golangci/golangci-lint-action@v8 + test: runs-on: ubuntu-latest strategy: diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..7638c33 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,30 @@ +version: "2" + +run: + issues-exit-code: 1 + +formatters: + enable: + - gofmt + - gci + +linters: + enable: + - wrapcheck + settings: + wrapcheck: + ignore-package-globs: + # We already make sure your own packages wrap errors properly + - github.com/symfony-cli/* + errcheck: + exclude-functions: + - github.com/symfony-cli/terminal.Printf + - github.com/symfony-cli/terminal.Println + - github.com/symfony-cli/terminal.Printfln + - github.com/symfony-cli/terminal.Eprintf + - github.com/symfony-cli/terminal.Eprintln + - github.com/symfony-cli/terminal.Eprintfln + - github.com/symfony-cli/terminal.Eprint + - fmt.Fprintln + - fmt.Fprintf + - fmt.Fprint diff --git a/application.go b/application.go index 93c2b04..cd870f5 100644 --- a/application.go +++ b/application.go @@ -96,7 +96,7 @@ func (a *Application) Run(arguments []string) (err error) { if err != nil { err = IncorrectUsageError{err} - ShowAppHelp(context) + _ = ShowAppHelp(context) fmt.Fprintln(a.Writer) HandleExitCoder(err) return err @@ -124,7 +124,7 @@ func (a *Application) Run(arguments []string) (err error) { beforeErr := a.Before(context) if beforeErr != nil { fmt.Fprintf(a.Writer, "%v\n\n", beforeErr) - ShowAppHelp(context) + _ = ShowAppHelp(context) HandleExitCoder(beforeErr) err = beforeErr return err @@ -151,6 +151,15 @@ func (a *Application) Run(arguments []string) (err error) { return err } +// MustRun is the entry point to the CLI app. Parses the arguments slice and routes +// to the proper flag/args combination. Under the hood it calls `Run` but will panic +// if any error happen +func (a *Application) MustRun(arguments []string) { + if err := a.Run(arguments); err != nil { + panic(err) + } +} + // Command returns the named command on App. Returns nil if the command does not // exist func (a *Application) Command(name string) *Command { diff --git a/application_test.go b/application_test.go index 53b745f..9485167 100644 --- a/application_test.go +++ b/application_test.go @@ -25,6 +25,7 @@ import ( "flag" "fmt" "io" + "log" "os" "reflect" "strings" @@ -92,7 +93,9 @@ func ExampleApplication_Run() { }, } - app.Run(os.Args) + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } // Output: // Hello Jeremy } @@ -115,7 +118,9 @@ func ExampleApplication_Run_quiet() { }, } - app.Run(os.Args) + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } // Output: } @@ -138,7 +143,9 @@ func ExampleApplication_Run_quietDisabled() { }, } - app.Run(os.Args) + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } // Output: // Hello Jeremy // Byebye Jeremy @@ -164,7 +171,9 @@ func (ts *ApplicationSuite) ExampleApplication_Run_quietInvalid(c *C) { }, } - app.Run(os.Args) + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } c.Assert(stdout.String(), Equals, `Output: greet version 0.0.0 A new cli application @@ -217,7 +226,9 @@ func (ts *ApplicationSuite) ExampleApplication_Run_appHelp(c *C) { }, }, } - app.Run(os.Args) + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } c.Assert(stdout.String(), Equals, `Output: greet version 0.1.0 A new cli application @@ -262,7 +273,9 @@ func ExampleApplication_Run_commandHelp() { }, }, } - app.Run(os.Args) + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } // Output: // Description: // use it to see a description @@ -341,7 +354,7 @@ func (ts *ApplicationSuite) TestApp_CommandWithFlagBeforeTerminator(c *C) { } app.Commands = []*Command{command} - app.Run([]string{"", "cmd", "--option", "my-option", "my-arg", "--", "--notARealFlag"}) + c.Assert(app.Run([]string{"", "cmd", "--option", "my-option", "my-arg", "--", "--notARealFlag"}), IsNil) c.Assert(parsedOption, Equals, "my-option") c.Assert(args, NotNil) @@ -366,7 +379,7 @@ func (ts *ApplicationSuite) TestApp_CommandWithDash(c *C) { } app.Commands = []*Command{command} - app.Run([]string{"", "cmd", "my-arg", "-"}) + c.Assert(app.Run([]string{"", "cmd", "my-arg", "-"}), IsNil) c.Assert(args, NotNil) c.Assert(args.Len(), Equals, 2) c.Assert(args.Get("first"), Equals, "my-arg") @@ -390,8 +403,7 @@ func (ts *ApplicationSuite) TestApp_CommandWithNoFlagBeforeTerminator(c *C) { } app.Commands = []*Command{command} - app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"}) - + c.Assert(app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"}), IsNil) c.Assert(args.Get("first"), Equals, "my-arg") c.Assert(args.Get("second"), Equals, "notAFlagAtAll") } @@ -465,7 +477,7 @@ func (ts *ApplicationSuite) TestApp_Float64Flag(c *C) { }, } - app.Run([]string{"", "--height", "1.93"}) + c.Assert(app.Run([]string{"", "--height", "1.93"}), IsNil) c.Assert(meters, Equals, 1.93) } @@ -493,7 +505,9 @@ func TestApp_ParseSliceFlags(t *testing.T) { } app.Commands = []*Command{command} - app.Run([]string{"", "cmd", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4", "my-first-arg"}) + if err := app.Run([]string{"", "cmd", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4", "my-first-arg"}); err != nil { + t.Error(err) + } IntsEquals := func(a, b []int) bool { if len(a) != len(b) { @@ -556,7 +570,9 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { } app.Commands = []*Command{command} - app.Run([]string{"", "cmd", "-a", "2", "-str", "A", "my-arg"}) + if err := app.Run([]string{"", "cmd", "-a", "2", "-str", "A", "my-arg"}); err != nil { + t.Error(err) + } var expectedIntSlice = []int{2} var expectedStringSlice = []string{"A"} @@ -784,7 +800,9 @@ func TestAppHelpPrinter(t *testing.T) { } app := &Application{} - app.Run([]string{"-h"}) + if err := app.Run([]string{"-h"}); err != nil { + t.Error(err) + } if wasCalled == false { t.Errorf("Help printer expected to be called, but was not") @@ -1036,7 +1054,9 @@ func TestApp_Run_Categories(t *testing.T) { versionCommand.Hidden = nil }() - app.Run([]string{"categories"}) + if err := app.Run([]string{"categories"}); err != nil { + t.Error(err) + } expect := commandCategories([]*commandCategory{ { diff --git a/command.go b/command.go index aba96d7..e2eefb4 100644 --- a/command.go +++ b/command.go @@ -127,7 +127,7 @@ func (c *Command) Run(ctx *Context) (err error) { err = checkRequiredArgs(c, context) } if err != nil { - ShowCommandHelp(ctx, c.FullName()) + _ = ShowCommandHelp(ctx, c.FullName()) fmt.Fprintln(ctx.App.Writer) return IncorrectUsageError{err} } @@ -153,7 +153,7 @@ func (c *Command) Run(ctx *Context) (err error) { if c.Before != nil { err = c.Before(context) if err != nil { - ShowCommandHelp(ctx, c.FullName()) + _ = ShowCommandHelp(ctx, c.FullName()) HandleExitCoder(err) return err } diff --git a/command_test.go b/command_test.go index 2aeb7a4..8d8ee1f 100644 --- a/command_test.go +++ b/command_test.go @@ -52,7 +52,7 @@ func (cs *CommandSuite) TestCommandFlagParsing(c *C) { app := &Application{} app.setup() set := flag.NewFlagSet("test", 0) - set.Parse(ca.testArgs) + c.Assert(set.Parse(ca.testArgs), IsNil) context := NewContext(app, set, nil) @@ -76,7 +76,7 @@ func (cs *CommandSuite) TestCommandFlagParsing(c *C) { err := command.Run(context) if ca.expectedErr == "" { - c.Assert(err, Equals, nil) + c.Assert(err, IsNil) } else { c.Assert(err, ErrorMatches, ca.expectedErr) } diff --git a/context.go b/context.go index 09a17a1..dee6287 100644 --- a/context.go +++ b/context.go @@ -22,6 +22,8 @@ package console import ( "flag" "fmt" + + "github.com/pkg/errors" ) // Context is a type that is passed through to @@ -45,7 +47,7 @@ func NewContext(app *Application, set *flag.FlagSet, parentCtx *Context) *Contex // Set assigns a value to a context flag. func (c *Context) Set(name, value string) error { if fs := lookupFlagSet(name, c); fs != nil { - return fs.Set(name, value) + return errors.WithStack(fs.Set(name, value)) } return fmt.Errorf("no such flag -%v", name) diff --git a/context_test.go b/context_test.go index a31084b..78cca42 100644 --- a/context_test.go +++ b/context_test.go @@ -148,7 +148,7 @@ func (cs *ContextSuite) TestContext_Args(c *C) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") ctx := NewContext(nil, set, nil) - set.Parse([]string{"--myflag", "bat", "baz"}) + c.Assert(set.Parse([]string{"--myflag", "bat", "baz"}), IsNil) c.Assert(ctx.Args().Len(), Equals, 2) c.Assert(ctx.Bool("myflag"), Equals, true) } @@ -157,7 +157,7 @@ func (cs *ContextSuite) TestContext_NArg(c *C) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") ctx := NewContext(nil, set, nil) - set.Parse([]string{"--myflag", "bat", "baz"}) + c.Assert(set.Parse([]string{"--myflag", "bat", "baz"}), IsNil) c.Assert(ctx.NArg(), Equals, 2) } @@ -204,8 +204,8 @@ func (cs *ContextSuite) TestContext_IsSet(c *C) { parentCtx := NewContext(nil, parentSet, nil) ctx := NewContext(nil, set, parentCtx) - set.Parse([]string{"--one-flag", "--two-flag", "frob"}) - parentSet.Parse([]string{"--top-flag"}) + c.Assert(set.Parse([]string{"--one-flag", "--two-flag", "frob"}), IsNil) + c.Assert(parentSet.Parse([]string{"--top-flag"}), IsNil) c.Assert(ctx.IsSet("one-flag"), Equals, true) c.Assert(ctx.IsSet("two-flag"), Equals, true) @@ -219,12 +219,14 @@ func (cs *ContextSuite) TestContext_Set(c *C) { set.Int("int", 5, "an int") ctx := NewContext(nil, set, nil) - ctx.Set("int", "1") + c.Assert(ctx.Set("int", "1"), IsNil) c.Assert(ctx.Int("int"), Equals, 1) } func (cs *ContextSuite) TestContext_Set_AppFlags(c *C) { - defer terminal.SetLogLevel(1) + defer func() { + c.Assert(terminal.SetLogLevel(1), IsNil) + }() app := &Application{ Commands: []*Command{ @@ -239,7 +241,7 @@ func (cs *ContextSuite) TestContext_Set_AppFlags(c *C) { }, }, } - app.Run([]string{"cmd", "foo"}) + app.MustRun([]string{"cmd", "foo"}) } func (cs *ContextSuite) TestContext_Lineage(c *C) { @@ -249,8 +251,8 @@ func (cs *ContextSuite) TestContext_Lineage(c *C) { parentSet.Bool("top-flag", true, "doc") parentCtx := NewContext(nil, parentSet, nil) ctx := NewContext(nil, set, parentCtx) - set.Parse([]string{"--local-flag"}) - parentSet.Parse([]string{"--top-flag"}) + c.Assert(set.Parse([]string{"--local-flag"}), IsNil) + c.Assert(parentSet.Parse([]string{"--top-flag"}), IsNil) lineage := ctx.Lineage() c.Assert(len(lineage), Equals, 2) @@ -265,8 +267,8 @@ func (cs *ContextSuite) TestContext_lookupFlagSet(c *C) { parentSet.Bool("top-flag", true, "doc") parentCtx := NewContext(nil, parentSet, nil) ctx := NewContext(nil, set, parentCtx) - set.Parse([]string{"--local-flag"}) - parentSet.Parse([]string{"--top-flag"}) + c.Assert(set.Parse([]string{"--local-flag"}), IsNil) + c.Assert(parentSet.Parse([]string{"--top-flag"}), IsNil) fs := lookupFlagSet("top-flag", ctx) c.Assert(fs, Equals, parentCtx.flagSet) diff --git a/errors_stacktrace.go b/errors_stacktrace.go index 042347a..0a6f3d4 100644 --- a/errors_stacktrace.go +++ b/errors_stacktrace.go @@ -157,7 +157,7 @@ func FormatErrorChain(buf *bytes.Buffer, err error, trimPaths bool) bool { if trimPaths { file = trimGOPATH(fn.Name(), file) } - buf.WriteString(fmt.Sprintf("%s\n\t%s:%d", fn.Name(), file, line)) + fmt.Fprintf(buf, "%s\n\t%s:%d", fn.Name(), file, line) } } diff --git a/flag.go b/flag.go index d833de8..a5b58fb 100644 --- a/flag.go +++ b/flag.go @@ -192,7 +192,7 @@ func (m *StringMap) String() string { // Serialized allows StringSlice to fulfill Serializeder func (m *StringMap) Serialized() string { - jsonBytes, _ := json.Marshal(m) + jsonBytes, _ := json.Marshal(m.m) return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) } diff --git a/flag_test.go b/flag_test.go index 0a8c854..fdd6777 100644 --- a/flag_test.go +++ b/flag_test.go @@ -43,7 +43,15 @@ var boolFlagTests = []struct { func resetEnv(env []string) { for _, e := range env { fields := strings.SplitN(e, "=", 2) - os.Setenv(fields[0], fields[1]) + if err := os.Setenv(fields[0], fields[1]); err != nil { + panic(err) + } + } +} + +func setEnv(t *testing.T, key, value string) { + if err := os.Setenv(key, value); err != nil { + t.Error(err) } } @@ -104,7 +112,7 @@ func TestStringFlagDefaultText(t *testing.T) { func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_FOO", "derp") + setEnv(t, "APP_FOO", "derp") for _, test := range stringFlagTests { flag := &StringFlag{Name: test.name, Aliases: test.aliases, DefaultValue: test.value, EnvVars: []string{"APP_FOO"}} output := flag.String() @@ -146,7 +154,7 @@ func TestStringSliceFlagHelpOutput(t *testing.T) { func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_QWWX", "11,4") + setEnv(t, "APP_QWWX", "11,4") for _, test := range stringSliceFlagTests { flag := &StringSliceFlag{Name: test.name, Aliases: test.aliases, Destination: test.value, EnvVars: []string{"APP_QWWX"}} output := flag.String() @@ -188,7 +196,7 @@ func TestStringMapFlagHelpOutput(t *testing.T) { func TestStringMapFlagWithEnvVarHelpOutput(t *testing.T) { defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_QWWX", "11,4") + setEnv(t, "APP_QWWX", "11,4") for _, test := range stringMapFlagTests { flag := &StringMapFlag{Name: test.name, Aliases: test.aliases, Destination: test.value, EnvVars: []string{"APP_QWWX"}} output := flag.String() @@ -225,7 +233,7 @@ func TestIntFlagHelpOutput(t *testing.T) { func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_BAR", "2") + setEnv(t, "APP_BAR", "2") for _, test := range intFlagTests { flag := &IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() @@ -262,7 +270,7 @@ func TestInt64FlagHelpOutput(t *testing.T) { func TestInt64FlagWithEnvVarHelpOutput(t *testing.T) { defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_BAR", "2") + setEnv(t, "APP_BAR", "2") for _, test := range int64FlagTests { flag := IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() @@ -299,7 +307,7 @@ func TestUintFlagHelpOutput(t *testing.T) { func TestUintFlagWithEnvVarHelpOutput(t *testing.T) { defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_BAR", "2") + setEnv(t, "APP_BAR", "2") for _, test := range uintFlagTests { flag := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() @@ -336,7 +344,7 @@ func TestUint64FlagHelpOutput(t *testing.T) { func TestUint64FlagWithEnvVarHelpOutput(t *testing.T) { defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_BAR", "2") + setEnv(t, "APP_BAR", "2") for _, test := range uint64FlagTests { flag := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() @@ -373,7 +381,7 @@ func TestDurationFlagHelpOutput(t *testing.T) { func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_BAR", "2h3m6s") + setEnv(t, "APP_BAR", "2h3m6s") for _, test := range durationFlagTests { flag := &DurationFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() @@ -413,7 +421,7 @@ func TestIntSliceFlagHelpOutput(t *testing.T) { func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_SMURF", "42,3") + setEnv(t, "APP_SMURF", "42,3") for _, test := range intSliceFlagTests { flag := &IntSliceFlag{Name: test.name, Aliases: test.aliases, Destination: test.value, EnvVars: []string{"APP_SMURF"}} output := flag.String() @@ -454,7 +462,7 @@ func TestInt64SliceFlagHelpOutput(t *testing.T) { func TestInt64SliceFlagWithEnvVarHelpOutput(t *testing.T) { defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_SMURF", "42,17179869184") + setEnv(t, "APP_SMURF", "42,17179869184") for _, test := range int64SliceFlagTests { flag := Int64SliceFlag{Name: test.name, Destination: test.value, EnvVars: []string{"APP_SMURF"}} output := flag.String() @@ -491,7 +499,7 @@ func TestFloat64FlagHelpOutput(t *testing.T) { func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_BAZ", "99.4") + setEnv(t, "APP_BAZ", "99.4") for _, test := range float64FlagTests { flag := &Float64Flag{Name: test.name, EnvVars: []string{"APP_BAZ"}} output := flag.String() @@ -532,7 +540,7 @@ func TestFloat64SliceFlagHelpOutput(t *testing.T) { func TestFloat64SliceFlagWithEnvVarHelpOutput(t *testing.T) { defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_SMURF", "0.1234,-10.5") + setEnv(t, "APP_SMURF", "0.1234,-10.5") for _, test := range float64SliceFlagTests { flag := Float64SliceFlag{Name: test.name, Destination: test.value, EnvVars: []string{"APP_SMURF"}} output := flag.String() @@ -570,7 +578,7 @@ func TestGenericFlagHelpOutput(t *testing.T) { func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_ZAP", "3") + setEnv(t, "APP_ZAP", "3") for _, test := range genericFlagTests { flag := &GenericFlag{Name: test.name, EnvVars: []string{"APP_ZAP"}} output := flag.String() @@ -599,7 +607,7 @@ func TestParseMultiString(t *testing.T) { } return nil }, - }).Run([]string{"run", "-s", "10"}) + }).MustRun([]string{"run", "-s", "10"}) } func TestParseDestinationString(t *testing.T) { @@ -618,14 +626,14 @@ func TestParseDestinationString(t *testing.T) { return nil }, } - a.Run([]string{"run", "--dest", "10"}) + a.MustRun([]string{"run", "--dest", "10"}) } func TestParseMultiStringFromEnv(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_COUNT", "20") + setEnv(t, "APP_COUNT", "20") (&Application{ Flags: []Flag{ &StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"APP_COUNT"}}, @@ -639,14 +647,14 @@ func TestParseMultiStringFromEnv(t *testing.T) { } return nil }, - }).Run([]string{"run"}) + }).MustRun([]string{"run"}) } func TestParseMultiStringFromEnvCascade(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_COUNT", "20") + setEnv(t, "APP_COUNT", "20") (&Application{ Flags: []Flag{ &StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"COMPAT_COUNT", "APP_COUNT"}}, @@ -660,7 +668,7 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) { } return nil }, - }).Run([]string{"run"}) + }).MustRun([]string{"run"}) } func TestParseMultiStringSlice(t *testing.T) { @@ -678,7 +686,7 @@ func TestParseMultiStringSlice(t *testing.T) { } return nil }, - }).Run([]string{"run", "-s", "10", "-s", "20"}) + }).MustRun([]string{"run", "-s", "10", "-s", "20"}) } func TestParseMultiStringSliceWithDefaults(t *testing.T) { @@ -696,7 +704,7 @@ func TestParseMultiStringSliceWithDefaults(t *testing.T) { } return nil }, - }).Run([]string{"run", "-s", "10", "-s", "20"}) + }).MustRun([]string{"run", "-s", "10", "-s", "20"}) } func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) { @@ -713,13 +721,13 @@ func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) { } return nil }, - }).Run([]string{"run"}) + }).MustRun([]string{"run"}) } func TestParseMultiStringSliceFromEnv(t *testing.T) { t.SkipNow() os.Clearenv() - os.Setenv("APP_INTERVALS", "20,30,40") + setEnv(t, "APP_INTERVALS", "20,30,40") (&Application{ Flags: []Flag{ @@ -734,14 +742,14 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) { } return nil }, - }).Run([]string{"run"}) + }).MustRun([]string{"run"}) } func TestParseMultiStringSliceFromEnvWithDefaults(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_INTERVALS", "20,30,40") + setEnv(t, "APP_INTERVALS", "20,30,40") (&Application{ Flags: []Flag{ @@ -756,14 +764,14 @@ func TestParseMultiStringSliceFromEnvWithDefaults(t *testing.T) { } return nil }, - }).Run([]string{"run"}) + }).MustRun([]string{"run"}) } func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_INTERVALS", "20,30,40") + setEnv(t, "APP_INTERVALS", "20,30,40") (&Application{ Flags: []Flag{ @@ -778,14 +786,14 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { } return nil }, - }).Run([]string{"run"}) + }).MustRun([]string{"run"}) } func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_INTERVALS", "20,30,40") + setEnv(t, "APP_INTERVALS", "20,30,40") (&Application{ Flags: []Flag{ @@ -800,7 +808,7 @@ func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) { } return nil }, - }).Run([]string{"run"}) + }).MustRun([]string{"run"}) } func TestParseMultiInt(t *testing.T) { @@ -818,7 +826,7 @@ func TestParseMultiInt(t *testing.T) { return nil }, } - a.Run([]string{"run", "-s", "10"}) + a.MustRun([]string{"run", "-s", "10"}) } func TestParseDestinationInt(t *testing.T) { @@ -837,14 +845,14 @@ func TestParseDestinationInt(t *testing.T) { return nil }, } - a.Run([]string{"run", "--dest", "10"}) + a.MustRun([]string{"run", "--dest", "10"}) } func TestParseMultiIntFromEnv(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_TIMEOUT_SECONDS", "10") + setEnv(t, "APP_TIMEOUT_SECONDS", "10") a := Application{ Flags: []Flag{ &IntFlag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}}, @@ -859,14 +867,14 @@ func TestParseMultiIntFromEnv(t *testing.T) { return nil }, } - a.Run([]string{"run"}) + a.MustRun([]string{"run"}) } func TestParseMultiIntFromEnvCascade(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_TIMEOUT_SECONDS", "10") + setEnv(t, "APP_TIMEOUT_SECONDS", "10") a := Application{ Flags: []Flag{ &IntFlag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS"}}, @@ -881,7 +889,7 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) { return nil }, } - a.Run([]string{"run"}) + a.MustRun([]string{"run"}) } func TestParseMultiIntSlice(t *testing.T) { @@ -898,7 +906,7 @@ func TestParseMultiIntSlice(t *testing.T) { } return nil }, - }).Run([]string{"run", "-s", "10", "-s", "20"}) + }).MustRun([]string{"run", "-s", "10", "-s", "20"}) } func TestParseMultiIntSliceWithDefaults(t *testing.T) { @@ -915,7 +923,7 @@ func TestParseMultiIntSliceWithDefaults(t *testing.T) { } return nil }, - }).Run([]string{"run", "-s", "10", "-s", "20"}) + }).MustRun([]string{"run", "-s", "10", "-s", "20"}) } func TestParseMultiIntSliceWithDefaultsUnset(t *testing.T) { @@ -932,14 +940,14 @@ func TestParseMultiIntSliceWithDefaultsUnset(t *testing.T) { } return nil }, - }).Run([]string{"run"}) + }).MustRun([]string{"run"}) } func TestParseMultiIntSliceFromEnv(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_INTERVALS", "20,30,40") + setEnv(t, "APP_INTERVALS", "20,30,40") (&Application{ Flags: []Flag{ @@ -954,14 +962,14 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) { } return nil }, - }).Run([]string{"run"}) + }).MustRun([]string{"run"}) } func TestParseMultiIntSliceFromEnvWithDefaults(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_INTERVALS", "20,30,40") + setEnv(t, "APP_INTERVALS", "20,30,40") (&Application{ Flags: []Flag{ @@ -976,14 +984,14 @@ func TestParseMultiIntSliceFromEnvWithDefaults(t *testing.T) { } return nil }, - }).Run([]string{"run"}) + }).MustRun([]string{"run"}) } func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_INTERVALS", "20,30,40") + setEnv(t, "APP_INTERVALS", "20,30,40") (&Application{ Flags: []Flag{ @@ -998,7 +1006,7 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { } return nil }, - }).Run([]string{"run"}) + }).MustRun([]string{"run"}) } func TestParseMultiInt64Slice(t *testing.T) { @@ -1015,14 +1023,14 @@ func TestParseMultiInt64Slice(t *testing.T) { } return nil }, - }).Run([]string{"run", "-s", "10", "-s", "17179869184"}) + }).MustRun([]string{"run", "-s", "10", "-s", "17179869184"}) } func TestParseMultiInt64SliceFromEnv(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_INTERVALS", "20,30,17179869184") + setEnv(t, "APP_INTERVALS", "20,30,17179869184") (&Application{ Flags: []Flag{ @@ -1037,14 +1045,14 @@ func TestParseMultiInt64SliceFromEnv(t *testing.T) { } return nil }, - }).Run([]string{"run"}) + }).MustRun([]string{"run"}) } func TestParseMultiInt64SliceFromEnvCascade(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_INTERVALS", "20,30,17179869184") + setEnv(t, "APP_INTERVALS", "20,30,17179869184") (&Application{ Flags: []Flag{ @@ -1059,7 +1067,7 @@ func TestParseMultiInt64SliceFromEnvCascade(t *testing.T) { } return nil }, - }).Run([]string{"run"}) + }).MustRun([]string{"run"}) } func TestParseMultiFloat64(t *testing.T) { @@ -1077,7 +1085,7 @@ func TestParseMultiFloat64(t *testing.T) { return nil }, } - a.Run([]string{"run", "-s", "10.2"}) + a.MustRun([]string{"run", "-s", "10.2"}) } func TestParseDestinationFloat64(t *testing.T) { @@ -1096,14 +1104,14 @@ func TestParseDestinationFloat64(t *testing.T) { return nil }, } - a.Run([]string{"run", "--dest", "10.2"}) + a.MustRun([]string{"run", "--dest", "10.2"}) } func TestParseMultiFloat64FromEnv(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_TIMEOUT_SECONDS", "15.5") + setEnv(t, "APP_TIMEOUT_SECONDS", "15.5") a := Application{ Flags: []Flag{ &Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}}, @@ -1118,14 +1126,14 @@ func TestParseMultiFloat64FromEnv(t *testing.T) { return nil }, } - a.Run([]string{"run"}) + a.MustRun([]string{"run"}) } func TestParseMultiFloat64FromEnvCascade(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_TIMEOUT_SECONDS", "15.5") + setEnv(t, "APP_TIMEOUT_SECONDS", "15.5") a := Application{ Flags: []Flag{ &Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS"}}, @@ -1140,14 +1148,14 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) { return nil }, } - a.Run([]string{"run"}) + a.MustRun([]string{"run"}) } func TestParseMultiFloat64SliceFromEnv(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_INTERVALS", "0.1,-10.5") + setEnv(t, "APP_INTERVALS", "0.1,-10.5") (&Application{ Flags: []Flag{ @@ -1162,14 +1170,14 @@ func TestParseMultiFloat64SliceFromEnv(t *testing.T) { } return nil }, - }).Run([]string{"run"}) + }).MustRun([]string{"run"}) } func TestParseMultiFloat64SliceFromEnvCascade(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_INTERVALS", "0.1234,-10.5") + setEnv(t, "APP_INTERVALS", "0.1234,-10.5") (&Application{ Flags: []Flag{ @@ -1184,7 +1192,7 @@ func TestParseMultiFloat64SliceFromEnvCascade(t *testing.T) { } return nil }, - }).Run([]string{"run"}) + }).MustRun([]string{"run"}) } func TestParseMultiBool(t *testing.T) { @@ -1202,7 +1210,7 @@ func TestParseMultiBool(t *testing.T) { return nil }, } - a.Run([]string{"run", "--serve"}) + a.MustRun([]string{"run", "--serve"}) } func TestParseDestinationBool(t *testing.T) { @@ -1221,14 +1229,14 @@ func TestParseDestinationBool(t *testing.T) { return nil }, } - a.Run([]string{"run", "--dest"}) + a.MustRun([]string{"run", "--dest"}) } func TestParseMultiBoolFromEnv(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_DEBUG", "1") + setEnv(t, "APP_DEBUG", "1") a := Application{ Flags: []Flag{ &BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"APP_DEBUG"}}, @@ -1243,14 +1251,14 @@ func TestParseMultiBoolFromEnv(t *testing.T) { return nil }, } - a.Run([]string{"run"}) + a.MustRun([]string{"run"}) } func TestParseMultiBoolFromEnvCascade(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_DEBUG", "1") + setEnv(t, "APP_DEBUG", "1") a := Application{ Flags: []Flag{ &BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"COMPAT_DEBUG", "APP_DEBUG"}}, @@ -1265,7 +1273,7 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) { return nil }, } - a.Run([]string{"run"}) + a.MustRun([]string{"run"}) } func TestParseMultiBoolTrue(t *testing.T) { @@ -1283,7 +1291,7 @@ func TestParseMultiBoolTrue(t *testing.T) { return nil }, } - a.Run([]string{"run", "--implode=false"}) + a.MustRun([]string{"run", "--implode=false"}) } func TestParseDestinationBoolTrue(t *testing.T) { @@ -1304,14 +1312,14 @@ func TestParseDestinationBoolTrue(t *testing.T) { return nil }, } - a.Run([]string{"run", "--dest=false"}) + a.MustRun([]string{"run", "--dest=false"}) } func TestParseMultiBoolTrueFromEnv(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_DEBUG", "0") + setEnv(t, "APP_DEBUG", "0") a := Application{ Flags: []Flag{ &BoolFlag{ @@ -1331,14 +1339,14 @@ func TestParseMultiBoolTrueFromEnv(t *testing.T) { return nil }, } - a.Run([]string{"run"}) + a.MustRun([]string{"run"}) } func TestParseMultiBoolTrueFromEnvCascade(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_DEBUG", "0") + setEnv(t, "APP_DEBUG", "0") a := Application{ Flags: []Flag{ &BoolFlag{ @@ -1358,7 +1366,7 @@ func TestParseMultiBoolTrueFromEnvCascade(t *testing.T) { return nil }, } - a.Run([]string{"run"}) + a.MustRun([]string{"run"}) } type Parser [2]string @@ -1394,14 +1402,14 @@ func TestParseGeneric(t *testing.T) { return nil }, } - a.Run([]string{"run", "-s", "10,20"}) + a.MustRun([]string{"run", "-s", "10,20"}) } func TestParseGenericFromEnv(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_SERVE", "20,30") + setEnv(t, "APP_SERVE", "20,30") a := Application{ Flags: []Flag{ &GenericFlag{ @@ -1421,14 +1429,14 @@ func TestParseGenericFromEnv(t *testing.T) { return nil }, } - a.Run([]string{"run"}) + a.MustRun([]string{"run"}) } func TestParseGenericFromEnvCascade(t *testing.T) { t.SkipNow() defer resetEnv(os.Environ()) os.Clearenv() - os.Setenv("APP_FOO", "99,2000") + setEnv(t, "APP_FOO", "99,2000") a := Application{ Flags: []Flag{ &GenericFlag{ @@ -1444,7 +1452,7 @@ func TestParseGenericFromEnvCascade(t *testing.T) { return nil }, } - a.Run([]string{"run"}) + a.MustRun([]string{"run"}) } func TestStringSlice_Serialized_Set(t *testing.T) { @@ -1456,7 +1464,9 @@ func TestStringSlice_Serialized_Set(t *testing.T) { } sl1 := NewStringSlice("c", "d") - sl1.Set(ser0) + if err := sl1.Set(ser0); err != nil { + t.Error(err) + } if sl0.String() != sl1.String() { t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1) @@ -1472,7 +1482,9 @@ func TestIntSlice_Serialized_Set(t *testing.T) { } sl1 := NewIntSlice(3, 4) - sl1.Set(ser0) + if err := sl1.Set(ser0); err != nil { + t.Error(err) + } if sl0.String() != sl1.String() { t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1) @@ -1488,7 +1500,9 @@ func TestInt64Slice_Serialized_Set(t *testing.T) { } sl1 := NewInt64Slice(int64(3), int64(4)) - sl1.Set(ser0) + if err := sl1.Set(ser0); err != nil { + t.Error(err) + } if sl0.String() != sl1.String() { t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1) diff --git a/flags_enhancement.go b/flags_enhancement.go index fe2bca5..e6dcc97 100644 --- a/flags_enhancement.go +++ b/flags_enhancement.go @@ -135,7 +135,7 @@ func parseFlagsFromEnv(prefixes []string, flags []Flag, fs *flag.FlagSet) { envVariableNames := flagStringSliceField(f, "EnvVars") for _, prefix := range prefixes { - envVariableNames = append(envVariableNames, strings.ToUpper(strings.Replace(fmt.Sprintf("%s_%s", prefix, fName), "-", "_", -1))) + envVariableNames = append(envVariableNames, strings.ToUpper(strings.ReplaceAll(fmt.Sprintf("%s_%s", prefix, fName), "-", "_"))) } // reverse slice order diff --git a/flags_enhancement_test.go b/flags_enhancement_test.go index 8dbc233..21c4dcd 100644 --- a/flags_enhancement_test.go +++ b/flags_enhancement_test.go @@ -231,7 +231,9 @@ func (ts *CliEnhancementSuite) TestFixAndParseArgsApplication(c *C) { func (ts *CliEnhancementSuite) TestFixAndParseArgsApplicationVerbosityFlag(c *C) { defaultLogLevel := terminal.GetLogLevel() - defer terminal.SetLogLevel(defaultLogLevel) + defer func() { + c.Assert(terminal.SetLogLevel(defaultLogLevel), IsNil) + }() testApp := Application{ Flags: []Flag{ diff --git a/help.go b/help.go index 743a768..e0ccb98 100644 --- a/help.go +++ b/help.go @@ -136,8 +136,7 @@ func ShowAppHelpAction(c *Context) error { return ShowCommandHelp(c, args.first()) } - ShowAppHelp(c) - return nil + return ShowAppHelp(c) } // ShowAppHelp is an action that displays the help. @@ -259,11 +258,12 @@ func printHelp(out io.Writer, templ string, data interface{}) { w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0) t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) - err := t.Execute(w, data) - if err != nil { + if err := t.Execute(w, data); err != nil { + panic(fmt.Errorf("CLI TEMPLATE ERROR: %#v", err.Error())) + } + if err := w.Flush(); err != nil { panic(fmt.Errorf("CLI TEMPLATE ERROR: %#v", err.Error())) } - w.Flush() } func checkVersion(c *Context) bool { @@ -294,7 +294,7 @@ func checkHelp(c *Context) bool { func checkCommandHelp(c *Context, name string) bool { if c.Bool("h") || c.Bool("help") { - ShowCommandHelp(c, name) + _ = ShowCommandHelp(c, name) return true } diff --git a/help_test.go b/help_test.go index 8cc866c..aa59928 100644 --- a/help_test.go +++ b/help_test.go @@ -33,7 +33,9 @@ func Test_ShowAppHelp_NoAuthor(t *testing.T) { c := NewContext(app, nil, nil) - ShowAppHelp(c) + if err := ShowAppHelp(c); err != nil { + t.Error(err) + } if bytes.Contains(output.Bytes(), []byte("AUTHOR(S):")) { t.Errorf("expected\n%snot to include %s", output.String(), "AUTHOR(S):") @@ -48,7 +50,9 @@ func Test_ShowAppHelp_NoVersion(t *testing.T) { c := NewContext(app, nil, nil) - ShowAppHelp(c) + if err := ShowAppHelp(c); err != nil { + t.Error(err) + } if bytes.Contains(output.Bytes(), []byte("VERSION:")) { t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:") @@ -80,7 +84,7 @@ func Test_Help_Custom_Flags(t *testing.T) { } output := new(bytes.Buffer) app.Writer = output - app.Run([]string{"test", "-h"}) + app.MustRun([]string{"test", "-h"}) if output.Len() > 0 { t.Errorf("unexpected output: %s", output.String()) } @@ -111,7 +115,7 @@ func Test_Version_Custom_Flags(t *testing.T) { } output := new(bytes.Buffer) app.Writer = output - app.Run([]string{"test", "-V"}) + app.MustRun([]string{"test", "-V"}) if output.Len() > 0 { t.Errorf("unexpected output: %s", output.String()) } @@ -122,7 +126,9 @@ func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) { app.Writer, app.ErrWriter = io.Discard, io.Discard set := flag.NewFlagSet("test", 0) - set.Parse([]string{"foo"}) + if err := set.Parse([]string{"foo"}); err != nil { + t.Error(err) + } c := NewContext(app, set, nil) app.setup() @@ -151,7 +157,7 @@ func Test_helpCommand_InHelpOutput(t *testing.T) { app := &Application{} output := &bytes.Buffer{} app.Writer = output - app.Run([]string{"test", "--help"}) + app.MustRun([]string{"test", "--help"}) s := output.String() @@ -168,7 +174,7 @@ func Test_helpCategories(t *testing.T) { app := &Application{} output := &bytes.Buffer{} app.Writer = output - app.Run([]string{"help"}) + app.MustRun([]string{"help"}) s := output.String() @@ -177,7 +183,7 @@ func Test_helpCategories(t *testing.T) { } output.Reset() - app.Run([]string{"help", "self"}) + app.MustRun([]string{"help", "self"}) s = output.String() if !strings.Contains(s, "Available commands for the \"self\" namespace:") { @@ -200,7 +206,7 @@ func TestShowAppHelp_CommandAliases(t *testing.T) { output := &bytes.Buffer{} app.Writer = output - app.Run([]string{"foo", "--help"}) + app.MustRun([]string{"foo", "--help"}) if !strings.Contains(output.String(), "frobbly, fr, frob") { t.Errorf("expected output to include all command aliases; got: %q", output.String()) @@ -226,7 +232,7 @@ func TestShowCommandHelp_CommandAliases(t *testing.T) { output := &bytes.Buffer{} app.Writer = output - app.Run([]string{"foo", "help", "fr"}) + app.MustRun([]string{"foo", "help", "fr"}) if !strings.Contains(output.String(), "frobbly") { t.Errorf("expected output to include command name; got: %q", output.String()) @@ -253,7 +259,7 @@ func TestShowCommandHelp_CommandShortcut(t *testing.T) { output := &bytes.Buffer{} app.Writer = output - app.Run([]string{"foo", "help", "f:b"}) + app.MustRun([]string{"foo", "help", "f:b"}) if !strings.Contains(output.String(), "foo:bar") { t.Errorf("expected output to include command name; got: %q", output.String()) @@ -278,7 +284,7 @@ func TestShowCommandHelp_DescriptionFunc(t *testing.T) { output := &bytes.Buffer{} app.Writer = output - app.Run([]string{"foo", "help", "frobbly"}) + app.MustRun([]string{"foo", "help", "frobbly"}) if !strings.Contains(output.String(), "this is my custom description") { t.Errorf("expected output to include result of DescriptionFunc; got: %q", output.String()) @@ -306,7 +312,7 @@ func TestShowAppHelp_HiddenCommand(t *testing.T) { output := &bytes.Buffer{} app.Writer = output - app.Run([]string{"app", "--help"}) + app.MustRun([]string{"app", "--help"}) if strings.Contains(output.String(), "secretfrob") { t.Errorf("expected output to exclude \"secretfrob\"; got: %q", output.String()) diff --git a/logging_flags.go b/logging_flags.go index 0129469..e29b4de 100644 --- a/logging_flags.go +++ b/logging_flags.go @@ -68,10 +68,10 @@ func (r *logLevelShortcutValue) IsBoolFlag() bool { return true } func (r *logLevelShortcutValue) Set(s string) error { if s != "" && s != "true" { - return r.set.Set(r.target, s) + return errors.WithStack(r.set.Set(r.target, s)) } - return r.set.Set(r.target, r.logLevel) + return errors.WithStack(r.set.Set(r.target, r.logLevel)) } func (r *logLevelShortcutValue) String() string { diff --git a/logging_flags_test.go b/logging_flags_test.go index 1cd1c27..ae4dff9 100644 --- a/logging_flags_test.go +++ b/logging_flags_test.go @@ -32,7 +32,9 @@ type LoggingFlagsSuite struct{} var _ = Suite(&LoggingFlagsSuite{}) func (ts *LoggingFlagsSuite) TestLogLevel(c *C) { - defer terminal.SetLogLevel(1) + defer func() { + c.Assert(terminal.SetLogLevel(1), IsNil) + }() value := &logLevelValue{} var err error @@ -58,7 +60,9 @@ func (ts *LoggingFlagsSuite) TestLogLevel(c *C) { } func (ts *LoggingFlagsSuite) TestLogLevelShortcuts(c *C) { - defer terminal.SetLogLevel(1) + defer func() { + c.Assert(terminal.SetLogLevel(1), IsNil) + }() fs := flag.NewFlagSet("foo", flag.ExitOnError) fs.Var(&logLevelValue{}, "log-level", "FooBar")