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:
@@ -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
|
||||
|
||||
|
||||
@@ -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 == "" {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user