diff --git a/README.md b/README.md index 47b1798..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: @@ -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 a65a33a..5afed24 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 } @@ -465,6 +499,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) } @@ -473,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 { @@ -748,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 @@ -783,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() { @@ -823,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 } @@ -845,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) }