cfgstruct.Bind: support nested config paths (#785)
this will allow some config cleanups in a future pr Change-Id: Ie873bcee567a72956d9337dfc13ab6ba46c9d1a0
This commit is contained in:
parent
ceb590fa67
commit
4a4f6ad53e
@ -46,8 +46,7 @@ var (
|
||||
func init() {
|
||||
rootCmd.AddCommand(setupCmd)
|
||||
cfgstruct.Bind(setupCmd.Flags(), &setupCfg,
|
||||
cfgstruct.ConfDir(defaultConfDir),
|
||||
)
|
||||
cfgstruct.ConfDir(defaultConfDir))
|
||||
}
|
||||
|
||||
func cmdSetup(cmd *cobra.Command, args []string) (err error) {
|
||||
|
@ -6,6 +6,7 @@ package cfgstruct
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@ -14,15 +15,33 @@ import (
|
||||
)
|
||||
|
||||
// BindOpt is an option for the Bind method
|
||||
type BindOpt func(vars map[string]string)
|
||||
type BindOpt func(vars map[string]confVar)
|
||||
|
||||
// ConfDir sets a variable for default options called $CONFDIR
|
||||
// ConfDir sets variables for default options called $CONFDIR and $CONFNAME.
|
||||
func ConfDir(confdir string) BindOpt {
|
||||
return BindOpt(func(vars map[string]string) {
|
||||
vars["CONFDIR"] = os.ExpandEnv(confdir)
|
||||
val := filepath.Clean(os.ExpandEnv(confdir))
|
||||
return BindOpt(func(vars map[string]confVar) {
|
||||
vars["CONFDIR"] = confVar{val: val, nested: false}
|
||||
vars["CONFNAME"] = confVar{val: val, nested: false}
|
||||
})
|
||||
}
|
||||
|
||||
// ConfDirNested sets variables for default options called $CONFDIR and $CONFNAME.
|
||||
// ConfDirNested also appends the parent struct field name to the paths before
|
||||
// descending into substructs.
|
||||
func ConfDirNested(confdir string) BindOpt {
|
||||
val := filepath.Clean(os.ExpandEnv(confdir))
|
||||
return BindOpt(func(vars map[string]confVar) {
|
||||
vars["CONFDIR"] = confVar{val: val, nested: true}
|
||||
vars["CONFNAME"] = confVar{val: val, nested: true}
|
||||
})
|
||||
}
|
||||
|
||||
type confVar struct {
|
||||
val string
|
||||
nested bool
|
||||
}
|
||||
|
||||
// Bind sets flags on a FlagSet that match the configuration struct
|
||||
// 'config'. This works by traversing the config struct using the 'reflect'
|
||||
// package.
|
||||
@ -32,7 +51,7 @@ func Bind(flags FlagSet, config interface{}, opts ...BindOpt) {
|
||||
panic(fmt.Sprintf("invalid config type: %#v. "+
|
||||
"Expecting pointer to struct.", config))
|
||||
}
|
||||
vars := map[string]string{}
|
||||
vars := map[string]confVar{}
|
||||
for _, opt := range opts {
|
||||
opt(vars)
|
||||
}
|
||||
@ -44,13 +63,25 @@ var (
|
||||
)
|
||||
|
||||
func bindConfig(flags FlagSet, prefix string, val reflect.Value,
|
||||
vars map[string]string) {
|
||||
vars map[string]confVar) {
|
||||
if val.Kind() != reflect.Struct {
|
||||
panic(fmt.Sprintf("invalid config type: %#v. Expecting struct.",
|
||||
val.Interface()))
|
||||
}
|
||||
typ := val.Type()
|
||||
|
||||
resolvedVars := make(map[string]string, len(vars))
|
||||
{
|
||||
structpath := strings.Replace(prefix, ".", "/", -1)
|
||||
for k, v := range vars {
|
||||
if !v.nested {
|
||||
resolvedVars[k] = v.val
|
||||
continue
|
||||
}
|
||||
resolvedVars[k] = filepath.Join(v.val, structpath)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
field := typ.Field(i)
|
||||
fieldval := val.Field(i)
|
||||
@ -107,7 +138,8 @@ func bindConfig(flags FlagSet, prefix string, val reflect.Value,
|
||||
check(err)
|
||||
flags.Float64Var(fieldaddr.(*float64), flagname, val, help)
|
||||
case reflect.TypeOf(string("")):
|
||||
flags.StringVar(fieldaddr.(*string), flagname, expand(vars, def), help)
|
||||
flags.StringVar(
|
||||
fieldaddr.(*string), flagname, expand(resolvedVars, def), help)
|
||||
case reflect.TypeOf(bool(false)):
|
||||
val, err := strconv.ParseBool(def)
|
||||
check(err)
|
||||
|
@ -74,3 +74,37 @@ func TestBind(t *testing.T) {
|
||||
assertEqual(c.Fields[0].AnotherInt, int(0))
|
||||
assertEqual(c.Fields[3].AnotherInt, int(1))
|
||||
}
|
||||
|
||||
func TestConfDir(t *testing.T) {
|
||||
f := flag.NewFlagSet("test", flag.PanicOnError)
|
||||
var c struct {
|
||||
String string `default:"-$CONFDIR+"`
|
||||
MyStruct1 struct {
|
||||
String string `default:"1${CONFDIR}2"`
|
||||
MyStruct2 struct {
|
||||
String string `default:"2${CONFDIR}3"`
|
||||
}
|
||||
}
|
||||
}
|
||||
Bind(f, &c, ConfDir("confpath"))
|
||||
assertEqual(f.Lookup("string").DefValue, "-confpath+")
|
||||
assertEqual(f.Lookup("my-struct1.string").DefValue, "1confpath2")
|
||||
assertEqual(f.Lookup("my-struct1.my-struct2.string").DefValue, "2confpath3")
|
||||
}
|
||||
|
||||
func TestNesting(t *testing.T) {
|
||||
f := flag.NewFlagSet("test", flag.PanicOnError)
|
||||
var c struct {
|
||||
String string `default:"-$CONFDIR+"`
|
||||
MyStruct1 struct {
|
||||
String string `default:"1${CONFDIR}2"`
|
||||
MyStruct2 struct {
|
||||
String string `default:"2${CONFDIR}3"`
|
||||
}
|
||||
}
|
||||
}
|
||||
Bind(f, &c, ConfDirNested("confpath"))
|
||||
assertEqual(f.Lookup("string").DefValue, "-confpath+")
|
||||
assertEqual(f.Lookup("my-struct1.string").DefValue, "1confpath/my-struct12")
|
||||
assertEqual(f.Lookup("my-struct1.my-struct2.string").DefValue, "2confpath/my-struct1/my-struct23")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user