From eb9c35c0e085300030532a48e234812767bf06e0 Mon Sep 17 00:00:00 2001 From: jl2005 Date: Sun, 30 Jun 2019 22:58:39 +0800 Subject: [PATCH 1/9] add travel and flags --- flags.go | 58 +++++++++++++++++++++++++++++++++++++++++++++++ travel.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++ travel_test.go | 37 ++++++++++++++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 flags.go create mode 100644 travel.go create mode 100644 travel_test.go diff --git a/flags.go b/flags.go new file mode 100644 index 0000000..56ebe5c --- /dev/null +++ b/flags.go @@ -0,0 +1,58 @@ +package configo + +// Flags 将类中的变量加入到flags中,从而可以通过命令行进行设置 +// 可以通过keys指定范围的成员加入到flags中,如果不指定则将所有的 +// 变量都加入到flags中 +// 不同类型变量的书写规则 +// 1. 一级变量,即只有一层变量 +// 2. 多级变量:使用 root.child 的形式作为变量名称 +// 3. 数组变量:数组下标作为一个层级,例如要设置root[0].key, 则flags中的key名称为root.0.key +func Flags(obj interface{}, keys ...string) { + /* + t := newTravel(func(path string, v reflect.Value) { + if len(keys) > 0 && !match(path, keys) { + return + } + switch v.Kind() { + case Int: + value := flags.IntVal() + addtomap + default: + } + }) + t.travel(obj) + */ + + // 1. 遍历所有变量 + // 2. 如果keys没有设置,则默认是将所有的变量都加入到flags中 + // 3. 如果指定了具体的变量名称,则只加入指定的变量到flags中 + // 4. 在读取配置文件之后,添加完默认配置之后,应用命令行中的配置 +} + +func ApplyFlags(obj interface{}) { + /* + actualFlags := make(map[string]bool) + flag.Visit(func(f *flag.Flag) { + actualFlags[f.Name] = f + }) + if len(actualFlags) == 0 { + return + } + t := newTravel(func(path string, v reflect.Value) { + if _, ok := actualFlags[path]; !ok { + return + } + + if len(keys) > 0 && !match(path, keys) { + return + } + switch v.Kind() { + case Int: + value := flags.IntVal() + addtomap + default: + } + }) + t.travel(obj) + */ +} diff --git a/travel.go b/travel.go new file mode 100644 index 0000000..6c87e27 --- /dev/null +++ b/travel.go @@ -0,0 +1,61 @@ +package configo + +import ( + "fmt" + "reflect" +) + +type TravelHandle func(path string, v reflect.Value) + +type Travel struct { + handle TravelHandle +} + +func NewTravel(h TravelHandle) *Travel { + return &Travel{handle: h} +} + +func (t *Travel) Travel(obj interface{}) { + t.travel("", reflect.ValueOf(obj)) +} + +func (t *Travel) travel(path string, v reflect.Value) { + switch v.Kind() { + case reflect.Ptr: + vValue := v.Elem() + if !vValue.IsValid() { + return + } + t.travel(path, vValue) + case reflect.Interface: + /* + vValue := v.Elem() + copyValue := reflect.New(vValue.Type()).Elem() + travel(copyValue, vValue) + copy.Set(copyValue) + */ + case reflect.Struct: + for i := 0; i < v.NumField(); i += 1 { + t.travel(path, v.Field(i)) + } + case reflect.Slice: + for i := 0; i < v.Len(); i += 1 { + p := fmt.Sprintf("%s.%d.", path, i) + t.travel(p, v.Index(i)) + } + case reflect.Map: + /* + for _, key := range v.MapKeys() { + vValue := v.MapIndex(key) + copyValue := reflect.New(vValue.Type()).Elem() + t.travel(copyValue, vValue) + copy.SetMapIndex(key, copyValue) + } + */ + case reflect.String: + // TODO get path + t.handle(path, v) + default: + //copy.Set(v) + } +} diff --git a/travel_test.go b/travel_test.go new file mode 100644 index 0000000..2cf58d2 --- /dev/null +++ b/travel_test.go @@ -0,0 +1,37 @@ +package configo + +import ( + "fmt" + "reflect" + "testing" +) + +func TestTravel_Travel(t *testing.T) { + tests := []struct { + name string + obj interface{} + handle TravelHandle + }{ + { + name: "test string", + obj: struct { + name string `cfg:"name;;;user name"` + }{ + name: "name", + }, + want: map[string]reflect.Vlaue{ + "name": a, + }, + handle: func(path string, v reflect.Value) { + fmt.Printf("paht=%s, v=%#v\n", path, v) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tr := NewTravel(tt.handle) + tr.Travel(tt.obj) + }) + } +} From dbfdd84bc6aca3a2258440bb602e11bacfc69a38 Mon Sep 17 00:00:00 2001 From: jl2005 Date: Mon, 1 Jul 2019 00:03:27 +0800 Subject: [PATCH 2/9] add some code --- travel.go | 10 +++++++++- travel_test.go | 30 ++++++++++++++++++++---------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/travel.go b/travel.go index 6c87e27..c720fbb 100644 --- a/travel.go +++ b/travel.go @@ -22,12 +22,14 @@ func (t *Travel) Travel(obj interface{}) { func (t *Travel) travel(path string, v reflect.Value) { switch v.Kind() { case reflect.Ptr: + fmt.Printf("travel ptr") vValue := v.Elem() if !vValue.IsValid() { return } t.travel(path, vValue) case reflect.Interface: + fmt.Printf("travel interface") /* vValue := v.Elem() copyValue := reflect.New(vValue.Type()).Elem() @@ -35,8 +37,13 @@ func (t *Travel) travel(path string, v reflect.Value) { copy.Set(copyValue) */ case reflect.Struct: + fmt.Printf("travel strcut") for i := 0; i < v.NumField(); i += 1 { - t.travel(path, v.Field(i)) + if !v.Field(i).IsValid() { + continue + } + tag := extractTag(v.Type().Field(i).Tag.Get(fieldTagName)) + t.travel(path+"."+tag.Name, v.Field(i)) } case reflect.Slice: for i := 0; i < v.Len(); i += 1 { @@ -56,6 +63,7 @@ func (t *Travel) travel(path string, v reflect.Value) { // TODO get path t.handle(path, v) default: + fmt.Printf("default set value %#v\n", v) //copy.Set(v) } } diff --git a/travel_test.go b/travel_test.go index 2cf58d2..11593b7 100644 --- a/travel_test.go +++ b/travel_test.go @@ -4,34 +4,44 @@ import ( "fmt" "reflect" "testing" + + "github.com/stretchr/testify/require" ) func TestTravel_Travel(t *testing.T) { tests := []struct { - name string - obj interface{} - handle TravelHandle + name string + obj interface{} + want map[string]interface{} }{ { name: "test string", obj: struct { name string `cfg:"name;;;user name"` }{ - name: "name", - }, - want: map[string]reflect.Vlaue{ - "name": a, + name: "name-value", }, - handle: func(path string, v reflect.Value) { - fmt.Printf("paht=%s, v=%#v\n", path, v) + want: map[string]interface{}{ + "name": "name-value", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tr := NewTravel(tt.handle) + handle := func(path string, v reflect.Value) { + fmt.Printf("paht=%s, v=%#v\n", path, v) + if w, ok := tt.want[path]; ok { + require.Equal(t, reflect.ValueOf(w), v) + delete(tt.want, path) + } + } + tr := NewTravel(handle) tr.Travel(tt.obj) + + if len(tt.want) > 0 { + require.FailNowf(t, "not get some path", "%#v", tt.want) + } }) } } From dff722ac82c041e635adac54232cc6c221351ae1 Mon Sep 17 00:00:00 2001 From: jl2005 Date: Mon, 1 Jul 2019 23:19:17 +0800 Subject: [PATCH 3/9] add travel test --- travel.go | 56 ++++++++++++++------------- travel_test.go | 103 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 130 insertions(+), 29 deletions(-) diff --git a/travel.go b/travel.go index c720fbb..b32c2ab 100644 --- a/travel.go +++ b/travel.go @@ -22,48 +22,52 @@ func (t *Travel) Travel(obj interface{}) { func (t *Travel) travel(path string, v reflect.Value) { switch v.Kind() { case reflect.Ptr: - fmt.Printf("travel ptr") vValue := v.Elem() if !vValue.IsValid() { return } t.travel(path, vValue) case reflect.Interface: - fmt.Printf("travel interface") - /* - vValue := v.Elem() - copyValue := reflect.New(vValue.Type()).Elem() - travel(copyValue, vValue) - copy.Set(copyValue) - */ + vValue := v.Elem() + if !vValue.IsValid() { + return + } + t.travel(path, vValue) case reflect.Struct: - fmt.Printf("travel strcut") for i := 0; i < v.NumField(); i += 1 { if !v.Field(i).IsValid() { continue } - tag := extractTag(v.Type().Field(i).Tag.Get(fieldTagName)) - t.travel(path+"."+tag.Name, v.Field(i)) + p := getPath(path, v.Type().Field(i)) + t.travel(p, v.Field(i)) } - case reflect.Slice: - for i := 0; i < v.Len(); i += 1 { - p := fmt.Sprintf("%s.%d.", path, i) + case reflect.Slice, reflect.Array: + for i := 0; i < v.Len(); i++ { + p := fmt.Sprintf("%d", i) + if len(path) > 0 { + p = path + "." + p + } t.travel(p, v.Index(i)) } - case reflect.Map: - /* - for _, key := range v.MapKeys() { - vValue := v.MapIndex(key) - copyValue := reflect.New(vValue.Type()).Elem() - t.travel(copyValue, vValue) - copy.SetMapIndex(key, copyValue) - } - */ + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + fallthrough + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + fallthrough + case reflect.Float32, reflect.Float64: + fallthrough + case reflect.Bool: + fallthrough case reflect.String: - // TODO get path t.handle(path, v) default: - fmt.Printf("default set value %#v\n", v) - //copy.Set(v) + panic(fmt.Sprintf("config file use unsupport type. %v", v.Type())) + } +} + +func getPath(prefix string, f reflect.StructField) string { + tag := extractTag(f.Tag.Get(fieldTagName)) + if len(prefix) == 0 { + return tag.Name } + return prefix + "." + tag.Name } diff --git a/travel_test.go b/travel_test.go index 11593b7..84fbad6 100644 --- a/travel_test.go +++ b/travel_test.go @@ -1,9 +1,9 @@ package configo import ( - "fmt" "reflect" "testing" + "time" "github.com/stretchr/testify/require" ) @@ -25,14 +25,86 @@ func TestTravel_Travel(t *testing.T) { "name": "name-value", }, }, + { + name: "test int,float,bool", + obj: struct { + name string `cfg:"name;;;user name"` + age int `cfg:"age;;;user age"` + score float64 `cfg:"score;;;user score"` + sex bool `cfg:"sex;;;user sex"` + }{ + name: "user-name", + age: 18, + score: 60.1, + sex: true, + }, + want: map[string]interface{}{ + "name": "user-name", + "age": 18, + "score": 60.1, + "sex": true, + }, + }, + { + name: "test slice", + obj: struct { + Cluster []string `cfg:"cluster;;;the address of redis cluster"` + }{ + Cluster: []string{"127.0.0.1:6379", "127.0.0.1:7379"}, + }, + want: map[string]interface{}{ + "cluster.0": "127.0.0.1:6379", + "cluster.1": "127.0.0.1:7379", + }, + }, + { + name: "test array", + obj: struct { + Cluster [2]string `cfg:"cluster;;;the address of redis cluster"` + }{ + Cluster: [2]string{"127.0.0.1:6379", "127.0.0.1:7379"}, + }, + want: map[string]interface{}{ + "cluster.0": "127.0.0.1:6379", + "cluster.1": "127.0.0.1:7379", + }, + }, + { + name: "test time duration", + obj: struct { + Timeout time.Duration `cfg:"timeout;10s;;time out"` + }{ + Timeout: 3 * time.Second, + }, + want: map[string]interface{}{ + "timeout": 3 * time.Second, + }, + }, + { + name: "test struct in struct", + obj: struct { + nest struct { + Timeout time.Duration `cfg:"timeout;10s;;time out"` + } `cfg:"nest;;;nest struct"` + }{ + nest: struct { + Timeout time.Duration `cfg:"timeout;10s;;time out"` + }{ + Timeout: 3 * time.Second, + }, + }, + want: map[string]interface{}{ + "nest.timeout": 3 * time.Second, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { handle := func(path string, v reflect.Value) { - fmt.Printf("paht=%s, v=%#v\n", path, v) if w, ok := tt.want[path]; ok { - require.Equal(t, reflect.ValueOf(w), v) + wt := reflect.ValueOf(w) + testEqual(t, wt, v) delete(tt.want, path) } } @@ -45,3 +117,28 @@ func TestTravel_Travel(t *testing.T) { }) } } + +func testEqual(t *testing.T, want, get reflect.Value) { + require.Equal(t, want.Kind(), get.Kind()) + switch get.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + require.Equal(t, want.Int(), get.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + require.Equal(t, want.Uint(), get.Uint()) + case reflect.Uintptr: + case reflect.Float32, reflect.Float64: + require.Equal(t, want.Float(), get.Float()) + case reflect.Bool: + require.Equal(t, want.Bool(), get.Bool()) + case reflect.String: + require.Equal(t, want.String(), get.String()) + + case reflect.Slice, reflect.Array: + require.Equal(t, want.Len(), get.Len()) + for i := 0; i < want.Len(); i++ { + testEqual(t, want.Index(i), get.Index(i)) + } + default: + t.Fatalf("uset type %v", get.Kind()) + } +} From e1666db76951e23fc3e9307ad1d68cca52880270 Mon Sep 17 00:00:00 2001 From: jl2005 Date: Tue, 2 Jul 2019 22:47:54 +0800 Subject: [PATCH 4/9] handle array&slice as a whole --- go.mod | 1 + travel.go | 3 +++ travel_test.go | 6 +++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 075fb0f..5b12bd6 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.12 require ( github.com/shafreeck/toml v0.0.0-20190326060449-44ad86712acc + github.com/stretchr/testify v1.3.0 golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a // indirect golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53 // indirect golang.org/x/sys v0.0.0-20190318195719-6c81ef8f67ca // indirect diff --git a/travel.go b/travel.go index b32c2ab..9ecaf87 100644 --- a/travel.go +++ b/travel.go @@ -42,11 +42,14 @@ func (t *Travel) travel(path string, v reflect.Value) { t.travel(p, v.Field(i)) } case reflect.Slice, reflect.Array: + // handle slice & array as a whole + t.handle(path, v) for i := 0; i < v.Len(); i++ { p := fmt.Sprintf("%d", i) if len(path) > 0 { p = path + "." + p } + // handle every element t.travel(p, v.Index(i)) } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: diff --git a/travel_test.go b/travel_test.go index 84fbad6..7d010f0 100644 --- a/travel_test.go +++ b/travel_test.go @@ -53,6 +53,7 @@ func TestTravel_Travel(t *testing.T) { Cluster: []string{"127.0.0.1:6379", "127.0.0.1:7379"}, }, want: map[string]interface{}{ + "cluster": []string{"127.0.0.1:6379", "127.0.0.1:7379"}, "cluster.0": "127.0.0.1:6379", "cluster.1": "127.0.0.1:7379", }, @@ -65,6 +66,7 @@ func TestTravel_Travel(t *testing.T) { Cluster: [2]string{"127.0.0.1:6379", "127.0.0.1:7379"}, }, want: map[string]interface{}{ + "cluster": [2]string{"127.0.0.1:6379", "127.0.0.1:7379"}, "cluster.0": "127.0.0.1:6379", "cluster.1": "127.0.0.1:7379", }, @@ -106,6 +108,8 @@ func TestTravel_Travel(t *testing.T) { wt := reflect.ValueOf(w) testEqual(t, wt, v) delete(tt.want, path) + } else { + require.FailNowf(t, "unknow path", "path=%s, v=%v", path, v) } } tr := NewTravel(handle) @@ -119,7 +123,7 @@ func TestTravel_Travel(t *testing.T) { } func testEqual(t *testing.T, want, get reflect.Value) { - require.Equal(t, want.Kind(), get.Kind()) + require.Equal(t, want.Kind(), get.Kind(), "data kind not same") switch get.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: require.Equal(t, want.Int(), get.Int()) From e0b1cfff9879d4530a193f673e2c0aac994a6fd4 Mon Sep 17 00:00:00 2001 From: jl2005 Date: Wed, 3 Jul 2019 23:07:15 +0800 Subject: [PATCH 5/9] add tag for param --- flags.go | 35 +++++++++++++++++++++-------------- travel.go | 34 ++++++++++++++++------------------ travel_test.go | 3 ++- 3 files changed, 39 insertions(+), 33 deletions(-) diff --git a/flags.go b/flags.go index 56ebe5c..5c4f117 100644 --- a/flags.go +++ b/flags.go @@ -1,5 +1,11 @@ package configo +import ( + "reflect" + + "github.com/shafreeck/toml" +) + // Flags 将类中的变量加入到flags中,从而可以通过命令行进行设置 // 可以通过keys指定范围的成员加入到flags中,如果不指定则将所有的 // 变量都加入到flags中 @@ -8,20 +14,21 @@ package configo // 2. 多级变量:使用 root.child 的形式作为变量名称 // 3. 数组变量:数组下标作为一个层级,例如要设置root[0].key, 则flags中的key名称为root.0.key func Flags(obj interface{}, keys ...string) { - /* - t := newTravel(func(path string, v reflect.Value) { - if len(keys) > 0 && !match(path, keys) { - return - } - switch v.Kind() { - case Int: - value := flags.IntVal() - addtomap - default: - } - }) - t.travel(obj) - */ + m := make(map[string]struct{}) + for i := range keys { + m[keys[i]] = struct{}{} + } + t := NewTravel(func(path string, tag *toml.CfgTag, v reflect.Value) { + if _, ok := m[path]; len(m) > 0 && !ok { + return + } + switch v.Kind() { + case reflect.Int: + //flags.Int(path, i, des) + default: + } + }) + t.Travel(obj) // 1. 遍历所有变量 // 2. 如果keys没有设置,则默认是将所有的变量都加入到flags中 diff --git a/travel.go b/travel.go index 9ecaf87..c5045d2 100644 --- a/travel.go +++ b/travel.go @@ -3,9 +3,11 @@ package configo import ( "fmt" "reflect" + + "github.com/shafreeck/toml" ) -type TravelHandle func(path string, v reflect.Value) +type TravelHandle func(path string, tag *toml.CfgTag, v reflect.Value) type Travel struct { handle TravelHandle @@ -16,41 +18,45 @@ func NewTravel(h TravelHandle) *Travel { } func (t *Travel) Travel(obj interface{}) { - t.travel("", reflect.ValueOf(obj)) + t.travel("", nil, reflect.ValueOf(obj)) } -func (t *Travel) travel(path string, v reflect.Value) { +func (t *Travel) travel(path string, tag *toml.CfgTag, v reflect.Value) { switch v.Kind() { case reflect.Ptr: vValue := v.Elem() if !vValue.IsValid() { return } - t.travel(path, vValue) + t.travel(path, tag, vValue) case reflect.Interface: vValue := v.Elem() if !vValue.IsValid() { return } - t.travel(path, vValue) + t.travel(path, tag, vValue) case reflect.Struct: for i := 0; i < v.NumField(); i += 1 { if !v.Field(i).IsValid() { continue } - p := getPath(path, v.Type().Field(i)) - t.travel(p, v.Field(i)) + tag := extractTag(v.Type().Field(i).Tag.Get(fieldTagName)) + p := tag.Name + if len(path) > 0 { + p = path + "." + tag.Name + } + t.travel(p, tag, v.Field(i)) } case reflect.Slice, reflect.Array: // handle slice & array as a whole - t.handle(path, v) + t.handle(path, tag, v) for i := 0; i < v.Len(); i++ { p := fmt.Sprintf("%d", i) if len(path) > 0 { p = path + "." + p } // handle every element - t.travel(p, v.Index(i)) + t.travel(p, tag, v.Index(i)) } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: fallthrough @@ -61,16 +67,8 @@ func (t *Travel) travel(path string, v reflect.Value) { case reflect.Bool: fallthrough case reflect.String: - t.handle(path, v) + t.handle(path, tag, v) default: panic(fmt.Sprintf("config file use unsupport type. %v", v.Type())) } } - -func getPath(prefix string, f reflect.StructField) string { - tag := extractTag(f.Tag.Get(fieldTagName)) - if len(prefix) == 0 { - return tag.Name - } - return prefix + "." + tag.Name -} diff --git a/travel_test.go b/travel_test.go index 7d010f0..17c6abe 100644 --- a/travel_test.go +++ b/travel_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/shafreeck/toml" "github.com/stretchr/testify/require" ) @@ -103,7 +104,7 @@ func TestTravel_Travel(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - handle := func(path string, v reflect.Value) { + handle := func(path string, tag *toml.CfgTag, v reflect.Value) { if w, ok := tt.want[path]; ok { wt := reflect.ValueOf(w) testEqual(t, wt, v) From 3d25a211442f099070c469fba79ae05f07bcc74f Mon Sep 17 00:00:00 2001 From: jl2005 Date: Sat, 6 Jul 2019 00:19:03 +0800 Subject: [PATCH 6/9] add flag test --- configo.go | 3 + example/conf/example.toml | 4 +- example/flag.go | 29 ++++++++ flags.go | 142 +++++++++++++++++++++++++++++++------- 4 files changed, 151 insertions(+), 27 deletions(-) create mode 100644 example/flag.go diff --git a/configo.go b/configo.go index f66c2bd..c7e4c5b 100644 --- a/configo.go +++ b/configo.go @@ -272,6 +272,9 @@ func Unmarshal(data []byte, v interface{}) error { if err := applyDefault(reflect.ValueOf(v), false); err != nil { return err } + + // apply flag param + ApplyFlags(v) return nil } diff --git a/example/conf/example.toml b/example/conf/example.toml index 0bf05f5..b8b2057 100644 --- a/example/conf/example.toml +++ b/example/conf/example.toml @@ -10,10 +10,10 @@ #default: 10000 #max-connection = 10000 -[redis] +#[redis] #type: []string #rules: dialstring #description: The addresses of redis cluster #required -cluster = [] +#cluster = [] diff --git a/example/flag.go b/example/flag.go new file mode 100644 index 0000000..6b04baf --- /dev/null +++ b/example/flag.go @@ -0,0 +1,29 @@ +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "log" + + "github.com/distributedio/configo" + "github.com/distributedio/configo/example/conf" +) + +func main() { + c := &conf.Config{} + + configo.Flags(c, "listen", "redis", "redis.0.cluster") + flag.Parse() + + data, err := ioutil.ReadFile("conf/example.toml") + if err != nil { + log.Fatalln(err) + } + + err = configo.Unmarshal(data, c) + if err != nil { + log.Fatalln(err) + } + fmt.Printf("%#v\n", c) +} diff --git a/flags.go b/flags.go index 5c4f117..0f44c43 100644 --- a/flags.go +++ b/flags.go @@ -1,11 +1,17 @@ package configo import ( + "flag" + "log" "reflect" + "strconv" + "time" "github.com/shafreeck/toml" ) +var flagMap = make(map[string]struct{}) + // Flags 将类中的变量加入到flags中,从而可以通过命令行进行设置 // 可以通过keys指定范围的成员加入到flags中,如果不指定则将所有的 // 变量都加入到flags中 @@ -14,18 +20,60 @@ import ( // 2. 多级变量:使用 root.child 的形式作为变量名称 // 3. 数组变量:数组下标作为一个层级,例如要设置root[0].key, 则flags中的key名称为root.0.key func Flags(obj interface{}, keys ...string) { - m := make(map[string]struct{}) for i := range keys { - m[keys[i]] = struct{}{} + flagMap[keys[i]] = struct{}{} } - t := NewTravel(func(path string, tag *toml.CfgTag, v reflect.Value) { - if _, ok := m[path]; len(m) > 0 && !ok { + t := NewTravel(func(path string, tag *toml.CfgTag, fv reflect.Value) { + if _, ok := flagMap[path]; len(flagMap) > 0 && !ok { return } - switch v.Kind() { - case reflect.Int: - //flags.Int(path, i, des) + var err error + //Set the default value + switch fv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, + reflect.Int32, reflect.Int64: + var v int64 + if v, err = strconv.ParseInt(tag.Value, 10, 64); err != nil { + if fv.Kind() == reflect.Int64 { + //try to parse a time.Duration + if d, err := time.ParseDuration(tag.Value); err == nil { + flag.Duration(path, time.Duration(d), tag.Description) + return + } + } + log.Fatalln(err) + return + } + flag.Int64(path, v, tag.Description) + case reflect.Uint, reflect.Uint8, reflect.Uint16, + reflect.Uint32, reflect.Uint64: + var v uint64 + if v, err = strconv.ParseUint(tag.Value, 10, 64); err != nil { + log.Fatalln(err) + return + } + flag.Uint64(path, v, tag.Description) + case reflect.Float32, reflect.Float64: + var v float64 + if v, err = strconv.ParseFloat(tag.Value, 64); err != nil { + log.Fatalln(err) + return + } + flag.Float64(path, v, tag.Description) + case reflect.Bool: + var v bool + if v, err = strconv.ParseBool(tag.Value); err != nil { + log.Fatalln(err) + return + } + flag.Bool(path, v, tag.Description) + case reflect.String: + flag.String(path, tag.Value, tag.Description) + case reflect.Slice, reflect.Array: + // set as string type + flag.String(path, tag.Value, tag.Description) default: + log.Fatalf("unsupport type %s for set flag", fv.Type()) } }) t.Travel(obj) @@ -37,29 +85,73 @@ func Flags(obj interface{}, keys ...string) { } func ApplyFlags(obj interface{}) { - /* - actualFlags := make(map[string]bool) - flag.Visit(func(f *flag.Flag) { - actualFlags[f.Name] = f - }) - if len(actualFlags) == 0 { + actualFlags := make(map[string]*flag.Flag) + flag.Visit(func(f *flag.Flag) { + actualFlags[f.Name] = f + }) + if len(actualFlags) == 0 { + return + } + t := NewTravel(func(path string, tag *toml.CfgTag, fv reflect.Value) { + f, ok := actualFlags[path] + if !ok { return } - t := newTravel(func(path string, v reflect.Value) { - if _, ok := actualFlags[path]; !ok { + if _, ok := flagMap[path]; len(flagMap) > 0 && !ok { + return + } + var err error + switch fv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, + reflect.Int32, reflect.Int64: + var v int64 + if v, err = strconv.ParseInt(f.Value.String(), 10, 64); err != nil { + if fv.Kind() == reflect.Int64 { + //try to parse a time.Duration + if d, err := time.ParseDuration(f.Value.String()); err == nil { + fv.SetInt(int64(d)) + return + } + } + log.Fatalln(err) return } - - if len(keys) > 0 && !match(path, keys) { + fv.SetInt(v) + case reflect.Uint, reflect.Uint8, reflect.Uint16, + reflect.Uint32, reflect.Uint64: + var v uint64 + if v, err = strconv.ParseUint(f.Value.String(), 10, 64); err != nil { + log.Fatalln(err) + return + } + fv.SetUint(v) + case reflect.Float32, reflect.Float64: + var v float64 + if v, err = strconv.ParseFloat(f.Value.String(), 64); err != nil { + log.Fatalln(err) return } - switch v.Kind() { - case Int: - value := flags.IntVal() - addtomap - default: + fv.SetFloat(v) + case reflect.Bool: + var v bool + if v, err = strconv.ParseBool(f.Value.String()); err != nil { + log.Fatalln(err) + return } - }) - t.travel(obj) - */ + fv.SetBool(v) + case reflect.String: + fv.SetString(f.Value.String()) + case reflect.Slice, reflect.Array: + // FIXME TODO + /* + v := rv.Addr().Interface() + if err := unmarshalArray(ft.Name, f.Value.String(), v); err != nil { + return err + } + */ + default: + log.Fatalf("unsupport type %s for set flag", fv.Type()) + } + }) + t.Travel(obj) } From edebc432f459c62a706b8d33ce212ffb5b63e1f4 Mon Sep 17 00:00:00 2001 From: jl2005 Date: Sat, 6 Jul 2019 23:20:12 +0800 Subject: [PATCH 7/9] add flags comment --- example/{flag.go => flags.go} | 2 +- flags.go | 92 ++++++++++++++++++++++++++--------- 2 files changed, 70 insertions(+), 24 deletions(-) rename example/{flag.go => flags.go} (86%) diff --git a/example/flag.go b/example/flags.go similarity index 86% rename from example/flag.go rename to example/flags.go index 6b04baf..55279ea 100644 --- a/example/flag.go +++ b/example/flags.go @@ -13,7 +13,7 @@ import ( func main() { c := &conf.Config{} - configo.Flags(c, "listen", "redis", "redis.0.cluster") + configo.AddFlags(c, "listen", "redis", "redis.0.cluster") flag.Parse() data, err := ioutil.ReadFile("conf/example.toml") diff --git a/flags.go b/flags.go index 0f44c43..47fbc2b 100644 --- a/flags.go +++ b/flags.go @@ -10,16 +10,63 @@ import ( "github.com/shafreeck/toml" ) -var flagMap = make(map[string]struct{}) +/* + `flags` 实现将对象中的变量添加到`flag`中,从而实现通过命令行设置变量的功能。 -// Flags 将类中的变量加入到flags中,从而可以通过命令行进行设置 -// 可以通过keys指定范围的成员加入到flags中,如果不指定则将所有的 -// 变量都加入到flags中 -// 不同类型变量的书写规则 -// 1. 一级变量,即只有一层变量 -// 2. 多级变量:使用 root.child 的形式作为变量名称 -// 3. 数组变量:数组下标作为一个层级,例如要设置root[0].key, 则flags中的key名称为root.0.key -func Flags(obj interface{}, keys ...string) { + import ( + "log" + + "github.com/distributedio/configo" + ) + + type Config struct { + Key string `cfg:"key; default;; simple type example"` + Child *Child `cfg:"child; ;; class type "` + Array []string `cfg:"array;;; simple array type"` + CompArray []*Child `cfg:"comp;;; complex array type"` + } + + type Child struct { + Name string `cfg:"name; noname;; child class item` + } + + func main() { + conf := &Config{} + configo.AddFlags(conf) + flag.Parse() + + if err := configo.Load("conf/example.toml", conf); err != nil { + log.Fatalln(err) + } + } + + 首先,需要在`flag.Parse()`之前调用`AddFlags()`将对象中的变量添加到`falg`中。 + `configo.Load()`会在内部调用`ApplyFlags()`方法,将`flag`中设置的变量应用到 + 对象中。 + + 对象中的变量按照如下规则对应`flag`中的`key`: + + * 简单数据类型,直接使用`cfg`中的`name`作为`flag`中的`key`。 + 如`Conf.Key`,对应`flag`中的`key`。 + * 对象数据类型,需要添加上一层对象的名称。 + 如 `Conf.Child.Name` 对应`flag`中的`child.name` + * 数组或slice类型,要增加下标作为一个层级。 + 如 `Conf.CompArray[0].Name`,对应`flag`中的`comp.0.name` + * 对于简单数据类型的数组或slice也可以使用名称作为`flag`中的`key`, + 使用字符串表示一个数组。 + 例如:`Conf.Array`,对应`flag`中的`array`。同时在执行中,使用如下的 + 方式设置`array`: + ./cmd -array="[\"a1\", \"a2\"]" +*/ + +var flagMap map[string]struct{} = nil + +// AddFlags 将对象中的变量加入到flag中,从而可以通过命令行设置对应的变量。 +// +// * `obj` 为待加入到`flag`中的对象的实例 +// * `keys` 限定加入`flag`中变量的范围,**不设置**的时候表示将所有变量都加入到`flag`中。 +func AddFlags(obj interface{}, keys ...string) { + flagMap = make(map[string]struct{}, len(keys)) for i := range keys { flagMap[keys[i]] = struct{}{} } @@ -28,7 +75,6 @@ func Flags(obj interface{}, keys ...string) { return } var err error - //Set the default value switch fv.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: @@ -70,26 +116,24 @@ func Flags(obj interface{}, keys ...string) { case reflect.String: flag.String(path, tag.Value, tag.Description) case reflect.Slice, reflect.Array: - // set as string type + // TODO 使用flag.Var设置变量 flag.String(path, tag.Value, tag.Description) default: - log.Fatalf("unsupport type %s for set flag", fv.Type()) + log.Printf("unknow type %s for set flag", fv.Type()) } }) t.Travel(obj) - - // 1. 遍历所有变量 - // 2. 如果keys没有设置,则默认是将所有的变量都加入到flags中 - // 3. 如果指定了具体的变量名称,则只加入指定的变量到flags中 - // 4. 在读取配置文件之后,添加完默认配置之后,应用命令行中的配置 } +// ApplyFlags 将命令行中设置的变量值应用到`obj`中。 +// +// **注意:** configo中的函数默认会调用这个函数设置配置文件,所以不需要显示调用。 func ApplyFlags(obj interface{}) { actualFlags := make(map[string]*flag.Flag) flag.Visit(func(f *flag.Flag) { actualFlags[f.Name] = f }) - if len(actualFlags) == 0 { + if len(actualFlags) == 0 || flagMap == nil { return } t := NewTravel(func(path string, tag *toml.CfgTag, fv reflect.Value) { @@ -142,15 +186,17 @@ func ApplyFlags(obj interface{}) { case reflect.String: fv.SetString(f.Value.String()) case reflect.Slice, reflect.Array: - // FIXME TODO + // TODO NOT support /* - v := rv.Addr().Interface() - if err := unmarshalArray(ft.Name, f.Value.String(), v); err != nil { - return err + if err := unmarshalArray("name", f.Value.String(), &s); err != nil { + log.Fatalln(err) + return } + fv.Set(reflect.ValueOf(s.Name)) + log.Printf("get list =%#v\n", s) */ default: - log.Fatalf("unsupport type %s for set flag", fv.Type()) + log.Printf("unknow type %s for set flag", fv.Type()) } }) t.Travel(obj) From c93b666c09d67909f64cf79c2d6c8ebbcd0ba5c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=84=A6=E9=BE=99?= Date: Thu, 18 Jul 2019 10:14:34 +0800 Subject: [PATCH 8/9] remove flagMap --- configo.go | 3 ++- example/flags.go | 2 +- flags.go | 51 +++++++++++++++++++++++------------------------- 3 files changed, 27 insertions(+), 29 deletions(-) diff --git a/configo.go b/configo.go index c7e4c5b..aa6148b 100644 --- a/configo.go +++ b/configo.go @@ -5,6 +5,7 @@ import ( "github.com/shafreeck/toml" "github.com/shafreeck/toml/ast" + "flag" "fmt" goast "go/ast" "reflect" @@ -274,7 +275,7 @@ func Unmarshal(data []byte, v interface{}) error { } // apply flag param - ApplyFlags(v) + ApplyFlags(flag.CommandLine, v) return nil } diff --git a/example/flags.go b/example/flags.go index 55279ea..c3eed55 100644 --- a/example/flags.go +++ b/example/flags.go @@ -13,7 +13,7 @@ import ( func main() { c := &conf.Config{} - configo.AddFlags(c, "listen", "redis", "redis.0.cluster") + configo.AddFlags(flag.CommandLine, c, "listen", "redis", "redis.0.cluster") flag.Parse() data, err := ioutil.ReadFile("conf/example.toml") diff --git a/flags.go b/flags.go index 47fbc2b..6802270 100644 --- a/flags.go +++ b/flags.go @@ -5,6 +5,7 @@ import ( "log" "reflect" "strconv" + "strings" "time" "github.com/shafreeck/toml" @@ -59,14 +60,16 @@ import ( ./cmd -array="[\"a1\", \"a2\"]" */ -var flagMap map[string]struct{} = nil +const ( + ConfigoFlagSuffix = "[configo]" +) // AddFlags 将对象中的变量加入到flag中,从而可以通过命令行设置对应的变量。 // // * `obj` 为待加入到`flag`中的对象的实例 // * `keys` 限定加入`flag`中变量的范围,**不设置**的时候表示将所有变量都加入到`flag`中。 -func AddFlags(obj interface{}, keys ...string) { - flagMap = make(map[string]struct{}, len(keys)) +func AddFlags(fs *flag.FlagSet, obj interface{}, keys ...string) { + flagMap := make(map[string]struct{}, len(keys)) for i := range keys { flagMap[keys[i]] = struct{}{} } @@ -83,14 +86,14 @@ func AddFlags(obj interface{}, keys ...string) { if fv.Kind() == reflect.Int64 { //try to parse a time.Duration if d, err := time.ParseDuration(tag.Value); err == nil { - flag.Duration(path, time.Duration(d), tag.Description) + fs.Duration(path, time.Duration(d), tag.Description+ConfigoFlagSuffix) return } } log.Fatalln(err) return } - flag.Int64(path, v, tag.Description) + fs.Int64(path, v, tag.Description+ConfigoFlagSuffix) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: var v uint64 @@ -98,26 +101,26 @@ func AddFlags(obj interface{}, keys ...string) { log.Fatalln(err) return } - flag.Uint64(path, v, tag.Description) + fs.Uint64(path, v, tag.Description+ConfigoFlagSuffix) case reflect.Float32, reflect.Float64: var v float64 if v, err = strconv.ParseFloat(tag.Value, 64); err != nil { log.Fatalln(err) return } - flag.Float64(path, v, tag.Description) + fs.Float64(path, v, tag.Description+ConfigoFlagSuffix) case reflect.Bool: var v bool if v, err = strconv.ParseBool(tag.Value); err != nil { log.Fatalln(err) return } - flag.Bool(path, v, tag.Description) + fs.Bool(path, v, tag.Description+ConfigoFlagSuffix) case reflect.String: - flag.String(path, tag.Value, tag.Description) + fs.String(path, tag.Value, tag.Description+ConfigoFlagSuffix) case reflect.Slice, reflect.Array: // TODO 使用flag.Var设置变量 - flag.String(path, tag.Value, tag.Description) + fs.String(path, tag.Value, tag.Description+ConfigoFlagSuffix) default: log.Printf("unknow type %s for set flag", fv.Type()) } @@ -128,22 +131,18 @@ func AddFlags(obj interface{}, keys ...string) { // ApplyFlags 将命令行中设置的变量值应用到`obj`中。 // // **注意:** configo中的函数默认会调用这个函数设置配置文件,所以不需要显示调用。 -func ApplyFlags(obj interface{}) { +func ApplyFlags(fs *flag.FlagSet, obj interface{}) { actualFlags := make(map[string]*flag.Flag) - flag.Visit(func(f *flag.Flag) { - actualFlags[f.Name] = f + fs.Visit(func(f *flag.Flag) { + if strings.Contains(f.Usage, ConfigoFlagSuffix) { + actualFlags[f.Name] = f + } }) - if len(actualFlags) == 0 || flagMap == nil { - return - } t := NewTravel(func(path string, tag *toml.CfgTag, fv reflect.Value) { f, ok := actualFlags[path] if !ok { return } - if _, ok := flagMap[path]; len(flagMap) > 0 && !ok { - return - } var err error switch fv.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, @@ -187,14 +186,12 @@ func ApplyFlags(obj interface{}) { fv.SetString(f.Value.String()) case reflect.Slice, reflect.Array: // TODO NOT support - /* - if err := unmarshalArray("name", f.Value.String(), &s); err != nil { - log.Fatalln(err) - return - } - fv.Set(reflect.ValueOf(s.Name)) - log.Printf("get list =%#v\n", s) - */ + // if err := unmarshalArray("name", f.Value.String(), &s); err != nil { + // log.Fatalln(err) + // return + // } + // fv.Set(reflect.ValueOf(s.Name)) + // log.Printf("get list =%#v\n", s) default: log.Printf("unknow type %s for set flag", fv.Type()) } From 7dc7b42fbaf724f635a7ba8ed4803d93a090b852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=84=A6=E9=BE=99?= Date: Tue, 23 Jul 2019 10:15:55 +0800 Subject: [PATCH 9/9] change prefix --- flags.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/flags.go b/flags.go index 6802270..65c8044 100644 --- a/flags.go +++ b/flags.go @@ -86,14 +86,14 @@ func AddFlags(fs *flag.FlagSet, obj interface{}, keys ...string) { if fv.Kind() == reflect.Int64 { //try to parse a time.Duration if d, err := time.ParseDuration(tag.Value); err == nil { - fs.Duration(path, time.Duration(d), tag.Description+ConfigoFlagSuffix) + fs.Duration(path, time.Duration(d), ConfigoFlagSuffix+tag.Description) return } } log.Fatalln(err) return } - fs.Int64(path, v, tag.Description+ConfigoFlagSuffix) + fs.Int64(path, v, ConfigoFlagSuffix+tag.Description) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: var v uint64 @@ -101,26 +101,26 @@ func AddFlags(fs *flag.FlagSet, obj interface{}, keys ...string) { log.Fatalln(err) return } - fs.Uint64(path, v, tag.Description+ConfigoFlagSuffix) + fs.Uint64(path, v, ConfigoFlagSuffix+tag.Description) case reflect.Float32, reflect.Float64: var v float64 if v, err = strconv.ParseFloat(tag.Value, 64); err != nil { log.Fatalln(err) return } - fs.Float64(path, v, tag.Description+ConfigoFlagSuffix) + fs.Float64(path, v, ConfigoFlagSuffix+tag.Description) case reflect.Bool: var v bool if v, err = strconv.ParseBool(tag.Value); err != nil { log.Fatalln(err) return } - fs.Bool(path, v, tag.Description+ConfigoFlagSuffix) + fs.Bool(path, v, ConfigoFlagSuffix+tag.Description) case reflect.String: - fs.String(path, tag.Value, tag.Description+ConfigoFlagSuffix) + fs.String(path, tag.Value, ConfigoFlagSuffix+tag.Description) case reflect.Slice, reflect.Array: // TODO 使用flag.Var设置变量 - fs.String(path, tag.Value, tag.Description+ConfigoFlagSuffix) + fs.String(path, tag.Value, ConfigoFlagSuffix+tag.Description) default: log.Printf("unknow type %s for set flag", fv.Type()) }