Support multiple key+value pairs for map flags.

This commit is contained in:
Alec Thomas
2018-07-26 20:35:20 +10:00
parent 539214f23e
commit 856d62e28a
3 changed files with 47 additions and 25 deletions
+2
View File
@@ -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 ## Custom named decoders
Kong includes a number of builtin custom type mappers. These can be used by Kong includes a number of builtin custom type mappers. These can be used by
+35 -25
View File
@@ -284,37 +284,47 @@ func mapDecoder(r *Registry) MapperFunc {
target.Set(reflect.MakeMap(target.Type())) target.Set(reflect.MakeMap(target.Type()))
} }
el := target.Type() el := target.Type()
token := ctx.Scan.PopValue("map") var childScanner *Scanner
parts := strings.SplitN(token, "=", 2) if ctx.Value.Flag != nil {
if len(parts) != 2 { // If decoding a flag, we need an argument.
return fmt.Errorf("expected \"<key>=<value>\" but got %q", token) 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] for !childScanner.Peek().IsEOL() {
token := childScanner.PopValue("map")
keyTypeName, valueTypeName := "", "" parts := strings.SplitN(token, "=", 2)
if typ := ctx.Value.Tag.Type; typ != "" {
parts := strings.Split(typ, ":")
if len(parts) != 2 { if len(parts) != 2 {
return fmt.Errorf("type:\"\" on map field must be in the form \"[<keytype>]:[<valuetype>]\"") return fmt.Errorf("expected \"<key>=<value>\" but got %q", token)
} }
keyTypeName, valueTypeName = parts[0], parts[1] key, value := parts[0], parts[1]
}
keyScanner := Scan(key) keyTypeName, valueTypeName := "", ""
keyDecoder := r.ForNamedType(keyTypeName, el.Key()) if typ := ctx.Value.Tag.Type; typ != "" {
keyValue := reflect.New(el.Key()).Elem() parts := strings.Split(typ, ":")
if err := keyDecoder.Decode(ctx.WithScanner(keyScanner), keyValue); err != nil { if len(parts) != 2 {
return fmt.Errorf("invalid map key %q", key) return fmt.Errorf("type:\"\" on map field must be in the form \"[<keytype>]:[<valuetype>]\"")
} }
keyTypeName, valueTypeName = parts[0], parts[1]
}
valueScanner := Scan(value) keyScanner := Scan(key)
valueDecoder := r.ForNamedType(valueTypeName, el.Elem()) keyDecoder := r.ForNamedType(keyTypeName, el.Key())
valueValue := reflect.New(el.Elem()).Elem() keyValue := reflect.New(el.Key()).Elem()
if err := valueDecoder.Decode(ctx.WithScanner(valueScanner), valueValue); err != nil { if err := keyDecoder.Decode(ctx.WithScanner(keyScanner), keyValue); err != nil {
return fmt.Errorf("invalid map value %q", value) 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 return nil
} }
} }
+10
View File
@@ -99,6 +99,16 @@ func TestMapWithNamedTypes(t *testing.T) {
require.Equal(t, map[string]string{"FIRST": "5s", "SECOND": "10s"}, cli.TypedKey) 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) { func TestURLMapper(t *testing.T) {
var cli struct { var cli struct {
URL *url.URL `arg:""` URL *url.URL `arg:""`