From 05720a433ec1dcf0f57c56bf7f91a239406ecb08 Mon Sep 17 00:00:00 2001
From: Vasiliy Tolstov <v.tolstov@unistack.org>
Date: Sat, 19 Jun 2021 16:00:47 +0300
Subject: [PATCH] complete

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
---
 flag.go      |  61 +++++++++++++---
 flag_test.go |  51 +++++++-------
 go.mod       |   2 +-
 go.sum       |   4 +-
 options.go   |  26 +++++++
 util.go      | 194 +++++++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 300 insertions(+), 38 deletions(-)
 create mode 100644 options.go

diff --git a/flag.go b/flag.go
index 1fa62a2..2cb4d77 100644
--- a/flag.go
+++ b/flag.go
@@ -5,17 +5,26 @@ import (
 	"errors"
 	"flag"
 	"reflect"
-	"strconv"
+	"time"
 
 	"github.com/unistack-org/micro/v3/config"
 	rutil "github.com/unistack-org/micro/v3/util/reflect"
 )
 
 var (
-	DefaultStructTag = "flag"
-	ErrInvalidStruct = errors.New("invalid struct specified")
+	DefaultStructTag  = "flag"
+	ErrInvalidValue   = errors.New("invalid value specified")
+	DefaultSliceDelim = ","
+	DefaultMapDelim   = ","
 )
 
+/*
+var (
+	timeTimeKind     = reflect.TypeOf(time.Time{}).Kind()
+	timeDurationKind = reflect.TypeOf(time.Duration(0)).Kind()
+)
+*/
+
 type flagConfig struct {
 	opts config.Options
 }
@@ -41,15 +50,51 @@ func (c *flagConfig) Init(opts ...config.Option) error {
 		}
 		fn, fv, fd := getFlagOpts(tf)
 
+		rcheck := true
+		switch sf.Value.Interface().(type) {
+		case time.Duration:
+			err = c.flagDuration(sf.Value, fn, fv, fd)
+			rcheck = false
+		case time.Time:
+			err = c.flagTime(sf.Value, fn, fv, fd)
+			rcheck = false
+		}
+		if err != nil {
+			return err
+		}
+
+		if !rcheck {
+			continue
+		}
+
+		if sf.Value.Kind() == reflect.Ptr {
+			sf.Value = sf.Value.Elem()
+		}
+
 		switch sf.Value.Kind() {
 		case reflect.String:
-			v := sf.Value.Addr().Interface().(*string)
-			flag.StringVar(v, fn, fv, fd)
+			err = c.flagString(sf.Value, fn, fv, fd)
 		case reflect.Bool:
-			v := sf.Value.Addr().Interface().(*bool)
-			i, _ := strconv.ParseBool(fv)
-			flag.BoolVar(v, fn, i, fd)
+			err = c.flagBool(sf.Value, fn, fv, fd)
+		case reflect.Int:
+			err = c.flagInt(sf.Value, fn, fv, fd)
+		case reflect.Int64:
+			err = c.flagInt64(sf.Value, fn, fv, fd)
+		case reflect.Float64:
+			err = c.flagFloat64(sf.Value, fn, fv, fd)
+		case reflect.Uint:
+			err = c.flagUint(sf.Value, fn, fv, fd)
+		case reflect.Uint64:
+			err = c.flagUint64(sf.Value, fn, fv, fd)
+		case reflect.Slice:
+			err = c.flagSlice(sf.Value, fn, fv, fd)
+		case reflect.Map:
+			err = c.flagMap(sf.Value, fn, fv, fd)
 		}
+		if err != nil {
+			return err
+		}
+
 	}
 
 	return nil
diff --git a/flag_test.go b/flag_test.go
index 20c611b..6f31fb9 100644
--- a/flag_test.go
+++ b/flag_test.go
@@ -1,49 +1,46 @@
 package flag
 
 import (
-	"flag"
+	"context"
 	"os"
-	"reflect"
-	"strconv"
 	"testing"
+	"time"
 
-	rutil "github.com/unistack-org/micro/v3/util/reflect"
+	"github.com/unistack-org/micro/v3/config"
 )
 
 func TestLoad(t *testing.T) {
 	os.Args = append(os.Args, "-broker", "5566:33")
-	type config struct {
-		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 value'"`
+	os.Args = append(os.Args, "-verbose")
+	os.Args = append(os.Args, "-wait", "5s")
+	os.Args = append(os.Args, "-addr", "33,44")
+	os.Args = append(os.Args, "-time", time.RFC822)
+	type Config struct {
+		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'"`
+		Addr    []string      `flag:"name=addr,desc='addrs',default='127.0.0.1:9092'"`
+		Wait    time.Duration `flag:"name=wait,desc='wait time',default='2s'"`
+		Time    time.Time     `flag:"name=time,desc='some time',default='02 Jan 06 15:04 MST'"`
 	}
 
-	cfg := &config{}
+	ctx := context.Background()
+	cfg := &Config{}
 
-	fields, err := rutil.StructFields(cfg)
-	if err != nil {
+	c := NewConfig(config.Struct(cfg), TimeFormat(time.RFC822))
+	if err := c.Init(); err != nil {
 		t.Fatal(err)
 	}
 
-	for _, sf := range fields {
-		tf, ok := sf.Field.Tag.Lookup("flag")
-		if !ok {
-			continue
-		}
-		fn, fv, fd := getFlagOpts(tf)
-
-		switch sf.Value.Kind() {
-		case reflect.String:
-			v := sf.Value.Addr().Interface().(*string)
-			flag.StringVar(v, fn, fv, fd)
-		case reflect.Bool:
-			v := sf.Value.Addr().Interface().(*bool)
-			i, _ := strconv.ParseBool(fv)
-			flag.BoolVar(v, fn, i, fd)
-		}
+	if err := c.Load(ctx); err != nil {
+		t.Fatal(err)
 	}
 
-	flag.Parse()
 	if cfg.Broker != "5566:33" {
 		t.Fatalf("failed to parse flags broker value invalid: %#+v", cfg)
 	}
+	if tf := cfg.Time.Format(time.RFC822); tf != "02 Jan 06 14:32 MSK" {
+		t.Fatalf("parse time error: %v", cfg.Time)
+	}
+
+	t.Logf("cfg %#+v", cfg)
 }
diff --git a/go.mod b/go.mod
index 4b9944b..f56cadf 100644
--- a/go.mod
+++ b/go.mod
@@ -2,4 +2,4 @@ module github.com/unistack-org/micro-config-flag/v3
 
 go 1.16
 
-require github.com/unistack-org/micro/v3 v3.3.22
+require github.com/unistack-org/micro/v3 v3.3.23
diff --git a/go.sum b/go.sum
index 21cf3a2..0669248 100644
--- a/go.sum
+++ b/go.sum
@@ -5,8 +5,8 @@ github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
 github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
 github.com/silas/dag v0.0.0-20210121180416-41cf55125c34/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
-github.com/unistack-org/micro/v3 v3.3.22 h1:4mY+lhqW6N/3A0LSfAnGHE65TakGOrbU144/FQEjZIs=
-github.com/unistack-org/micro/v3 v3.3.22/go.mod h1:LXmPfbJnJNvL0kQs8HfnkV3Wya2Wb+C7keVq++RCZnk=
+github.com/unistack-org/micro/v3 v3.3.23 h1:iQtvVF4p+HPtWgm/zPt7+gN78EQMf1rHSMppxYlbRHQ=
+github.com/unistack-org/micro/v3 v3.3.23/go.mod h1:LXmPfbJnJNvL0kQs8HfnkV3Wya2Wb+C7keVq++RCZnk=
 golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/options.go b/options.go
new file mode 100644
index 0000000..e584811
--- /dev/null
+++ b/options.go
@@ -0,0 +1,26 @@
+package flag
+
+import (
+	"github.com/unistack-org/micro/v3/config"
+)
+
+type sliceDelimKey struct{}
+
+// SliceDelim set the slice delimeter
+func SliceDelim(s string) config.Option {
+	return config.SetOption(sliceDelimKey{}, s)
+}
+
+type mapDelimKey struct{}
+
+// MapDelim set the map delimeter
+func MapDelim(s string) config.Option {
+	return config.SetOption(mapDelimKey{}, s)
+}
+
+type timeFormatKey struct{}
+
+// TimeFormat set the time format
+func TimeFormat(s string) config.Option {
+	return config.SetOption(timeFormatKey{}, s)
+}
diff --git a/util.go b/util.go
index 0777ac0..556d1c6 100644
--- a/util.go
+++ b/util.go
@@ -1,9 +1,203 @@
 package flag
 
 import (
+	"flag"
+	"reflect"
+	"strconv"
 	"strings"
+	"time"
 )
 
+func (c *flagConfig) flagSlice(v reflect.Value, fn, fv, fd string) error {
+	delim := DefaultSliceDelim
+	if c.opts.Context != nil {
+		if d, ok := c.opts.Context.Value(sliceDelimKey{}).(string); ok {
+			delim = d
+		}
+	}
+
+	flag.Func(fn, fd, func(s string) error {
+		p := strings.Split(s, delim)
+		v.Set(reflect.MakeSlice(v.Type(), len(p), len(p)))
+		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
+				}
+				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.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
+}
+
+func (c *flagConfig) flagMap(v reflect.Value, fn, fv, fd string) error {
+	return nil
+}
+
+func (c *flagConfig) flagTime(v reflect.Value, fn, fv, fd string) error {
+	var format string
+	if c.opts.Context != nil {
+		if tf, ok := c.opts.Context.Value(timeFormatKey{}).(string); ok {
+			format = tf
+		}
+	}
+	if format == "" {
+		return ErrInvalidValue
+	}
+	flag.Func(fn, fd, func(s string) error {
+		t, err := time.Parse(s, format)
+		if err != nil {
+			return err
+		}
+		v.Set(reflect.ValueOf(t))
+		return nil
+	})
+
+	return nil
+}
+
+func (c *flagConfig) flagDuration(v reflect.Value, fn, fv, fd string) error {
+	nv, ok := v.Addr().Interface().(*time.Duration)
+	if !ok {
+		return ErrInvalidValue
+	}
+	i, err := time.ParseDuration(fd)
+	if err != nil {
+		return err
+	}
+	flag.DurationVar(nv, fn, i, fd)
+	return nil
+}
+
+func (c *flagConfig) flagBool(v reflect.Value, fn, fv, fd string) error {
+	nv, ok := v.Addr().Interface().(*bool)
+	if !ok {
+		return ErrInvalidValue
+	}
+	i, err := strconv.ParseBool(fd)
+	if err != nil {
+		return err
+	}
+	flag.BoolVar(nv, fn, i, fd)
+	return nil
+}
+
+func (c *flagConfig) flagString(v reflect.Value, fn, fv, fd string) error {
+	nv, ok := v.Addr().Interface().(*string)
+	if !ok {
+		return ErrInvalidValue
+	}
+	flag.StringVar(nv, fn, fv, fd)
+	return nil
+}
+
+func (c *flagConfig) flagInt(v reflect.Value, fn, fv, fd string) error {
+	nv, ok := v.Addr().Interface().(*int)
+	if !ok {
+		return ErrInvalidValue
+	}
+	i, err := strconv.ParseInt(fd, 10, 64)
+	if err != nil {
+		return err
+	}
+	flag.IntVar(nv, fn, int(i), fd)
+	return nil
+}
+
+func (c *flagConfig) flagInt64(v reflect.Value, fn, fv, fd string) error {
+	nv, ok := v.Addr().Interface().(*int64)
+	if !ok {
+		return ErrInvalidValue
+	}
+	i, err := strconv.ParseInt(fd, 10, 64)
+	if err != nil {
+		return err
+	}
+	flag.Int64Var(nv, fn, int64(i), fd)
+	return nil
+}
+
+func (c *flagConfig) flagUint(v reflect.Value, fn, fv, fd string) error {
+	nv, ok := v.Addr().Interface().(*uint)
+	if !ok {
+		return ErrInvalidValue
+	}
+	i, err := strconv.ParseUint(fd, 10, 64)
+	if err != nil {
+		return err
+	}
+	flag.UintVar(nv, fn, uint(i), fd)
+	return nil
+}
+
+func (c *flagConfig) flagUint64(v reflect.Value, fn, fv, fd string) error {
+	nv, ok := v.Addr().Interface().(*uint64)
+	if !ok {
+		return ErrInvalidValue
+	}
+	i, err := strconv.ParseUint(fd, 10, 64)
+	if err != nil {
+		return err
+	}
+	flag.Uint64Var(nv, fn, uint64(i), fd)
+	return nil
+}
+
+func (c *flagConfig) flagFloat64(v reflect.Value, fn, fv, fd string) error {
+	nv, ok := v.Addr().Interface().(*float64)
+	if !ok {
+		return ErrInvalidValue
+	}
+	i, err := strconv.ParseFloat(fd, 64)
+	if err != nil {
+		return err
+	}
+	flag.Float64Var(nv, fn, float64(i), fd)
+	return nil
+}
+
+func (c *flagConfig) flagStringSlice(v reflect.Value, fn, fv, fd string) error {
+	nv, ok := v.Addr().Interface().(*string)
+	if !ok {
+		return ErrInvalidValue
+	}
+	flag.StringVar(nv, fn, fv, fd)
+	return nil
+}
+
 func getFlagOpts(tf string) (string, string, string) {
 	ret := make([]string, 3)
 	vals := strings.Split(tf, ",")