From 24b9be48054e0fe8b97672f3c59a891c1904e229 Mon Sep 17 00:00:00 2001 From: Bill Robbins Date: Fri, 6 Feb 2015 14:35:00 -0600 Subject: [PATCH 1/7] adding cascading file support, off by default --- .gitignore | 1 + viper.go | 85 ++++++++++++++++++++++++++++++++++++++++++++--- viper_test.go | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 8365624..31ec60d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ # Folders _obj _test +.idea # Architecture specific extensions/prefixes *.[568vq] diff --git a/viper.go b/viper.go index 89834b7..a4baa45 100644 --- a/viper.go +++ b/viper.go @@ -77,11 +77,13 @@ type viper struct { envPrefix string automaticEnvApplied bool + cascadeConfigurations bool config map[string]interface{} override map[string]interface{} defaults map[string]interface{} kvstore map[string]interface{} + cascadingConfigs map[string]map[string]interface{} pflags map[string]*pflag.Flag env map[string]string aliases map[string]string @@ -98,6 +100,7 @@ func New() *viper { v.pflags = make(map[string]*pflag.Flag) v.env = make(map[string]string) v.aliases = make(map[string]string) + v.cascadeConfigurations = false return v } @@ -136,6 +139,12 @@ func (v *viper) SetEnvPrefix(in string) { } } +// Enable cascading configuration values for files. Will traverse down +// ConfigPaths in an attempt to find keys +func (v *viper) EnableCascading(enable bool){ + v.cascadeConfigurations = enable; +} + func (v *viper) mergeWithEnvPrefix(in string) string { if v.envPrefix != "" { return strings.ToUpper(v.envPrefix + "_" + in) @@ -389,6 +398,7 @@ func (v *viper) BindEnv(input ...string) (err error) { func (v *viper) find(key string) interface{} { var val interface{} var exists bool + var file string // if the requested key is an alias, then return the proper key key = v.realKey(key) @@ -434,6 +444,15 @@ func (v *viper) find(key string) interface{} { return val } + if( v.cascadeConfigurations) { + //cascade down the rest of the files + val, exists, file = v.findCascading(key) + if exists { + jww.TRACE.Printf("%s found in config: %s (%s)", key, val, file) + return val + } + } + val, exists = v.kvstore[key] if exists { jww.TRACE.Println(key, "found in key/value store:", val) @@ -449,6 +468,49 @@ func (v *viper) find(key string) interface{} { return nil } +func (v *viper) findCascading(key string) (interface{}, bool, string) { + + if( v.cascadingConfigs != nil ){ + for file,config := range v.cascadingConfigs { + result := config[key] + if( result != nil ){ + return result,true,file + } + } + } + + v.cascadingConfigs = make(map[string]map[string]interface{}) + + configFiles := v.findAllConfigFiles() + for _, configFile := range configFiles { + + if(v.cascadingConfigs[configFile] != nil){ + //already cached + continue + } + + jww.TRACE.Printf("Looking in %s for key %s",configFile,key) + file, err := ioutil.ReadFile(configFile) + if err != nil { + jww.ERROR.Print(err) + continue + } + + jww.TRACE.Printf("marshalling %s for cascading",configFile) + var config = make(map[string]interface{}) + + marshallConfigReader(bytes.NewReader(file), config, filepath.Ext(configFile)[1:]) + v.cascadingConfigs[configFile] = config + + result := config[key] + + if( result != nil){ + return result,true,configFile + } + } + return "", false, "" +} + // Check to see if the key has been set in any of the data locations func IsSet(key string) bool { return v.IsSet(key) } func (v *viper) IsSet(key string) bool { @@ -733,26 +795,41 @@ func (v *viper) searchInPath(in string) (filename string) { func (v *viper) findConfigFile() (string, error) { jww.INFO.Println("Searching for config in ", v.configPaths) + var validFiles = v.findAllConfigFiles() + + if len(validFiles) == 0 { + return "", fmt.Errorf("config file not found in: %s", v.configPaths) + } + + return validFiles[0], nil +} + +func (v *viper) findAllConfigFiles() []string { + + var validFiles []string for _, cp := range v.configPaths { file := v.searchInPath(cp) if file != "" { - return file, nil + jww.TRACE.Println("Found config file in: %s",file) + validFiles = append(validFiles, file) } } cwd, _ := findCWD() file := v.searchInPath(cwd) if file != "" { - return file, nil + validFiles = append(validFiles, file) } // try the current working directory wd, _ := os.Getwd() file = v.searchInPath(wd) if file != "" { - return file, nil + validFiles = append(validFiles, file) } - return "", fmt.Errorf("config file not found in: %s", v.configPaths) + + + return validFiles } func Debug() { v.Debug() } diff --git a/viper_test.go b/viper_test.go index ce1aa3c..0bbe84f 100644 --- a/viper_test.go +++ b/viper_test.go @@ -12,6 +12,9 @@ import ( "sort" "testing" "time" + "os/exec" + "path" + "io/ioutil" "github.com/spf13/pflag" "github.com/stretchr/testify/assert" @@ -352,3 +355,91 @@ func TestBoundCaseSensitivity(t *testing.T) { assert.Equal(t, "green", Get("eyes")) } + +func TestCanCascadeConfigurationValues(t *testing.T) { + + v2 := New() + + generateCascadingTests(v2,"cascading") + + v2.ReadInConfig() + v2.EnableCascading(true) + + assert.Equal(t,"high",v2.GetString("0"),"Key 0 should be high") + assert.Equal(t,"med",v2.GetString("1"),"Key 1 should be med") + assert.Equal(t,"low",v2.GetString("2"),"key 2 should be low") + + v2.EnableCascading(false) + + assert.Nil(t,v2.Get("1"),"With enable cascading disabled, no value for 1 should exist") + assert.Nil(t,v2.Get("2"),"With enable cascading disabled, no value for 2 should exist") +} + +func TestFindAllConfigPaths(t *testing.T){ + + v2 := New() + + file := "viper_test" + + var expected = generateCascadingTests(v2,file) + + found := v2.findAllConfigFiles() + + for _,fp := range expected{ + command := exec.Command("rm",fp) + command.Run() + } + + assert.Equal(t,expected,found,"All files should exist") +} + +func generateCascadingTests(v2 *viper, file_name string) []string { + + v2.SetConfigName(file_name) + + tmp := os.Getenv("TMPDIR") + // $TMPDIR/a > $TMPDIR/b > %TMPDIR + paths := []string{path.Join(tmp,"a"),path.Join(tmp,"b"),tmp} + + v2.SetConfigName(file_name) + + var expected []string + + for idx,fp := range paths { + v2.AddConfigPath(fp) + + exec.Command("mkdir","-m","777",fp).Run() + + full_path := path.Join(fp,file_name + ".json") + + var val string + switch idx{ + case 0 : + val = "high" + break + case 1 : + val = "med" + break + case 2 : + val = "low" + } + + config := "{" + for i := 0; i <= idx; i++ { + config += fmt.Sprintf("\"%d\": \"%s\"",i,val) + if( i == idx) { + config += "\n" + }else{ + config += ",\n" + } + } + + config += "}" + + ioutil.WriteFile(full_path,[]byte(config),0777) + + expected = append(expected,full_path) + } + + return expected +} From 10178ad8474aaec4f9c6636dea40c3a206518a70 Mon Sep 17 00:00:00 2001 From: Bill Robbins Date: Fri, 6 Feb 2015 15:00:04 -0600 Subject: [PATCH 2/7] Update for enabling cascading --- README.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a06bedb..60906ad 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -viper [![Build Status](https://travis-ci.org/spf13/viper.svg)](https://travis-ci.org/spf13/viper) +viper ===== Go configuration with fangs @@ -69,13 +69,29 @@ Examples: If you want to support a config file, Viper requires a minimal configuration so it knows where to look for the config file. Viper supports yaml, toml and json files. Viper can search multiple paths, but -currently a single viper only supports a single config file. +currently a single viper only supports a single config file, unless cascading is enabled. viper.SetConfigName("config") // name of config file (without extension) viper.AddConfigPath("/etc/appname/") // path to look for the config file in viper.AddConfigPath("$HOME/.appname") // call multiple times to add many search paths viper.ReadInConfig() // Find and read the config file +#### Enabling Cascading + +By default Viper stops reading configuration once it encounters the first available configuration file. +That means each configuration file must contain all configuration values you need. +By enabling cascading you can create sparse configuration files. Configuration will cascade down in +the order that files are added by AddConfigPath. For more see viper_test's cascading tests. + +Consider: + + * \etc\myapp\myapp.json + * (%GOPATH)\src\myapp\myapp.json + +You can check in a default myapp.json for development and only override certain kvps in production + + viper.EnableCascading(true) + ### Setting Overrides These could be from a command line flag, or from your own application logic. From f8427a63f04d489535047293889acf8435c59ffa Mon Sep 17 00:00:00 2001 From: dude Date: Fri, 6 Feb 2015 15:00:58 -0600 Subject: [PATCH 3/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 60906ad..e600ce7 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ the order that files are added by AddConfigPath. For more see viper_test's casc Consider: * \etc\myapp\myapp.json - * (%GOPATH)\src\myapp\myapp.json + * ($GOPATH)\src\myapp\myapp.json You can check in a default myapp.json for development and only override certain kvps in production From b31edd964746d8bdb1157271994330f06c64c135 Mon Sep 17 00:00:00 2001 From: dude Date: Fri, 6 Feb 2015 15:01:11 -0600 Subject: [PATCH 4/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e600ce7..3d0581f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -viper +viper ===== Go configuration with fangs From 997cc95aa85e5f1eb31b38723ce2e69171929eb4 Mon Sep 17 00:00:00 2001 From: Bill Robbins Date: Fri, 6 Feb 2015 15:20:54 -0600 Subject: [PATCH 5/7] handle TMPDIR not existing for tests --- viper_test.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/viper_test.go b/viper_test.go index 0bbe84f..1f4d684 100644 --- a/viper_test.go +++ b/viper_test.go @@ -14,6 +14,7 @@ import ( "time" "os/exec" "path" + "path/filepath" "io/ioutil" "github.com/spf13/pflag" @@ -390,7 +391,7 @@ func TestFindAllConfigPaths(t *testing.T){ command.Run() } - assert.Equal(t,expected,found,"All files should exist") + assert.Equal(t,expected,removeDuplicates(found),"All files should exist") } func generateCascadingTests(v2 *viper, file_name string) []string { @@ -398,6 +399,9 @@ func generateCascadingTests(v2 *viper, file_name string) []string { v2.SetConfigName(file_name) tmp := os.Getenv("TMPDIR") + if( tmp == ""){ + tmp,_ = filepath.Abs(filepath.Dir("./")) + } // $TMPDIR/a > $TMPDIR/b > %TMPDIR paths := []string{path.Join(tmp,"a"),path.Join(tmp,"b"),tmp} @@ -443,3 +447,15 @@ func generateCascadingTests(v2 *viper, file_name string) []string { return expected } + +func removeDuplicates(a []string) []string { + result := []string{} + seen := map[string]string{} + for _, val := range a { + if _, ok := seen[val]; !ok { + result = append(result, val) + seen[val] = val + } + } + return result +} From 64fc762506cfefeb0e9d2a4fea78e1333c97def9 Mon Sep 17 00:00:00 2001 From: Bill Robbins Date: Fri, 6 Feb 2015 15:58:40 -0600 Subject: [PATCH 6/7] enable 'static' method call of EnableCascading --- viper.go | 1 + 1 file changed, 1 insertion(+) diff --git a/viper.go b/viper.go index a4baa45..cf4a31d 100644 --- a/viper.go +++ b/viper.go @@ -141,6 +141,7 @@ func (v *viper) SetEnvPrefix(in string) { // Enable cascading configuration values for files. Will traverse down // ConfigPaths in an attempt to find keys +func EnableCascading(enable bool){ v.EnableCascading(enable) } func (v *viper) EnableCascading(enable bool){ v.cascadeConfigurations = enable; } From e69822897f58dfe47362479b98e3cb09604c968e Mon Sep 17 00:00:00 2001 From: Bill Robbins Date: Sun, 8 Feb 2015 20:21:37 -0600 Subject: [PATCH 7/7] bug fix: maps are not guaranteed to have consistent key ordering - always look in the order of AddConfigPath IntelliJ reformat code simplify cascade lookup --- viper.go | 116 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 76 insertions(+), 40 deletions(-) diff --git a/viper.go b/viper.go index cf4a31d..bfc3efd 100644 --- a/viper.go +++ b/viper.go @@ -76,7 +76,7 @@ type viper struct { configType string envPrefix string - automaticEnvApplied bool + automaticEnvApplied bool cascadeConfigurations bool config map[string]interface{} @@ -125,6 +125,7 @@ var SupportedRemoteProviders []string = []string{"etcd", "consul"} // Explicitly define the path, name and extension of the config file // Viper will use this and not check any of the config paths func SetConfigFile(in string) { v.SetConfigFile(in) } + func (v *viper) SetConfigFile(in string) { if in != "" { v.configFile = in @@ -133,6 +134,7 @@ func (v *viper) SetConfigFile(in string) { // Define a prefix that ENVIRONMENT variables will use. func SetEnvPrefix(in string) { v.SetEnvPrefix(in) } + func (v *viper) SetEnvPrefix(in string) { if in != "" { v.envPrefix = in @@ -142,7 +144,7 @@ func (v *viper) SetEnvPrefix(in string) { // Enable cascading configuration values for files. Will traverse down // ConfigPaths in an attempt to find keys func EnableCascading(enable bool){ v.EnableCascading(enable) } -func (v *viper) EnableCascading(enable bool){ +func (v *viper) EnableCascading(enable bool) { v.cascadeConfigurations = enable; } @@ -155,12 +157,14 @@ func (v *viper) mergeWithEnvPrefix(in string) string { } // Return the config file used -func ConfigFileUsed() string { return v.ConfigFileUsed() } +func ConfigFileUsed() string { return v.ConfigFileUsed() } + func (v *viper) ConfigFileUsed() string { return v.configFile } // Add a path for viper to search for the config file in. // Can be called multiple times to define multiple search paths. func AddConfigPath(in string) { v.AddConfigPath(in) } + func (v *viper) AddConfigPath(in string) { if in != "" { absin := absPathify(in) @@ -182,6 +186,7 @@ func (v *viper) AddConfigPath(in string) { func AddRemoteProvider(provider, endpoint, path string) error { return v.AddRemoteProvider(provider, endpoint, path) } + func (v *viper) AddRemoteProvider(provider, endpoint, path string) error { if !stringInSlice(provider, SupportedRemoteProviders) { return UnsupportedRemoteProviderError(provider) @@ -249,6 +254,7 @@ func (v *viper) providerPathExists(p *remoteProvider) bool { // // Get returns an interface. For a specific value use one of the Get____ methods. func Get(key string) interface{} { return v.Get(key) } + func (v *viper) Get(key string) interface{} { key = strings.ToLower(key) val := v.find(key) @@ -275,53 +281,63 @@ func (v *viper) Get(key string) interface{} { } func GetString(key string) string { return v.GetString(key) } + func (v *viper) GetString(key string) string { return cast.ToString(v.Get(key)) } func GetBool(key string) bool { return v.GetBool(key) } + func (v *viper) GetBool(key string) bool { return cast.ToBool(v.Get(key)) } func GetInt(key string) int { return v.GetInt(key) } + func (v *viper) GetInt(key string) int { return cast.ToInt(v.Get(key)) } func GetFloat64(key string) float64 { return v.GetFloat64(key) } + func (v *viper) GetFloat64(key string) float64 { return cast.ToFloat64(v.Get(key)) } func GetTime(key string) time.Time { return v.GetTime(key) } + func (v *viper) GetTime(key string) time.Time { return cast.ToTime(v.Get(key)) } func GetStringSlice(key string) []string { return v.GetStringSlice(key) } + func (v *viper) GetStringSlice(key string) []string { return cast.ToStringSlice(v.Get(key)) } func GetStringMap(key string) map[string]interface{} { return v.GetStringMap(key) } + func (v *viper) GetStringMap(key string) map[string]interface{} { return cast.ToStringMap(v.Get(key)) } func GetStringMapString(key string) map[string]string { return v.GetStringMapString(key) } + func (v *viper) GetStringMapString(key string) map[string]string { return cast.ToStringMapString(v.Get(key)) } // Takes a single key and marshals it into a Struct func MarshalKey(key string, rawVal interface{}) error { return v.MarshalKey(key, rawVal) } + func (v *viper) MarshalKey(key string, rawVal interface{}) error { return mapstructure.Decode(v.Get(key), rawVal) } // Marshals the config into a Struct func Marshal(rawVal interface{}) error { return v.Marshal(rawVal) } + func (v *viper) Marshal(rawVal interface{}) error { err := mapstructure.Decode(v.defaults, rawVal) if err != nil { @@ -351,6 +367,7 @@ func (v *viper) Marshal(rawVal interface{}) error { // viper.BindPFlag("port", serverCmd.Flags().Lookup("port")) // func BindPFlag(key string, flag *pflag.Flag) (err error) { return v.BindPFlag(key, flag) } + func (v *viper) BindPFlag(key string, flag *pflag.Flag) (err error) { if flag == nil { return fmt.Errorf("flag for %q is nil", key) @@ -373,6 +390,7 @@ func (v *viper) BindPFlag(key string, flag *pflag.Flag) (err error) { // If only a key is provided, it will use the env key matching the key, uppercased. // EnvPrefix will be used when set when env name is not provided. func BindEnv(input ...string) (err error) { return v.BindEnv(input...) } + func (v *viper) BindEnv(input ...string) (err error) { var key, envkey string if len(input) == 0 { @@ -445,7 +463,7 @@ func (v *viper) find(key string) interface{} { return val } - if( v.cascadeConfigurations) { + if ( v.cascadeConfigurations) { //cascade down the rest of the files val, exists, file = v.findCascading(key) if exists { @@ -471,49 +489,54 @@ func (v *viper) find(key string) interface{} { func (v *viper) findCascading(key string) (interface{}, bool, string) { - if( v.cascadingConfigs != nil ){ - for file,config := range v.cascadingConfigs { - result := config[key] - if( result != nil ){ - return result,true,file - } - } - } - - v.cascadingConfigs = make(map[string]map[string]interface{}) - configFiles := v.findAllConfigFiles() - for _, configFile := range configFiles { - if(v.cascadingConfigs[configFile] != nil){ - //already cached - continue - } - - jww.TRACE.Printf("Looking in %s for key %s",configFile,key) - file, err := ioutil.ReadFile(configFile) - if err != nil { - jww.ERROR.Print(err) - continue - } - - jww.TRACE.Printf("marshalling %s for cascading",configFile) - var config = make(map[string]interface{}) - - marshallConfigReader(bytes.NewReader(file), config, filepath.Ext(configFile)[1:]) - v.cascadingConfigs[configFile] = config - - result := config[key] - - if( result != nil){ - return result,true,configFile - } + if ( v.cascadingConfigs == nil) { + v.cascadingConfigs = make(map[string]map[string]interface{}) } + + for _, configFile := range configFiles { + config := v.cascadingConfigs[configFile] + + if ( config == nil ) { + var err error + config,err = readConfigFile(configFile) + if( err != nil){ + jww.ERROR.Print(err) + continue + } + v.cascadingConfigs[configFile] = config + } + + jww.TRACE.Printf("Looking in %s for key %s", configFile, key) + result := config[key] + if ( result != nil ) { + return result, true, configFile + } + + } + return "", false, "" } +func readConfigFile(configFile string) (map[string]interface{},error) { + jww.TRACE.Printf("marshalling %s ", configFile) + + file, err := ioutil.ReadFile(configFile) + if err != nil { + return nil,err + } + + var config = make(map[string]interface{}) + + marshallConfigReader(bytes.NewReader(file), config, filepath.Ext(configFile)[1:]) + + return config,nil +} + // Check to see if the key has been set in any of the data locations func IsSet(key string) bool { return v.IsSet(key) } + func (v *viper) IsSet(key string) bool { t := v.Get(key) return t != nil @@ -522,6 +545,7 @@ func (v *viper) IsSet(key string) bool { // Have viper check ENV variables for all // keys set in config, default & flags func AutomaticEnv() { v.AutomaticEnv() } + func (v *viper) AutomaticEnv() { v.automaticEnvApplied = true } @@ -529,6 +553,7 @@ func (v *viper) AutomaticEnv() { // Aliases provide another accessor for the same key. // This enables one to change a name without breaking the application func RegisterAlias(alias string, key string) { v.RegisterAlias(alias, key) } + func (v *viper) RegisterAlias(alias string, key string) { v.registerAlias(alias, strings.ToLower(key)) } @@ -577,6 +602,7 @@ func (v *viper) realKey(key string) string { // Check to see if the given key (or an alias) is in the config file func InConfig(key string) bool { return v.InConfig(key) } + func (v *viper) InConfig(key string) bool { // if the requested key is an alias, then return the proper key key = v.realKey(key) @@ -588,6 +614,7 @@ func (v *viper) InConfig(key string) bool { // Set the default value for this key. // Default only used when no value is provided by the user via flag, config or ENV. func SetDefault(key string, value interface{}) { v.SetDefault(key, value) } + func (v *viper) SetDefault(key string, value interface{}) { // If alias passed in, then set the proper default key = v.realKey(strings.ToLower(key)) @@ -598,6 +625,7 @@ func (v *viper) SetDefault(key string, value interface{}) { // Will be used instead of values obtained via // config file, ENV, default, or key/value store func Set(key string, value interface{}) { v.Set(key, value) } + func (v *viper) Set(key string, value interface{}) { // If alias passed in, then set the proper override key = v.realKey(strings.ToLower(key)) @@ -607,6 +635,7 @@ func (v *viper) Set(key string, value interface{}) { // Viper will discover and load the configuration file from disk // and key/value stores, searching in one of the defined paths. func ReadInConfig() error { return v.ReadInConfig() } + func (v *viper) ReadInConfig() error { jww.INFO.Println("Attempting to read in config file") if !stringInSlice(v.getConfigType(), SupportedExts) { @@ -623,6 +652,7 @@ func (v *viper) ReadInConfig() error { } func ReadRemoteConfig() error { return v.ReadRemoteConfig() } + func (v *viper) ReadRemoteConfig() error { err := v.getKeyValueConfig() if err != nil { @@ -634,6 +664,7 @@ func (v *viper) ReadRemoteConfig() error { // Marshall a Reader into a map // Should probably be an unexported function func marshalReader(in io.Reader, c map[string]interface{}) { v.marshalReader(in, c) } + func (v *viper) marshalReader(in io.Reader, c map[string]interface{}) { marshallConfigReader(in, c, v.getConfigType()) } @@ -694,6 +725,7 @@ func (v *viper) getRemoteConfig(provider *remoteProvider) (map[string]interface{ // Return all keys regardless where they are set func AllKeys() []string { return v.AllKeys() } + func (v *viper) AllKeys() []string { m := map[string]struct{}{} @@ -723,6 +755,7 @@ func (v *viper) AllKeys() []string { // Return all settings as a map[string]interface{} func AllSettings() map[string]interface{} { return v.AllSettings() } + func (v *viper) AllSettings() map[string]interface{} { m := map[string]interface{}{} for _, x := range v.AllKeys() { @@ -735,6 +768,7 @@ func (v *viper) AllSettings() map[string]interface{} { // Name for the config file. // Does not include extension. func SetConfigName(in string) { v.SetConfigName(in) } + func (v *viper) SetConfigName(in string) { if in != "" { v.configName = in @@ -742,6 +776,7 @@ func (v *viper) SetConfigName(in string) { } func SetConfigType(in string) { v.SetConfigType(in) } + func (v *viper) SetConfigType(in string) { if in != "" { v.configType = in @@ -811,7 +846,7 @@ func (v *viper) findAllConfigFiles() []string { for _, cp := range v.configPaths { file := v.searchInPath(cp) if file != "" { - jww.TRACE.Println("Found config file in: %s",file) + jww.TRACE.Println("Found config file in: %s", file) validFiles = append(validFiles, file) } } @@ -834,6 +869,7 @@ func (v *viper) findAllConfigFiles() []string { } func Debug() { v.Debug() } + func (v *viper) Debug() { fmt.Println("Config:") pretty.Println(v.config)