Support multiple key+value pairs for map flags.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:""`
|
||||
|
||||
Reference in New Issue
Block a user