fix: values that look like flags would not be parsed correctly

Specifically, when parsing into slices or maps.

Fixes #290
This commit is contained in:
Alec Thomas
2022-04-08 06:41:09 +10:00
parent 556f8b773b
commit 7c6ff10d33
3 changed files with 31 additions and 9 deletions
+5 -6
View File
@@ -262,8 +262,7 @@ func (r *Registry) RegisterDefaults() *Registry {
RegisterKind(reflect.Float32, floatDecoder(32)). RegisterKind(reflect.Float32, floatDecoder(32)).
RegisterKind(reflect.Float64, floatDecoder(64)). RegisterKind(reflect.Float64, floatDecoder(64)).
RegisterKind(reflect.String, MapperFunc(func(ctx *DecodeContext, target reflect.Value) error { RegisterKind(reflect.String, MapperFunc(func(ctx *DecodeContext, target reflect.Value) error {
err := ctx.Scan.PopValueInto("string", target.Addr().Interface()) return ctx.Scan.PopValueInto("string", target.Addr().Interface())
return err
})). })).
RegisterKind(reflect.Bool, boolMapper{}). RegisterKind(reflect.Bool, boolMapper{}).
RegisterKind(reflect.Slice, sliceDecoder(r)). RegisterKind(reflect.Slice, sliceDecoder(r)).
@@ -450,7 +449,7 @@ func mapDecoder(r *Registry) MapperFunc {
} }
switch v := t.Value.(type) { switch v := t.Value.(type) {
case string: case string:
childScanner = Scan(SplitEscaped(v, sep)...) childScanner = ScanAsType(t.Type, SplitEscaped(v, sep)...)
case []map[string]interface{}: case []map[string]interface{}:
for _, m := range v { for _, m := range v {
@@ -492,14 +491,14 @@ func mapDecoder(r *Registry) MapperFunc {
keyTypeName, valueTypeName = parts[0], parts[1] keyTypeName, valueTypeName = parts[0], parts[1]
} }
keyScanner := Scan(key) keyScanner := ScanAsType(FlagValueToken, key)
keyDecoder := r.ForNamedType(keyTypeName, el.Key()) keyDecoder := r.ForNamedType(keyTypeName, el.Key())
keyValue := reflect.New(el.Key()).Elem() keyValue := reflect.New(el.Key()).Elem()
if err := keyDecoder.Decode(ctx.WithScanner(keyScanner), keyValue); err != nil { if err := keyDecoder.Decode(ctx.WithScanner(keyScanner), keyValue); err != nil {
return fmt.Errorf("invalid map key %q", key) return fmt.Errorf("invalid map key %q", key)
} }
valueScanner := Scan(value) valueScanner := ScanAsType(FlagValueToken, value)
valueDecoder := r.ForNamedType(valueTypeName, el.Elem()) valueDecoder := r.ForNamedType(valueTypeName, el.Elem())
valueValue := reflect.New(el.Elem()).Elem() valueValue := reflect.New(el.Elem()).Elem()
if err := valueDecoder.Decode(ctx.WithScanner(valueScanner), valueValue); err != nil { if err := valueDecoder.Decode(ctx.WithScanner(valueScanner), valueValue); err != nil {
@@ -525,7 +524,7 @@ func sliceDecoder(r *Registry) MapperFunc {
} }
switch v := t.Value.(type) { switch v := t.Value.(type) {
case string: case string:
childScanner = Scan(SplitEscaped(v, sep)...) childScanner = ScanAsType(t.Type, SplitEscaped(v, sep)...)
case []interface{}: case []interface{}:
return jsonTranscode(v, target.Addr().Interface()) return jsonTranscode(v, target.Addr().Interface())
+18
View File
@@ -495,3 +495,21 @@ func (t testMapperVarsContributor) Decode(ctx *kong.DecodeContext, target reflec
target.SetString("hi") target.SetString("hi")
return nil return nil
} }
func TestValuesThatLookLikeFlags(t *testing.T) {
var cli struct {
Slice []string
Map map[string]string
}
k := mustNew(t, &cli)
_, err := k.Parse([]string{"--slice", "-foo"})
require.Error(t, err)
_, err = k.Parse([]string{"--map", "-foo=-bar"})
require.Error(t, err)
_, err = k.Parse([]string{"--slice=-foo", "--slice=-bar"})
require.NoError(t, err)
require.Equal(t, []string{"-foo", "-bar"}, cli.Slice)
_, err = k.Parse([]string{"--map=-foo=-bar"})
require.NoError(t, err)
require.Equal(t, map[string]string{"-foo": "-bar"}, cli.Map)
}
+8 -3
View File
@@ -114,15 +114,20 @@ type Scanner struct {
args []Token args []Token
} }
// Scan creates a new Scanner from args with untyped tokens. // ScanAsType creates a new Scanner from args with the given type.
func Scan(args ...string) *Scanner { func ScanAsType(ttype TokenType, args ...string) *Scanner {
s := &Scanner{} s := &Scanner{}
for _, arg := range args { for _, arg := range args {
s.args = append(s.args, Token{Value: arg}) s.args = append(s.args, Token{Value: arg, Type: ttype})
} }
return s return s
} }
// Scan creates a new Scanner from args with untyped tokens.
func Scan(args ...string) *Scanner {
return ScanAsType(UntypedToken, args...)
}
// ScanFromTokens creates a new Scanner from a slice of tokens. // ScanFromTokens creates a new Scanner from a slice of tokens.
func ScanFromTokens(tokens ...Token) *Scanner { func ScanFromTokens(tokens ...Token) *Scanner {
return &Scanner{args: tokens} return &Scanner{args: tokens}