Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions configo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感觉放到这里,或默认启用flag不太合适,比如我只想解析toml,不想使用flag呢?

是不是考虑将Unmarshal, ApplyFlags, ApplyEnvs 当做独立的函数,然后通过更上层的函数合并起来?

return nil
}

Expand Down
4 changes: 2 additions & 2 deletions example/conf/example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
#default: 10000
#max-connection = 10000

[redis]
#[redis]
Comment thread
jl2005 marked this conversation as resolved.

#type: []string
#rules: dialstring
#description: The addresses of redis cluster
#required
cluster = []
#cluster = []
Comment thread
jl2005 marked this conversation as resolved.
29 changes: 29 additions & 0 deletions example/flags.go
Original file line number Diff line number Diff line change
@@ -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.AddFlags(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)
}
203 changes: 203 additions & 0 deletions flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package configo

import (
"flag"
"log"
"reflect"
"strconv"
"time"

"github.com/shafreeck/toml"
)

/*
`flags` 实现将对象中的变量添加到`flag`中,从而实现通过命令行设置变量的功能。

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flagMap 能否省掉? 比如直接查询FlagSet?


// AddFlags 将对象中的变量加入到flag中,从而可以通过命令行设置对应的变量。
//
// * `obj` 为待加入到`flag`中的对象的实例
// * `keys` 限定加入`flag`中变量的范围,**不设置**的时候表示将所有变量都加入到`flag`中。
func AddFlags(obj interface{}, keys ...string) {
Comment thread
jl2005 marked this conversation as resolved.
Outdated
flagMap = make(map[string]struct{}, len(keys))
for i := range keys {
flagMap[keys[i]] = struct{}{}
}
t := NewTravel(func(path string, tag *toml.CfgTag, fv reflect.Value) {
Comment thread
jl2005 marked this conversation as resolved.
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(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:
// TODO 使用flag.Var设置变量
flag.String(path, tag.Value, tag.Description)
default:
log.Printf("unknow type %s for set flag", fv.Type())
}
})
t.Travel(obj)
}

// 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 || 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,
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
}
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
}
fv.SetFloat(v)
case reflect.Bool:
var v bool
if v, err = strconv.ParseBool(f.Value.String()); err != nil {
log.Fatalln(err)
return
}
fv.SetBool(v)
case reflect.String:
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)
*/
default:
log.Printf("unknow type %s for set flag", fv.Type())
}
})
t.Travel(obj)
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
74 changes: 74 additions & 0 deletions travel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package configo

import (
"fmt"
"reflect"

"github.com/shafreeck/toml"
)

type TravelHandle func(path string, tag *toml.CfgTag, 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("", nil, reflect.ValueOf(obj))
}

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, tag, vValue)
case reflect.Interface:
vValue := v.Elem()
if !vValue.IsValid() {
return
}
t.travel(path, tag, vValue)
case reflect.Struct:
for i := 0; i < v.NumField(); i += 1 {
if !v.Field(i).IsValid() {
continue
}
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, 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, tag, v.Index(i))
}
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:
t.handle(path, tag, v)
default:
panic(fmt.Sprintf("config file use unsupport type. %v", v.Type()))
}
}
Loading