Repository: alecthomas/kong
Pull request #53 (2020-02-14 09:51:49)

add support to make map separator configurable
As promised in #52 

alecthomas: Beautiful, thanks!

Patch:
diff --git a/README.md b/README.md
index fa90ad5..f0f519b 100644
--- a/README.md
+++ b/README.md
@@ -370,7 +370,7 @@ var CLI struct {
 }
 ```
 
-For flags, multiple key+value pairs should be separated by `;` eg. `--set="key1=value1;key2=value2"`.
+For flags, multiple key+value pairs should be separated by `mapsep:"rune"` tag (defaults to `;`) eg. `--set="key1=value1;key2=value2"`.
 
 <a id="markdown-custom-named-decoders" name="custom-named-decoders"></a>
 ## Custom named decoders
@@ -428,6 +428,7 @@ Tag                    | Description
 `hidden`               | If present, command or flag is hidden.
 `format:"X"`           | Format for parsing input, if supported.
 `sep:"X"`              | Separator for sequences (defaults to ","). May be `none` to disable splitting.
+`mapsep:"X"`           | Separator for maps (defaults to ";"). May be `none` to disable splitting.
 `enum:"X,Y,..."`       | Set of valid values allowed for this flag.
 `group:"X"`            | Logical group for a flag or command.
 `xor:"X"`              | Exclusive OR group for flags. Only one flag in the group can be used which is restricted within the same command.
diff --git a/mapper.go b/mapper.go
index 1482f1b..a1d9058 100644
--- a/mapper.go
+++ b/mapper.go
@@ -408,6 +408,7 @@ func mapDecoder(r *Registry) MapperFunc {
 			target.Set(reflect.MakeMap(target.Type()))
 		}
 		el := target.Type()
+		sep := ctx.Value.Tag.MapSep
 		var childScanner *Scanner
 		if ctx.Value.Flag != nil {
 			t := ctx.Scan.Pop()
@@ -417,7 +418,7 @@ func mapDecoder(r *Registry) MapperFunc {
 			}
 			switch v := t.Value.(type) {
 			case string:
-				childScanner = Scan(SplitEscaped(v, ';')...)
+				childScanner = Scan(SplitEscaped(v, sep)...)
 
 			case []map[string]interface{}:
 				for _, m := range v {
diff --git a/tag.go b/tag.go
index a642e99..88380fe 100644
--- a/tag.go
+++ b/tag.go
@@ -25,6 +25,7 @@ type Tag struct {
 	Short       rune
 	Hidden      bool
 	Sep         rune
+	MapSep      rune
 	Enum        string
 	Group       string
 	Xor         string
@@ -151,6 +152,7 @@ func parseTag(fv reflect.Value, ft reflect.StructField) *Tag {
 	t.Hidden = t.Has("hidden")
 	t.Format = t.Get("format")
 	t.Sep, _ = t.GetRune("sep")
+	t.MapSep, _ = t.GetRune("mapsep")
 	t.Group = t.Get("group")
 	t.Xor = t.Get("xor")
 	t.Prefix = t.Get("prefix")
@@ -162,6 +164,13 @@ func parseTag(fv reflect.Value, ft reflect.StructField) *Tag {
 			t.Sep = ','
 		}
 	}
+	if t.MapSep == 0 {
+		if t.Get("mapsep") == "none" {
+			t.MapSep = -1
+		} else {
+			t.MapSep = ';'
+		}
+	}
 	t.Vars = Vars{}
 	for _, set := range t.GetAll("set") {
 		parts := strings.SplitN(set, "=", 2)


Test patch:
diff --git a/mapper_test.go b/mapper_test.go
index e67cfc2..1ec9441 100644
--- a/mapper_test.go
+++ b/mapper_test.go
@@ -161,6 +161,16 @@ func TestMapWithMultipleValues(t *testing.T) {
 	require.Equal(t, map[string]string{"a": "b", "c": "d"}, cli.Value)
 }
 
+func TestMapWithDifferentSeparator(t *testing.T) {
+	var cli struct {
+		Value map[string]string `mapsep:","`
+	}
+	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:""`