From 00ed41cdba2612ddf07559b1cae78ca23d213ba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Mon, 19 Mar 2018 19:12:24 +0100 Subject: [PATCH 1/7] Export and fix GetConfigFile --- nohup.out | 1 - viper.go | 32 ++++++++++++++------------------ viper_test.go | 4 ++-- 3 files changed, 16 insertions(+), 21 deletions(-) delete mode 100644 nohup.out diff --git a/nohup.out b/nohup.out deleted file mode 100644 index 8973bf2..0000000 --- a/nohup.out +++ /dev/null @@ -1 +0,0 @@ -QProcess::start: Process is already running diff --git a/viper.go b/viper.go index ad8a037..f1e7944 100644 --- a/viper.go +++ b/viper.go @@ -268,7 +268,7 @@ func (v *Viper) WatchConfig() { defer watcher.Close() // we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way - filename, err := v.getConfigFile() + filename, err := v.GetConfigFile() if err != nil { log.Println("error:", err) return @@ -1131,7 +1131,7 @@ func (v *Viper) Set(key string, value interface{}) { func ReadInConfig() error { return v.ReadInConfig() } func (v *Viper) ReadInConfig() error { jww.INFO.Println("Attempting to read in config file") - filename, err := v.getConfigFile() + filename, err := v.GetConfigFile() if err != nil { return err } @@ -1161,7 +1161,7 @@ func (v *Viper) ReadInConfig() error { func MergeInConfig() error { return v.MergeInConfig() } func (v *Viper) MergeInConfig() error { jww.INFO.Println("Attempting to merge in config file") - filename, err := v.getConfigFile() + filename, err := v.GetConfigFile() if err != nil { return err } @@ -1203,7 +1203,7 @@ func (v *Viper) MergeConfig(in io.Reader) error { // WriteConfig writes the current configuration to a file. func WriteConfig() error { return v.WriteConfig() } func (v *Viper) WriteConfig() error { - filename, err := v.getConfigFile() + filename, err := v.GetConfigFile() if err != nil { return err } @@ -1213,7 +1213,7 @@ func (v *Viper) WriteConfig() error { // SafeWriteConfig writes current configuration to file only if the file does not exist. func SafeWriteConfig() error { return v.SafeWriteConfig() } func (v *Viper) SafeWriteConfig() error { - filename, err := v.getConfigFile() + filename, err := v.GetConfigFile() if err != nil { return err } @@ -1705,7 +1705,7 @@ func (v *Viper) getConfigType() string { return v.configType } - cf, err := v.getConfigFile() + cf, err := v.GetConfigFile() if err != nil { return "" } @@ -1719,19 +1719,15 @@ func (v *Viper) getConfigType() string { return "" } -func (v *Viper) getConfigFile() (string, error) { - // if explicitly set, then use it - if v.configFile != "" { - return v.configFile, nil +func (v *Viper) GetConfigFile() (string, error) { + if v.configFile == "" { + cf, err := v.findConfigFile() + if err != nil { + return "", err + } + v.configFile = cf } - - cf, err := v.findConfigFile() - if err != nil { - return "", err - } - - v.configFile = cf - return v.getConfigFile() + return v.configFile, nil } func (v *Viper) searchInPath(in string) (filename string) { diff --git a/viper_test.go b/viper_test.go index c93480e..4e81f55 100644 --- a/viper_test.go +++ b/viper_test.go @@ -244,7 +244,7 @@ func (s *stringValue) String() string { func TestBasics(t *testing.T) { SetConfigFile("/tmp/config.yaml") - filename, err := v.getConfigFile() + filename, err := v.GetConfigFile() assert.Equal(t, "/tmp/config.yaml", filename) assert.NoError(t, err) } @@ -1177,7 +1177,7 @@ func TestUnmarshalingWithAliases(t *testing.T) { func TestSetConfigNameClearsFileCache(t *testing.T) { SetConfigFile("/tmp/config.yaml") SetConfigName("default") - f, err := v.getConfigFile() + f, err := v.GetConfigFile() if err == nil { t.Fatalf("config file cache should have been cleared") } From b5e8006cbee93ec955a89ab31e0e3ce3204f3736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Mon, 19 Mar 2018 19:50:19 +0100 Subject: [PATCH 2/7] Undexport GetConfigFile It was exported in previous commit, but we have GetConfigFileUsed -- so use that. --- viper.go | 14 +++++++------- viper_test.go | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/viper.go b/viper.go index f1e7944..e9966ba 100644 --- a/viper.go +++ b/viper.go @@ -268,7 +268,7 @@ func (v *Viper) WatchConfig() { defer watcher.Close() // we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way - filename, err := v.GetConfigFile() + filename, err := v.getConfigFile() if err != nil { log.Println("error:", err) return @@ -1131,7 +1131,7 @@ func (v *Viper) Set(key string, value interface{}) { func ReadInConfig() error { return v.ReadInConfig() } func (v *Viper) ReadInConfig() error { jww.INFO.Println("Attempting to read in config file") - filename, err := v.GetConfigFile() + filename, err := v.getConfigFile() if err != nil { return err } @@ -1161,7 +1161,7 @@ func (v *Viper) ReadInConfig() error { func MergeInConfig() error { return v.MergeInConfig() } func (v *Viper) MergeInConfig() error { jww.INFO.Println("Attempting to merge in config file") - filename, err := v.GetConfigFile() + filename, err := v.getConfigFile() if err != nil { return err } @@ -1203,7 +1203,7 @@ func (v *Viper) MergeConfig(in io.Reader) error { // WriteConfig writes the current configuration to a file. func WriteConfig() error { return v.WriteConfig() } func (v *Viper) WriteConfig() error { - filename, err := v.GetConfigFile() + filename, err := v.getConfigFile() if err != nil { return err } @@ -1213,7 +1213,7 @@ func (v *Viper) WriteConfig() error { // SafeWriteConfig writes current configuration to file only if the file does not exist. func SafeWriteConfig() error { return v.SafeWriteConfig() } func (v *Viper) SafeWriteConfig() error { - filename, err := v.GetConfigFile() + filename, err := v.getConfigFile() if err != nil { return err } @@ -1705,7 +1705,7 @@ func (v *Viper) getConfigType() string { return v.configType } - cf, err := v.GetConfigFile() + cf, err := v.getConfigFile() if err != nil { return "" } @@ -1719,7 +1719,7 @@ func (v *Viper) getConfigType() string { return "" } -func (v *Viper) GetConfigFile() (string, error) { +func (v *Viper) getConfigFile() (string, error) { if v.configFile == "" { cf, err := v.findConfigFile() if err != nil { diff --git a/viper_test.go b/viper_test.go index 4e81f55..c93480e 100644 --- a/viper_test.go +++ b/viper_test.go @@ -244,7 +244,7 @@ func (s *stringValue) String() string { func TestBasics(t *testing.T) { SetConfigFile("/tmp/config.yaml") - filename, err := v.GetConfigFile() + filename, err := v.getConfigFile() assert.Equal(t, "/tmp/config.yaml", filename) assert.NoError(t, err) } @@ -1177,7 +1177,7 @@ func TestUnmarshalingWithAliases(t *testing.T) { func TestSetConfigNameClearsFileCache(t *testing.T) { SetConfigFile("/tmp/config.yaml") SetConfigName("default") - f, err := v.GetConfigFile() + f, err := v.getConfigFile() if err == nil { t.Fatalf("config file cache should have been cleared") } From 8dc2790b029dc41e2b8ff772c63c26adbb1db70d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20K=C3=A4ufl?= Date: Thu, 22 Mar 2018 08:00:28 +0100 Subject: [PATCH 3/7] travis: update go versions --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 55960d1..fa39805 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,8 @@ go_import_path: github.com/spf13/viper language: go go: - - 1.7.x - - 1.8.x - 1.9.x + - 1.10.x - tip os: From 15738813a09db5c8e5b60a19d67d3f9bd38da3a4 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Sat, 5 May 2018 21:38:06 -0400 Subject: [PATCH 4/7] Add GetInt32 --- viper.go | 6 ++++++ viper_test.go | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/viper.go b/viper.go index e9966ba..907a102 100644 --- a/viper.go +++ b/viper.go @@ -682,6 +682,12 @@ func (v *Viper) GetInt(key string) int { return cast.ToInt(v.Get(key)) } +// GetInt32 returns the value associated with the key as an integer. +func GetInt32(key string) int32 { return v.GetInt32(key) } +func (v *Viper) GetInt32(key string) int32 { + return cast.ToInt32(v.Get(key)) +} + // GetInt64 returns the value associated with the key as an integer. func GetInt64(key string) int64 { return v.GetInt64(key) } func (v *Viper) GetInt64(key string) int64 { diff --git a/viper_test.go b/viper_test.go index c93480e..60543f5 100644 --- a/viper_test.go +++ b/viper_test.go @@ -1068,6 +1068,10 @@ func TestMergeConfig(t *testing.T) { t.Fatalf("lagrenum != 765432101234567, = %d", pop) } + if pop := v.GetInt32("hello.pop"); pop != int32(37890) { + t.Fatalf("pop != 37890, = %d", pop) + } + if pop := v.GetInt64("hello.lagrenum"); pop != int64(765432101234567) { t.Fatalf("int64 lagrenum != 765432101234567, = %d", pop) } @@ -1092,6 +1096,10 @@ func TestMergeConfig(t *testing.T) { t.Fatalf("lagrenum != 7654321001234567, = %d", pop) } + if pop := v.GetInt32("hello.pop"); pop != int32(45000) { + t.Fatalf("pop != 45000, = %d", pop) + } + if pop := v.GetInt64("hello.lagrenum"); pop != int64(7654321001234567) { t.Fatalf("int64 lagrenum != 7654321001234567, = %d", pop) } From fb7a06477f21a08e24c82d4bddc131d56fdef129 Mon Sep 17 00:00:00 2001 From: Brice Fernandes Date: Tue, 10 Jul 2018 12:30:24 +0100 Subject: [PATCH 5/7] Add example of marshalling to string (#531) --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 64bf474..d1b8737 100644 --- a/README.md +++ b/README.md @@ -437,6 +437,7 @@ The following functions and methods exist: * `GetTime(key string) : time.Time` * `GetDuration(key string) : time.Duration` * `IsSet(key string) : bool` + * `AllSettings() : map[string]interface{}` One important thing to recognize is that each Get function will return a zero value if it’s not found. To check if a given key exists, the `IsSet()` method @@ -590,6 +591,27 @@ if err != nil { } ``` +### Marshalling to string + +You may need to marhsal all the settings held in viper into a string rather than write them to a file. +You can use your favorite format's marshaller with the config returned by `AllSettings()`. + +```go +import ( + yaml "gopkg.in/yaml.v2" + // ... +) + +func yamlStringSettings() string { + c := viper.AllSettings() + bs, err := yaml.Marshal(c) + if err != nil { + t.Fatalf("unable to marshal config to YAML: %v", err) + } + return string(bs) +} +``` + ## Viper or Vipers? Viper comes ready to use out of the box. There is no configuration or From d493c32b69b8c6f2377bf30bc4d70267ffbc0793 Mon Sep 17 00:00:00 2001 From: Adriano Date: Tue, 10 Jul 2018 08:30:20 -0400 Subject: [PATCH 6/7] Update README.md (#470) Fix typo in the environment variable usage documentation. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d1b8737..d752822 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,7 @@ _When working with ENV variables, it’s important to recognize that Viper treats ENV variables as case sensitive._ Viper provides a mechanism to try to ensure that ENV variables are unique. By -using `SetEnvPrefix`, you can tell Viper to use add a prefix while reading from +using `SetEnvPrefix`, you can tell Viper to use a prefix while reading from the environment variables. Both `BindEnv` and `AutomaticEnv` will use this prefix. From 907c19d40d9a6c9bb55f040ff4ae45271a4754b9 Mon Sep 17 00:00:00 2001 From: Chris Reeves Date: Thu, 28 Jun 2018 10:55:33 +0100 Subject: [PATCH 7/7] Support customising mapstructure.DecoderConfig for Unmarshal * Added a new `DecoderConfigOption` type allowing the user to write custom functions that can override the default mapstructure.DecoderConfig settings * Added a new `DecodeHook` function which returns a `DecoderConfigOption`. This allows the user to easily set their own Decode hooks when Unmarshaling * Updated Unmarshal, UnmarshalKey and defaultDecoderConfig to support variadic trailing `DecoderConfigOption` functions to allow for customisation of the default mapstructure.DecoderConfig * Added a test case with example usage --- viper.go | 41 +++++++++++++++++++++++++++++++++-------- viper_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 8 deletions(-) diff --git a/viper.go b/viper.go index 907a102..f657b20 100644 --- a/viper.go +++ b/viper.go @@ -113,6 +113,23 @@ func (fnfe ConfigFileNotFoundError) Error() string { return fmt.Sprintf("Config File %q Not Found in %q", fnfe.name, fnfe.locations) } +// A DecoderConfigOption can be passed to viper.Unmarshal to configure +// mapstructure.DecoderConfig options +type DecoderConfigOption func(*mapstructure.DecoderConfig) + +// DecodeHook returns a DecoderConfigOption which overrides the default +// DecoderConfig.DecodeHook value, the default is: +// +// mapstructure.ComposeDecodeHookFunc( +// mapstructure.StringToTimeDurationHookFunc(), +// mapstructure.StringToSliceHookFunc(","), +// ) +func DecodeHook(hook mapstructure.DecodeHookFunc) DecoderConfigOption { + return func(c *mapstructure.DecoderConfig) { + c.DecodeHook = hook + } +} + // Viper is a prioritized configuration registry. It // maintains a set of configuration sources, fetches // values to populate those, and provides them according @@ -745,9 +762,11 @@ func (v *Viper) GetSizeInBytes(key string) uint { } // UnmarshalKey takes a single key and unmarshals it into a Struct. -func UnmarshalKey(key string, rawVal interface{}) error { return v.UnmarshalKey(key, rawVal) } -func (v *Viper) UnmarshalKey(key string, rawVal interface{}) error { - err := decode(v.Get(key), defaultDecoderConfig(rawVal)) +func UnmarshalKey(key string, rawVal interface{}, opts ...DecoderConfigOption) error { + return v.UnmarshalKey(key, rawVal, opts...) +} +func (v *Viper) UnmarshalKey(key string, rawVal interface{}, opts ...DecoderConfigOption) error { + err := decode(v.Get(key), defaultDecoderConfig(rawVal, opts...)) if err != nil { return err @@ -760,9 +779,11 @@ func (v *Viper) UnmarshalKey(key string, rawVal interface{}) error { // Unmarshal unmarshals the config into a Struct. Make sure that the tags // on the fields of the structure are properly set. -func Unmarshal(rawVal interface{}) error { return v.Unmarshal(rawVal) } -func (v *Viper) Unmarshal(rawVal interface{}) error { - err := decode(v.AllSettings(), defaultDecoderConfig(rawVal)) +func Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error { + return v.Unmarshal(rawVal, opts...) +} +func (v *Viper) Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error { + err := decode(v.AllSettings(), defaultDecoderConfig(rawVal, opts...)) if err != nil { return err @@ -775,8 +796,8 @@ func (v *Viper) Unmarshal(rawVal interface{}) error { // defaultDecoderConfig returns default mapsstructure.DecoderConfig with suppot // of time.Duration values & string slices -func defaultDecoderConfig(output interface{}) *mapstructure.DecoderConfig { - return &mapstructure.DecoderConfig{ +func defaultDecoderConfig(output interface{}, opts ...DecoderConfigOption) *mapstructure.DecoderConfig { + c := &mapstructure.DecoderConfig{ Metadata: nil, Result: output, WeaklyTypedInput: true, @@ -785,6 +806,10 @@ func defaultDecoderConfig(output interface{}) *mapstructure.DecoderConfig { mapstructure.StringToSliceHookFunc(","), ), } + for _, opt := range opts { + opt(c) + } + return c } // A wrapper around mapstructure.Decode that mimics the WeakDecode functionality diff --git a/viper_test.go b/viper_test.go index 60543f5..be11f3e 100644 --- a/viper_test.go +++ b/viper_test.go @@ -7,6 +7,7 @@ package viper import ( "bytes" + "encoding/json" "fmt" "io" "io/ioutil" @@ -18,6 +19,7 @@ import ( "testing" "time" + "github.com/mitchellh/mapstructure" "github.com/spf13/afero" "github.com/spf13/cast" @@ -503,6 +505,42 @@ func TestUnmarshal(t *testing.T) { assert.Equal(t, &config{Name: "Steve", Port: 1234, Duration: time.Second + time.Millisecond}, &C) } +func TestUnmarshalWithDecoderOptions(t *testing.T) { + Set("credentials", "{\"foo\":\"bar\"}") + + opt := DecodeHook(mapstructure.ComposeDecodeHookFunc( + mapstructure.StringToTimeDurationHookFunc(), + mapstructure.StringToSliceHookFunc(","), + // Custom Decode Hook Function + func(rf reflect.Kind, rt reflect.Kind, data interface{}) (interface{}, error) { + if rf != reflect.String || rt != reflect.Map { + return data, nil + } + m := map[string]string{} + raw := data.(string) + if raw == "" { + return m, nil + } + return m, json.Unmarshal([]byte(raw), &m) + }, + )) + + type config struct { + Credentials map[string]string + } + + var C config + + err := Unmarshal(&C, opt) + if err != nil { + t.Fatalf("unable to decode into struct, %v", err) + } + + assert.Equal(t, &config{ + Credentials: map[string]string{"foo": "bar"}, + }, &C) +} + func TestBindPFlags(t *testing.T) { v := New() // create independent Viper object flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)