From 9bc3bf9925397be48270da0e258bfb0a4f6ed96a Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Thu, 15 May 2025 19:31:29 +1000 Subject: [PATCH] chore: optionally allow parsing of hyphen-prefixied flag parameters This allows for eg. `foo --number -10`, `foo --flag -bar`. Fixes #478, #315. --- context.go | 2 +- kong.go | 21 +++++++++++---------- kong_test.go | 45 ++++++++++++++++++++++++++++++++++++--------- options.go | 10 ++++++++++ scanner.go | 13 +++++++++++-- 5 files changed, 69 insertions(+), 22 deletions(-) diff --git a/context.go b/context.go index 16a7353..6a4989f 100644 --- a/context.go +++ b/context.go @@ -99,7 +99,7 @@ type Context struct { // This just constructs a new trace. To fully apply the trace you must call Reset(), Resolve(), // Validate() and Apply(). func Trace(k *Kong, args []string) (*Context, error) { - s := Scan(args...) + s := Scan(args...).AllowHyphenPrefixedParameters(k.allowHyphenated) c := &Context{ Kong: k, Args: args, diff --git a/kong.go b/kong.go index 4f3901f..2334a8a 100644 --- a/kong.go +++ b/kong.go @@ -56,16 +56,17 @@ type Kong struct { registry *Registry ignoreFields []*regexp.Regexp - noDefaultHelp bool - usageOnError usageOnError - help HelpPrinter - shortHelp HelpPrinter - helpFormatter HelpValueFormatter - helpOptions HelpOptions - helpFlag *Flag - groups []Group - vars Vars - flagNamer func(string) string + noDefaultHelp bool + allowHyphenated bool + usageOnError usageOnError + help HelpPrinter + shortHelp HelpPrinter + helpFormatter HelpValueFormatter + helpOptions HelpOptions + helpFlag *Flag + groups []Group + vars Vars + flagNamer func(string) string // Set temporarily by Options. These are applied after build(). postBuildOptions []Option diff --git a/kong_test.go b/kong_test.go index f2d315a..834155d 100644 --- a/kong_test.go +++ b/kong_test.go @@ -1043,15 +1043,6 @@ func TestParentBindings(t *testing.T) { assert.Equal(t, "foo", cli.Command.value) } -func TestNumericParamErrors(t *testing.T) { - var cli struct { - Name string - } - parser := mustNew(t, &cli) - _, err := parser.Parse([]string{"--name", "-10"}) - assert.EqualError(t, err, `--name: expected string value but got "-10" (short flag); perhaps try --name="-10"?`) -} - func TestDefaultValueIsHyphen(t *testing.T) { var cli struct { Flag string `default:"-"` @@ -2677,3 +2668,39 @@ func TestProviderWithoutError(t *testing.T) { err = kctx.Run() assert.NoError(t, err) } + +func TestParseHyphenParameter(t *testing.T) { + type shortFlag struct { + Flag string `short:"f"` + Other string `short:"o"` + Numeric int `short:"n"` + } + + t.Run("ShortFlag", func(t *testing.T) { + actual := &shortFlag{} + _, err := mustNew(t, actual, kong.WithHyphenPrefixedParameters(true)).Parse([]string{"-f", "-foo"}) + assert.NoError(t, err) + assert.Equal(t, &shortFlag{Flag: "-foo"}, actual) + }) + + t.Run("LongFlag", func(t *testing.T) { + actual := &shortFlag{} + _, err := mustNew(t, actual, kong.WithHyphenPrefixedParameters(true)).Parse([]string{"--flag", "-foo"}) + assert.NoError(t, err) + assert.Equal(t, &shortFlag{Flag: "-foo"}, actual) + }) + + t.Run("ParamMatchesFlag", func(t *testing.T) { + actual := &shortFlag{} + _, err := mustNew(t, actual, kong.WithHyphenPrefixedParameters(true)).Parse([]string{"--flag", "-oo"}) + assert.NoError(t, err) + assert.Equal(t, &shortFlag{Flag: "-oo"}, actual) + }) + + t.Run("NegativeNumber", func(t *testing.T) { + actual := &shortFlag{} + _, err := mustNew(t, actual, kong.WithHyphenPrefixedParameters(true)).Parse([]string{"--numeric", "-10"}) + assert.NoError(t, err) + assert.Equal(t, &shortFlag{Numeric: -10}, actual) + }) +} diff --git a/options.go b/options.go index 5fe3532..a1fa242 100644 --- a/options.go +++ b/options.go @@ -55,6 +55,16 @@ func Exit(exit func(int)) Option { }) } +// WithHyphenPrefixedParameters enables or disables hyphen-prefixed parameters. +// +// These are disabled by default. +func WithHyphenPrefixedParameters(enable bool) Option { + return OptionFunc(func(k *Kong) error { + k.allowHyphenated = enable + return nil + }) +} + type embedded struct { strct any tags []string diff --git a/scanner.go b/scanner.go index 68f708e..511bf8f 100644 --- a/scanner.go +++ b/scanner.go @@ -111,7 +111,8 @@ func (t Token) IsValue() bool { // // [{FlagToken, "foo"}, {FlagValueToken, "bar"}] type Scanner struct { - args []Token + allowHyphenated bool + args []Token } // ScanAsType creates a new Scanner from args with the given type. @@ -133,6 +134,14 @@ func ScanFromTokens(tokens ...Token) *Scanner { return &Scanner{args: tokens} } +// AllowHyphenPrefixedParameters enables or disables hyphen-prefixed flag parameters on this Scanner. +// +// Disabled by default. +func (s *Scanner) AllowHyphenPrefixedParameters(enable bool) *Scanner { + s.allowHyphenated = enable + return s +} + // Len returns the number of input arguments. func (s *Scanner) Len() int { return len(s.args) @@ -162,7 +171,7 @@ func (e *expectedError) Error() string { // "context" is used to assist the user if the value can not be popped, eg. "expected value but got " func (s *Scanner) PopValue(context string) (Token, error) { t := s.Pop() - if !t.IsValue() { + if !s.allowHyphenated && !t.IsValue() { return t, &expectedError{context, t} } return t, nil