Support for embedded structs + sep:"none".

Map key/value separator is now hardcoded to `=`. This allows the
`sep` tag to continue to be used for slice separation, cascading to
support maps with slice values eg. `--set a=1,2,3`.
This commit is contained in:
Alec Thomas
2018-06-14 11:27:10 +10:00
parent 51ca3e76a8
commit 9a68d32e72
5 changed files with 73 additions and 21 deletions
+1 -1
View File
@@ -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
+24 -6
View File
@@ -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 == "" {
+34
View File
@@ -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)
}
+2 -3
View File
@@ -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 \"<key>%c<value>\" but got %q", sep, token)
return fmt.Errorf("expected \"<key>=<value>\" but got %q", token)
}
key, value := parts[0], parts[1]
+12 -11
View File
@@ -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.