Merge remote-tracking branch 'spf13/master'

This commit is contained in:
Vlad Didenko 2015-11-03 14:14:14 -06:00
commit d027b73aef
4 changed files with 97 additions and 57 deletions

View file

@ -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? ## 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 to work within an application, and can handle all types of configuration needs
and formats. It supports: and formats. It supports:
@ -30,7 +30,7 @@ Viper is here to help with that.
Viper does the following for you: 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 2. Provide a mechanism to set default values for your different
configuration options. configuration options.
3. Provide a mechanism to set override values for options specified through 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. // read from remote config the first time.
err := runtime_viper.ReadRemoteConfig() err := runtime_viper.ReadRemoteConfig()
// marshal config // unmarshal config
runtime_viper.Marshal(&runtime_conf) runtime_viper.Unmarshal(&runtime_conf)
// open a goroutine to wath remote changes forever // open a goroutine to wath remote changes forever
go func(){ go func(){
@ -287,9 +287,9 @@ go func(){
continue 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 // 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" 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. etc.
There are two methods to do this: There are two methods to do this:
* `Marshal(rawVal interface{}) : error` * `Unmarshal(rawVal interface{}) : error`
* `MarshalKey(key string, rawVal interface{}) : error` * `UnmarshalKey(key string, rawVal interface{}) : error`
Example: Example:
@ -409,7 +409,7 @@ type config struct {
var C config var C config
err := Marshal(&C) err := Unmarshal(&C)
if err != nil { if err != nil {
t.Fatalf("unable to decode into struct, %v", err) t.Fatalf("unable to decode into struct, %v", err)
} }

View file

@ -129,7 +129,7 @@ func findCWD() (string, error) {
return path, nil 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 := new(bytes.Buffer)
buf.ReadFrom(in) buf.ReadFrom(in)

View file

@ -143,13 +143,14 @@ type Viper struct {
automaticEnvApplied bool automaticEnvApplied bool
envKeyReplacer *strings.Replacer envKeyReplacer *strings.Replacer
config map[string]interface{} config map[string]interface{}
override map[string]interface{} override map[string]interface{}
defaults map[string]interface{} defaults map[string]interface{}
kvstore map[string]interface{} kvstore map[string]interface{}
pflags map[string]*pflag.Flag pflags map[string]*pflag.Flag
env map[string]string env map[string]string
aliases map[string]string aliases map[string]string
typeByDefValue bool
} }
// Returns an initialized Viper instance. // Returns an initialized Viper instance.
@ -164,6 +165,7 @@ func New() *Viper {
v.pflags = make(map[string]*pflag.Flag) v.pflags = make(map[string]*pflag.Flag)
v.env = make(map[string]string) v.env = make(map[string]string)
v.aliases = make(map[string]string) v.aliases = make(map[string]string)
v.typeByDefValue = false
return v 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 // Viper is essentially repository for configurations
// Get can retrieve any value given the key to use // Get can retrieve any value given the key to use
// Get has the behavior of returning the value associated with the first // 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{} { func (v *Viper) Get(key string) interface{} {
path := strings.Split(key, v.keyDelim) path := strings.Split(key, v.keyDelim)
val := v.find(strings.ToLower(key)) lcaseKey := strings.ToLower(key)
val := v.find(lcaseKey)
if val == nil { if val == nil {
source := v.find(path[0]) 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: case bool:
return cast.ToBool(val) return cast.ToBool(val)
case string: case string:
@ -406,7 +440,7 @@ func (v *Viper) Get(key string) interface{} {
case time.Duration: case time.Duration:
return cast.ToDuration(val) return cast.ToDuration(val)
case []string: case []string:
return val return cast.ToStringSlice(val)
} }
return val return val
} }
@ -465,6 +499,12 @@ func (v *Viper) GetStringMapString(key string) map[string]string {
return cast.ToStringMapString(v.Get(key)) 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 // Returns the size of the value associated with the given key
// in bytes. // in bytes.
func GetSizeInBytes(key string) uint { return v.GetSizeInBytes(key) } func GetSizeInBytes(key string) uint { return v.GetSizeInBytes(key) }
@ -473,16 +513,16 @@ func (v *Viper) GetSizeInBytes(key string) uint {
return parseSizeInBytes(sizeStr) return parseSizeInBytes(sizeStr)
} }
// Takes a single key and marshals it into a Struct // Takes a single key and unmarshals it into a Struct
func MarshalKey(key string, rawVal interface{}) error { return v.MarshalKey(key, rawVal) } func UnmarshalKey(key string, rawVal interface{}) error { return v.UnmarshalKey(key, rawVal) }
func (v *Viper) MarshalKey(key string, rawVal interface{}) error { func (v *Viper) UnmarshalKey(key string, rawVal interface{}) error {
return mapstructure.Decode(v.Get(key), rawVal) 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. // on the fields of the structure are properly set.
func Marshal(rawVal interface{}) error { return v.Marshal(rawVal) } func Unmarshal(rawVal interface{}) error { return v.Unmarshal(rawVal) }
func (v *Viper) Marshal(rawVal interface{}) error { func (v *Viper) Unmarshal(rawVal interface{}) error {
err := mapstructure.WeakDecode(v.AllSettings(), rawVal) err := mapstructure.WeakDecode(v.AllSettings(), rawVal)
if err != nil { if err != nil {
@ -748,19 +788,19 @@ func (v *Viper) ReadInConfig() error {
v.config = make(map[string]interface{}) 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 ReadConfig(in io.Reader) error { return v.ReadConfig(in) }
func (v *Viper) ReadConfig(in io.Reader) error { func (v *Viper) ReadConfig(in io.Reader) error {
v.config = make(map[string]interface{}) 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 ReadBufConfig(buf *bytes.Buffer) error { return v.ReadBufConfig(buf) }
// func (v *Viper) ReadBufConfig(buf *bytes.Buffer) error { // func (v *Viper) ReadBufConfig(buf *bytes.Buffer) error {
// v.config = make(map[string]interface{}) // 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 // Attempts to get configuration from a remote source
@ -783,14 +823,14 @@ func (v *Viper) WatchRemoteConfig() error {
return nil return nil
} }
// Marshall a Reader into a map // Unmarshall a Reader into a map
// Should probably be an unexported function // Should probably be an unexported function
func marshalReader(in io.Reader, c map[string]interface{}) error { func unmarshalReader(in io.Reader, c map[string]interface{}) error {
return v.marshalReader(in, c) return v.unmarshalReader(in, c)
} }
func (v *Viper) marshalReader(in io.Reader, c map[string]interface{}) error { func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
return marshallConfigReader(in, c, v.getConfigType()) return unmarshallConfigReader(in, c, v.getConfigType())
} }
func (v *Viper) insensitiviseMaps() { func (v *Viper) insensitiviseMaps() {
@ -823,7 +863,7 @@ func (v *Viper) getRemoteConfig(provider *defaultRemoteProvider) (map[string]int
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = v.marshalReader(reader, v.kvstore) err = v.unmarshalReader(reader, v.kvstore)
return v.kvstore, err return v.kvstore, err
} }
@ -845,7 +885,7 @@ func (v *Viper) watchRemoteConfig(provider *defaultRemoteProvider) (map[string]i
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = v.marshalReader(reader, v.kvstore) err = v.unmarshalReader(reader, v.kvstore)
return v.kvstore, err return v.kvstore, err
} }

View file

@ -78,23 +78,23 @@ func initConfigs() {
Reset() Reset()
SetConfigType("yaml") SetConfigType("yaml")
r := bytes.NewReader(yamlExample) r := bytes.NewReader(yamlExample)
marshalReader(r, v.config) unmarshalReader(r, v.config)
SetConfigType("json") SetConfigType("json")
r = bytes.NewReader(jsonExample) r = bytes.NewReader(jsonExample)
marshalReader(r, v.config) unmarshalReader(r, v.config)
SetConfigType("properties") SetConfigType("properties")
r = bytes.NewReader(propertiesExample) r = bytes.NewReader(propertiesExample)
marshalReader(r, v.config) unmarshalReader(r, v.config)
SetConfigType("toml") SetConfigType("toml")
r = bytes.NewReader(tomlExample) r = bytes.NewReader(tomlExample)
marshalReader(r, v.config) unmarshalReader(r, v.config)
SetConfigType("json") SetConfigType("json")
remote := bytes.NewReader(remoteExample) remote := bytes.NewReader(remoteExample)
marshalReader(remote, v.kvstore) unmarshalReader(remote, v.kvstore)
} }
func initYAML() { func initYAML() {
@ -102,7 +102,7 @@ func initYAML() {
SetConfigType("yaml") SetConfigType("yaml")
r := bytes.NewReader(yamlExample) r := bytes.NewReader(yamlExample)
marshalReader(r, v.config) unmarshalReader(r, v.config)
} }
func initJSON() { func initJSON() {
@ -110,7 +110,7 @@ func initJSON() {
SetConfigType("json") SetConfigType("json")
r := bytes.NewReader(jsonExample) r := bytes.NewReader(jsonExample)
marshalReader(r, v.config) unmarshalReader(r, v.config)
} }
func initProperties() { func initProperties() {
@ -118,7 +118,7 @@ func initProperties() {
SetConfigType("properties") SetConfigType("properties")
r := bytes.NewReader(propertiesExample) r := bytes.NewReader(propertiesExample)
marshalReader(r, v.config) unmarshalReader(r, v.config)
} }
func initTOML() { func initTOML() {
@ -126,7 +126,7 @@ func initTOML() {
SetConfigType("toml") SetConfigType("toml")
r := bytes.NewReader(tomlExample) r := bytes.NewReader(tomlExample)
marshalReader(r, v.config) unmarshalReader(r, v.config)
} }
// make directories for testing // make directories for testing
@ -201,11 +201,11 @@ func TestDefault(t *testing.T) {
assert.Equal(t, 45, Get("age")) assert.Equal(t, 45, Get("age"))
} }
func TestMarshalling(t *testing.T) { func TestUnmarshalling(t *testing.T) {
SetConfigType("yaml") SetConfigType("yaml")
r := bytes.NewReader(yamlExample) r := bytes.NewReader(yamlExample)
marshalReader(r, v.config) unmarshalReader(r, v.config)
assert.True(t, InConfig("name")) assert.True(t, InConfig("name"))
assert.False(t, InConfig("state")) assert.False(t, InConfig("state"))
assert.Equal(t, "steve", Get("name")) assert.Equal(t, "steve", Get("name"))
@ -266,7 +266,7 @@ func TestRemotePrecedence(t *testing.T) {
remote := bytes.NewReader(remoteExample) remote := bytes.NewReader(remoteExample)
assert.Equal(t, "0001", Get("id")) assert.Equal(t, "0001", Get("id"))
marshalReader(remote, v.kvstore) unmarshalReader(remote, v.kvstore)
assert.Equal(t, "0001", Get("id")) assert.Equal(t, "0001", Get("id"))
assert.NotEqual(t, "cronut", Get("type")) assert.NotEqual(t, "cronut", Get("type"))
assert.Equal(t, "remote", Get("newkey")) assert.Equal(t, "remote", Get("newkey"))
@ -378,7 +378,7 @@ func TestRecursiveAliases(t *testing.T) {
RegisterAlias("Roo", "baz") RegisterAlias("Roo", "baz")
} }
func TestMarshal(t *testing.T) { func TestUnmarshal(t *testing.T) {
SetDefault("port", 1313) SetDefault("port", 1313)
Set("name", "Steve") Set("name", "Steve")
@ -389,7 +389,7 @@ func TestMarshal(t *testing.T) {
var C config var C config
err := Marshal(&C) err := Unmarshal(&C)
if err != nil { if err != nil {
t.Fatalf("unable to decode into struct, %v", err) 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}) assert.Equal(t, &C, &config{Name: "Steve", Port: 1313})
Set("port", 1234) Set("port", 1234)
err = Marshal(&C) err = Unmarshal(&C)
if err != nil { if err != nil {
t.Fatalf("unable to decode into struct, %v", err) t.Fatalf("unable to decode into struct, %v", err)
} }