diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22bb4f4..671fb9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,15 +3,42 @@ on: branches: - master pull_request: + name: CI jobs: test: name: Test runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + go-version: ['1.17', '1.18', '1.19', '1.20', '1.21', 'stable'] steps: - - name: Checkout code + - name: Checkout Repository uses: actions/checkout@v4 + + - name: Setup Golang Environment + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go-version }} + - name: Init Hermit run: ./bin/hermit env -r >> $GITHUB_ENV + - name: Test - run: go test ./... + run: go test ./... -race -shuffle=on -v + + lint: + name: Go Lint + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Golang Environment + uses: actions/setup-go@v5 + with: + go-version: stable + + - name: Lint Go + uses: golangci/golangci-lint-action@v6 diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..fa89471 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,56 @@ +linters-settings: + misspell: + locale: US + govet: + enable-all: true +linters: + enable: + - asasalint + - asciicheck + - bidichk + - contextcheck + - dupword + - durationcheck + - errchkjson + - errname + - exportloopref + - fatcontext + - gocheckcompilerdirectives + - gochecksumtype + - gocritic + - godot + - gofmt + - gofumpt + - goimports + - gosec + - gosimple + - gosmopolitan + - govet + - ineffassign + - makezero + - misspell + - musttag + - nilerr + - noctx + - nolintlint + - paralleltest + - prealloc + - predeclared + - reassign + - staticcheck + - tagalign + - tenv + - thelper + - tparallel + - typecheck + - unparam + - unused + - usestdlibvars + - wastedassign + - whitespace + disable-all: true +issues: + max-issues-per-linter: 0 + max-same-issues: 0 +run: + timeout: 5m diff --git a/app.go b/app.go index 4f1f31b..6e11f47 100644 --- a/app.go +++ b/app.go @@ -11,10 +11,7 @@ import ( var ( ErrCommandNotSpecified = fmt.Errorf("command not specified") -) - -var ( - envarTransformRegexp = regexp.MustCompile(`[^a-zA-Z0-9_]+`) + envarTransformRegexp = regexp.MustCompile(`[^a-zA-Z0-9_]+`) ) type ApplicationValidator func(*Application) error @@ -22,30 +19,24 @@ type ApplicationValidator func(*Application) error // An Application contains the definitions of flags, arguments and commands // for an application. type Application struct { + errorWriter io.Writer + usageWriter io.Writer + usageFuncs template.FuncMap + VersionFlag *FlagClause + HelpCommand *CmdClause + HelpFlag *FlagClause + terminate func(status int) + validator ApplicationValidator + usageTemplate string + version string + author string + Help string + Name string cmdMixin - initialized bool - - Name string - Help string - - author string - version string - errorWriter io.Writer // Destination for errors. - usageWriter io.Writer // Destination for usage - usageTemplate string - usageFuncs template.FuncMap - validator ApplicationValidator - terminate func(status int) // See Terminate() - noInterspersed bool // can flags be interspersed with args (or must they come first) + noInterspersed bool defaultEnvars bool completion bool - - // Help flag. Exposed for user customisation. - HelpFlag *FlagClause - // Help command. Exposed for user customisation. May be nil. - HelpCommand *CmdClause - // Version flag. Exposed for user customisation. May be nil. - VersionFlag *FlagClause + initialized bool } // New creates a new Kingpin application instance. @@ -129,7 +120,7 @@ func (a *Application) Terminate(terminate func(int)) *Application { } // Writer specifies the writer to use for usage and errors. Defaults to os.Stderr. -// DEPRECATED: See ErrorWriter and UsageWriter. +// Deprecated: See ErrorWriter and UsageWriter. func (a *Application) Writer(w io.Writer) *Application { a.errorWriter = w a.usageWriter = w @@ -189,9 +180,8 @@ func (a *Application) parseContext(ignoreDefault bool, args []string) (*ParseCon // This will populate all flag and argument values, call all callbacks, and so // on. func (a *Application) Parse(args []string) (command string, err error) { - context, parseErr := a.ParseContext(args) - selected := []string{} + var selected []string var setValuesErr error if context == nil { @@ -239,8 +229,8 @@ func (a *Application) writeUsage(context *ParseContext, err error) { if err != nil { a.Errorf("%s", err) } - if err := a.UsageForContext(context); err != nil { - panic(err) + if errUsage := a.UsageForContext(context); errUsage != nil { + panic(errUsage) } if err != nil { a.terminate(1) diff --git a/app_test.go b/app_test.go index 27c95e5..3c519a4 100644 --- a/app_test.go +++ b/app_test.go @@ -2,14 +2,13 @@ package kingpin import ( "errors" - "io/ioutil" - - "github.com/stretchr/testify/assert" - + "io" "sort" "strings" "testing" "time" + + "github.com/stretchr/testify/assert" ) func newTestApp() *Application { @@ -17,6 +16,7 @@ func newTestApp() *Application { } func TestCommander(t *testing.T) { + t.Parallel() c := newTestApp() ping := c.Command("ping", "Ping an IP address.") pingTTL := ping.Flag("ttl", "TTL for ICMP packets").Short('t').Default("5s").Duration() @@ -33,6 +33,7 @@ func TestCommander(t *testing.T) { } func TestRequiredFlags(t *testing.T) { + t.Parallel() c := newTestApp() c.Flag("a", "a").String() c.Flag("b", "b").Required().String() @@ -44,6 +45,7 @@ func TestRequiredFlags(t *testing.T) { } func TestRepeatableFlags(t *testing.T) { + t.Parallel() c := newTestApp() c.Flag("a", "a").String() c.Flag("b", "b").Strings() @@ -54,6 +56,7 @@ func TestRepeatableFlags(t *testing.T) { } func TestInvalidDefaultFlagValueErrors(t *testing.T) { + t.Parallel() c := newTestApp() c.Flag("foo", "foo").Default("a").Int() _, err := c.Parse([]string{}) @@ -61,6 +64,7 @@ func TestInvalidDefaultFlagValueErrors(t *testing.T) { } func TestInvalidDefaultArgValueErrors(t *testing.T) { + t.Parallel() c := newTestApp() cmd := c.Command("cmd", "cmd") cmd.Arg("arg", "arg").Default("one").Int() @@ -69,6 +73,7 @@ func TestInvalidDefaultArgValueErrors(t *testing.T) { } func TestArgsRequiredAfterNonRequiredErrors(t *testing.T) { + t.Parallel() c := newTestApp() cmd := c.Command("cmd", "") cmd.Arg("a", "a").String() @@ -78,7 +83,8 @@ func TestArgsRequiredAfterNonRequiredErrors(t *testing.T) { } func TestArgsMultipleRequiredThenNonRequired(t *testing.T) { - c := newTestApp().Writer(ioutil.Discard) + t.Parallel() + c := newTestApp().Writer(io.Discard) cmd := c.Command("cmd", "") cmd.Arg("a", "a").Required().String() cmd.Arg("b", "b").Required().String() @@ -91,6 +97,7 @@ func TestArgsMultipleRequiredThenNonRequired(t *testing.T) { } func TestDispatchCallbackIsCalled(t *testing.T) { + t.Parallel() dispatched := false c := newTestApp() c.Command("cmd", "").Action(func(*ParseContext) error { @@ -104,6 +111,7 @@ func TestDispatchCallbackIsCalled(t *testing.T) { } func TestTopLevelArgWorks(t *testing.T) { + t.Parallel() c := newTestApp() s := c.Arg("arg", "help").String() _, err := c.Parse([]string{"foo"}) @@ -112,6 +120,7 @@ func TestTopLevelArgWorks(t *testing.T) { } func TestTopLevelArgCantBeUsedWithCommands(t *testing.T) { + t.Parallel() c := newTestApp() c.Arg("arg", "help").String() c.Command("cmd", "help") @@ -120,6 +129,7 @@ func TestTopLevelArgCantBeUsedWithCommands(t *testing.T) { } func TestTooManyArgs(t *testing.T) { + t.Parallel() a := newTestApp() a.Arg("a", "").String() _, err := a.Parse([]string{"a", "b"}) @@ -127,6 +137,7 @@ func TestTooManyArgs(t *testing.T) { } func TestTooManyArgsAfterCommand(t *testing.T) { + t.Parallel() a := newTestApp() a.Command("a", "") assert.NoError(t, a.init()) @@ -135,6 +146,7 @@ func TestTooManyArgsAfterCommand(t *testing.T) { } func TestArgsLooksLikeFlagsWithConsumeRemainder(t *testing.T) { + t.Parallel() a := newTestApp() a.Arg("opts", "").Required().Strings() _, err := a.Parse([]string{"hello", "-world"}) @@ -142,6 +154,7 @@ func TestArgsLooksLikeFlagsWithConsumeRemainder(t *testing.T) { } func TestCommandParseDoesNotResetFlagsToDefault(t *testing.T) { + t.Parallel() app := newTestApp() flag := app.Flag("flag", "").Default("default").String() app.Command("cmd", "") @@ -152,6 +165,7 @@ func TestCommandParseDoesNotResetFlagsToDefault(t *testing.T) { } func TestCommandParseDoesNotFailRequired(t *testing.T) { + t.Parallel() app := newTestApp() flag := app.Flag("flag", "").Required().String() app.Command("cmd", "") @@ -162,6 +176,7 @@ func TestCommandParseDoesNotFailRequired(t *testing.T) { } func TestSelectedCommand(t *testing.T) { + t.Parallel() app := newTestApp() c0 := app.Command("c0", "") c0.Command("c1", "") @@ -171,6 +186,7 @@ func TestSelectedCommand(t *testing.T) { } func TestSubCommandRequired(t *testing.T) { + t.Parallel() app := newTestApp() c0 := app.Command("c0", "") c0.Command("c1", "") @@ -179,6 +195,7 @@ func TestSubCommandRequired(t *testing.T) { } func TestInterspersedFalse(t *testing.T) { + t.Parallel() app := newTestApp().Interspersed(false) a1 := app.Arg("a1", "").String() a2 := app.Arg("a2", "").String() @@ -192,6 +209,7 @@ func TestInterspersedFalse(t *testing.T) { } func TestInterspersedTrue(t *testing.T) { + t.Parallel() // test once with the default value and once with explicit true for i := 0; i < 2; i++ { app := newTestApp() @@ -214,6 +232,7 @@ func TestInterspersedTrue(t *testing.T) { } func TestDefaultEnvars(t *testing.T) { + t.Parallel() a := New("some-app", "").Terminate(nil).DefaultEnvars() f0 := a.Flag("some-flag", "") f0.Bool() @@ -229,6 +248,7 @@ func TestDefaultEnvars(t *testing.T) { } func TestBashCompletionOptionsWithEmptyApp(t *testing.T) { + t.Parallel() a := newTestApp() context, err := a.ParseContext([]string{"--completion-bash"}) if err != nil { @@ -239,6 +259,7 @@ func TestBashCompletionOptionsWithEmptyApp(t *testing.T) { } func TestBashCompletionOptions(t *testing.T) { + t.Parallel() a := newTestApp() a.Command("one", "") a.Flag("flag-0", "").String() @@ -411,10 +432,10 @@ func TestBashCompletionOptions(t *testing.T) { assert.Equal(t, c.ExpectedOptions, args, "Expected != Actual: [%v] != [%v]. \nInput was: [%v]", c.ExpectedOptions, args, c.Args) } - } func TestCmdValidation(t *testing.T) { + t.Parallel() c := newTestApp() cmd := c.Command("cmd", "") @@ -436,6 +457,7 @@ func TestCmdValidation(t *testing.T) { } func TestVersion(t *testing.T) { + t.Parallel() c := newTestApp() c.Flag("config", "path to config file").Default("config.yaml").ExistingFile() c.Version("1.0.0") diff --git a/args.go b/args.go index 54e4107..fc906b8 100644 --- a/args.go +++ b/args.go @@ -64,14 +64,14 @@ func (a *argGroup) init() error { } type ArgClause struct { - actionMixin parserMixin + name string + help string + placeholder string + actionMixin completionsMixin envarMixin - name string - help string defaultValues []string - placeholder string hidden bool required bool } @@ -129,7 +129,7 @@ func (a *ArgClause) Hidden() *ArgClause { } // PlaceHolder sets the place-holder string used for arg values in the help. The -// default behaviour is to use the arg name between < > brackets. +// default behavior is to use the arg name between < > brackets. func (a *ArgClause) PlaceHolder(value string) *ArgClause { a.placeholder = value return a @@ -174,13 +174,13 @@ func (a *ArgClause) PreAction(action Action) *ArgClause { return a } -// HintAction registers a HintAction (function) for the arg to provide completions +// HintAction registers a HintAction (function) for the arg to provide completions. func (a *ArgClause) HintAction(action HintAction) *ArgClause { a.addHintAction(action) return a } -// HintOptions registers any number of options for the flag to provide completions +// HintOptions registers any number of options for the flag to provide completions. func (a *ArgClause) HintOptions(options ...string) *ArgClause { a.addHintAction(func() []string { return options diff --git a/args_test.go b/args_test.go index 695b208..0207960 100644 --- a/args_test.go +++ b/args_test.go @@ -1,14 +1,14 @@ package kingpin import ( - "io/ioutil" - "os" + "io" "testing" "github.com/stretchr/testify/assert" ) func TestArgRemainder(t *testing.T) { + t.Parallel() app := New("test", "") v := app.Arg("test", "").Strings() args := []string{"hello", "world"} @@ -18,6 +18,7 @@ func TestArgRemainder(t *testing.T) { } func TestArgRemainderErrorsWhenNotLast(t *testing.T) { + t.Parallel() a := newArgGroup() a.Arg("test", "").Strings() a.Arg("test2", "").String() @@ -25,9 +26,10 @@ func TestArgRemainderErrorsWhenNotLast(t *testing.T) { } func TestArgMultipleRequired(t *testing.T) { + t.Parallel() terminated := false app := New("test", "") - app.Version("0.0.0").Writer(ioutil.Discard) + app.Version("0.0.0").Writer(io.Discard) app.Arg("a", "").Required().String() app.Arg("b", "").Required().String() app.Terminate(func(int) { terminated = true }) @@ -43,6 +45,7 @@ func TestArgMultipleRequired(t *testing.T) { } func TestInvalidArgsDefaultCanBeOverridden(t *testing.T) { + t.Parallel() app := New("test", "") app.Arg("a", "").Default("invalid").Bool() _, err := app.Parse([]string{}) @@ -50,6 +53,7 @@ func TestInvalidArgsDefaultCanBeOverridden(t *testing.T) { } func TestArgMultipleValuesDefault(t *testing.T) { + t.Parallel() app := New("test", "") a := app.Arg("a", "").Default("default1", "default2").Strings() _, err := app.Parse([]string{}) @@ -58,6 +62,7 @@ func TestArgMultipleValuesDefault(t *testing.T) { } func TestRequiredArgWithEnvarMissingErrors(t *testing.T) { + t.Parallel() app := newTestApp() app.Arg("t", "").Envar("TEST_ARG_ENVAR").Required().Int() _, err := app.Parse([]string{}) @@ -65,7 +70,7 @@ func TestRequiredArgWithEnvarMissingErrors(t *testing.T) { } func TestArgRequiredWithEnvar(t *testing.T) { - os.Setenv("TEST_ARG_ENVAR", "123") + t.Setenv("TEST_ARG_ENVAR", "123") app := newTestApp() flag := app.Arg("t", "").Envar("TEST_ARG_ENVAR").Required().Int() _, err := app.Parse([]string{}) @@ -74,7 +79,7 @@ func TestArgRequiredWithEnvar(t *testing.T) { } func TestSubcommandArgRequiredWithEnvar(t *testing.T) { - os.Setenv("TEST_ARG_ENVAR", "123") + t.Setenv("TEST_ARG_ENVAR", "123") app := newTestApp() cmd := app.Command("command", "") flag := cmd.Arg("t", "").Envar("TEST_ARG_ENVAR").Required().Int() diff --git a/cmd.go b/cmd.go index cd7e612..29adf85 100644 --- a/cmd.go +++ b/cmd.go @@ -124,7 +124,6 @@ func (c *cmdMixin) FlagCompletion(flagName string, flagValue string) (choices [] } // No Flag directly matched. return options, false, false - } type cmdGroup struct { @@ -166,16 +165,6 @@ func newCmdGroup(app *Application) *cmdGroup { } } -func (c *cmdGroup) flattenedCommands() (out []*CmdClause) { - for _, cmd := range c.commandOrder { - if len(cmd.commands) == 0 { - out = append(out, cmd) - } - out = append(out, cmd.flattenedCommands()...) - } - return -} - func (c *cmdGroup) addCommand(name, help string) *CmdClause { cmd := newCommand(c.app, name, help) c.commands[name] = cmd @@ -222,16 +211,16 @@ type CmdClauseValidator func(*CmdClause) error // A CmdClause is a single top-level command. It encapsulates a set of flags // and either subcommands or positional arguments. type CmdClause struct { + app *Application + validator CmdClauseValidator + name string + help string + helpLong string cmdMixin - app *Application - name string aliases []string - help string - helpLong string + completionAlts []string isDefault bool - validator CmdClauseValidator hidden bool - completionAlts []string } func newCommand(app *Application, name, help string) *CmdClause { diff --git a/cmd/genvalues/main.go b/cmd/genvalues/main.go index a2c6fec..d7fb621 100644 --- a/cmd/genvalues/main.go +++ b/cmd/genvalues/main.go @@ -6,6 +6,9 @@ import ( "os/exec" "strings" "text/template" + + "golang.org/x/text/cases" + "golang.org/x/text/language" ) const ( @@ -15,7 +18,7 @@ const ( {{range .}} {{if not .NoValueParser}} -// -- {{.Type}} Value +// -- {{.Type}} Value. type {{.|ValueName}} struct { v *{{.Type}} } func new{{.|Name}}Value(p *{{.Type}}) *{{.|ValueName}} { @@ -69,12 +72,12 @@ func (p *parserMixin) {{.|Plural}}Var(target *[]{{.Type}}) { type Value struct { Name string `json:"name"` - NoValueParser bool `json:"no_value_parser"` Type string `json:"type"` Parser string `json:"parser"` Format string `json:"format"` Plural string `json:"plural"` Help string `json:"help"` + NoValueParser bool `json:"no_value_parser"` } func fatalIfError(err error) { @@ -92,11 +95,12 @@ func main() { err = json.NewDecoder(r).Decode(&v) fatalIfError(err) + c := cases.Title(language.English) valueName := func(v *Value) string { if v.Name != "" { return v.Name } - return strings.Title(v.Type) + return c.String(v.Type) } t, err := template.New("genvalues").Funcs(template.FuncMap{ diff --git a/cmd_test.go b/cmd_test.go index 8f307a0..10e0942 100644 --- a/cmd_test.go +++ b/cmd_test.go @@ -3,10 +3,9 @@ package kingpin import ( "sort" "strings" + "testing" "github.com/stretchr/testify/assert" - - "testing" ) func parseAndExecute(app *Application, context *ParseContext) (string, error) { @@ -23,6 +22,7 @@ func parseAndExecute(app *Application, context *ParseContext) (string, error) { } func complete(t *testing.T, app *Application, args ...string) []string { + t.Helper() context, err := app.ParseContext(args) assert.NoError(t, err) if err != nil { @@ -36,6 +36,7 @@ func complete(t *testing.T, app *Application, args ...string) []string { } func TestNestedCommands(t *testing.T) { + t.Parallel() app := New("app", "") sub1 := app.Command("sub1", "") sub1.Flag("sub1", "") @@ -54,6 +55,7 @@ func TestNestedCommands(t *testing.T) { } func TestNestedCommandsWithArgs(t *testing.T) { + t.Parallel() app := New("app", "") cmd := app.Command("a", "").Command("b", "") a := cmd.Arg("a", "").String() @@ -68,6 +70,7 @@ func TestNestedCommandsWithArgs(t *testing.T) { } func TestNestedCommandsWithFlags(t *testing.T) { + t.Parallel() app := New("app", "") cmd := app.Command("a", "").Command("b", "") a := cmd.Flag("aaa", "").Short('a').String() @@ -84,6 +87,7 @@ func TestNestedCommandsWithFlags(t *testing.T) { } func TestNestedCommandWithMergedFlags(t *testing.T) { + t.Parallel() app := New("app", "") cmd0 := app.Command("a", "") cmd0f0 := cmd0.Flag("aflag", "").Bool() @@ -102,6 +106,7 @@ func TestNestedCommandWithMergedFlags(t *testing.T) { } func TestNestedCommandWithDuplicateFlagErrors(t *testing.T) { + t.Parallel() app := New("app", "") app.Flag("test", "").Bool() app.Command("cmd0", "").Flag("test", "").Bool() @@ -110,6 +115,7 @@ func TestNestedCommandWithDuplicateFlagErrors(t *testing.T) { } func TestNestedCommandWithArgAndMergedFlags(t *testing.T) { + t.Parallel() app := New("app", "") cmd0 := app.Command("a", "") cmd0f0 := cmd0.Flag("aflag", "").Bool() @@ -130,6 +136,7 @@ func TestNestedCommandWithArgAndMergedFlags(t *testing.T) { } func TestDefaultSubcommandEOL(t *testing.T) { + t.Parallel() app := newTestApp() c0 := app.Command("c0", "").Default() c0.Command("c01", "").Default() @@ -141,6 +148,7 @@ func TestDefaultSubcommandEOL(t *testing.T) { } func TestDefaultSubcommandWithArg(t *testing.T) { + t.Parallel() app := newTestApp() c0 := app.Command("c0", "").Default() c01 := c0.Command("c01", "").Default() @@ -155,6 +163,7 @@ func TestDefaultSubcommandWithArg(t *testing.T) { } func TestDefaultSubcommandWithFlags(t *testing.T) { + t.Parallel() app := newTestApp() c0 := app.Command("c0", "").Default() _ = c0.Flag("f0", "").Int() @@ -169,6 +178,7 @@ func TestDefaultSubcommandWithFlags(t *testing.T) { } func TestMultipleDefaultCommands(t *testing.T) { + t.Parallel() app := newTestApp() app.Command("c0", "").Default() app.Command("c1", "").Default() @@ -177,6 +187,7 @@ func TestMultipleDefaultCommands(t *testing.T) { } func TestAliasedCommand(t *testing.T) { + t.Parallel() app := newTestApp() app.Command("one", "").Alias("two") selected, _ := app.Parse([]string{"one"}) @@ -188,6 +199,7 @@ func TestAliasedCommand(t *testing.T) { } func TestDuplicateAlias(t *testing.T) { + t.Parallel() app := newTestApp() app.Command("one", "") app.Command("two", "").Alias("one") @@ -196,6 +208,7 @@ func TestDuplicateAlias(t *testing.T) { } func TestFlagCompletion(t *testing.T) { + t.Parallel() app := newTestApp() app.Command("one", "") two := app.Command("two", "") @@ -204,12 +217,12 @@ func TestFlagCompletion(t *testing.T) { two.Flag("flag-3", "") cases := []struct { - target cmdMixin flagName string flagValue string + target cmdMixin + expectedFlags []string expectedFlagMatch bool expectedOptionMatch bool - expectedFlags []string }{ { // Test top level flags @@ -275,10 +288,10 @@ func TestFlagCompletion(t *testing.T) { assert.Equal(t, c.expectedFlagMatch, flagMatch, "Test case %d: expectedFlagMatch != flagMatch", i+1) assert.Equal(t, c.expectedOptionMatch, optionMatch, "Test case %d: expectedOptionMatch != optionMatch", i+1) } - } func TestCmdCompletion(t *testing.T) { + t.Parallel() app := newTestApp() app.Command("one", "") two := app.Command("two", "") @@ -290,6 +303,7 @@ func TestCmdCompletion(t *testing.T) { } func TestHiddenCmdCompletion(t *testing.T) { + t.Parallel() app := newTestApp() // top level visible & hidden cmds, with no sub-cmds @@ -317,6 +331,7 @@ func TestHiddenCmdCompletion(t *testing.T) { } func TestDefaultCmdCompletion(t *testing.T) { + t.Parallel() app := newTestApp() cmd1 := app.Command("cmd1", "") @@ -375,6 +390,7 @@ func TestDefaultCmdCompletion(t *testing.T) { } func TestPartialCmdCompletion(t *testing.T) { + t.Parallel() app := newTestApp() cmd1 := app.Command("cmd1", "") @@ -388,7 +404,6 @@ func TestPartialCmdCompletion(t *testing.T) { cmd4.Arg("cmd4-arg2", "").HintOptions("cmd4-arg2").String() cmd4.Arg("cmd4-arg3", "").HintOptions("cmd4-arg3").String() - // partial matches assert.Equal(t, []string{"cmd1-arg1-opt1", "cmd1-arg1-opt2", "cmd1-arg1-opt3"}, complete(t, app, "cmd1", "cmd1-arg1-opt")) assert.Equal(t, []string{"cmd2-123456", "cmd2-123789", "cmd2-456789"}, complete(t, app, "cmd2", "cmd2-")) diff --git a/completions.go b/completions.go index 6e7b409..2221350 100644 --- a/completions.go +++ b/completions.go @@ -2,17 +2,19 @@ package kingpin // HintAction is a function type who is expected to return a slice of possible // command line arguments. -type HintAction func() []string -type completionsMixin struct { - hintActions []HintAction - builtinHintActions []HintAction -} +type ( + HintAction func() []string + completionsMixin struct { + hintActions []HintAction + builtinHintActions []HintAction + } +) func (a *completionsMixin) addHintAction(action HintAction) { a.hintActions = append(a.hintActions, action) } -// Allow adding of HintActions which are added internally, ie, EnumVar +// Allow adding of HintActions which are added internally, ie, EnumVar. func (a *completionsMixin) addHintActionBuiltin(action HintAction) { a.builtinHintActions = append(a.builtinHintActions, action) } diff --git a/completions_test.go b/completions_test.go index 7da9c06..06eea77 100644 --- a/completions_test.go +++ b/completions_test.go @@ -7,6 +7,7 @@ import ( ) func TestResolveWithBuiltin(t *testing.T) { + t.Parallel() a := completionsMixin{} hintAction1 := func() []string { @@ -23,6 +24,7 @@ func TestResolveWithBuiltin(t *testing.T) { } func TestResolveWithUser(t *testing.T) { + t.Parallel() a := completionsMixin{} hintAction1 := func() []string { return []string{"opt1", "opt2"} @@ -38,6 +40,7 @@ func TestResolveWithUser(t *testing.T) { } func TestResolveWithCombination(t *testing.T) { + t.Parallel() a := completionsMixin{} builtin := func() []string { return []string{"opt1", "opt2"} @@ -55,6 +58,7 @@ func TestResolveWithCombination(t *testing.T) { } func TestAddHintAction(t *testing.T) { + t.Parallel() a := completionsMixin{} hintFunc := func() []string { return []string{"opt1", "opt2"} @@ -66,6 +70,7 @@ func TestAddHintAction(t *testing.T) { } func TestAddHintActionBuiltin(t *testing.T) { + t.Parallel() a := completionsMixin{} hintFunc := func() []string { return []string{"opt1", "opt2"} diff --git a/doc.go b/doc.go index 8a72729..a8ca25d 100644 --- a/doc.go +++ b/doc.go @@ -1,68 +1,68 @@ // Package kingpin provides command line interfaces like this: // -// $ chat -// usage: chat [] [] [ ...] +// $ chat +// usage: chat [] [] [ ...] // -// Flags: -// --debug enable debug mode -// --help Show help. -// --server=127.0.0.1 server address +// Flags: +// --debug enable debug mode +// --help Show help. +// --server=127.0.0.1 server address // -// Commands: -// help -// Show help for a command. +// Commands: +// help +// Show help for a command. // -// post [] -// Post a message to a channel. +// post [] +// Post a message to a channel. // -// register -// Register a new user. +// register +// Register a new user. // -// $ chat help post -// usage: chat [] post [] [] +// $ chat help post +// usage: chat [] post [] [] // -// Post a message to a channel. +// Post a message to a channel. // -// Flags: -// --image=IMAGE image to post +// Flags: +// --image=IMAGE image to post // -// Args: -// channel to post to -// [] text to post -// $ chat post --image=~/Downloads/owls.jpg pics +// Args: +// channel to post to +// [] text to post +// $ chat post --image=~/Downloads/owls.jpg pics // // From code like this: // -// package main -// -// import "github.com/alecthomas/kingpin/v2" -// -// var ( -// debug = kingpin.Flag("debug", "enable debug mode").Default("false").Bool() -// serverIP = kingpin.Flag("server", "server address").Default("127.0.0.1").IP() -// -// register = kingpin.Command("register", "Register a new user.") -// registerNick = register.Arg("nick", "nickname for user").Required().String() -// registerName = register.Arg("name", "name of user").Required().String() -// -// post = kingpin.Command("post", "Post a message to a channel.") -// postImage = post.Flag("image", "image to post").ExistingFile() -// postChannel = post.Arg("channel", "channel to post to").Required().String() -// postText = post.Arg("text", "text to post").String() -// ) -// -// func main() { -// switch kingpin.Parse() { -// // Register user -// case "register": -// println(*registerNick) -// -// // Post message -// case "post": -// if *postImage != nil { -// } -// if *postText != "" { -// } -// } -// } +// package main +// +// import "github.com/alecthomas/kingpin/v2" +// +// var ( +// debug = kingpin.Flag("debug", "enable debug mode").Default("false").Bool() +// serverIP = kingpin.Flag("server", "server address").Default("127.0.0.1").IP() +// +// register = kingpin.Command("register", "Register a new user.") +// registerNick = register.Arg("nick", "nickname for user").Required().String() +// registerName = register.Arg("name", "name of user").Required().String() +// +// post = kingpin.Command("post", "Post a message to a channel.") +// postImage = post.Flag("image", "image to post").ExistingFile() +// postChannel = post.Arg("channel", "channel to post to").Required().String() +// postText = post.Arg("text", "text to post").String() +// ) +// +// func main() { +// switch kingpin.Parse() { +// // Register user +// case "register": +// println(*registerNick) +// +// // Post message +// case "post": +// if *postImage != nil { +// } +// if *postText != "" { +// } +// } +// } package kingpin diff --git a/flags.go b/flags.go index 2b2938b..ecb0621 100644 --- a/flags.go +++ b/flags.go @@ -82,7 +82,7 @@ loop: case TokenLong, TokenShort: flagToken := token - defaultValue := "" + var defaultValue string var flag *FlagClause var ok bool invert := false @@ -144,17 +144,17 @@ loop: // FlagClause is a fluid interface used to build flags. type FlagClause struct { + setByUser *bool parserMixin + name string + help string + placeholder string actionMixin completionsMixin envarMixin - name string - shorthand rune - help string defaultValues []string - placeholder string + shorthand rune hidden bool - setByUser *bool } func newFlag(name, help string) *FlagClause { @@ -227,13 +227,13 @@ func (f *FlagClause) PreAction(action Action) *FlagClause { return f } -// HintAction registers a HintAction (function) for the flag to provide completions +// HintAction registers a HintAction (function) for the flag to provide completions. func (a *FlagClause) HintAction(action HintAction) *FlagClause { a.addHintAction(action) return a } -// HintOptions registers any number of options for the flag to provide completions +// HintOptions registers any number of options for the flag to provide completions. func (a *FlagClause) HintOptions(options ...string) *FlagClause { a.addHintAction(func() []string { return options @@ -255,7 +255,7 @@ func (a *FlagClause) Enum(options ...string) (target *string) { return a.parserMixin.Enum(options...) } -// IsSetByUser let to know if the flag was set by the user +// IsSetByUser let to know if the flag was set by the user. func (f *FlagClause) IsSetByUser(setByUser *bool) *FlagClause { if setByUser != nil { *setByUser = false @@ -270,7 +270,7 @@ func (f *FlagClause) Default(values ...string) *FlagClause { return f } -// DEPRECATED: Use Envar(name) instead. +// Deprecated: Use Envar(name) instead. func (f *FlagClause) OverrideDefaultFromEnvar(envar string) *FlagClause { return f.Envar(envar) } @@ -293,7 +293,7 @@ func (f *FlagClause) NoEnvar() *FlagClause { } // PlaceHolder sets the place-holder string used for flag values in the help. The -// default behaviour is to use the value provided by Default() if provided, +// default behavior is to use the value provided by Default() if provided, // then fall back on the capitalized flag name. func (f *FlagClause) PlaceHolder(placeholder string) *FlagClause { f.placeholder = placeholder diff --git a/flags_test.go b/flags_test.go index 1d28e14..f08b3bf 100644 --- a/flags_test.go +++ b/flags_test.go @@ -1,15 +1,14 @@ package kingpin import ( - "io/ioutil" - "os" + "io" + "testing" "github.com/stretchr/testify/assert" - - "testing" ) func TestBool(t *testing.T) { + t.Parallel() app := newTestApp() b := app.Flag("b", "").Bool() _, err := app.Parse([]string{"--b"}) @@ -18,6 +17,7 @@ func TestBool(t *testing.T) { } func TestNoBool(t *testing.T) { + t.Parallel() fg := newFlagGroup() f := fg.Flag("b", "").Default("true") b := f.Bool() @@ -29,6 +29,7 @@ func TestNoBool(t *testing.T) { } func TestNegateNonBool(t *testing.T) { + t.Parallel() fg := newFlagGroup() f := fg.Flag("b", "") f.Int() @@ -39,6 +40,7 @@ func TestNegateNonBool(t *testing.T) { } func TestNegativePrefixLongFlag(t *testing.T) { + t.Parallel() fg := newFlagGroup() f := fg.Flag("no-comment", "") b := f.Bool() @@ -50,6 +52,7 @@ func TestNegativePrefixLongFlag(t *testing.T) { } func TestInvalidFlagDefaultCanBeOverridden(t *testing.T) { + t.Parallel() app := newTestApp() app.Flag("a", "").Default("invalid").Bool() _, err := app.Parse([]string{}) @@ -57,8 +60,9 @@ func TestInvalidFlagDefaultCanBeOverridden(t *testing.T) { } func TestRequiredFlag(t *testing.T) { + t.Parallel() app := newTestApp() - app.Version("0.0.0").Writer(ioutil.Discard) + app.Version("0.0.0").Writer(io.Discard) exits := 0 app.Terminate(func(int) { exits++ }) app.Flag("a", "").Required().Bool() @@ -71,6 +75,7 @@ func TestRequiredFlag(t *testing.T) { } func TestShortFlag(t *testing.T) { + t.Parallel() app := newTestApp() f := app.Flag("long", "").Short('s').Bool() _, err := app.Parse([]string{"-s"}) @@ -79,6 +84,7 @@ func TestShortFlag(t *testing.T) { } func TestUnicodeShortFlag(t *testing.T) { + t.Parallel() app := newTestApp() f := app.Flag("aaa", "").Short('ä').Bool() _, err := app.Parse([]string{"-ä"}) @@ -87,6 +93,7 @@ func TestUnicodeShortFlag(t *testing.T) { } func TestCombinedShortFlags(t *testing.T) { + t.Parallel() app := newTestApp() a := app.Flag("short0", "").Short('0').Bool() b := app.Flag("short1", "").Short('1').Bool() @@ -99,6 +106,7 @@ func TestCombinedShortFlags(t *testing.T) { } func TestCombinedUnicodeShortFlags(t *testing.T) { + t.Parallel() app := newTestApp() a := app.Flag("short0", "").Short('0').Bool() b := app.Flag("short1", "").Short('1').Bool() @@ -113,6 +121,7 @@ func TestCombinedUnicodeShortFlags(t *testing.T) { } func TestCombinedShortFlagArg(t *testing.T) { + t.Parallel() a := newTestApp() n := a.Flag("short", "").Short('s').Int() _, err := a.Parse([]string{"-s10"}) @@ -121,6 +130,7 @@ func TestCombinedShortFlagArg(t *testing.T) { } func TestCombinedUnicodeShortFlagArg(t *testing.T) { + t.Parallel() app := newTestApp() a := app.Flag("short", "").Short('ä').Int() _, err := app.Parse([]string{"-ä10"}) @@ -129,6 +139,7 @@ func TestCombinedUnicodeShortFlagArg(t *testing.T) { } func TestCombinedUnicodeShortFlagUnicodeArg(t *testing.T) { + t.Parallel() app := newTestApp() a := app.Flag("short", "").Short('ä').String() _, err := app.Parse([]string{"-äöö"}) @@ -137,11 +148,13 @@ func TestCombinedUnicodeShortFlagUnicodeArg(t *testing.T) { } func TestEmptyShortFlagIsAnError(t *testing.T) { + t.Parallel() _, err := newTestApp().Parse([]string{"-"}) assert.Error(t, err) } func TestRequiredWithEnvarMissingErrors(t *testing.T) { + t.Parallel() app := newTestApp() app.Flag("t", "").Envar("TEST_ENVAR").Required().Int() _, err := app.Parse([]string{}) @@ -149,7 +162,7 @@ func TestRequiredWithEnvarMissingErrors(t *testing.T) { } func TestRequiredWithEnvar(t *testing.T) { - os.Setenv("TEST_ENVAR", "123") + t.Setenv("TEST_ENVAR", "123") app := newTestApp() flag := app.Flag("t", "").Envar("TEST_ENVAR").Required().Int() _, err := app.Parse([]string{}) @@ -158,7 +171,7 @@ func TestRequiredWithEnvar(t *testing.T) { } func TestSubcommandFlagRequiredWithEnvar(t *testing.T) { - os.Setenv("TEST_ENVAR", "123") + t.Setenv("TEST_ENVAR", "123") app := newTestApp() cmd := app.Command("command", "") flag := cmd.Flag("t", "").Envar("TEST_ENVAR").Required().Int() @@ -168,6 +181,7 @@ func TestSubcommandFlagRequiredWithEnvar(t *testing.T) { } func TestRegexp(t *testing.T) { + t.Parallel() app := newTestApp() flag := app.Flag("reg", "").Regexp() _, err := app.Parse([]string{"--reg", "^abc$"}) @@ -179,6 +193,7 @@ func TestRegexp(t *testing.T) { } func TestDuplicateShortFlag(t *testing.T) { + t.Parallel() app := newTestApp() app.Flag("a", "").Short('a').String() app.Flag("b", "").Short('a').String() @@ -187,6 +202,7 @@ func TestDuplicateShortFlag(t *testing.T) { } func TestDuplicateLongFlag(t *testing.T) { + t.Parallel() app := newTestApp() app.Flag("a", "").String() app.Flag("a", "").String() @@ -195,6 +211,7 @@ func TestDuplicateLongFlag(t *testing.T) { } func TestGetFlagAndOverrideDefault(t *testing.T) { + t.Parallel() app := newTestApp() a := app.Flag("a", "").Default("default").String() _, err := app.Parse([]string{}) @@ -207,7 +224,7 @@ func TestGetFlagAndOverrideDefault(t *testing.T) { } func TestEnvarOverrideDefault(t *testing.T) { - os.Setenv("TEST_ENVAR", "123") + t.Setenv("TEST_ENVAR", "123") app := newTestApp() flag := app.Flag("t", "").Default("default").Envar("TEST_ENVAR").String() _, err := app.Parse([]string{}) @@ -216,6 +233,7 @@ func TestEnvarOverrideDefault(t *testing.T) { } func TestFlagMultipleValuesDefault(t *testing.T) { + t.Parallel() app := newTestApp() a := app.Flag("a", "").Default("default1", "default2").Strings() _, err := app.Parse([]string{}) @@ -224,6 +242,7 @@ func TestFlagMultipleValuesDefault(t *testing.T) { } func TestFlagMultipleValuesDefaultNonRepeatable(t *testing.T) { + t.Parallel() c := newTestApp() c.Flag("foo", "foo").Default("a", "b").String() _, err := c.Parse([]string{}) @@ -233,7 +252,7 @@ func TestFlagMultipleValuesDefaultNonRepeatable(t *testing.T) { func TestFlagMultipleValuesDefaultEnvarUnix(t *testing.T) { app := newTestApp() a := app.Flag("a", "").Envar("TEST_MULTIPLE_VALUES").Strings() - os.Setenv("TEST_MULTIPLE_VALUES", "123\n456\n") + t.Setenv("TEST_MULTIPLE_VALUES", "123\n456\n") _, err := app.Parse([]string{}) assert.NoError(t, err) assert.Equal(t, []string{"123", "456"}, *a) @@ -242,7 +261,7 @@ func TestFlagMultipleValuesDefaultEnvarUnix(t *testing.T) { func TestFlagMultipleValuesDefaultEnvarWindows(t *testing.T) { app := newTestApp() a := app.Flag("a", "").Envar("TEST_MULTIPLE_VALUES").Strings() - os.Setenv("TEST_MULTIPLE_VALUES", "123\r\n456\r\n") + t.Setenv("TEST_MULTIPLE_VALUES", "123\r\n456\r\n") _, err := app.Parse([]string{}) assert.NoError(t, err) assert.Equal(t, []string{"123", "456"}, *a) @@ -251,13 +270,14 @@ func TestFlagMultipleValuesDefaultEnvarWindows(t *testing.T) { func TestFlagMultipleValuesDefaultEnvarNonRepeatable(t *testing.T) { c := newTestApp() a := c.Flag("foo", "foo").Envar("TEST_MULTIPLE_VALUES_NON_REPEATABLE").String() - os.Setenv("TEST_MULTIPLE_VALUES_NON_REPEATABLE", "123\n456") + t.Setenv("TEST_MULTIPLE_VALUES_NON_REPEATABLE", "123\n456") _, err := c.Parse([]string{}) assert.NoError(t, err) assert.Equal(t, "123\n456", *a) } func TestFlagHintAction(t *testing.T) { + t.Parallel() c := newTestApp() action := func() []string { @@ -270,6 +290,7 @@ func TestFlagHintAction(t *testing.T) { } func TestFlagHintOptions(t *testing.T) { + t.Parallel() c := newTestApp() a := c.Flag("foo", "foo").HintOptions("opt1", "opt2") @@ -278,6 +299,7 @@ func TestFlagHintOptions(t *testing.T) { } func TestFlagEnumVar(t *testing.T) { + t.Parallel() c := newTestApp() var bar string @@ -294,13 +316,16 @@ func TestFlagEnumVar(t *testing.T) { } func TestMultiHintOptions(t *testing.T) { + t.Parallel() c := newTestApp() a := c.Flag("foo", "foo").HintOptions("opt1").HintOptions("opt2") args := a.resolveCompletions() assert.Equal(t, []string{"opt1", "opt2"}, args) } + func TestMultiHintActions(t *testing.T) { + t.Parallel() c := newTestApp() a := c.Flag("foo", "foo"). @@ -315,6 +340,7 @@ func TestMultiHintActions(t *testing.T) { } func TestCombinationHintActionsOptions(t *testing.T) { + t.Parallel() c := newTestApp() a := c.Flag("foo", "foo").HintAction(func() []string { @@ -325,6 +351,7 @@ func TestCombinationHintActionsOptions(t *testing.T) { } func TestCombinationEnumActions(t *testing.T) { + t.Parallel() c := newTestApp() var foo string @@ -349,6 +376,7 @@ func TestCombinationEnumActions(t *testing.T) { } func TestCombinationEnumOptions(t *testing.T) { + t.Parallel() c := newTestApp() var foo string @@ -364,10 +392,10 @@ func TestCombinationEnumOptions(t *testing.T) { args = b.resolveCompletions() assert.Equal(t, []string{"opt5", "opt6"}, args) - } func TestIsSetByUser(t *testing.T) { + t.Parallel() app := newTestApp() var isSet bool b := app.Flag("b", "").IsSetByUser(&isSet).Bool() diff --git a/go.mod b/go.mod index bef9c21..fb79e98 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 github.com/stretchr/testify v1.9.0 github.com/xhit/go-str2duration/v2 v2.1.0 + golang.org/x/text v0.16.0 ) require ( diff --git a/go.sum b/go.sum index d8e9ead..1d8428e 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,7 @@ github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30/go.mod h1:fvzegU4 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -16,6 +17,66 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/guesswidth_unix.go b/guesswidth_unix.go index ad8163f..3429240 100644 --- a/guesswidth_unix.go +++ b/guesswidth_unix.go @@ -1,3 +1,4 @@ +//go:build (!appengine && linux) || freebsd || darwin || dragonfly || netbsd || openbsd // +build !appengine,linux freebsd darwin dragonfly netbsd openbsd package kingpin diff --git a/model.go b/model.go index 382616c..d3659d8 100644 --- a/model.go +++ b/model.go @@ -8,16 +8,14 @@ import ( // Data model for Kingpin command-line structure. -var ( - ignoreInCount = map[string]bool{ - "help": true, - "help-long": true, - "help-man": true, - "completion-bash": true, - "completion-script-bash": true, - "completion-script-zsh": true, - } -) +var ignoreInCount = map[string]bool{ + "help": true, + "help-long": true, + "help-man": true, + "completion-bash": true, + "completion-script-bash": true, + "completion-script-zsh": true, +} type FlagGroupModel struct { Flags []*FlagModel @@ -28,7 +26,6 @@ func (f *FlagGroupModel) FlagSummary() string { count := 0 for _, flag := range f.Flags { - if !ignoreInCount[flag.Name] { count++ } @@ -48,15 +45,15 @@ func (f *FlagGroupModel) FlagSummary() string { } type FlagModel struct { + Value Value Name string Help string - Short rune - Default []string Envar string PlaceHolder string + Default []string + Short rune Required bool Hidden bool - Value Value } func (f *FlagModel) String() string { @@ -129,14 +126,14 @@ func (a *ArgModel) HelpWithEnvar() string { } type ArgModel struct { + Value Value Name string Help string - Default []string Envar string PlaceHolder string + Default []string Required bool Hidden bool - Value Value } func (a *ArgModel) String() string { @@ -162,17 +159,17 @@ func (c *CmdGroupModel) FlattenedCommands() (out []*CmdModel) { } type CmdModel struct { + *FlagGroupModel + *ArgGroupModel + *CmdGroupModel Name string - Aliases []string Help string HelpLong string FullCommand string + Aliases []string Depth int Hidden bool Default bool - *FlagGroupModel - *ArgGroupModel - *CmdGroupModel } func (c *CmdModel) String() string { @@ -180,13 +177,13 @@ func (c *CmdModel) String() string { } type ApplicationModel struct { + *ArgGroupModel + *CmdGroupModel + *FlagGroupModel Name string Help string Version string Author string - *ArgGroupModel - *CmdGroupModel - *FlagGroupModel } func (a *Application) Model() *ApplicationModel { diff --git a/parser.go b/parser.go index 5f28c78..6ccce94 100644 --- a/parser.go +++ b/parser.go @@ -35,14 +35,12 @@ func (t TokenType) String() string { return "?" } -var ( - TokenEOLMarker = Token{-1, TokenEOL, ""} -) +var TokenEOLMarker = Token{Value: "", Index: -1, Type: TokenEOL} type Token struct { + Value string Index int Type TokenType - Value string } func (t *Token) Equal(o *Token) bool { @@ -88,17 +86,16 @@ type ParseElement struct { // any). type ParseContext struct { SelectedCommand *CmdClause - ignoreDefault bool - argsOnly bool + flags *flagGroup + arguments *argGroup peek []*Token - argi int // Index of current command-line arg we're processing. args []string rawArgs []string - flags *flagGroup - arguments *argGroup - argumenti int // Cursor into arguments - // Flags, arguments and commands encountered and collected during parse. - Elements []*ParseElement + Elements []*ParseElement + argi int + argumenti int + ignoreDefault bool + argsOnly bool } func (p *ParseContext) nextArg() *ArgClause { @@ -174,7 +171,7 @@ func (p *ParseContext) Next() *Token { p.next() if p.argsOnly { - return &Token{p.argi, TokenArg, arg} + return &Token{Index: p.argi, Type: TokenArg, Value: arg} } if arg == "--" { @@ -183,9 +180,9 @@ func (p *ParseContext) Next() *Token { if strings.HasPrefix(arg, "--") { parts := strings.SplitN(arg[2:], "=", 2) - token := &Token{p.argi, TokenLong, parts[0]} + token := &Token{Index: p.argi, Type: TokenLong, Value: parts[0]} if len(parts) == 2 { - p.Push(&Token{p.argi, TokenArg, parts[1]}) + p.Push(&Token{Index: p.argi, Type: TokenArg, Value: parts[1]}) } return token } @@ -203,9 +200,9 @@ func (p *ParseContext) Next() *Token { // Bool short flag. } else { // Short flag with combined argument: -fARG - token := &Token{p.argi, TokenShort, short} + token := &Token{Index: p.argi, Type: TokenShort, Value: short} if len(arg) > size+1 { - p.Push(&Token{p.argi, TokenArg, arg[size+1:]}) + p.Push(&Token{Index: p.argi, Type: TokenArg, Value: arg[size+1:]}) } return token } @@ -213,11 +210,11 @@ func (p *ParseContext) Next() *Token { if len(arg) > size+1 { p.args = append([]string{"-" + arg[size+1:]}, p.args...) } - return &Token{p.argi, TokenShort, short} + return &Token{Index: p.argi, Type: TokenShort, Value: short} } else if EnableFileExpansion && strings.HasPrefix(arg, "@") { expanded, err := ExpandArgsFromFile(arg[1:]) if err != nil { - return &Token{p.argi, TokenError, err.Error()} + return &Token{Index: p.argi, Type: TokenError, Value: err.Error()} } if len(p.args) == 0 { p.args = expanded @@ -227,7 +224,7 @@ func (p *ParseContext) Next() *Token { return p.Next() } - return &Token{p.argi, TokenArg, arg} + return &Token{Index: p.argi, Type: TokenArg, Value: arg} } func (p *ParseContext) Peek() *Token { @@ -321,7 +318,8 @@ loop: } case TokenArg: - if cmds.have() { + switch { + case cmds.have(): selectedDefault := false cmd, ok := cmds.commands[token.String()] if !ok { @@ -344,7 +342,7 @@ loop: if !selectedDefault { context.Next() } - } else if context.arguments.have() { + case context.arguments.have(): if app.noInterspersed { // no more flags context.argsOnly = true @@ -355,7 +353,7 @@ loop: } context.matchedArg(arg, token.String()) context.Next() - } else { + default: break loop } diff --git a/parser_test.go b/parser_test.go index 43dfde9..c72cc2c 100644 --- a/parser_test.go +++ b/parser_test.go @@ -1,7 +1,6 @@ package kingpin import ( - "io/ioutil" "os" "testing" @@ -9,7 +8,8 @@ import ( ) func TestParserExpandFromFile(t *testing.T) { - f, err := ioutil.TempFile("", "") + t.Parallel() + f, err := os.CreateTemp("", "") assert.NoError(t, err) defer os.Remove(f.Name()) f.WriteString("hello\nworld\n") @@ -26,7 +26,8 @@ func TestParserExpandFromFile(t *testing.T) { } func TestParserExpandFromFileLeadingArg(t *testing.T) { - f, err := ioutil.TempFile("", "") + t.Parallel() + f, err := os.CreateTemp("", "") assert.NoError(t, err) defer os.Remove(f.Name()) f.WriteString("hello\nworld\n") @@ -45,7 +46,8 @@ func TestParserExpandFromFileLeadingArg(t *testing.T) { } func TestParserExpandFromFileTrailingArg(t *testing.T) { - f, err := ioutil.TempFile("", "") + t.Parallel() + f, err := os.CreateTemp("", "") assert.NoError(t, err) defer os.Remove(f.Name()) f.WriteString("hello\nworld\n") @@ -64,7 +66,8 @@ func TestParserExpandFromFileTrailingArg(t *testing.T) { } func TestParserExpandFromFileMultipleSurroundingArgs(t *testing.T) { - f, err := ioutil.TempFile("", "") + t.Parallel() + f, err := os.CreateTemp("", "") assert.NoError(t, err) defer os.Remove(f.Name()) f.WriteString("hello\nworld\n") @@ -85,7 +88,8 @@ func TestParserExpandFromFileMultipleSurroundingArgs(t *testing.T) { } func TestParserExpandFromFileMultipleFlags(t *testing.T) { - f, err := ioutil.TempFile("", "") + t.Parallel() + f, err := os.CreateTemp("", "") assert.NoError(t, err) defer os.Remove(f.Name()) f.WriteString("--flag1=f1\n--flag2=f2\n") @@ -106,6 +110,7 @@ func TestParserExpandFromFileMultipleFlags(t *testing.T) { } func TestParseContextPush(t *testing.T) { + t.Parallel() app := New("test", "") app.Command("foo", "").Command("bar", "") c := tokenize([]string{"foo", "bar"}, false) diff --git a/parsers.go b/parsers.go index 5a06882..02add63 100644 --- a/parsers.go +++ b/parsers.go @@ -40,7 +40,7 @@ func (p *parserMixin) Duration() (target *time.Duration) { return } -// Bytes parses numeric byte units. eg. 1.5KB +// Bytes parses numeric byte units. eg. 1.5KB. func (p *parserMixin) Bytes() (target *units.Base2Bytes) { target = new(units.Base2Bytes) p.BytesVar(target) @@ -128,7 +128,7 @@ func (p *parserMixin) DurationVar(target *time.Duration) { p.SetValue(newDurationValue(target)) } -// BytesVar parses numeric byte units. eg. 1.5KB +// BytesVar parses numeric byte units. eg. 1.5KB. func (p *parserMixin) BytesVar(target *units.Base2Bytes) { p.SetValue(newBytesValue(target)) } @@ -158,7 +158,7 @@ func (p *parserMixin) FileVar(target **os.File) { p.SetValue(newFileValue(target, os.O_RDONLY, 0)) } -// OpenFileVar calls os.OpenFile(flag, perm) +// OpenFileVar calls os.OpenFile(flag, perm). func (p *parserMixin) OpenFileVar(target **os.File, flag int, perm os.FileMode) { p.SetValue(newFileValue(target, flag, perm)) } diff --git a/parsers_test.go b/parsers_test.go index 81708c7..fe60e26 100644 --- a/parsers_test.go +++ b/parsers_test.go @@ -1,17 +1,16 @@ package kingpin import ( - "io/ioutil" "net" "net/url" "os" + "testing" "github.com/stretchr/testify/assert" - - "testing" ) func TestParseStrings(t *testing.T) { + t.Parallel() p := parserMixin{} v := p.Strings() p.value.Set("a") @@ -20,6 +19,7 @@ func TestParseStrings(t *testing.T) { } func TestStringsStringer(t *testing.T) { + t.Parallel() target := []string{} v := newAccumulator(&target, func(v interface{}) Value { return newStringValue(v.(*string)) }) v.Set("hello") @@ -28,6 +28,7 @@ func TestStringsStringer(t *testing.T) { } func TestParseStringMap(t *testing.T) { + t.Parallel() p := parserMixin{} v := p.StringMap() p.value.Set("a:b") @@ -36,6 +37,7 @@ func TestParseStringMap(t *testing.T) { } func TestParseIP(t *testing.T) { + t.Parallel() p := parserMixin{} v := p.IP() p.value.Set("10.1.1.2") @@ -44,6 +46,7 @@ func TestParseIP(t *testing.T) { } func TestParseURL(t *testing.T) { + t.Parallel() p := parserMixin{} v := p.URL() p.value.Set("http://w3.org") @@ -53,7 +56,8 @@ func TestParseURL(t *testing.T) { } func TestParseExistingFile(t *testing.T) { - f, err := ioutil.TempFile("", "") + t.Parallel() + f, err := os.CreateTemp("", "") if err != nil { t.Fatal(err) } @@ -70,6 +74,7 @@ func TestParseExistingFile(t *testing.T) { } func TestParseTCPAddr(t *testing.T) { + t.Parallel() p := parserMixin{} v := p.TCP() err := p.value.Set("127.0.0.1:1234") @@ -80,6 +85,7 @@ func TestParseTCPAddr(t *testing.T) { } func TestParseTCPAddrList(t *testing.T) { + t.Parallel() p := parserMixin{} _ = p.TCPList() err := p.value.Set("127.0.0.1:1234") @@ -90,6 +96,7 @@ func TestParseTCPAddrList(t *testing.T) { } func TestFloat32(t *testing.T) { + t.Parallel() p := parserMixin{} v := p.Float32() err := p.value.Set("123.45") diff --git a/templates.go b/templates.go index 703c2cd..05dd50b 100644 --- a/templates.go +++ b/templates.go @@ -47,7 +47,7 @@ Commands: {{end -}} ` -// Usage template where command's optional flags are listed separately +// Usage template where command's optional flags are listed separately. var SeparateOptionalFlagsUsageTemplate = `{{define "FormatCommand" -}} {{if .FlagSummary}} {{.FlagSummary}}{{end -}} {{range .Args}}{{if not .Hidden}} {{if not .Required}}[{{end}}{{if .PlaceHolder}}{{.PlaceHolder}}{{else}}<{{.Name}}>{{end}}{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}{{end -}} diff --git a/usage.go b/usage.go index 9b3dd73..a8b0f2b 100644 --- a/usage.go +++ b/usage.go @@ -9,9 +9,7 @@ import ( "text/template" ) -var ( - preIndent = " " -) +var preIndent = " " func formatTwoColumns(w io.Writer, indent, padding, width int, rows [][2]string) { // Find size of first column. @@ -104,8 +102,8 @@ type templateParseContext struct { type templateContext struct { App *ApplicationModel - Width int Context *templateParseContext + Width int } // UsageForContext displays usage information from a ParseContext (obtained from diff --git a/usage_test.go b/usage_test.go index a438748..725e987 100644 --- a/usage_test.go +++ b/usage_test.go @@ -10,6 +10,7 @@ import ( ) func TestFormatTwoColumns(t *testing.T) { + t.Parallel() buf := bytes.NewBuffer(nil) formatTwoColumns(buf, 2, 2, 20, [][2]string{ {"--hello", "Hello world help with something that is cool."}, @@ -25,9 +26,11 @@ func TestFormatTwoColumns(t *testing.T) { } func TestFormatTwoColumnsWide(t *testing.T) { + t.Parallel() samples := [][2]string{ {strings.Repeat("x", 29), "29 chars"}, - {strings.Repeat("x", 30), "30 chars"}} + {strings.Repeat("x", 30), "30 chars"}, + } buf := bytes.NewBuffer(nil) formatTwoColumns(buf, 0, 0, 200, samples) expected := `xxxxxxxxxxxxxxxxxxxxxxxxxxxxx29 chars @@ -38,6 +41,7 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx } func TestHiddenCommand(t *testing.T) { + t.Parallel() templates := []struct{ name, template string }{ {"default", DefaultUsageTemplate}, {"Compact", CompactUsageTemplate}, @@ -66,6 +70,7 @@ func TestHiddenCommand(t *testing.T) { } func TestUsageFuncs(t *testing.T) { + t.Parallel() var buf bytes.Buffer a := New("test", "Test").Writer(&buf).Terminate(nil) tpl := `{{ add 2 1 }}` @@ -79,6 +84,7 @@ func TestUsageFuncs(t *testing.T) { } func TestCmdClause_HelpLong(t *testing.T) { + t.Parallel() var buf bytes.Buffer tpl := `{{define "FormatUsage"}}{{.HelpLong}}{{end -}} {{template "FormatUsage" .Context.SelectedCommand}}` @@ -93,6 +99,7 @@ func TestCmdClause_HelpLong(t *testing.T) { } func TestArgEnvVar(t *testing.T) { + t.Parallel() var buf bytes.Buffer a := New("test", "Test").Writer(&buf).Terminate(nil) diff --git a/values.go b/values.go index e6e05bc..cb6bd82 100644 --- a/values.go +++ b/values.go @@ -87,9 +87,10 @@ type accumulator struct { // Use reflection to accumulate values into a slice. // // target := []string{} -// newAccumulator(&target, func (value interface{}) Value { -// return newStringValue(value.(*string)) -// }) +// +// newAccumulator(&target, func (value interface{}) Value { +// return newStringValue(value.(*string)) +// }) func newAccumulator(slice interface{}, element func(value interface{}) Value) *accumulator { typ := reflect.TypeOf(slice) if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Slice { @@ -131,7 +132,7 @@ func (a *accumulator) IsCumulative() bool { func (b *boolValue) IsBoolFlag() bool { return true } -// -- time.Duration Value +// -- time.Duration Value. type durationValue time.Duration func newDurationValue(p *time.Duration) *durationValue { @@ -148,7 +149,7 @@ func (d *durationValue) Get() interface{} { return time.Duration(*d) } func (d *durationValue) String() string { return (*time.Duration)(d).String() } -// -- map[string]string Value +// -- map[string]string Value. type stringMapValue map[string]string func newStringMapValue(p *map[string]string) *stringMapValue { @@ -178,7 +179,7 @@ func (s *stringMapValue) IsCumulative() bool { return true } -// -- net.IP Value +// -- net.IP Value. type ipValue net.IP func newIPValue(p *net.IP) *ipValue { @@ -202,7 +203,7 @@ func (i *ipValue) String() string { return (*net.IP)(i).String() } -// -- *net.TCPAddr Value +// -- *net.TCPAddr Value. type tcpAddrValue struct { addr **net.TCPAddr } @@ -294,7 +295,7 @@ func (f *fileValue) String() string { return (*f.f).Name() } -// -- url.URL Value +// -- url.URL Value. type urlValue struct { u **url.URL } @@ -323,7 +324,7 @@ func (u *urlValue) String() string { return (*u.u).String() } -// -- []*url.URL Value +// -- []*url.URL Value. type urlListValue []*url.URL func newURLListValue(p *[]*url.URL) *urlListValue { @@ -386,7 +387,7 @@ func (e *enumValue) Get() interface{} { return (string)(*e.value) } -// -- []string Enum Value +// -- []string Enum Value. type enumsValue struct { value *[]string options []string @@ -421,7 +422,7 @@ func (s *enumsValue) IsCumulative() bool { return true } -// -- units.Base2Bytes Value +// -- units.Base2Bytes Value. type bytesValue units.Base2Bytes func newBytesValue(p *units.Base2Bytes) *bytesValue { diff --git a/values_generated.go b/values_generated.go index 8d492bf..a8a2704 100644 --- a/values_generated.go +++ b/values_generated.go @@ -11,7 +11,7 @@ import ( // This file is autogenerated by "go generate .". Do not modify. -// -- bool Value +// -- bool Value. type boolValue struct{ v *bool } func newBoolValue(p *bool) *boolValue { @@ -54,7 +54,7 @@ func (p *parserMixin) BoolListVar(target *[]bool) { })) } -// -- string Value +// -- string Value. type stringValue struct{ v *string } func newStringValue(p *string) *stringValue { @@ -97,7 +97,7 @@ func (p *parserMixin) StringsVar(target *[]string) { })) } -// -- uint Value +// -- uint Value. type uintValue struct{ v *uint } func newUintValue(p *uint) *uintValue { @@ -140,7 +140,7 @@ func (p *parserMixin) UintsVar(target *[]uint) { })) } -// -- uint8 Value +// -- uint8 Value. type uint8Value struct{ v *uint8 } func newUint8Value(p *uint8) *uint8Value { @@ -183,7 +183,7 @@ func (p *parserMixin) Uint8ListVar(target *[]uint8) { })) } -// -- uint16 Value +// -- uint16 Value. type uint16Value struct{ v *uint16 } func newUint16Value(p *uint16) *uint16Value { @@ -226,7 +226,7 @@ func (p *parserMixin) Uint16ListVar(target *[]uint16) { })) } -// -- uint32 Value +// -- uint32 Value. type uint32Value struct{ v *uint32 } func newUint32Value(p *uint32) *uint32Value { @@ -269,7 +269,7 @@ func (p *parserMixin) Uint32ListVar(target *[]uint32) { })) } -// -- uint64 Value +// -- uint64 Value. type uint64Value struct{ v *uint64 } func newUint64Value(p *uint64) *uint64Value { @@ -312,7 +312,7 @@ func (p *parserMixin) Uint64ListVar(target *[]uint64) { })) } -// -- int Value +// -- int Value. type intValue struct{ v *int } func newIntValue(p *int) *intValue { @@ -355,7 +355,7 @@ func (p *parserMixin) IntsVar(target *[]int) { })) } -// -- int8 Value +// -- int8 Value. type int8Value struct{ v *int8 } func newInt8Value(p *int8) *int8Value { @@ -398,7 +398,7 @@ func (p *parserMixin) Int8ListVar(target *[]int8) { })) } -// -- int16 Value +// -- int16 Value. type int16Value struct{ v *int16 } func newInt16Value(p *int16) *int16Value { @@ -441,7 +441,7 @@ func (p *parserMixin) Int16ListVar(target *[]int16) { })) } -// -- int32 Value +// -- int32 Value. type int32Value struct{ v *int32 } func newInt32Value(p *int32) *int32Value { @@ -484,7 +484,7 @@ func (p *parserMixin) Int32ListVar(target *[]int32) { })) } -// -- int64 Value +// -- int64 Value. type int64Value struct{ v *int64 } func newInt64Value(p *int64) *int64Value { @@ -527,7 +527,7 @@ func (p *parserMixin) Int64ListVar(target *[]int64) { })) } -// -- float64 Value +// -- float64 Value. type float64Value struct{ v *float64 } func newFloat64Value(p *float64) *float64Value { @@ -570,7 +570,7 @@ func (p *parserMixin) Float64ListVar(target *[]float64) { })) } -// -- float32 Value +// -- float32 Value. type float32Value struct{ v *float32 } func newFloat32Value(p *float32) *float32Value { @@ -691,7 +691,7 @@ func (p *parserMixin) ExistingFilesOrDirsVar(target *[]string) { })) } -// -- *regexp.Regexp Value +// -- *regexp.Regexp Value. type regexpValue struct{ v **regexp.Regexp } func newRegexpValue(p **regexp.Regexp) *regexpValue { @@ -734,7 +734,7 @@ func (p *parserMixin) RegexpListVar(target *[]*regexp.Regexp) { })) } -// -- net.IP Value +// -- net.IP Value. type resolvedIPValue struct{ v *net.IP } func newResolvedIPValue(p *net.IP) *resolvedIPValue { @@ -777,7 +777,7 @@ func (p *parserMixin) ResolvedIPListVar(target *[]net.IP) { })) } -// -- []byte Value +// -- []byte Value. type hexBytesValue struct{ v *[]byte } func newHexBytesValue(p *[]byte) *hexBytesValue { diff --git a/values_test.go b/values_test.go index e16ee2a..043b37d 100644 --- a/values_test.go +++ b/values_test.go @@ -2,13 +2,13 @@ package kingpin import ( "net" + "testing" "github.com/stretchr/testify/assert" - - "testing" ) func TestAccumulatorStrings(t *testing.T) { + t.Parallel() target := []string{} acc := newAccumulator(&target, func(v interface{}) Value { return newStringValue(v.(*string)) }) acc.Set("a") @@ -18,6 +18,7 @@ func TestAccumulatorStrings(t *testing.T) { } func TestStrings(t *testing.T) { + t.Parallel() app := New("", "") app.Arg("a", "").Required().String() app.Arg("b", "").Required().String() @@ -27,6 +28,7 @@ func TestStrings(t *testing.T) { } func TestEnum(t *testing.T) { + t.Parallel() app := New("", "") a := app.Arg("a", "").Enum("one", "two", "three") _, err := app.Parse([]string{"moo"}) @@ -37,6 +39,7 @@ func TestEnum(t *testing.T) { } func TestEnumVar(t *testing.T) { + t.Parallel() app := New("", "") var a string app.Arg("a", "").EnumVar(&a, "one", "two", "three") @@ -48,6 +51,7 @@ func TestEnumVar(t *testing.T) { } func TestCounter(t *testing.T) { + t.Parallel() app := New("", "") c := app.Flag("f", "").Counter() _, err := app.Parse([]string{"--f", "--f", "--f"}) @@ -56,6 +60,7 @@ func TestCounter(t *testing.T) { } func TestIPv4Addr(t *testing.T) { + t.Parallel() app := newTestApp() flag := app.Flag("addr", "").ResolvedIP() _, err := app.Parse([]string{"--addr", net.IPv4(1, 2, 3, 4).String()}) @@ -65,6 +70,7 @@ func TestIPv4Addr(t *testing.T) { } func TestInvalidIPv4Addr(t *testing.T) { + t.Parallel() app := newTestApp() app.Flag("addr", "").ResolvedIP() _, err := app.Parse([]string{"--addr", "1.2.3.256"}) @@ -72,6 +78,7 @@ func TestInvalidIPv4Addr(t *testing.T) { } func TestIPv6Addr(t *testing.T) { + t.Parallel() app := newTestApp() flag := app.Flag("addr", "").ResolvedIP() _, err := app.Parse([]string{"--addr", net.IPv6interfacelocalallnodes.String()}) @@ -81,6 +88,7 @@ func TestIPv6Addr(t *testing.T) { } func TestHexBytes(t *testing.T) { + t.Parallel() app := newTestApp() actual := app.Arg("bytes", "").HexBytes() _, err := app.Parse([]string{"01020aff"}) @@ -89,6 +97,7 @@ func TestHexBytes(t *testing.T) { } func TestSetValueDoesNotReset(t *testing.T) { + t.Parallel() app := newTestApp() mapping := map[string]string{ "key": "value",