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
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()))
}
el := target.Type()
token := ctx.Scan.PopValue("map")
parts := strings.SplitN(token, "=", 2)
if len(parts) != 2 {
return fmt.Errorf("expected \"<key>=<value>\" 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 \"[<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)
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 \"[<keytype>]:[<valuetype>]\"")
}
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
}
}
+10
View File
@@ -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:""`