diff --git a/cmd/captplanet/setup.go b/cmd/captplanet/setup.go index b9a5db837..0fb7bf1da 100644 --- a/cmd/captplanet/setup.go +++ b/cmd/captplanet/setup.go @@ -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) { diff --git a/pkg/cfgstruct/bind.go b/pkg/cfgstruct/bind.go index e57e0edb0..498d10f1d 100644 --- a/pkg/cfgstruct/bind.go +++ b/pkg/cfgstruct/bind.go @@ -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) diff --git a/pkg/cfgstruct/bind_test.go b/pkg/cfgstruct/bind_test.go index da06838da..574f38334 100644 --- a/pkg/cfgstruct/bind_test.go +++ b/pkg/cfgstruct/bind_test.go @@ -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") +}