rework map and slice support #35

Merged
vtolstov merged 2 commits from fixup into v3 2022-03-23 01:00:51 +03:00
3 changed files with 227 additions and 120 deletions
Showing only changes of commit 7a2461f7ce - Show all commits

10
flag.go
View File

@ -53,7 +53,15 @@ func (c *flagConfig) Init(opts ...config.Option) error {
rcheck := true rcheck := true
switch sf.Value.Interface().(type) { if !sf.Value.IsValid() {
continue
}
vi := sf.Value.Interface()
if vi == nil {
continue
}
switch vi.(type) {
case time.Duration: case time.Duration:
err = c.flagDuration(sf.Value, fn, fv, fd) err = c.flagDuration(sf.Value, fn, fv, fd)
rcheck = false rcheck = false

View File

@ -17,6 +17,10 @@ func TestLoad(t *testing.T) {
os.Args = append(os.Args, "-addr", "33,44") os.Args = append(os.Args, "-addr", "33,44")
os.Args = append(os.Args, "-time", time.RFC822) os.Args = append(os.Args, "-time", time.RFC822)
os.Args = append(os.Args, "-metadata", "key=20") os.Args = append(os.Args, "-metadata", "key=20")
os.Args = append(os.Args, "-components", "all=info,api=debug")
type NestedConfig struct {
Value string `flag:"name=nested_value"`
}
type Config struct { type Config struct {
Broker string `flag:"name=broker,desc='description with, comma',default='127.0.0.1:9092'"` Broker string `flag:"name=broker,desc='description with, comma',default='127.0.0.1:9092'"`
Verbose bool `flag:"name=verbose,desc='verbose output',default='false'"` Verbose bool `flag:"name=verbose,desc='verbose output',default='false'"`
@ -27,10 +31,12 @@ func TestLoad(t *testing.T) {
WithoutDefault string `flag:"name=without_default,desc='with'"` WithoutDefault string `flag:"name=without_default,desc='with'"`
WithoutDesc string `flag:"name=without_desc,default='without_default'"` WithoutDesc string `flag:"name=without_desc,default='without_default'"`
WithoutAll string `flag:"name=without_all"` WithoutAll string `flag:"name=without_all"`
Components map[string]string `flag:"name=components,desc='components logging'"`
Nested *NestedConfig
} }
ctx := context.Background() ctx := context.Background()
cfg := &Config{} cfg := &Config{Nested: &NestedConfig{}}
c := NewConfig(config.Struct(cfg), TimeFormat(time.RFC822)) c := NewConfig(config.Struct(cfg), TimeFormat(time.RFC822))
if err := c.Init(); err != nil { if err := c.Init(); err != nil {
@ -47,4 +53,8 @@ func TestLoad(t *testing.T) {
if tf := cfg.Time.Format(time.RFC822); tf != "02 Jan 06 15:04 MST" { if tf := cfg.Time.Format(time.RFC822); tf != "02 Jan 06 15:04 MST" {
t.Fatalf("parse time error: %s != %s", tf, "02 Jan 06 15:04 MST") t.Fatalf("parse time error: %s != %s", tf, "02 Jan 06 15:04 MST")
} }
if len(cfg.Components) != 2 {
t.Fatalf("cant parse map components %#+v", cfg)
}
} }

293
util.go
View File

@ -2,12 +2,131 @@ package flag
import ( import (
"flag" "flag"
"fmt"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"time" "time"
) )
type mapValue struct {
v reflect.Value
delim string
def string
}
func (v mapValue) String() string {
if v.v.Kind() != reflect.Invalid {
var kv []string
it := v.v.MapRange()
for it.Next() {
k := it.Key().Interface()
v := it.Value().Interface()
kv = append(kv, fmt.Sprintf("%v=%v", k, v))
}
return strings.Join(kv, ",")
}
return v.def
}
func (v mapValue) Set(s string) error {
ps := strings.Split(s, v.delim)
if len(ps) == 0 {
return nil
}
v.v.Set(reflect.MakeMapWithSize(v.v.Type(), len(ps)))
kt := v.v.Type().Key().Kind()
vt := v.v.Type().Elem().Kind()
for i := 0; i < len(ps); i++ {
fs := strings.Split(ps[i], "=")
switch len(fs) {
case 0:
return nil
case 1:
if len(fs[0]) == 0 {
return nil
}
return ErrInvalidValue
case 2:
break
default:
return ErrInvalidValue
}
key, err := convertType(reflect.ValueOf(fs[0]), kt)
if err != nil {
return err
}
val, err := convertType(reflect.ValueOf(fs[1]), vt)
if err != nil {
return err
}
v.v.SetMapIndex(key.Convert(v.v.Type().Key()), val.Convert(v.v.Type().Elem()))
}
return nil
}
type sliceValue struct {
v reflect.Value
delim string
def string
}
func (v sliceValue) String() string {
if v.v.Kind() != reflect.Invalid {
var kv []string
for idx := 0; idx < v.v.Len(); idx++ {
kv = append(kv, fmt.Sprintf("%v", v.v.Index(idx).Interface()))
}
return strings.Join(kv, ",")
}
return v.def
}
func (v sliceValue) Set(s string) error {
p := strings.Split(s, v.delim)
v.v.Set(reflect.MakeSlice(v.v.Type(), len(p), len(p)))
switch v.v.Type().Elem().Kind() {
case reflect.Int, reflect.Int64:
for idx := range p {
i, err := strconv.ParseInt(p[idx], 10, 64)
if err != nil {
return err
}
v.v.Index(idx).SetInt(i)
}
case reflect.Uint, reflect.Uint64:
for idx := range p {
i, err := strconv.ParseUint(p[idx], 10, 64)
if err != nil {
return err
}
v.v.Index(idx).SetUint(i)
}
case reflect.Float64:
for idx := range p {
i, err := strconv.ParseFloat(p[idx], 64)
if err != nil {
return err
}
v.v.Index(idx).SetFloat(i)
}
case reflect.Bool:
for idx := range p {
i, err := strconv.ParseBool(p[idx])
if err != nil {
return err
}
v.v.Index(idx).SetBool(i)
}
case reflect.String:
for idx := range p {
v.v.Index(idx).SetString(p[idx])
}
}
return nil
}
func convertType(v reflect.Value, t reflect.Kind) (reflect.Value, error) { func convertType(v reflect.Value, t reflect.Kind) (reflect.Value, error) {
switch v.Kind() { switch v.Kind() {
case reflect.String: case reflect.String:
@ -51,50 +170,12 @@ func (c *flagConfig) flagSlice(v reflect.Value, fn, fv, fd string) error {
} }
} }
flag.Func(fn, fd, func(s string) error { v.Set(reflect.MakeSlice(v.Type(), 0, 0))
p := strings.Split(s, delim) mp := &sliceValue{v: v, def: fv, delim: delim}
v.Set(reflect.MakeSlice(v.Type(), len(p), len(p))) if err := mp.Set(fv); err != nil {
switch v.Type().Elem().Kind() {
case reflect.Int, reflect.Int64:
for idx := range p {
i, err := strconv.ParseInt(p[idx], 10, 64)
if err != nil {
return err return err
} }
v.Index(idx).SetInt(i) flag.Var(mp, fn, fd)
}
case reflect.Uint, reflect.Uint64:
for idx := range p {
i, err := strconv.ParseUint(p[idx], 10, 64)
if err != nil {
return err
}
v.Index(idx).SetUint(i)
}
case reflect.Float64:
for idx := range p {
i, err := strconv.ParseFloat(p[idx], 64)
if err != nil {
return err
}
v.Index(idx).SetFloat(i)
}
case reflect.Bool:
for idx := range p {
i, err := strconv.ParseBool(p[idx])
if err != nil {
return err
}
v.Index(idx).SetBool(i)
}
case reflect.String:
for idx := range p {
v.Index(idx).SetString(p[idx])
}
}
return nil
})
return nil return nil
} }
@ -105,43 +186,12 @@ func (c *flagConfig) flagMap(v reflect.Value, fn, fv, fd string) error {
delim = d delim = d
} }
} }
flag.Func(fn, fv, func(s string) error { v.Set(reflect.MakeMapWithSize(v.Type(), 0))
ps := strings.Split(s, delim) mp := &mapValue{v: v, def: fv, delim: delim}
if len(ps) == 0 { if err := mp.Set(fv); err != nil {
return nil
}
v.Set(reflect.MakeMapWithSize(v.Type(), len(ps)))
kt := v.Type().Key().Kind()
vt := v.Type().Elem().Kind()
for i := 0; i < len(ps); i++ {
fs := strings.Split(ps[i], "=")
switch len(fs) {
case 0:
return nil
case 1:
if len(fs[0]) == 0 {
return nil
}
return ErrInvalidValue
case 2:
break
default:
return ErrInvalidValue
}
key, err := convertType(reflect.ValueOf(fs[0]), kt)
if err != nil {
return err return err
} }
val, err := convertType(reflect.ValueOf(fs[1]), vt) flag.Var(mp, fn, fd)
if err != nil {
return err
}
v.SetMapIndex(key.Convert(v.Type().Key()), val.Convert(v.Type().Elem()))
}
return nil
})
return nil return nil
} }
@ -277,35 +327,74 @@ func (c *flagConfig) flagStringSlice(v reflect.Value, fn, fv, fd string) error {
} }
func getFlagOpts(tf string) (string, string, string) { func getFlagOpts(tf string) (string, string, string) {
ret := make([]string, 3) var name, desc, def string
vals := strings.Split(tf, ",") delim := ","
f := 0
for _, val := range vals {
p := strings.Split(val, "=")
switch p[0] {
case "name":
f = 0
case "desc":
f = 1
case "default":
f = 2
default:
ret[f] += "," + val
continue
}
ret[f] = p[1]
}
for idx := range ret { var buf string
if len(ret[idx]) == 0 { for idx := 0; idx < len(tf); idx++ {
buf += string(tf[idx])
switch buf {
case "name":
ndx := idx + 2
stop := ","
var quote bool
for ; ndx < len(tf); ndx++ {
if string(tf[ndx]) == stop {
if quote {
ndx++
}
break
}
if string(tf[ndx]) == "'" {
stop = "'"
quote = true
continue continue
} }
if ret[idx][0] == '\'' { name += string(tf[ndx])
ret[idx] = ret[idx][1:]
} }
if ret[idx][len(ret[idx])-1] == '\'' { idx = ndx
ret[idx] = ret[idx][:len(ret[idx])-1] buf = ""
case "desc":
ndx := idx + 2
stop := ","
var quote bool
for ; ndx < len(tf); ndx++ {
if string(tf[ndx]) == stop {
if quote {
ndx++
}
break
}
if string(tf[ndx]) == "'" {
stop = "'"
quote = true
continue
}
desc += string(tf[ndx])
}
idx = ndx
buf = ""
case "default":
ndx := idx + 2
stop := ","
var quote bool
for ; ndx < len(tf); ndx++ {
if string(tf[ndx]) == stop && (stop != delim) {
if quote {
ndx++
}
break
}
if string(tf[ndx]) == "'" {
stop = "'"
quote = true
continue
}
def += string(tf[ndx])
}
idx = ndx
buf = ""
} }
} }
return ret[0], ret[1], ret[2] return name, desc, def
} }