diff --git a/README.md b/README.md index b071d50..0b5233e 100644 --- a/README.md +++ b/README.md @@ -225,7 +225,7 @@ Both can coexist with standard Tag parsing. | `optional` | If present, flag/arg is optional. | | `hidden` | If present, flag is hidden. | | `format:"X"` | Format for parsing input, if supported. | -| `sep:"X"` | Separator for sequences (defaults to ",") or maps (defaults to "=") | +| `sep:"X"` | Separator for sequences (defaults to ","). May be `none` to disable splitting. | ## Modifying Kong's behaviour diff --git a/build.go b/build.go index 15b1f44..85eb204 100644 --- a/build.go +++ b/build.go @@ -33,17 +33,35 @@ func dashedString(s string) string { return strings.Join(camelCase(s), "-") } +type flattenedField struct { + field reflect.StructField + value reflect.Value +} + +func flattenedFields(v reflect.Value) (out []flattenedField) { + for i := 0; i < v.NumField(); i++ { + ft := v.Type().Field(i) + fv := v.Field(i) + if ft.Anonymous { + out = append(out, flattenedFields(fv)...) + continue + } + if !fv.CanSet() { + continue + } + out = append(out, flattenedField{ft, fv}) + } + return +} + func buildNode(k *Kong, v reflect.Value, typ NodeType, seenFlags map[string]bool) *Node { node := &Node{ Type: typ, Target: v, } - for i := 0; i < v.NumField(); i++ { - ft := v.Type().Field(i) - if strings.ToLower(ft.Name[0:1]) == ft.Name[0:1] { - continue - } - fv := v.Field(i) + for _, field := range flattenedFields(v) { + ft := field.field + fv := field.value name := ft.Tag.Get("name") if name == "" { diff --git a/kong_test.go b/kong_test.go index 2eaaf72..8590603 100644 --- a/kong_test.go +++ b/kong_test.go @@ -410,3 +410,37 @@ func TestMapFlag(t *testing.T) { require.NoError(t, err) require.Equal(t, map[string]int{"a": 10, "b": 20}, cli.Set) } + +func TestMapFlagWithSliceValue(t *testing.T) { + var cli struct { + Set map[string][]int + } + _, err := mustNew(t, &cli).Parse([]string{"--set", "a=1,2", "--set", "b=3"}) + require.NoError(t, err) + require.Equal(t, map[string][]int{"a": {1, 2}, "b": {3}}, cli.Set) +} + +type embeddedFlags struct { + Embedded string +} + +func TestEmbeddedStruct(t *testing.T) { + var cli struct { + embeddedFlags + NotEmbedded string + } + + _, err := mustNew(t, &cli).Parse([]string{"--embedded=moo", "--not-embedded=foo"}) + require.NoError(t, err) + require.Equal(t, "moo", cli.Embedded) + require.Equal(t, "foo", cli.NotEmbedded) +} + +func TestSliceWithDisabledSeparator(t *testing.T) { + var cli struct { + Flag []string `sep:"none"` + } + _, err := mustNew(t, &cli).Parse([]string{"--flag=a,b", "--flag=b,c"}) + require.NoError(t, err) + require.Equal(t, []string{"a,b", "b,c"}, cli.Flag) +} diff --git a/mapper.go b/mapper.go index e37ca27..6e01a76 100644 --- a/mapper.go +++ b/mapper.go @@ -233,11 +233,10 @@ func mapDecoder(d *Registry) MapperFunc { target.Set(reflect.MakeMap(target.Type())) } el := target.Type() - sep := ctx.Value.Tag.Sep token := ctx.Scan.PopValue("map") - parts := SplitEscaped(token, sep) + parts := strings.SplitN(token, "=", 2) if len(parts) != 2 { - return fmt.Errorf("expected \"%c\" but got %q", sep, token) + return fmt.Errorf("expected \"=\" but got %q", token) } key, value := parts[0], parts[1] diff --git a/tag.go b/tag.go index 1351471..594c75a 100644 --- a/tag.go +++ b/tag.go @@ -122,22 +122,22 @@ func parseTag(fv reflect.Value, ft reflect.StructField) *Tag { } t.Required = required t.Optional = optional - t.Default, _ = t.Get("default") - t.Help, _ = t.Get("help") - t.Type, _ = t.Get("type") - t.Env, _ = t.Get("env") + t.Default = t.Get("default") + t.Help = t.Get("help") + t.Type = t.Get("type") + t.Env = t.Get("env") t.Short, _ = t.GetRune("short") t.Hidden = t.Has("hidden") - t.Format, _ = t.Get("format") + t.Format = t.Get("format") t.Sep, _ = t.GetRune("sep") if t.Sep == 0 { - if fv.Kind() == reflect.Map { - t.Sep = '=' + if t.Get("sep") == "none" { + t.Sep = -1 } else { t.Sep = ',' } } - t.PlaceHolder, _ = t.Get("placeholder") + t.PlaceHolder = t.Get("placeholder") if t.PlaceHolder == "" { t.PlaceHolder = strings.ToUpper(dashedString(fv.Type().Name())) } @@ -152,9 +152,10 @@ func (t *Tag) Has(k string) bool { } // Get returns the value of the given tag. -func (t *Tag) Get(k string) (string, bool) { - s, ok := t.items[k] - return s, ok +// +// Note that this will return the empty string if the tag is missing. +func (t *Tag) Get(k string) string { + return t.items[k] } // GetBool returns true if the given tag looks like a boolean truth string.