From b9316c3299b95d429c49f2f67d5ec824cd87c95e Mon Sep 17 00:00:00 2001 From: jackspirou Date: Thu, 30 Jul 2015 15:27:34 -0500 Subject: [PATCH 1/9] adding GetStringMapStringSlice method --- viper.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/viper.go b/viper.go index a65a33a..f68226b 100644 --- a/viper.go +++ b/viper.go @@ -465,6 +465,12 @@ func (v *Viper) GetStringMapString(key string) map[string]string { return cast.ToStringMapString(v.Get(key)) } +// Returns the value associated with the key as a map to a slice of strings +func GetStringMapStringSlice(key string) map[string]string { return v.GetStringMapStringSlice(key) } +func (v *Viper) GetStringMapStringSlice(key string) map[string]string { + return cast.ToStringMapStringSlice(v.Get(key)) +} + // Returns the size of the value associated with the given key // in bytes. func GetSizeInBytes(key string) uint { return v.GetSizeInBytes(key) } From 0a12778a8c4105a49084fe17833c25de12fdf9c0 Mon Sep 17 00:00:00 2001 From: jackspirou Date: Thu, 30 Jul 2015 15:38:59 -0500 Subject: [PATCH 2/9] using my own version of github.com/spf13/cast for now --- viper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper.go b/viper.go index f68226b..639cf3b 100644 --- a/viper.go +++ b/viper.go @@ -30,9 +30,9 @@ import ( "strings" "time" + "github.com/jackspirou/cast" "github.com/kr/pretty" "github.com/mitchellh/mapstructure" - "github.com/spf13/cast" jww "github.com/spf13/jwalterweatherman" "github.com/spf13/pflag" ) From cc1c9a82a5b88b5d08a4cb9a097eeaf76aff12a5 Mon Sep 17 00:00:00 2001 From: jackspirou Date: Thu, 30 Jul 2015 15:43:18 -0500 Subject: [PATCH 3/9] typo: slice type needed --- viper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper.go b/viper.go index 639cf3b..ce86ce3 100644 --- a/viper.go +++ b/viper.go @@ -466,7 +466,7 @@ func (v *Viper) GetStringMapString(key string) map[string]string { } // Returns the value associated with the key as a map to a slice of strings -func GetStringMapStringSlice(key string) map[string]string { return v.GetStringMapStringSlice(key) } +func GetStringMapStringSlice(key string) map[string][]string { return v.GetStringMapStringSlice(key) } func (v *Viper) GetStringMapStringSlice(key string) map[string]string { return cast.ToStringMapStringSlice(v.Get(key)) } From d028fd65ba5c356e75b72f496f6a8b41fa2a2506 Mon Sep 17 00:00:00 2001 From: jackspirou Date: Thu, 30 Jul 2015 15:44:12 -0500 Subject: [PATCH 4/9] changing import statements back --- viper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper.go b/viper.go index ce86ce3..9eba0cd 100644 --- a/viper.go +++ b/viper.go @@ -30,9 +30,9 @@ import ( "strings" "time" - "github.com/jackspirou/cast" "github.com/kr/pretty" "github.com/mitchellh/mapstructure" + "github.com/spf13/cast" jww "github.com/spf13/jwalterweatherman" "github.com/spf13/pflag" ) From 09ba0a69542e17a89deef22ab2ccf130abc49960 Mon Sep 17 00:00:00 2001 From: jackspirou Date: Thu, 30 Jul 2015 15:46:38 -0500 Subject: [PATCH 5/9] fixing second slice type needed --- viper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper.go b/viper.go index 9eba0cd..47d027f 100644 --- a/viper.go +++ b/viper.go @@ -467,7 +467,7 @@ func (v *Viper) GetStringMapString(key string) map[string]string { // Returns the value associated with the key as a map to a slice of strings func GetStringMapStringSlice(key string) map[string][]string { return v.GetStringMapStringSlice(key) } -func (v *Viper) GetStringMapStringSlice(key string) map[string]string { +func (v *Viper) GetStringMapStringSlice(key string) map[string][]string { return cast.ToStringMapStringSlice(v.Get(key)) } From 3c0ff861e3d9906ecc4ebfb7e41557d44d3e277b Mon Sep 17 00:00:00 2001 From: jackspirou Date: Mon, 3 Aug 2015 11:42:26 -0500 Subject: [PATCH 6/9] running tests again --- viper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper.go b/viper.go index 47d027f..5e01aa5 100644 --- a/viper.go +++ b/viper.go @@ -465,7 +465,7 @@ func (v *Viper) GetStringMapString(key string) map[string]string { return cast.ToStringMapString(v.Get(key)) } -// Returns the value associated with the key as a map to a slice of strings +// Returns the value associated with the key as a map to a slice of strings. func GetStringMapStringSlice(key string) map[string][]string { return v.GetStringMapStringSlice(key) } func (v *Viper) GetStringMapStringSlice(key string) map[string][]string { return cast.ToStringMapStringSlice(v.Get(key)) From 0a4a93b685e891150e26386f3be5f505cfd1a6ee Mon Sep 17 00:00:00 2001 From: akutz Date: Sat, 29 Aug 2015 10:54:20 -0500 Subject: [PATCH 7/9] [110] Default Values Specify Type This patch adds a feature, if enabled, will infer a value's type from its default value no matter from where else the value is set. This is particularly important when working with environment variables. For example: package main import ( "fmt" "os" "github.com/spf13/viper" ) func print(name string, val interface{}) { fmt.Printf("%-15[1]s%-15[2]T%[2]v\n", name, val) } func main() { viper.BindEnv("mykey", "MYPREFIX_MYKEY") viper.SetDefault("mykey", []string{}) os.Setenv("MYPREFIX_MYKEY", "a b c") v1 := viper.GetStringSlice("mykey") v2 := viper.Get("mykey") print("v1", v1) print("v2", v2) } When this program is executed the following is emitted: [0]akutz@pax:ex$ ./ex1 v1 []string [a b c] v2 string a b c [0]akutz@pax:ex$ You may wonder, why is this important? Just use the GetStringSlice function. Well, it *becomes* important when dealing with marshaling. If we update the above program to this: package main import ( "fmt" "os" "github.com/spf13/viper" ) type Data struct { MyKey []string } func print(name string, val interface{}) { fmt.Printf("%-15[1]s%-15[2]T%[2]v\n", name, val) } func main() { viper.BindEnv("mykey", "MYPREFIX_MYKEY") viper.SetDefault("mykey", []string{}) os.Setenv("MYPREFIX_MYKEY", "a b c") v1 := viper.GetStringSlice("mykey") v2 := viper.Get("mykey") print("v1", v1) print("v2", v2) d := &Data{} viper.Marshal(d) print("d.MyKey", d.MyKey) } Now we can see the issue when we execute the updated program: [0]akutz@pax:ex$ ./ex2 v1 []string [a b c] v2 string a b c d.MyKey []string [] [0]akutz@pax:ex$ The marshalled data structure's field MyKey is empty when in fact it should have a string slice equal to, in value, []string {"a", "b", "c"}. The problem is that viper's Marshal function calls AllSettings which ultimately uses the Get function. The Get function does try to infer the value's type, but it does so using the type of the value retrieved using this logic: Get has the behavior of returning the value associated with the first place from where it is set. Viper will check in the following order: * override * flag * env * config file * key/value store * default While the above order is the one we want when retrieving the values, this patch enables users to decide if it's the order they want to be used when inferring a value's type. To that end the function SetTypeByDefaultValue is introduced. When SetTypeByDefaultValue(true) is called, a call to the Get function will now first check a key's default value, if set, when inferring a value's type. This is demonstrated using a modified version of the same program above: package main import ( "fmt" "os" "github.com/spf13/viper" ) type Data struct { MyKey []string } func print(name string, val interface{}) { fmt.Printf("%-15[1]s%-15[2]T%[2]v\n", name, val) } func main() { viper.BindEnv("mykey", "MYPREFIX_MYKEY") viper.SetDefault("mykey", []string{}) os.Setenv("MYPREFIX_MYKEY", "a b c") v1 := viper.GetStringSlice("mykey") v2 := viper.Get("mykey") print("v1", v1) print("v2", v2) d1 := &Data{} viper.Marshal(d1) print("d1.MyKey", d1.MyKey) viper.SetTypeByDefaultValue(true) d2 := &Data{} viper.Marshal(d2) print("d2.MyKey", d2.MyKey) } Now the following is emitted: [0]akutz@pax:ex$ ./ex3 v1 []string [a b c] v2 string a b c d1.MyKey []string [] d2.MyKey []string [a b c] [0]akutz@pax:ex$ --- viper.go | 54 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/viper.go b/viper.go index 5e01aa5..e249430 100644 --- a/viper.go +++ b/viper.go @@ -143,13 +143,14 @@ type Viper struct { automaticEnvApplied bool envKeyReplacer *strings.Replacer - config map[string]interface{} - override map[string]interface{} - defaults map[string]interface{} - kvstore map[string]interface{} - pflags map[string]*pflag.Flag - env map[string]string - aliases map[string]string + config map[string]interface{} + override map[string]interface{} + defaults map[string]interface{} + kvstore map[string]interface{} + pflags map[string]*pflag.Flag + env map[string]string + aliases map[string]string + typeByDefValue bool } // Returns an initialized Viper instance. @@ -164,6 +165,7 @@ func New() *Viper { v.pflags = make(map[string]*pflag.Flag) v.env = make(map[string]string) v.aliases = make(map[string]string) + v.typeByDefValue = false return v } @@ -368,6 +370,25 @@ func (v *Viper) searchMap(source map[string]interface{}, path []string) interfac } } +// SetTypeByDefaultValue enables or disables the inference of a key value's +// type when the Get function is used based upon a key's default value as +// opposed to the value returned based on the normal fetch logic. +// +// For example, if a key has a default value of []string{} and the same key +// is set via an environment variable to "a b c", a call to the Get function +// would return a string slice for the key if the key's type is inferred by +// the default value and the Get function would return: +// +// []string {"a", "b", "c"} +// +// Otherwise the Get function would return: +// +// "a b c" +func SetTypeByDefaultValue(enable bool) { v.SetTypeByDefaultValue(enable) } +func (v *Viper) SetTypeByDefaultValue(enable bool) { + v.typeByDefValue = enable +} + // Viper is essentially repository for configurations // Get can retrieve any value given the key to use // Get has the behavior of returning the value associated with the first @@ -379,7 +400,8 @@ func Get(key string) interface{} { return v.Get(key) } func (v *Viper) Get(key string) interface{} { path := strings.Split(key, v.keyDelim) - val := v.find(strings.ToLower(key)) + lcaseKey := strings.ToLower(key) + val := v.find(lcaseKey) if val == nil { source := v.find(path[0]) @@ -392,7 +414,19 @@ func (v *Viper) Get(key string) interface{} { } } - switch val.(type) { + var valType interface{} + if !v.typeByDefValue { + valType = val + } else { + defVal, defExists := v.defaults[lcaseKey] + if defExists { + valType = defVal + } else { + valType = val + } + } + + switch valType.(type) { case bool: return cast.ToBool(val) case string: @@ -406,7 +440,7 @@ func (v *Viper) Get(key string) interface{} { case time.Duration: return cast.ToDuration(val) case []string: - return val + return cast.ToStringSlice(val) } return val } From 1967d93db724f4a5c0e101307e96d82ff520a067 Mon Sep 17 00:00:00 2001 From: Matt Surabian Date: Sun, 23 Aug 2015 23:40:56 -0400 Subject: [PATCH 8/9] Fixed #36: Changed Marshal to Unmarshal throughout. --- README.md | 20 ++++++++++---------- util.go | 2 +- viper.go | 32 ++++++++++++++++---------------- viper_test.go | 30 +++++++++++++++--------------- 4 files changed, 42 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 47b1798..1e06e32 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Viper is here to help with that. Viper does the following for you: -1. Find, load, and marshal a configuration file in JSON, TOML, or YAML. +1. Find, load, and unmarshal a configuration file in JSON, TOML, or YAML. 2. Provide a mechanism to set default values for your different configuration options. 3. Provide a mechanism to set override values for options specified through @@ -272,8 +272,8 @@ runtime_viper.SetConfigType("yaml") // because there is no file extension in a s // read from remote config the first time. err := runtime_viper.ReadRemoteConfig() -// marshal config -runtime_viper.Marshal(&runtime_conf) +// unmarshal config +runtime_viper.Unmarshal(&runtime_conf) // open a goroutine to wath remote changes forever go func(){ @@ -287,9 +287,9 @@ go func(){ continue } - // marshal new config into our runtime config struct. you can also use channel + // unmarshal new config into our runtime config struct. you can also use channel // to implement a signal to notify the system of the changes - runtime_viper.Marshal(&runtime_conf) + runtime_viper.Unmarshal(&runtime_conf) } }() ``` @@ -389,15 +389,15 @@ will be returned instead. E.g. GetString("datastore.metric.host") //returns "0.0.0.0" ``` -### Marshaling +### Unmarshaling -You also have the option of Marshaling all or a specific value to a struct, map, +You also have the option of Unmarshaling all or a specific value to a struct, map, etc. There are two methods to do this: - * `Marshal(rawVal interface{}) : error` - * `MarshalKey(key string, rawVal interface{}) : error` + * `Unmarshal(rawVal interface{}) : error` + * `UnmarshalKey(key string, rawVal interface{}) : error` Example: @@ -409,7 +409,7 @@ type config struct { var C config -err := Marshal(&C) +err := Unmarshal(&C) if err != nil { t.Fatalf("unable to decode into struct, %v", err) } diff --git a/util.go b/util.go index a605e62..dc60a44 100644 --- a/util.go +++ b/util.go @@ -129,7 +129,7 @@ func findCWD() (string, error) { return path, nil } -func marshallConfigReader(in io.Reader, c map[string]interface{}, configType string) error { +func unmarshallConfigReader(in io.Reader, c map[string]interface{}, configType string) error { buf := new(bytes.Buffer) buf.ReadFrom(in) diff --git a/viper.go b/viper.go index e249430..5afed24 100644 --- a/viper.go +++ b/viper.go @@ -513,16 +513,16 @@ func (v *Viper) GetSizeInBytes(key string) uint { return parseSizeInBytes(sizeStr) } -// 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 { +// 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) } -// Marshals the config into a Struct. Make sure that the tags +// Unmarshals the config into a Struct. Make sure that the tags // on the fields of the structure are properly set. -func Marshal(rawVal interface{}) error { return v.Marshal(rawVal) } -func (v *Viper) Marshal(rawVal interface{}) error { +func Unmarshal(rawVal interface{}) error { return v.Unmarshal(rawVal) } +func (v *Viper) Unmarshal(rawVal interface{}) error { err := mapstructure.WeakDecode(v.AllSettings(), rawVal) if err != nil { @@ -788,19 +788,19 @@ func (v *Viper) ReadInConfig() error { v.config = make(map[string]interface{}) - return v.marshalReader(bytes.NewReader(file), v.config) + return v.unmarshalReader(bytes.NewReader(file), v.config) } func ReadConfig(in io.Reader) error { return v.ReadConfig(in) } func (v *Viper) ReadConfig(in io.Reader) error { v.config = make(map[string]interface{}) - return v.marshalReader(in, v.config) + return v.unmarshalReader(in, v.config) } // func ReadBufConfig(buf *bytes.Buffer) error { return v.ReadBufConfig(buf) } // func (v *Viper) ReadBufConfig(buf *bytes.Buffer) error { // v.config = make(map[string]interface{}) -// return v.marshalReader(buf, v.config) +// return v.unmarshalReader(buf, v.config) // } // Attempts to get configuration from a remote source @@ -823,14 +823,14 @@ func (v *Viper) WatchRemoteConfig() error { return nil } -// Marshall a Reader into a map +// Unmarshall a Reader into a map // Should probably be an unexported function -func marshalReader(in io.Reader, c map[string]interface{}) error { - return v.marshalReader(in, c) +func unmarshalReader(in io.Reader, c map[string]interface{}) error { + return v.unmarshalReader(in, c) } -func (v *Viper) marshalReader(in io.Reader, c map[string]interface{}) error { - return marshallConfigReader(in, c, v.getConfigType()) +func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error { + return unmarshallConfigReader(in, c, v.getConfigType()) } func (v *Viper) insensitiviseMaps() { @@ -863,7 +863,7 @@ func (v *Viper) getRemoteConfig(provider *defaultRemoteProvider) (map[string]int if err != nil { return nil, err } - err = v.marshalReader(reader, v.kvstore) + err = v.unmarshalReader(reader, v.kvstore) return v.kvstore, err } @@ -885,7 +885,7 @@ func (v *Viper) watchRemoteConfig(provider *defaultRemoteProvider) (map[string]i if err != nil { return nil, err } - err = v.marshalReader(reader, v.kvstore) + err = v.unmarshalReader(reader, v.kvstore) return v.kvstore, err } diff --git a/viper_test.go b/viper_test.go index a5f2c52..6b9db6e 100644 --- a/viper_test.go +++ b/viper_test.go @@ -78,23 +78,23 @@ func initConfigs() { Reset() SetConfigType("yaml") r := bytes.NewReader(yamlExample) - marshalReader(r, v.config) + unmarshalReader(r, v.config) SetConfigType("json") r = bytes.NewReader(jsonExample) - marshalReader(r, v.config) + unmarshalReader(r, v.config) SetConfigType("properties") r = bytes.NewReader(propertiesExample) - marshalReader(r, v.config) + unmarshalReader(r, v.config) SetConfigType("toml") r = bytes.NewReader(tomlExample) - marshalReader(r, v.config) + unmarshalReader(r, v.config) SetConfigType("json") remote := bytes.NewReader(remoteExample) - marshalReader(remote, v.kvstore) + unmarshalReader(remote, v.kvstore) } func initYAML() { @@ -102,7 +102,7 @@ func initYAML() { SetConfigType("yaml") r := bytes.NewReader(yamlExample) - marshalReader(r, v.config) + unmarshalReader(r, v.config) } func initJSON() { @@ -110,7 +110,7 @@ func initJSON() { SetConfigType("json") r := bytes.NewReader(jsonExample) - marshalReader(r, v.config) + unmarshalReader(r, v.config) } func initProperties() { @@ -118,7 +118,7 @@ func initProperties() { SetConfigType("properties") r := bytes.NewReader(propertiesExample) - marshalReader(r, v.config) + unmarshalReader(r, v.config) } func initTOML() { @@ -126,7 +126,7 @@ func initTOML() { SetConfigType("toml") r := bytes.NewReader(tomlExample) - marshalReader(r, v.config) + unmarshalReader(r, v.config) } // make directories for testing @@ -201,11 +201,11 @@ func TestDefault(t *testing.T) { assert.Equal(t, 45, Get("age")) } -func TestMarshalling(t *testing.T) { +func TestUnmarshalling(t *testing.T) { SetConfigType("yaml") r := bytes.NewReader(yamlExample) - marshalReader(r, v.config) + unmarshalReader(r, v.config) assert.True(t, InConfig("name")) assert.False(t, InConfig("state")) assert.Equal(t, "steve", Get("name")) @@ -266,7 +266,7 @@ func TestRemotePrecedence(t *testing.T) { remote := bytes.NewReader(remoteExample) assert.Equal(t, "0001", Get("id")) - marshalReader(remote, v.kvstore) + unmarshalReader(remote, v.kvstore) assert.Equal(t, "0001", Get("id")) assert.NotEqual(t, "cronut", Get("type")) assert.Equal(t, "remote", Get("newkey")) @@ -378,7 +378,7 @@ func TestRecursiveAliases(t *testing.T) { RegisterAlias("Roo", "baz") } -func TestMarshal(t *testing.T) { +func TestUnmarshal(t *testing.T) { SetDefault("port", 1313) Set("name", "Steve") @@ -389,7 +389,7 @@ func TestMarshal(t *testing.T) { var C config - err := Marshal(&C) + err := Unmarshal(&C) if err != nil { t.Fatalf("unable to decode into struct, %v", err) } @@ -397,7 +397,7 @@ func TestMarshal(t *testing.T) { assert.Equal(t, &C, &config{Name: "Steve", Port: 1313}) Set("port", 1234) - err = Marshal(&C) + err = Unmarshal(&C) if err != nil { t.Fatalf("unable to decode into struct, %v", err) } From d2f75b4befcf56b1627807acc5f10fd19e9784cf Mon Sep 17 00:00:00 2001 From: Steve Francia Date: Mon, 2 Nov 2015 11:07:43 -0500 Subject: [PATCH 9/9] Add logo to readme --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1e06e32..754588f 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -viper [![Build Status](https://travis-ci.org/spf13/viper.svg)](https://travis-ci.org/spf13/viper) -===== +![viper logo](https://cloud.githubusercontent.com/assets/173412/10886745/998df88a-8151-11e5-9448-4736db51020d.png) -[![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) +Go configuration with fangs! + + [![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) -Go configuration with fangs ## What is Viper? -Viper is a complete configuration solution for go applications. 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: