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() {
|
func init() {
|
||||||
rootCmd.AddCommand(setupCmd)
|
rootCmd.AddCommand(setupCmd)
|
||||||
cfgstruct.Bind(setupCmd.Flags(), &setupCfg,
|
cfgstruct.Bind(setupCmd.Flags(), &setupCfg,
|
||||||
cfgstruct.ConfDir(defaultConfDir),
|
cfgstruct.ConfDir(defaultConfDir))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdSetup(cmd *cobra.Command, args []string) (err error) {
|
func cmdSetup(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
@ -6,6 +6,7 @@ package cfgstruct
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -14,15 +15,33 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// BindOpt is an option for the Bind method
|
// 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 {
|
func ConfDir(confdir string) BindOpt {
|
||||||
return BindOpt(func(vars map[string]string) {
|
val := filepath.Clean(os.ExpandEnv(confdir))
|
||||||
vars["CONFDIR"] = 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
|
// Bind sets flags on a FlagSet that match the configuration struct
|
||||||
// 'config'. This works by traversing the config struct using the 'reflect'
|
// 'config'. This works by traversing the config struct using the 'reflect'
|
||||||
// package.
|
// package.
|
||||||
@ -32,7 +51,7 @@ func Bind(flags FlagSet, config interface{}, opts ...BindOpt) {
|
|||||||
panic(fmt.Sprintf("invalid config type: %#v. "+
|
panic(fmt.Sprintf("invalid config type: %#v. "+
|
||||||
"Expecting pointer to struct.", config))
|
"Expecting pointer to struct.", config))
|
||||||
}
|
}
|
||||||
vars := map[string]string{}
|
vars := map[string]confVar{}
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(vars)
|
opt(vars)
|
||||||
}
|
}
|
||||||
@ -44,13 +63,25 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func bindConfig(flags FlagSet, prefix string, val reflect.Value,
|
func bindConfig(flags FlagSet, prefix string, val reflect.Value,
|
||||||
vars map[string]string) {
|
vars map[string]confVar) {
|
||||||
if val.Kind() != reflect.Struct {
|
if val.Kind() != reflect.Struct {
|
||||||
panic(fmt.Sprintf("invalid config type: %#v. Expecting struct.",
|
panic(fmt.Sprintf("invalid config type: %#v. Expecting struct.",
|
||||||
val.Interface()))
|
val.Interface()))
|
||||||
}
|
}
|
||||||
typ := val.Type()
|
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++ {
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
field := typ.Field(i)
|
field := typ.Field(i)
|
||||||
fieldval := val.Field(i)
|
fieldval := val.Field(i)
|
||||||
@ -107,7 +138,8 @@ func bindConfig(flags FlagSet, prefix string, val reflect.Value,
|
|||||||
check(err)
|
check(err)
|
||||||
flags.Float64Var(fieldaddr.(*float64), flagname, val, help)
|
flags.Float64Var(fieldaddr.(*float64), flagname, val, help)
|
||||||
case reflect.TypeOf(string("")):
|
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)):
|
case reflect.TypeOf(bool(false)):
|
||||||
val, err := strconv.ParseBool(def)
|
val, err := strconv.ParseBool(def)
|
||||||
check(err)
|
check(err)
|
||||||
|
@ -74,3 +74,37 @@ func TestBind(t *testing.T) {
|
|||||||
assertEqual(c.Fields[0].AnotherInt, int(0))
|
assertEqual(c.Fields[0].AnotherInt, int(0))
|
||||||
assertEqual(c.Fields[3].AnotherInt, int(1))
|
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