Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

增加命令行设置配置参数 #6

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions configo.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/shafreeck/toml"
"github.com/shafreeck/toml/ast"

"flag"
"fmt"
goast "go/ast"
"reflect"
Expand Down Expand Up @@ -272,6 +273,9 @@ func Unmarshal(data []byte, v interface{}) error {
if err := applyDefault(reflect.ValueOf(v), false); err != nil {
return err
}

// apply flag param
ApplyFlags(flag.CommandLine, v)
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]
jl2005 marked this conversation as resolved.
Show resolved Hide resolved

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

import (
"flag"
"log"
"reflect"
"strconv"
"strings"
"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\"]"
*/

const (
ConfigoFlagSuffix = "[configo]"
)

// AddFlags 将对象中的变量加入到flag中,从而可以通过命令行设置对应的变量。
//
// * `obj` 为待加入到`flag`中的对象的实例
// * `keys` 限定加入`flag`中变量的范围,**不设置**的时候表示将所有变量都加入到`flag`中。
func AddFlags(fs *flag.FlagSet, obj interface{}, keys ...string) {
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) {
jl2005 marked this conversation as resolved.
Show resolved Hide 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 {
fs.Duration(path, time.Duration(d), ConfigoFlagSuffix+tag.Description)
return
}
}
log.Fatalln(err)
return
}
fs.Int64(path, v, ConfigoFlagSuffix+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
}
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, 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, ConfigoFlagSuffix+tag.Description)
case reflect.String:
fs.String(path, tag.Value, ConfigoFlagSuffix+tag.Description)
case reflect.Slice, reflect.Array:
// TODO 使用flag.Var设置变量
fs.String(path, tag.Value, ConfigoFlagSuffix+tag.Description)
default:
log.Printf("unknow type %s for set flag", fv.Type())
}
})
t.Travel(obj)
}

// ApplyFlags 将命令行中设置的变量值应用到`obj`中。
//
// **注意:** configo中的函数默认会调用这个函数设置配置文件,所以不需要显示调用。
func ApplyFlags(fs *flag.FlagSet, obj interface{}) {
actualFlags := make(map[string]*flag.Flag)
fs.Visit(func(f *flag.Flag) {
if strings.Contains(f.Usage, ConfigoFlagSuffix) {
actualFlags[f.Name] = f
}
})
t := NewTravel(func(path string, tag *toml.CfgTag, fv reflect.Value) {
f, ok := actualFlags[path]
if !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