chore: optionally allow parsing of hyphen-prefixied flag parameters

This allows for eg. `foo --number -10`, `foo --flag -bar`.

Fixes #478, #315.
This commit is contained in:
Alec Thomas
2025-05-15 19:31:29 +10:00
parent 8e03dbeaf6
commit 9bc3bf9925
5 changed files with 69 additions and 22 deletions
+1 -1
View File
@@ -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,
+11 -10
View File
@@ -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
+36 -9
View File
@@ -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)
})
}
+10
View File
@@ -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
+11 -2
View File
@@ -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 <context> value but got <type>"
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