From d90f2bb139e1c12fa276ee449cd68e8c6c5c1602 Mon Sep 17 00:00:00 2001 From: Cameron Moore Date: Tue, 27 Dec 2016 21:49:59 -0600 Subject: [PATCH 01/26] Only save config on success in ReadInConfig If the user creates a invalid config file while watching for config changes, the previous, valid config is not retained. This commit only overwrites the running config if unmarshalling was successful. --- viper.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/viper.go b/viper.go index 2603c78..fce13b1 100644 --- a/viper.go +++ b/viper.go @@ -1093,9 +1093,15 @@ func (v *Viper) ReadInConfig() error { return err } - v.config = make(map[string]interface{}) + config := make(map[string]interface{}) - return v.unmarshalReader(bytes.NewReader(file), v.config) + err = v.unmarshalReader(bytes.NewReader(file), config) + if err != nil { + return err + } + + v.config = config + return nil } // MergeInConfig merges a new configuration with an existing config. From 7538d73b4eb9511d85a9f1dfef202eeb8ac260f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Fri, 17 Feb 2017 17:38:17 +0100 Subject: [PATCH 02/26] travis: Bump to Go 1.7.5 and 1.8 (Yay!) --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index e793edb..d4c2559 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,8 @@ go_import_path: github.com/spf13/viper language: go go: - - 1.5.4 - - 1.6.3 - - 1.7 + - 1.7.5 + - 1.8 - tip os: From 11ca61e88892f9cb3cff255826cb7ace534d340d Mon Sep 17 00:00:00 2001 From: Wolfgang Friedl Date: Wed, 15 Mar 2017 08:08:46 +0100 Subject: [PATCH 03/26] remote: Avoid the start of go-routines which are never get stopped --- remote/remote.go | 15 ++++++++++++--- viper.go | 23 +++++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/remote/remote.go b/remote/remote.go index faaf3b3..f36f035 100644 --- a/remote/remote.go +++ b/remote/remote.go @@ -33,14 +33,23 @@ func (rc remoteConfigProvider) Watch(rp viper.RemoteProvider) (io.Reader, error) if err != nil { return nil, err } - resp := <-cm.Watch(rp.Path(), nil) - err = resp.Error + resp,err := cm.Get(rp.Path()) if err != nil { return nil, err } - return bytes.NewReader(resp.Value), nil + return bytes.NewReader(resp), nil } +func (rc remoteConfigProvider) WatchChannel(rp viper.RemoteProvider) (<-chan *crypt.Response, chan bool) { + cm, err := getConfigManager(rp) + if err != nil { + return nil, nil + } + quit := make(chan bool) + return cm.Watch(rp.Path(), quit) ,quit + +} + func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) { diff --git a/viper.go b/viper.go index fce13b1..56278f3 100644 --- a/viper.go +++ b/viper.go @@ -36,6 +36,7 @@ import ( "github.com/spf13/cast" jww "github.com/spf13/jwalterweatherman" "github.com/spf13/pflag" + crypt "github.com/xordataexchange/crypt/config" ) var v *Viper @@ -47,6 +48,7 @@ func init() { type remoteConfigFactory interface { Get(rp RemoteProvider) (io.Reader, error) Watch(rp RemoteProvider) (io.Reader, error) + WatchChannel(rp RemoteProvider)(<-chan *crypt.Response, chan bool) } // RemoteConfig is optional, see the remote package @@ -1255,6 +1257,10 @@ func (v *Viper) WatchRemoteConfig() error { return v.watchKeyValueConfig() } +func (v *Viper) WatchRemoteConfigOnChannel() error { + return v.watchKeyValueConfigOnChannel() +} + // Unmarshall a Reader into a map. // Should probably be an unexported function. func unmarshalReader(in io.Reader, c map[string]interface{}) error { @@ -1298,6 +1304,23 @@ func (v *Viper) getRemoteConfig(provider RemoteProvider) (map[string]interface{} return v.kvstore, err } +// Retrieve the first found remote configuration. +func (v *Viper) watchKeyValueConfigOnChannel() error { + for _, rp := range v.remoteProviders { + respc, _ := RemoteConfig.WatchChannel(rp) + //Todo: Add quit channel + go func(rc <-chan *crypt.Response) { + for { + b := <-rc + reader := bytes.NewReader(b.Value) + v.unmarshalReader(reader, v.kvstore) + } + }(respc) + return nil + } + return RemoteConfigError("No Files Found") +} + // Retrieve the first found remote configuration. func (v *Viper) watchKeyValueConfig() error { for _, rp := range v.remoteProviders { From 0b5690fd875739811249ca79d24afd8f89f188c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Wed, 15 Mar 2017 08:09:33 +0100 Subject: [PATCH 04/26] Revert "remote: Avoid the start of go-routines which are never get stopped" This reverts commit 11ca61e88892f9cb3cff255826cb7ace534d340d. --- remote/remote.go | 15 +++------------ viper.go | 23 ----------------------- 2 files changed, 3 insertions(+), 35 deletions(-) diff --git a/remote/remote.go b/remote/remote.go index f36f035..faaf3b3 100644 --- a/remote/remote.go +++ b/remote/remote.go @@ -33,23 +33,14 @@ func (rc remoteConfigProvider) Watch(rp viper.RemoteProvider) (io.Reader, error) if err != nil { return nil, err } - resp,err := cm.Get(rp.Path()) + resp := <-cm.Watch(rp.Path(), nil) + err = resp.Error if err != nil { return nil, err } - return bytes.NewReader(resp), nil + return bytes.NewReader(resp.Value), nil } -func (rc remoteConfigProvider) WatchChannel(rp viper.RemoteProvider) (<-chan *crypt.Response, chan bool) { - cm, err := getConfigManager(rp) - if err != nil { - return nil, nil - } - quit := make(chan bool) - return cm.Watch(rp.Path(), quit) ,quit - -} - func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) { diff --git a/viper.go b/viper.go index 56278f3..fce13b1 100644 --- a/viper.go +++ b/viper.go @@ -36,7 +36,6 @@ import ( "github.com/spf13/cast" jww "github.com/spf13/jwalterweatherman" "github.com/spf13/pflag" - crypt "github.com/xordataexchange/crypt/config" ) var v *Viper @@ -48,7 +47,6 @@ func init() { type remoteConfigFactory interface { Get(rp RemoteProvider) (io.Reader, error) Watch(rp RemoteProvider) (io.Reader, error) - WatchChannel(rp RemoteProvider)(<-chan *crypt.Response, chan bool) } // RemoteConfig is optional, see the remote package @@ -1257,10 +1255,6 @@ func (v *Viper) WatchRemoteConfig() error { return v.watchKeyValueConfig() } -func (v *Viper) WatchRemoteConfigOnChannel() error { - return v.watchKeyValueConfigOnChannel() -} - // Unmarshall a Reader into a map. // Should probably be an unexported function. func unmarshalReader(in io.Reader, c map[string]interface{}) error { @@ -1304,23 +1298,6 @@ func (v *Viper) getRemoteConfig(provider RemoteProvider) (map[string]interface{} return v.kvstore, err } -// Retrieve the first found remote configuration. -func (v *Viper) watchKeyValueConfigOnChannel() error { - for _, rp := range v.remoteProviders { - respc, _ := RemoteConfig.WatchChannel(rp) - //Todo: Add quit channel - go func(rc <-chan *crypt.Response) { - for { - b := <-rc - reader := bytes.NewReader(b.Value) - v.unmarshalReader(reader, v.kvstore) - } - }(respc) - return nil - } - return RemoteConfigError("No Files Found") -} - // Retrieve the first found remote configuration. func (v *Viper) watchKeyValueConfig() error { for _, rp := range v.remoteProviders { From 84f94806c67f59dd7ae87bc5351f7a9c94a4558d Mon Sep 17 00:00:00 2001 From: Wolfgang Friedl Date: Wed, 15 Mar 2017 14:43:09 +0100 Subject: [PATCH 05/26] Avoid the start of go-routines which are never get stopped --- remote/remote.go | 36 +++++++++++++++++++++++++++++++++--- viper.go | 27 +++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/remote/remote.go b/remote/remote.go index faaf3b3..f100a9c 100644 --- a/remote/remote.go +++ b/remote/remote.go @@ -33,14 +33,44 @@ func (rc remoteConfigProvider) Watch(rp viper.RemoteProvider) (io.Reader, error) if err != nil { return nil, err } - resp := <-cm.Watch(rp.Path(), nil) - err = resp.Error + resp,err := cm.Get(rp.Path()) if err != nil { return nil, err } - return bytes.NewReader(resp.Value), nil + return bytes.NewReader(resp), nil } +func (rc remoteConfigProvider) WatchChannel(rp viper.RemoteProvider) (<-chan *viper.RemoteResponse, chan bool) { + cm, err := getConfigManager(rp) + if err != nil { + return nil, nil + } + quit := make(chan bool) + quitwc := make(chan bool) + viperResponsCh := make(chan *viper.RemoteResponse) + cryptoResponseCh := cm.Watch(rp.Path(), quit) + // need this function to convert the Channel response form crypt.Response to viper.Response + go func(cr <-chan *crypt.Response,vr chan<- *viper.RemoteResponse, quitwc <-chan bool, quit chan<- bool) { + for { + select { + case <- quitwc: + quit <- true + return + case resp := <-cr: + vr <- &viper.RemoteResponse{ + Error: resp.Error, + Value: resp.Value, + } + + } + + } + }(cryptoResponseCh,viperResponsCh,quitwc,quit) + + return viperResponsCh,quitwc + +} + func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) { diff --git a/viper.go b/viper.go index fce13b1..22a2ed8 100644 --- a/viper.go +++ b/viper.go @@ -40,6 +40,11 @@ import ( var v *Viper +type RemoteResponse struct { + Value []byte + Error error +} + func init() { v = New() } @@ -47,6 +52,7 @@ func init() { type remoteConfigFactory interface { Get(rp RemoteProvider) (io.Reader, error) Watch(rp RemoteProvider) (io.Reader, error) + WatchChannel(rp RemoteProvider)(<-chan *RemoteResponse, chan bool) } // RemoteConfig is optional, see the remote package @@ -1255,6 +1261,10 @@ func (v *Viper) WatchRemoteConfig() error { return v.watchKeyValueConfig() } +func (v *Viper) WatchRemoteConfigOnChannel() error { + return v.watchKeyValueConfigOnChannel() +} + // Unmarshall a Reader into a map. // Should probably be an unexported function. func unmarshalReader(in io.Reader, c map[string]interface{}) error { @@ -1298,6 +1308,23 @@ func (v *Viper) getRemoteConfig(provider RemoteProvider) (map[string]interface{} return v.kvstore, err } +// Retrieve the first found remote configuration. +func (v *Viper) watchKeyValueConfigOnChannel() error { + for _, rp := range v.remoteProviders { + respc, _ := RemoteConfig.WatchChannel(rp) + //Todo: Add quit channel + go func(rc <-chan *RemoteResponse) { + for { + b := <-rc + reader := bytes.NewReader(b.Value) + v.unmarshalReader(reader, v.kvstore) + } + }(respc) + return nil + } + return RemoteConfigError("No Files Found") +} + // Retrieve the first found remote configuration. func (v *Viper) watchKeyValueConfig() error { for _, rp := range v.remoteProviders { From 5d46e70da8c0b6f812e0b170b7a985753b5c63cb Mon Sep 17 00:00:00 2001 From: Miguel Eduardo Gil Biraud Date: Mon, 10 Apr 2017 11:26:50 +0200 Subject: [PATCH 06/26] Fix UnmarshalKey handling of time.Duration * Failing test for key unmarshaling with nested structs containing time.Duration * Fix UnmarshalKey to use of defaultDecoderConfig --- viper.go | 10 +++++++++- viper_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/viper.go b/viper.go index 22a2ed8..5ca66ae 100644 --- a/viper.go +++ b/viper.go @@ -719,7 +719,15 @@ 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 { - return mapstructure.Decode(v.Get(key), rawVal) + err := decode(v.Get(key), defaultDecoderConfig(rawVal)) + + if err != nil { + return err + } + + v.insensitiviseMaps() + + return nil } // Unmarshal unmarshals the config into a Struct. Make sure that the tags diff --git a/viper_test.go b/viper_test.go index cd7b65c..cd10603 100644 --- a/viper_test.go +++ b/viper_test.go @@ -1102,6 +1102,35 @@ func TestCaseInsensitiveSet(t *testing.T) { } } +func TestParseNested(t *testing.T) { + type duration struct { + Delay time.Duration + } + + type item struct { + Name string + Delay time.Duration + Nested duration + } + + config := `[[parent]] + delay="100ms" + [parent.nested] + delay="200ms" +` + initConfig("toml", config) + + var items []item + err := v.UnmarshalKey("parent", &items) + if err != nil { + t.Fatalf("unable to decode into struct, %v", err) + } + + assert.Equal(t, 1, len(items)) + assert.Equal(t, 100*time.Millisecond, items[0].Delay) + assert.Equal(t, 200*time.Millisecond, items[0].Nested.Delay) +} + func doTestCaseInsensitive(t *testing.T, typ, config string) { initConfig(typ, config) Set("RfD", true) From 0967fc9aceab2ce9da34061253ac10fb99bba5b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Szczur?= Date: Mon, 17 Apr 2017 10:08:15 +0200 Subject: [PATCH 07/26] Properly handle string slice values --- viper.go | 18 ++++++++++++++++-- viper_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/viper.go b/viper.go index 5ca66ae..31b41a6 100644 --- a/viper.go +++ b/viper.go @@ -21,6 +21,7 @@ package viper import ( "bytes" + "encoding/csv" "fmt" "io" "log" @@ -894,7 +895,9 @@ func (v *Viper) find(lcaseKey string) interface{} { return cast.ToBool(flag.ValueString()) case "stringSlice": s := strings.TrimPrefix(flag.ValueString(), "[") - return strings.TrimSuffix(s, "]") + s = strings.TrimSuffix(s, "]") + res, _ := readAsCSV(s) + return res default: return flag.ValueString() } @@ -961,7 +964,9 @@ func (v *Viper) find(lcaseKey string) interface{} { return cast.ToBool(flag.ValueString()) case "stringSlice": s := strings.TrimPrefix(flag.ValueString(), "[") - return strings.TrimSuffix(s, "]") + s = strings.TrimSuffix(s, "]") + res, _ := readAsCSV(s) + return res default: return flag.ValueString() } @@ -971,6 +976,15 @@ func (v *Viper) find(lcaseKey string) interface{} { return nil } +func readAsCSV(val string) ([]string, error) { + if val == "" { + return []string{}, nil + } + stringReader := strings.NewReader(val) + csvReader := csv.NewReader(stringReader) + return csvReader.Read() +} + // IsSet checks to see if the key has been set in any of the data locations. // IsSet is case-insensitive for a key. func IsSet(key string) bool { return v.IsSet(key) } diff --git a/viper_test.go b/viper_test.go index cd10603..774ca11 100644 --- a/viper_test.go +++ b/viper_test.go @@ -538,6 +538,44 @@ func TestBindPFlags(t *testing.T) { } +func TestBindPFlagsStringSlice(t *testing.T) { + for _, testValue := range []struct { + Expected []string + Value string + }{ + {[]string{}, ""}, + {[]string{"jeden"}, "jeden"}, + {[]string{"dwa", "trzy"}, "dwa,trzy"}, + {[]string{"cztery", "piec , szesc"}, "cztery,\"piec , szesc\""}} { + + for _, changed := range []bool{true, false} { + v := New() // create independent Viper object + flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) + flagSet.StringSlice("stringslice", testValue.Expected, "test") + flagSet.Visit(func(f *pflag.Flag) { + if len(testValue.Value) > 0 { + f.Value.Set(testValue.Value) + f.Changed = changed + } + }) + + err := v.BindPFlags(flagSet) + if err != nil { + t.Fatalf("error binding flag set, %v", err) + } + + type TestStr struct { + StringSlice []string + } + val := &TestStr{} + if err := v.Unmarshal(val); err != nil { + t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err) + } + assert.Equal(t, testValue.Expected, val.StringSlice) + } + } +} + func TestBindPFlag(t *testing.T) { var testString = "testing" var testValue = newStringValue(testString, &testString) From a1ecfa6a20bd4ef9e9caded262ee1b1b26847675 Mon Sep 17 00:00:00 2001 From: Nick Miyake Date: Fri, 9 Jun 2017 14:46:44 -0700 Subject: [PATCH 08/26] Fix apostrophes in README Fix "it's" -> "its". --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 25181df..577088f 100644 --- a/README.md +++ b/README.md @@ -575,13 +575,13 @@ initialization needed to begin using Viper. Since most applications will want to use a single central repository for their configuration, the viper package provides this. It is similar to a singleton. -In all of the examples above, they demonstrate using viper in it's singleton +In all of the examples above, they demonstrate using viper in its singleton style approach. ### Working with multiple vipers You can also create many different vipers for use in your application. Each will -have it’s own unique set of configurations and values. Each can read from a +have its own unique set of configurations and values. Each can read from a different config file, key value store, etc. All of the functions that viper package supports are mirrored as methods on a viper. From c1de95864d73a5465492829d7cb2dd422b19ac96 Mon Sep 17 00:00:00 2001 From: Albert Nigmatzianov Date: Mon, 19 Jun 2017 12:35:39 +0200 Subject: [PATCH 09/26] Prevent redundant type assertion in Get MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is no need to assert variables which already have appropriate type. name old time/op new time/op delta GetBool-4 554ns ± 3% 493ns ± 8% -10.86% (p=0.000 n=14+15) Get-4 484ns ± 4% 414ns ± 7% -14.37% (p=0.000 n=14+15) GetBoolFromMap-4 8.38ns ± 6% 7.83ns ± 7% -6.59% (p=0.000 n=15+15) name old alloc/op new alloc/op delta GetBool-4 65.0B ± 0% 64.0B ± 0% -1.54% (p=0.000 n=15+15) Get-4 64.0B ± 0% 64.0B ± 0% ~ (all equal) GetBoolFromMap-4 0.00B 0.00B ~ (all equal) name old allocs/op new allocs/op delta GetBool-4 5.00 ± 0% 4.00 ± 0% -20.00% (p=0.000 n=15+15) Get-4 5.00 ± 0% 4.00 ± 0% -20.00% (p=0.000 n=15+15) GetBoolFromMap-4 0.00 0.00 ~ (all equal) --- viper.go | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/viper.go b/viper.go index 31b41a6..39a3c06 100644 --- a/viper.go +++ b/viper.go @@ -53,7 +53,7 @@ func init() { type remoteConfigFactory interface { Get(rp RemoteProvider) (io.Reader, error) Watch(rp RemoteProvider) (io.Reader, error) - WatchChannel(rp RemoteProvider)(<-chan *RemoteResponse, chan bool) + WatchChannel(rp RemoteProvider) (<-chan *RemoteResponse, chan bool) } // RemoteConfig is optional, see the remote package @@ -597,32 +597,33 @@ func (v *Viper) Get(key string) interface{} { return nil } - valType := val if v.typeByDefValue { // TODO(bep) this branch isn't covered by a single test. + valType := val path := strings.Split(lcaseKey, v.keyDelim) defVal := v.searchMap(v.defaults, path) if defVal != nil { valType = defVal } + + switch valType.(type) { + case bool: + return cast.ToBool(val) + case string: + return cast.ToString(val) + case int64, int32, int16, int8, int: + return cast.ToInt(val) + case float64, float32: + return cast.ToFloat64(val) + case time.Time: + return cast.ToTime(val) + case time.Duration: + return cast.ToDuration(val) + case []string: + return cast.ToStringSlice(val) + } } - switch valType.(type) { - case bool: - return cast.ToBool(val) - case string: - return cast.ToString(val) - case int64, int32, int16, int8, int: - return cast.ToInt(val) - case float64, float32: - return cast.ToFloat64(val) - case time.Time: - return cast.ToTime(val) - case time.Duration: - return cast.ToDuration(val) - case []string: - return cast.ToStringSlice(val) - } return val } From df7314a14e26fea5443bb6e696b207d14d9a266d Mon Sep 17 00:00:00 2001 From: Anthony Fok Date: Fri, 21 Jul 2017 04:20:10 -0600 Subject: [PATCH 10/26] Make minor copy-editing changes to README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 577088f..8e723d4 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Many Go projects are built using Viper including: * [Hugo](http://gohugo.io) * [EMC RexRay](http://rexray.readthedocs.org/en/stable/) -* [Imgur's Incus](https://github.com/Imgur/incus) +* [Imgur’s Incus](https://github.com/Imgur/incus) * [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack) * [Docker Notary](https://github.com/docker/Notary) * [BloomApi](https://www.bloomapi.com/) @@ -17,7 +17,7 @@ Many Go projects are built using Viper including: ## What is Viper? -Viper is a complete configuration solution for go applications including 12 factor apps. It is designed +Viper is a complete configuration solution for Go applications including 12-Factor apps. It is designed to work within an application, and can handle all types of configuration needs and formats. It supports: @@ -68,7 +68,7 @@ Viper configuration keys are case insensitive. ### Establishing Defaults A good configuration system will support default values. A default value is not -required for a key, but it's useful in the event that a key hasn’t been set via +required for a key, but it’s useful in the event that a key hasn’t been set via config file, environment variable, remote configuration or flag. Examples: @@ -271,7 +271,7 @@ func main() { #### Flag interfaces -Viper provides two Go interfaces to bind other flag systems if you don't use `Pflags`. +Viper provides two Go interfaces to bind other flag systems if you don’t use `Pflags`. `FlagValue` represents a single flag. This is a very simple example on how to implement this interface: @@ -401,7 +401,7 @@ go func(){ ## Getting Values From Viper -In Viper, there are a few ways to get a value depending on the value's type. +In Viper, there are a few ways to get a value depending on the value’s type. The following functions and methods exist: * `Get(key string) : interface{}` @@ -531,7 +531,7 @@ func NewCache(cfg *Viper) *Cache {...} ``` which creates a cache based on config information formatted as `subv`. -Now it's easy to create these 2 caches separately as: +Now it’s easy to create these 2 caches separately as: ```go cfg1 := viper.Sub("app.cache1") From 16a945857303e8ea14318664816932b07a0ed5ce Mon Sep 17 00:00:00 2001 From: Albert Nigmatzianov Date: Sun, 23 Jul 2017 06:54:24 +0200 Subject: [PATCH 11/26] Fix indentation in README.md See #345 --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8e723d4..e088cbf 100644 --- a/README.md +++ b/README.md @@ -116,10 +116,10 @@ Optionally you can provide a function for Viper to run each time a change occurs **Make sure you add all of the configPaths prior to calling `WatchConfig()`** ```go - viper.WatchConfig() - viper.OnConfigChange(func(e fsnotify.Event) { - fmt.Println("Config file changed:", e.Name) - }) +viper.WatchConfig() +viper.OnConfigChange(func(e fsnotify.Event) { + fmt.Println("Config file changed:", e.Name) +}) ``` ### Reading Config from io.Reader From 9766537120e76571a9a2dcafe4b25a3201dcac22 Mon Sep 17 00:00:00 2001 From: Nikola Kovacs Date: Sun, 23 Jul 2017 07:09:41 +0200 Subject: [PATCH 12/26] Fix grammar/typo in comment for getEnv() See #310 --- viper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper.go b/viper.go index 39a3c06..4631e31 100644 --- a/viper.go +++ b/viper.go @@ -315,7 +315,7 @@ func (v *Viper) mergeWithEnvPrefix(in string) string { // (cammel case to snake case for JSON keys perhaps) // getEnv is a wrapper around os.Getenv which replaces characters in the original -// key. This allows env vars which have different keys then the config object +// key. This allows env vars which have different keys than the config object // keys func (v *Viper) getEnv(key string) string { if v.envKeyReplacer != nil { From 04691bc570c10b492d8bf7b66aae05e0c9ecb0db Mon Sep 17 00:00:00 2001 From: Brad Peabody Date: Sat, 22 Jul 2017 22:26:21 -0700 Subject: [PATCH 13/26] Improve documentation for flags See #329 --- README.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e088cbf..5221c14 100644 --- a/README.md +++ b/README.md @@ -236,7 +236,7 @@ Like `BindEnv`, the value is not set when the binding method is called, but when it is accessed. This means you can bind as early as you want, even in an `init()` function. -The `BindPFlag()` method provides this functionality. +For individual flags, the `BindPFlag()` method provides this functionality. Example: @@ -245,6 +245,19 @@ serverCmd.Flags().Int("port", 1138, "Port to run Application server on") viper.BindPFlag("port", serverCmd.Flags().Lookup("port")) ``` +You can also bind an existing set of pflags (pflag.FlagSet): + +Example: + +```go +pflag.Int("flagname", 1234, "help message for flagname") + +pflag.Parse() +viper.BindPFlags(pflag.CommandLine) + +i := viper.GetInt("flagname") // retrieve values from viper instead of pflag +``` + The use of [pflag](https://github.com/spf13/pflag/) in Viper does not preclude the use of other packages that use the [flag](https://golang.org/pkg/flag/) package from the standard library. The pflag package can handle the flags @@ -263,9 +276,17 @@ import ( ) func main() { + + // using standard library "flag" package + flag.Int("flagname", 1234, "help message for flagname") + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) pflag.Parse() - ... + viper.BindPFlags(pflag.CommandLine) + + i := viper.GetInt("flagname") // retrieve value from viper + + ... } ``` From f257d19100ad11ec71a89d431c0fbd3933c0b235 Mon Sep 17 00:00:00 2001 From: Nick Miyake Date: Sat, 22 Jul 2017 22:39:01 -0700 Subject: [PATCH 14/26] Fix a few minor typos/formatting issues with comments See #350 --- viper.go | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/viper.go b/viper.go index 4631e31..e7c6090 100644 --- a/viper.go +++ b/viper.go @@ -69,8 +69,7 @@ func (str UnsupportedConfigError) Error() string { } // UnsupportedRemoteProviderError denotes encountering an unsupported remote -// provider. Currently only etcd and Consul are -// supported. +// provider. Currently only etcd and Consul are supported. type UnsupportedRemoteProviderError string // Error returns the formatted remote provider error. @@ -283,8 +282,8 @@ func (v *Viper) WatchConfig() { }() } -// SetConfigFile explicitly defines the path, name and extension of the config file -// Viper will use this and not check any of the config paths +// SetConfigFile explicitly defines 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 != "" { @@ -293,8 +292,8 @@ func (v *Viper) SetConfigFile(in string) { } // SetEnvPrefix defines a prefix that ENVIRONMENT variables will use. -// E.g. if your prefix is "spf", the env registry -// will look for env. variables that start with "SPF_" +// E.g. if your prefix is "spf", the env registry will look for env +// variables that start with "SPF_". func SetEnvPrefix(in string) { v.SetEnvPrefix(in) } func (v *Viper) SetEnvPrefix(in string) { if in != "" { @@ -312,11 +311,11 @@ func (v *Viper) mergeWithEnvPrefix(in string) string { // TODO: should getEnv logic be moved into find(). Can generalize the use of // rewriting keys many things, Ex: Get('someKey') -> some_key -// (cammel case to snake case for JSON keys perhaps) +// (camel case to snake case for JSON keys perhaps) // getEnv is a wrapper around os.Getenv which replaces characters in the original // key. This allows env vars which have different keys than the config object -// keys +// keys. func (v *Viper) getEnv(key string) string { if v.envKeyReplacer != nil { key = v.envKeyReplacer.Replace(key) @@ -324,7 +323,7 @@ func (v *Viper) getEnv(key string) string { return os.Getenv(key) } -// ConfigFileUsed returns the file used to populate the config registry +// ConfigFileUsed returns the file used to populate the config registry. func ConfigFileUsed() string { return v.ConfigFileUsed() } func (v *Viper) ConfigFileUsed() string { return v.configFile } @@ -815,7 +814,7 @@ func (v *Viper) BindFlagValues(flags FlagValueSet) (err error) { } // BindFlagValue binds a specific key to a FlagValue. -// Example(where serverCmd is a Cobra instance): +// Example (where serverCmd is a Cobra instance): // // serverCmd.Flags().Int("port", 1138, "Port to run Application server on") // Viper.BindFlagValue("port", serverCmd.Flags().Lookup("port")) @@ -1288,7 +1287,7 @@ func (v *Viper) WatchRemoteConfigOnChannel() error { return v.watchKeyValueConfigOnChannel() } -// Unmarshall a Reader into a map. +// Unmarshal a Reader into a map. // Should probably be an unexported function. func unmarshalReader(in io.Reader, c map[string]interface{}) error { return v.unmarshalReader(in, c) From 8ac2e2e20f3d24e5b3b85ae0fcd11bdb0caf0f15 Mon Sep 17 00:00:00 2001 From: Nick Miyake Date: Sat, 22 Jul 2017 22:47:47 -0700 Subject: [PATCH 15/26] Run gofmt on all existing code Also: * Add check to .travis.yml that verifies that all code is gofmt-compliant * Touch up some newlines See #351 --- .travis.yml | 1 + flags_test.go | 1 - remote/remote.go | 17 +++++++---------- util_test.go | 1 - viper.go | 1 - 5 files changed, 8 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index d4c2559..f1deac3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,7 @@ matrix: script: - go install ./... + - diff -u <(echo -n) <(gofmt -d .) - go test -v ./... after_success: diff --git a/flags_test.go b/flags_test.go index 5bffca3..0b976b6 100644 --- a/flags_test.go +++ b/flags_test.go @@ -62,5 +62,4 @@ func TestBindFlagValue(t *testing.T) { flag.Changed = true //hack for pflag usage assert.Equal(t, "testing_mutate", Get("testvalue")) - } diff --git a/remote/remote.go b/remote/remote.go index f100a9c..68a35d6 100644 --- a/remote/remote.go +++ b/remote/remote.go @@ -33,13 +33,14 @@ func (rc remoteConfigProvider) Watch(rp viper.RemoteProvider) (io.Reader, error) if err != nil { return nil, err } - resp,err := cm.Get(rp.Path()) + resp, err := cm.Get(rp.Path()) if err != nil { return nil, err } return bytes.NewReader(resp), nil } + func (rc remoteConfigProvider) WatchChannel(rp viper.RemoteProvider) (<-chan *viper.RemoteResponse, chan bool) { cm, err := getConfigManager(rp) if err != nil { @@ -47,13 +48,13 @@ func (rc remoteConfigProvider) WatchChannel(rp viper.RemoteProvider) (<-chan *vi } quit := make(chan bool) quitwc := make(chan bool) - viperResponsCh := make(chan *viper.RemoteResponse) + viperResponsCh := make(chan *viper.RemoteResponse) cryptoResponseCh := cm.Watch(rp.Path(), quit) // need this function to convert the Channel response form crypt.Response to viper.Response - go func(cr <-chan *crypt.Response,vr chan<- *viper.RemoteResponse, quitwc <-chan bool, quit chan<- bool) { + go func(cr <-chan *crypt.Response, vr chan<- *viper.RemoteResponse, quitwc <-chan bool, quit chan<- bool) { for { select { - case <- quitwc: + case <-quitwc: quit <- true return case resp := <-cr: @@ -65,15 +66,12 @@ func (rc remoteConfigProvider) WatchChannel(rp viper.RemoteProvider) (<-chan *vi } } - }(cryptoResponseCh,viperResponsCh,quitwc,quit) - - return viperResponsCh,quitwc + }(cryptoResponseCh, viperResponsCh, quitwc, quit) + return viperResponsCh, quitwc } - func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) { - var cm crypt.ConfigManager var err error @@ -99,7 +97,6 @@ func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) { return nil, err } return cm, nil - } func init() { diff --git a/util_test.go b/util_test.go index 5949e09..0af80bb 100644 --- a/util_test.go +++ b/util_test.go @@ -16,7 +16,6 @@ import ( ) func TestCopyAndInsensitiviseMap(t *testing.T) { - var ( given = map[string]interface{}{ "Foo": 32, diff --git a/viper.go b/viper.go index e7c6090..2a221e5 100644 --- a/viper.go +++ b/viper.go @@ -1546,7 +1546,6 @@ func (v *Viper) searchInPath(in string) (filename string) { // Search all configPaths for any config file. // Returns the first path that exists (and is a config file). func (v *Viper) findConfigFile() (string, error) { - jww.INFO.Println("Searching for config in ", v.configPaths) for _, cp := range v.configPaths { From 25b30aa063fc18e48662b86996252eabdcf2f0c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JG=C2=B2?= Date: Sun, 23 Jul 2017 07:52:07 +0200 Subject: [PATCH 16/26] Add Clairctl as a Go project that uses Viper See #354 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5221c14..848d92d 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Many Go projects are built using Viper including: * [Docker Notary](https://github.com/docker/Notary) * [BloomApi](https://www.bloomapi.com/) * [doctl](https://github.com/digitalocean/doctl) +* [Clairctl](https://github.com/jgsqware/clairctl) [![Build Status](https://travis-ci.org/spf13/viper.svg)](https://travis-ci.org/spf13/viper) [![Join the chat at https://gitter.im/spf13/viper](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/spf13/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![GoDoc](https://godoc.org/github.com/spf13/viper?status.svg)](https://godoc.org/github.com/spf13/viper) From 266e588e9eca7ee317e9f0446a01934675a4d261 Mon Sep 17 00:00:00 2001 From: Alexander Krasnukhin Date: Fri, 3 Mar 2017 12:09:20 +0100 Subject: [PATCH 17/26] Add string slice support to defaultDecoderConfig This way it correctly decodes string slices as well. --- viper.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/viper.go b/viper.go index 2a221e5..92366d6 100644 --- a/viper.go +++ b/viper.go @@ -747,13 +747,16 @@ func (v *Viper) Unmarshal(rawVal interface{}) error { } // defaultDecoderConfig returns default mapsstructure.DecoderConfig with suppot -// of time.Duration values +// of time.Duration values & string slices func defaultDecoderConfig(output interface{}) *mapstructure.DecoderConfig { return &mapstructure.DecoderConfig{ Metadata: nil, Result: output, WeaklyTypedInput: true, - DecodeHook: mapstructure.StringToTimeDurationHookFunc(), + DecodeHook: mapstructure.ComposeDecodeHookFunc( + mapstructure.StringToTimeDurationHookFunc(), + mapstructure.StringToSliceHookFunc(","), + ), } } From d9cca5ef33035202efb1586825bdbb15ff9ec3ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Fri, 29 Sep 2017 23:06:42 +0200 Subject: [PATCH 18/26] Go fmt viper.go --- viper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper.go b/viper.go index 92366d6..963861a 100644 --- a/viper.go +++ b/viper.go @@ -753,7 +753,7 @@ func defaultDecoderConfig(output interface{}) *mapstructure.DecoderConfig { Metadata: nil, Result: output, WeaklyTypedInput: true, - DecodeHook: mapstructure.ComposeDecodeHookFunc( + DecodeHook: mapstructure.ComposeDecodeHookFunc( mapstructure.StringToTimeDurationHookFunc(), mapstructure.StringToSliceHookFunc(","), ), From 8ef37cbca71638bf32f3d5e194117d4cb46da163 Mon Sep 17 00:00:00 2001 From: Kamil Wargula Date: Fri, 20 Oct 2017 12:22:28 +0200 Subject: [PATCH 19/26] Fix incorrect name of function in README.md Change `SetEnvReplacer` to `SetEnvKeyReplacer` --- README.md | 4 ++-- viper_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 848d92d..64bf474 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ with ENV: * `AutomaticEnv()` * `BindEnv(string...) : error` * `SetEnvPrefix(string)` - * `SetEnvReplacer(string...) *strings.Replacer` + * `SetEnvKeyReplacer(string...) *strings.Replacer` _When working with ENV variables, it’s important to recognize that Viper treats ENV variables as case sensitive._ @@ -212,7 +212,7 @@ time a `viper.Get` request is made. It will apply the following rules. It will check for a environment variable with a name matching the key uppercased and prefixed with the `EnvPrefix` if set. -`SetEnvReplacer` allows you to use a `strings.Replacer` object to rewrite Env +`SetEnvKeyReplacer` allows you to use a `strings.Replacer` object to rewrite Env keys to an extent. This is useful if you want to use `-` or something in your `Get()` calls, but want your environmental variables to use `_` delimiters. An example of using it can be found in `viper_test.go`. diff --git a/viper_test.go b/viper_test.go index 774ca11..7050d5a 100644 --- a/viper_test.go +++ b/viper_test.go @@ -417,7 +417,7 @@ func TestAutoEnvWithPrefix(t *testing.T) { assert.Equal(t, "13", Get("bar")) } -func TestSetEnvReplacer(t *testing.T) { +func TestSetEnvKeyReplacer(t *testing.T) { Reset() AutomaticEnv() From 4dddf7c62e16bce5807744018f5b753bfe21bbd2 Mon Sep 17 00:00:00 2001 From: Jeff Lindsay Date: Thu, 9 Nov 2017 14:57:16 -0600 Subject: [PATCH 20/26] Allow exists util function to take afero.Fs so it can be used with non-deafult instances of Viper (#405) Signed-off-by: Jeff Lindsay --- util.go | 5 +++-- viper.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/util.go b/util.go index 3ebada9..c784dad 100644 --- a/util.go +++ b/util.go @@ -24,6 +24,7 @@ import ( "github.com/hashicorp/hcl" "github.com/magiconair/properties" toml "github.com/pelletier/go-toml" + "github.com/spf13/afero" "github.com/spf13/cast" jww "github.com/spf13/jwalterweatherman" "gopkg.in/yaml.v2" @@ -121,8 +122,8 @@ func absPathify(inPath string) string { } // Check if File / Directory Exists -func exists(path string) (bool, error) { - _, err := v.fs.Stat(path) +func exists(fs afero.Fs, path string) (bool, error) { + _, err := fs.Stat(path) if err == nil { return true, nil } diff --git a/viper.go b/viper.go index 963861a..64f006a 100644 --- a/viper.go +++ b/viper.go @@ -1537,7 +1537,7 @@ func (v *Viper) searchInPath(in string) (filename string) { jww.DEBUG.Println("Searching for config in ", in) for _, ext := range SupportedExts { jww.DEBUG.Println("Checking for", filepath.Join(in, v.configName+"."+ext)) - if b, _ := exists(filepath.Join(in, v.configName+"."+ext)); b { + if b, _ := exists(v.fs, filepath.Join(in, v.configName+"."+ext)); b { jww.DEBUG.Println("Found: ", filepath.Join(in, v.configName+"."+ext)) return filepath.Join(in, v.configName+"."+ext) } From 1a0c4a370c3e8286b835467d2dfcdaf636c3538b Mon Sep 17 00:00:00 2001 From: Adam Sherwood Date: Wed, 6 Dec 2017 20:26:31 -0800 Subject: [PATCH 21/26] Feature/write config (#287) * Added method to write into TOML file. * Added functionality to export configuration based on config type. The feature supports JSON and TOML. * Added method to write into YAML file. * Fixed the issue of incorrect defer and error checking order. The error checking must be first otherwise it will cause panic. * Add WriteConfig methods * Add support for toml * Add shared write function and safe methods * Fix incorrectly modified imports * Remove extra comments * Fix spelling * Make marshal spelling consistent throughout * Add support for remaining configuration types This commit moves a significant portion of the code back to viper.go to facilitate having access to the object when reading the files. The purpose is to add properties to the viper object at read time, so that we can add the comments back to the file when writing. * Add tests for each written file type * Modify test for updated HCL specification * Modify to only support HCL write in Go 1.7 * Revert "Modify to only support HCL write in Go 1.7" This reverts commit 12b34bc4eb92cbf8ebfd56b79519f448607e3e51. * Need to truncate the file before writing * Write all settings including overrides * Use filename variable * Lint remote.go * Fix toml return count error --- remote/remote.go | 5 +- util.go | 62 ------------- viper.go | 223 ++++++++++++++++++++++++++++++++++++++++++++--- viper_test.go | 187 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 401 insertions(+), 76 deletions(-) diff --git a/remote/remote.go b/remote/remote.go index 68a35d6..810d070 100644 --- a/remote/remote.go +++ b/remote/remote.go @@ -8,10 +8,11 @@ package remote import ( "bytes" - "github.com/spf13/viper" - crypt "github.com/xordataexchange/crypt/config" "io" "os" + + "github.com/spf13/viper" + crypt "github.com/xordataexchange/crypt/config" ) type remoteConfigProvider struct{} diff --git a/util.go b/util.go index c784dad..952cad4 100644 --- a/util.go +++ b/util.go @@ -11,23 +11,16 @@ package viper import ( - "bytes" - "encoding/json" "fmt" - "io" "os" "path/filepath" "runtime" "strings" "unicode" - "github.com/hashicorp/hcl" - "github.com/magiconair/properties" - toml "github.com/pelletier/go-toml" "github.com/spf13/afero" "github.com/spf13/cast" jww "github.com/spf13/jwalterweatherman" - "gopkg.in/yaml.v2" ) // ConfigParseError denotes failing to parse configuration file. @@ -153,61 +146,6 @@ func userHomeDir() string { return os.Getenv("HOME") } -func unmarshallConfigReader(in io.Reader, c map[string]interface{}, configType string) error { - buf := new(bytes.Buffer) - buf.ReadFrom(in) - - switch strings.ToLower(configType) { - case "yaml", "yml": - if err := yaml.Unmarshal(buf.Bytes(), &c); err != nil { - return ConfigParseError{err} - } - - case "json": - if err := json.Unmarshal(buf.Bytes(), &c); err != nil { - return ConfigParseError{err} - } - - case "hcl": - obj, err := hcl.Parse(string(buf.Bytes())) - if err != nil { - return ConfigParseError{err} - } - if err = hcl.DecodeObject(&c, obj); err != nil { - return ConfigParseError{err} - } - - case "toml": - tree, err := toml.LoadReader(buf) - if err != nil { - return ConfigParseError{err} - } - tmap := tree.ToMap() - for k, v := range tmap { - c[k] = v - } - - case "properties", "props", "prop": - var p *properties.Properties - var err error - if p, err = properties.Load(buf.Bytes(), properties.UTF8); err != nil { - return ConfigParseError{err} - } - for _, key := range p.Keys() { - value, _ := p.Get(key) - // recursively build nested maps - path := strings.Split(key, ".") - lastKey := strings.ToLower(path[len(path)-1]) - deepestMap := deepSearch(c, path[0:len(path)-1]) - // set innermost value - deepestMap[lastKey] = value - } - } - - insensitiviseMap(c) - return nil -} - func safeMul(a, b uint) uint { c := a * b if a > 1 && b > 1 && c/b != a { diff --git a/viper.go b/viper.go index 64f006a..ad8a037 100644 --- a/viper.go +++ b/viper.go @@ -22,6 +22,7 @@ package viper import ( "bytes" "encoding/csv" + "encoding/json" "fmt" "io" "log" @@ -31,14 +32,30 @@ import ( "strings" "time" + yaml "gopkg.in/yaml.v2" + "github.com/fsnotify/fsnotify" + "github.com/hashicorp/hcl" + "github.com/hashicorp/hcl/hcl/printer" + "github.com/magiconair/properties" "github.com/mitchellh/mapstructure" + toml "github.com/pelletier/go-toml" "github.com/spf13/afero" "github.com/spf13/cast" jww "github.com/spf13/jwalterweatherman" "github.com/spf13/pflag" ) +// ConfigMarshalError happens when failing to marshal the configuration. +type ConfigMarshalError struct { + err error +} + +// Error returns the formatted configuration error. +func (e ConfigMarshalError) Error() string { + return fmt.Sprintf("While marshaling config: %s", e.err.Error()) +} + var v *Viper type RemoteResponse struct { @@ -162,6 +179,10 @@ type Viper struct { aliases map[string]string typeByDefValue bool + // Store read properties on the object so that we can write back in order with comments. + // This will only be used if the configuration read is a properties file. + properties *properties.Properties + onConfigChange func(fsnotify.Event) } @@ -188,7 +209,7 @@ func New() *Viper { // can use it in their testing as well. func Reset() { v = New() - SupportedExts = []string{"json", "toml", "yaml", "yml", "hcl"} + SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl"} SupportedRemoteProviders = []string{"etcd", "consul"} } @@ -1119,6 +1140,7 @@ func (v *Viper) ReadInConfig() error { return UnsupportedConfigError(v.getConfigType()) } + jww.DEBUG.Println("Reading file: ", filename) file, err := afero.ReadFile(v.fs, filename) if err != nil { return err @@ -1178,6 +1200,195 @@ func (v *Viper) MergeConfig(in io.Reader) error { return nil } +// WriteConfig writes the current configuration to a file. +func WriteConfig() error { return v.WriteConfig() } +func (v *Viper) WriteConfig() error { + filename, err := v.getConfigFile() + if err != nil { + return err + } + return v.writeConfig(filename, true) +} + +// 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() + if err != nil { + return err + } + return v.writeConfig(filename, false) +} + +// WriteConfigAs writes current configuration to a given filename. +func WriteConfigAs(filename string) error { return v.WriteConfigAs(filename) } +func (v *Viper) WriteConfigAs(filename string) error { + return v.writeConfig(filename, true) +} + +// SafeWriteConfigAs writes current configuration to a given filename if it does not exist. +func SafeWriteConfigAs(filename string) error { return v.SafeWriteConfigAs(filename) } +func (v *Viper) SafeWriteConfigAs(filename string) error { + return v.writeConfig(filename, false) +} + +func writeConfig(filename string, force bool) error { return v.writeConfig(filename, force) } +func (v *Viper) writeConfig(filename string, force bool) error { + jww.INFO.Println("Attempting to write configuration to file.") + ext := filepath.Ext(filename) + if len(ext) <= 1 { + return fmt.Errorf("Filename: %s requires valid extension.", filename) + } + configType := ext[1:] + if !stringInSlice(configType, SupportedExts) { + return UnsupportedConfigError(configType) + } + if v.config == nil { + v.config = make(map[string]interface{}) + } + var flags int + if force == true { + flags = os.O_CREATE | os.O_TRUNC | os.O_WRONLY + } else { + if _, err := os.Stat(filename); os.IsNotExist(err) { + flags = os.O_WRONLY + } else { + return fmt.Errorf("File: %s exists. Use WriteConfig to overwrite.", filename) + } + } + f, err := v.fs.OpenFile(filename, flags, os.FileMode(0644)) + if err != nil { + return err + } + return v.marshalWriter(f, configType) +} + +// Unmarshal a Reader into a map. +// Should probably be an unexported function. +func unmarshalReader(in io.Reader, c map[string]interface{}) error { + return v.unmarshalReader(in, c) +} +func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error { + buf := new(bytes.Buffer) + buf.ReadFrom(in) + + switch strings.ToLower(v.getConfigType()) { + case "yaml", "yml": + if err := yaml.Unmarshal(buf.Bytes(), &c); err != nil { + return ConfigParseError{err} + } + + case "json": + if err := json.Unmarshal(buf.Bytes(), &c); err != nil { + return ConfigParseError{err} + } + + case "hcl": + obj, err := hcl.Parse(string(buf.Bytes())) + if err != nil { + return ConfigParseError{err} + } + if err = hcl.DecodeObject(&c, obj); err != nil { + return ConfigParseError{err} + } + + case "toml": + tree, err := toml.LoadReader(buf) + if err != nil { + return ConfigParseError{err} + } + tmap := tree.ToMap() + for k, v := range tmap { + c[k] = v + } + + case "properties", "props", "prop": + v.properties = properties.NewProperties() + var err error + if v.properties, err = properties.Load(buf.Bytes(), properties.UTF8); err != nil { + return ConfigParseError{err} + } + for _, key := range v.properties.Keys() { + value, _ := v.properties.Get(key) + // recursively build nested maps + path := strings.Split(key, ".") + lastKey := strings.ToLower(path[len(path)-1]) + deepestMap := deepSearch(c, path[0:len(path)-1]) + // set innermost value + deepestMap[lastKey] = value + } + } + + insensitiviseMap(c) + return nil +} + +// Marshal a map into Writer. +func marshalWriter(f afero.File, configType string) error { + return v.marshalWriter(f, configType) +} +func (v *Viper) marshalWriter(f afero.File, configType string) error { + c := v.AllSettings() + switch configType { + case "json": + b, err := json.MarshalIndent(c, "", " ") + if err != nil { + return ConfigMarshalError{err} + } + _, err = f.WriteString(string(b)) + if err != nil { + return ConfigMarshalError{err} + } + + case "hcl": + b, err := json.Marshal(c) + ast, err := hcl.Parse(string(b)) + if err != nil { + return ConfigMarshalError{err} + } + err = printer.Fprint(f, ast.Node) + if err != nil { + return ConfigMarshalError{err} + } + + case "prop", "props", "properties": + if v.properties == nil { + v.properties = properties.NewProperties() + } + p := v.properties + for _, key := range v.AllKeys() { + _, _, err := p.Set(key, v.GetString(key)) + if err != nil { + return ConfigMarshalError{err} + } + } + _, err := p.WriteComment(f, "#", properties.UTF8) + if err != nil { + return ConfigMarshalError{err} + } + + case "toml": + t, err := toml.TreeFromMap(c) + if err != nil { + return ConfigMarshalError{err} + } + s := t.String() + if _, err := f.WriteString(s); err != nil { + return ConfigMarshalError{err} + } + + case "yaml", "yml": + b, err := yaml.Marshal(c) + if err != nil { + return ConfigMarshalError{err} + } + if _, err = f.WriteString(string(b)); err != nil { + return ConfigMarshalError{err} + } + } + return nil +} + func keyExists(k string, m map[string]interface{}) string { lk := strings.ToLower(k) for mk := range m { @@ -1290,16 +1501,6 @@ func (v *Viper) WatchRemoteConfigOnChannel() error { return v.watchKeyValueConfigOnChannel() } -// Unmarshal a Reader into a map. -// Should probably be an unexported function. -func unmarshalReader(in io.Reader, c map[string]interface{}) error { - return v.unmarshalReader(in, c) -} - -func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error { - return unmarshallConfigReader(in, c, v.getConfigType()) -} - func (v *Viper) insensitiviseMaps() { insensitiviseMap(v.config) insensitiviseMap(v.defaults) diff --git a/viper_test.go b/viper_test.go index 7050d5a..c93480e 100644 --- a/viper_test.go +++ b/viper_test.go @@ -18,6 +18,7 @@ import ( "testing" "time" + "github.com/spf13/afero" "github.com/spf13/cast" "github.com/spf13/pflag" @@ -262,7 +263,7 @@ func TestDefault(t *testing.T) { assert.Equal(t, "leather", Get("clothing.jacket")) } -func TestUnmarshalling(t *testing.T) { +func TestUnmarshaling(t *testing.T) { SetConfigType("yaml") r := bytes.NewReader(yamlExample) @@ -847,6 +848,190 @@ func TestSub(t *testing.T) { assert.Equal(t, (*Viper)(nil), subv) } +var hclWriteExpected = []byte(`"foos" = { + "foo" = { + "key" = 1 + } + + "foo" = { + "key" = 2 + } + + "foo" = { + "key" = 3 + } + + "foo" = { + "key" = 4 + } +} + +"id" = "0001" + +"name" = "Cake" + +"ppu" = 0.55 + +"type" = "donut"`) + +func TestWriteConfigHCL(t *testing.T) { + v := New() + fs := afero.NewMemMapFs() + v.SetFs(fs) + v.SetConfigName("c") + v.SetConfigType("hcl") + err := v.ReadConfig(bytes.NewBuffer(hclExample)) + if err != nil { + t.Fatal(err) + } + if err := v.WriteConfigAs("c.hcl"); err != nil { + t.Fatal(err) + } + read, err := afero.ReadFile(fs, "c.hcl") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, hclWriteExpected, read) +} + +var jsonWriteExpected = []byte(`{ + "batters": { + "batter": [ + { + "type": "Regular" + }, + { + "type": "Chocolate" + }, + { + "type": "Blueberry" + }, + { + "type": "Devil's Food" + } + ] + }, + "id": "0001", + "name": "Cake", + "ppu": 0.55, + "type": "donut" +}`) + +func TestWriteConfigJson(t *testing.T) { + v := New() + fs := afero.NewMemMapFs() + v.SetFs(fs) + v.SetConfigName("c") + v.SetConfigType("json") + err := v.ReadConfig(bytes.NewBuffer(jsonExample)) + if err != nil { + t.Fatal(err) + } + if err := v.WriteConfigAs("c.json"); err != nil { + t.Fatal(err) + } + read, err := afero.ReadFile(fs, "c.json") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, jsonWriteExpected, read) +} + +var propertiesWriteExpected = []byte(`p_id = 0001 +p_type = donut +p_name = Cake +p_ppu = 0.55 +p_batters.batter.type = Regular +`) + +func TestWriteConfigProperties(t *testing.T) { + v := New() + fs := afero.NewMemMapFs() + v.SetFs(fs) + v.SetConfigName("c") + v.SetConfigType("properties") + err := v.ReadConfig(bytes.NewBuffer(propertiesExample)) + if err != nil { + t.Fatal(err) + } + if err := v.WriteConfigAs("c.properties"); err != nil { + t.Fatal(err) + } + read, err := afero.ReadFile(fs, "c.properties") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, propertiesWriteExpected, read) +} + +func TestWriteConfigTOML(t *testing.T) { + fs := afero.NewMemMapFs() + v := New() + v.SetFs(fs) + v.SetConfigName("c") + v.SetConfigType("toml") + err := v.ReadConfig(bytes.NewBuffer(tomlExample)) + if err != nil { + t.Fatal(err) + } + if err := v.WriteConfigAs("c.toml"); err != nil { + t.Fatal(err) + } + + // The TOML String method does not order the contents. + // Therefore, we must read the generated file and compare the data. + v2 := New() + v2.SetFs(fs) + v2.SetConfigName("c") + v2.SetConfigType("toml") + v2.SetConfigFile("c.toml") + err = v2.ReadInConfig() + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, v.GetString("title"), v2.GetString("title")) + assert.Equal(t, v.GetString("owner.bio"), v2.GetString("owner.bio")) + assert.Equal(t, v.GetString("owner.dob"), v2.GetString("owner.dob")) + assert.Equal(t, v.GetString("owner.organization"), v2.GetString("owner.organization")) +} + +var yamlWriteExpected = []byte(`age: 35 +beard: true +clothing: + jacket: leather + pants: + size: large + trousers: denim +eyes: brown +hacker: true +hobbies: +- skateboarding +- snowboarding +- go +name: steve +`) + +func TestWriteConfigYAML(t *testing.T) { + v := New() + fs := afero.NewMemMapFs() + v.SetFs(fs) + v.SetConfigName("c") + v.SetConfigType("yaml") + err := v.ReadConfig(bytes.NewBuffer(yamlExample)) + if err != nil { + t.Fatal(err) + } + if err := v.WriteConfigAs("c.yaml"); err != nil { + t.Fatal(err) + } + read, err := afero.ReadFile(fs, "c.yaml") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, yamlWriteExpected, read) +} + var yamlMergeExampleTgt = []byte(` hello: pop: 37890 From aafc9e6bc7b7bb53ddaa75a5ef49a17d6e654be5 Mon Sep 17 00:00:00 2001 From: Davor Kapsa Date: Wed, 29 Nov 2017 10:51:06 +0100 Subject: [PATCH 22/26] travis: update go versions --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index f1deac3..55960d1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,9 @@ go_import_path: github.com/spf13/viper language: go go: - - 1.7.5 - - 1.8 + - 1.7.x + - 1.8.x + - 1.9.x - tip os: 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 23/26] 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 24/26] 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 25/26] 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 26/26] 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) }