From 856d62e28a90921ac7b399478165fec1a0353eda Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Thu, 26 Jul 2018 20:35:20 +1000 Subject: [PATCH] Support multiple key+value pairs for map flags. --- README.md | 2 ++ mapper.go | 60 +++++++++++++++++++++++++++++--------------------- mapper_test.go | 10 +++++++++ 3 files changed, 47 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index f39285c..9a54198 100644 --- a/README.md +++ b/README.md @@ -346,6 +346,8 @@ var CLI struct { } ``` +For flags, multiple key+value pairs should be separated by `;` eg. `--set="key1=value1;key2=value2"`. + ## Custom named decoders Kong includes a number of builtin custom type mappers. These can be used by diff --git a/mapper.go b/mapper.go index 03acc02..20d1dac 100644 --- a/mapper.go +++ b/mapper.go @@ -284,37 +284,47 @@ func mapDecoder(r *Registry) MapperFunc { target.Set(reflect.MakeMap(target.Type())) } el := target.Type() - token := ctx.Scan.PopValue("map") - parts := strings.SplitN(token, "=", 2) - if len(parts) != 2 { - return fmt.Errorf("expected \"=\" but got %q", token) + var childScanner *Scanner + if ctx.Value.Flag != nil { + // If decoding a flag, we need an argument. + childScanner = Scan(SplitEscaped(ctx.Scan.PopValue("map"), ';')...) + } else { + tokens := ctx.Scan.PopWhile(func(t Token) bool { return t.IsValue() }) + childScanner = ScanFromTokens(tokens...) } - key, value := parts[0], parts[1] - - keyTypeName, valueTypeName := "", "" - if typ := ctx.Value.Tag.Type; typ != "" { - parts := strings.Split(typ, ":") + for !childScanner.Peek().IsEOL() { + token := childScanner.PopValue("map") + parts := strings.SplitN(token, "=", 2) if len(parts) != 2 { - return fmt.Errorf("type:\"\" on map field must be in the form \"[]:[]\"") + return fmt.Errorf("expected \"=\" but got %q", token) } - keyTypeName, valueTypeName = parts[0], parts[1] - } + key, value := parts[0], parts[1] - keyScanner := Scan(key) - keyDecoder := r.ForNamedType(keyTypeName, el.Key()) - keyValue := reflect.New(el.Key()).Elem() - if err := keyDecoder.Decode(ctx.WithScanner(keyScanner), keyValue); err != nil { - return fmt.Errorf("invalid map key %q", key) - } + keyTypeName, valueTypeName := "", "" + if typ := ctx.Value.Tag.Type; typ != "" { + parts := strings.Split(typ, ":") + if len(parts) != 2 { + return fmt.Errorf("type:\"\" on map field must be in the form \"[]:[]\"") + } + keyTypeName, valueTypeName = parts[0], parts[1] + } - valueScanner := Scan(value) - valueDecoder := r.ForNamedType(valueTypeName, el.Elem()) - valueValue := reflect.New(el.Elem()).Elem() - if err := valueDecoder.Decode(ctx.WithScanner(valueScanner), valueValue); err != nil { - return fmt.Errorf("invalid map value %q", value) - } + keyScanner := Scan(key) + keyDecoder := r.ForNamedType(keyTypeName, el.Key()) + keyValue := reflect.New(el.Key()).Elem() + if err := keyDecoder.Decode(ctx.WithScanner(keyScanner), keyValue); err != nil { + return fmt.Errorf("invalid map key %q", key) + } - target.SetMapIndex(keyValue, valueValue) + valueScanner := Scan(value) + valueDecoder := r.ForNamedType(valueTypeName, el.Elem()) + valueValue := reflect.New(el.Elem()).Elem() + if err := valueDecoder.Decode(ctx.WithScanner(valueScanner), valueValue); err != nil { + return fmt.Errorf("invalid map value %q", value) + } + + target.SetMapIndex(keyValue, valueValue) + } return nil } } diff --git a/mapper_test.go b/mapper_test.go index ce43564..86e3a41 100644 --- a/mapper_test.go +++ b/mapper_test.go @@ -99,6 +99,16 @@ func TestMapWithNamedTypes(t *testing.T) { require.Equal(t, map[string]string{"FIRST": "5s", "SECOND": "10s"}, cli.TypedKey) } +func TestMapWithMultipleValues(t *testing.T) { + var cli struct { + Value map[string]string + } + k := mustNew(t, &cli) + _, err := k.Parse([]string{"--value=a=b;c=d"}) + require.NoError(t, err) + require.Equal(t, map[string]string{"a": "b", "c": "d"}, cli.Value) +} + func TestURLMapper(t *testing.T) { var cli struct { URL *url.URL `arg:""`