Merge upstream

This commit is contained in:
Jonathan Anderson 2016-10-12 23:14:15 -02:30
commit 4339546753
9 changed files with 1037 additions and 333 deletions

1
.gitignore vendored
View file

@ -21,3 +21,4 @@ _testmain.go
*.exe *.exe
*.test *.test
*.bench

View file

@ -1,9 +1,27 @@
go_import_path: github.com/spf13/viper
language: go language: go
go: go:
- 1.3 - 1.5.4
- release - 1.6.3
- 1.7
- tip - tip
os:
- linux
- osx
matrix:
allow_failures:
- go: tip
fast_finish: true
script: script:
- go install ./...
- go test -v ./... - go test -v ./...
after_success:
- go get -u -d github.com/spf13/hugo
- cd $GOPATH/src/github.com/spf13/hugo && make && ./hugo -s docs && cd -
sudo: false sudo: false

View file

@ -2,7 +2,17 @@
Go configuration with fangs! 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) 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)
* [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack)
* [Docker Notary](https://github.com/docker/Notary)
* [BloomApi](https://www.bloomapi.com/)
* [doctl](https://github.com/digitalocean/doctl)
[![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)
## What is Viper? ## What is Viper?
@ -12,7 +22,7 @@ to work within an application, and can handle all types of configuration needs
and formats. It supports: and formats. It supports:
* setting defaults * setting defaults
* reading from JSON, TOML, YAML and HCL config files * reading from JSON, TOML, YAML, HCL, and Java properties config files
* live watching and re-reading of config files (optional) * live watching and re-reading of config files (optional)
* reading from environment variables * reading from environment variables
* reading from remote config systems (etcd or Consul), and watching changes * reading from remote config systems (etcd or Consul), and watching changes
@ -31,7 +41,7 @@ Viper is here to help with that.
Viper does the following for you: Viper does the following for you:
1. Find, load, and unmarshal a configuration file in JSON, TOML, YAML or HCL. 1. Find, load, and unmarshal a configuration file in JSON, TOML, YAML, HCL, or Java properties formats.
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
@ -72,7 +82,7 @@ viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "cat
### Reading Config Files ### Reading Config Files
Viper requires minimal configuration so it knows where to look for config files. Viper requires minimal configuration so it knows where to look for config files.
Viper supports JSON, TOML, YAML and HCL files. Viper can search multiple paths, but Viper supports JSON, TOML, YAML, HCL, and Java Properties files. Viper can search multiple paths, but
currently a single Viper instance only supports a single configuration file. currently a single Viper instance only supports a single configuration file.
Viper does not default to any configuration search paths leaving defaults decision Viper does not default to any configuration search paths leaving defaults decision
to an application. to an application.
@ -267,10 +277,10 @@ Viper provides two Go interfaces to bind other flag systems if you don't use `Pf
```go ```go
type myFlag struct {} type myFlag struct {}
func (f myFlag) IsChanged() { return false } func (f myFlag) HasChanged() bool { return false }
func (f myFlag) Name() { return "my-flag-name" } func (f myFlag) Name() string { return "my-flag-name" }
func (f myFlag) ValueString() { return "my-flag-value" } func (f myFlag) ValueString() string { return "my-flag-value" }
func (f myFlag) ValueType() { return "string" } func (f myFlag) ValueType() string { return "string" }
``` ```
Once your flag implements this interface, you can simply tell Viper to bind it: Once your flag implements this interface, you can simply tell Viper to bind it:
@ -343,7 +353,7 @@ how to use Consul.
```go ```go
viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json") viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json")
viper.SetConfigType("json") // because there is no file extension in a stream of bytes viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop"
err := viper.ReadRemoteConfig() err := viper.ReadRemoteConfig()
``` ```
@ -351,7 +361,7 @@ err := viper.ReadRemoteConfig()
```go ```go
viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg") viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg")
viper.SetConfigType("json") // because there is no file extension in a stream of bytes viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop"
err := viper.ReadRemoteConfig() err := viper.ReadRemoteConfig()
``` ```
@ -362,7 +372,7 @@ err := viper.ReadRemoteConfig()
var runtime_viper = viper.New() var runtime_viper = viper.New()
runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml") runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml")
runtime_viper.SetConfigType("yaml") // because there is no file extension in a stream of bytes runtime_viper.SetConfigType("yaml") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop"
// read from remote config the first time. // read from remote config the first time.
err := runtime_viper.ReadRemoteConfig() err := runtime_viper.ReadRemoteConfig()
@ -448,16 +458,17 @@ Viper can access a nested field by passing a `.` delimited path of keys:
GetString("datastore.metric.host") // (returns "127.0.0.1") GetString("datastore.metric.host") // (returns "127.0.0.1")
``` ```
This obeys the precedence rules established above; the search for the root key This obeys the precedence rules established above; the search for the path
(in this example, `datastore`) will cascade through the remaining configuration will cascade through the remaining configuration registries until found.
registries until found. The search for the sub-keys (`metric` and `host`),
however, will not.
For example, if the `metric` key was not defined in the configuration loaded For example, given this configuration file, both `datastore.metric.host` and
from file, but was defined in the defaults, Viper would return the zero value. `datastore.metric.port` are already defined (and may be overridden). If in addition
`datastore.metric.protocol` was defined in the defaults, Viper would also find it.
On the other hand, if the primary key was not defined, Viper would go through However, if `datastore.metric` was overridden (by a flag, an environment variable,
the remaining registries looking for it. the `Set()` method, …) with an immediate value, then all sub-keys of
`datastore.metric` become undefined, they are “shadowed” by the higher-priority
configuration level.
Lastly, if there exists a key that matches the delimited key path, its value Lastly, if there exists a key that matches the delimited key path, its value
will be returned instead. E.g. will be returned instead. E.g.
@ -481,7 +492,7 @@ will be returned instead. E.g.
} }
} }
GetString("datastore.metric.host") //returns "0.0.0.0" GetString("datastore.metric.host") // returns "0.0.0.0"
``` ```
### Extract sub-tree ### Extract sub-tree

View file

@ -22,7 +22,7 @@ func TestBindFlagValueSet(t *testing.T) {
"endpoint": "/public", "endpoint": "/public",
} }
for name, _ := range testValues { for name := range testValues {
testValues[name] = flagSet.String(name, "", "test") testValues[name] = flagSet.String(name, "", "test")
} }

1
nohup.out Normal file
View file

@ -0,0 +1 @@
QProcess::start: Process is already running

173
overrides_test.go Normal file
View file

@ -0,0 +1,173 @@
package viper
import (
"fmt"
"strings"
"testing"
"github.com/spf13/cast"
"github.com/stretchr/testify/assert"
)
type layer int
const (
defaultLayer layer = iota + 1
overrideLayer
)
func TestNestedOverrides(t *testing.T) {
assert := assert.New(t)
var v *Viper
// Case 0: value overridden by a value
overrideDefault(assert, "tom", 10, "tom", 20) // "tom" is first given 10 as default value, then overridden by 20
override(assert, "tom", 10, "tom", 20) // "tom" is first given value 10, then overridden by 20
overrideDefault(assert, "tom.age", 10, "tom.age", 20)
override(assert, "tom.age", 10, "tom.age", 20)
overrideDefault(assert, "sawyer.tom.age", 10, "sawyer.tom.age", 20)
override(assert, "sawyer.tom.age", 10, "sawyer.tom.age", 20)
// Case 1: key:value overridden by a value
v = overrideDefault(assert, "tom.age", 10, "tom", "boy") // "tom.age" is first given 10 as default value, then "tom" is overridden by "boy"
assert.Nil(v.Get("tom.age")) // "tom.age" should not exist anymore
v = override(assert, "tom.age", 10, "tom", "boy")
assert.Nil(v.Get("tom.age"))
// Case 2: value overridden by a key:value
overrideDefault(assert, "tom", "boy", "tom.age", 10) // "tom" is first given "boy" as default value, then "tom" is overridden by map{"age":10}
override(assert, "tom.age", 10, "tom", "boy")
// Case 3: key:value overridden by a key:value
v = overrideDefault(assert, "tom.size", 4, "tom.age", 10)
assert.Equal(4, v.Get("tom.size")) // value should still be reachable
v = override(assert, "tom.size", 4, "tom.age", 10)
assert.Equal(4, v.Get("tom.size"))
deepCheckValue(assert, v, overrideLayer, []string{"tom", "size"}, 4)
// Case 4: key:value overridden by a map
v = overrideDefault(assert, "tom.size", 4, "tom", map[string]interface{}{"age": 10}) // "tom.size" is first given "4" as default value, then "tom" is overridden by map{"age":10}
assert.Equal(4, v.Get("tom.size")) // "tom.size" should still be reachable
assert.Equal(10, v.Get("tom.age")) // new value should be there
deepCheckValue(assert, v, overrideLayer, []string{"tom", "age"}, 10) // new value should be there
v = override(assert, "tom.size", 4, "tom", map[string]interface{}{"age": 10})
assert.Nil(v.Get("tom.size"))
assert.Equal(10, v.Get("tom.age"))
deepCheckValue(assert, v, overrideLayer, []string{"tom", "age"}, 10)
// Case 5: array overridden by a value
overrideDefault(assert, "tom", []int{10, 20}, "tom", 30)
override(assert, "tom", []int{10, 20}, "tom", 30)
overrideDefault(assert, "tom.age", []int{10, 20}, "tom.age", 30)
override(assert, "tom.age", []int{10, 20}, "tom.age", 30)
// Case 6: array overridden by an array
overrideDefault(assert, "tom", []int{10, 20}, "tom", []int{30, 40})
override(assert, "tom", []int{10, 20}, "tom", []int{30, 40})
overrideDefault(assert, "tom.age", []int{10, 20}, "tom.age", []int{30, 40})
v = override(assert, "tom.age", []int{10, 20}, "tom.age", []int{30, 40})
// explicit array merge:
s, ok := v.Get("tom.age").([]int)
if assert.True(ok, "tom[\"age\"] is not a slice") {
v.Set("tom.age", append(s, []int{50, 60}...))
assert.Equal([]int{30, 40, 50, 60}, v.Get("tom.age"))
deepCheckValue(assert, v, overrideLayer, []string{"tom", "age"}, []int{30, 40, 50, 60})
}
}
func overrideDefault(assert *assert.Assertions, firstPath string, firstValue interface{}, secondPath string, secondValue interface{}) *Viper {
return overrideFromLayer(defaultLayer, assert, firstPath, firstValue, secondPath, secondValue)
}
func override(assert *assert.Assertions, firstPath string, firstValue interface{}, secondPath string, secondValue interface{}) *Viper {
return overrideFromLayer(overrideLayer, assert, firstPath, firstValue, secondPath, secondValue)
}
// overrideFromLayer performs the sequential override and low-level checks.
//
// First assignment is made on layer l for path firstPath with value firstValue,
// the second one on the override layer (i.e., with the Set() function)
// for path secondPath with value secondValue.
//
// firstPath and secondPath can include an arbitrary number of dots to indicate
// a nested element.
//
// After each assignment, the value is checked, retrieved both by its full path
// and by its key sequence (successive maps).
func overrideFromLayer(l layer, assert *assert.Assertions, firstPath string, firstValue interface{}, secondPath string, secondValue interface{}) *Viper {
v := New()
firstKeys := strings.Split(firstPath, v.keyDelim)
if assert == nil ||
len(firstKeys) == 0 || len(firstKeys[0]) == 0 {
return v
}
// Set and check first value
switch l {
case defaultLayer:
v.SetDefault(firstPath, firstValue)
case overrideLayer:
v.Set(firstPath, firstValue)
default:
return v
}
assert.Equal(firstValue, v.Get(firstPath))
deepCheckValue(assert, v, l, firstKeys, firstValue)
// Override and check new value
secondKeys := strings.Split(secondPath, v.keyDelim)
if len(secondKeys) == 0 || len(secondKeys[0]) == 0 {
return v
}
v.Set(secondPath, secondValue)
assert.Equal(secondValue, v.Get(secondPath))
deepCheckValue(assert, v, overrideLayer, secondKeys, secondValue)
return v
}
// deepCheckValue checks that all given keys correspond to a valid path in the
// configuration map of the given layer, and that the final value equals the one given
func deepCheckValue(assert *assert.Assertions, v *Viper, l layer, keys []string, value interface{}) {
if assert == nil || v == nil ||
len(keys) == 0 || len(keys[0]) == 0 {
return
}
// init
var val interface{}
var ms string
switch l {
case defaultLayer:
val = v.defaults
ms = "v.defaults"
case overrideLayer:
val = v.override
ms = "v.override"
}
// loop through map
var m map[string]interface{}
err := false
for _, k := range keys {
if val == nil {
assert.Fail(fmt.Sprintf("%s is not a map[string]interface{}", ms))
return
}
// deep scan of the map to get the final value
switch val.(type) {
case map[interface{}]interface{}:
m = cast.ToStringMap(val)
case map[string]interface{}:
m = val.(map[string]interface{})
default:
assert.Fail(fmt.Sprintf("%s is not a map[string]interface{}", ms))
return
}
ms = ms + "[\"" + k + "\"]"
val = m[k]
}
if !err {
assert.Equal(value, val)
}
}

84
util.go
View file

@ -21,20 +21,20 @@ import (
"strings" "strings"
"unicode" "unicode"
"github.com/BurntSushi/toml"
"github.com/hashicorp/hcl" "github.com/hashicorp/hcl"
"github.com/magiconair/properties" "github.com/magiconair/properties"
toml "github.com/pelletier/go-toml"
"github.com/spf13/cast" "github.com/spf13/cast"
jww "github.com/spf13/jwalterweatherman" jww "github.com/spf13/jwalterweatherman"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
// Denotes failing to parse configuration file. // ConfigParseError denotes failing to parse configuration file.
type ConfigParseError struct { type ConfigParseError struct {
err error err error
} }
// Returns the formatted configuration error. // Error returns the formatted configuration error.
func (pe ConfigParseError) Error() string { func (pe ConfigParseError) Error() string {
return fmt.Sprintf("While parsing config: %s", pe.err.Error()) return fmt.Sprintf("While parsing config: %s", pe.err.Error())
} }
@ -45,6 +45,10 @@ func insensitiviseMap(m map[string]interface{}) {
if key != lower { if key != lower {
delete(m, key) delete(m, key)
m[lower] = val m[lower] = val
if m2, ok := val.(map[string]interface{}); ok {
// nested map: recursively insensitivise
insensitiviseMap(m2)
}
} }
} }
} }
@ -68,16 +72,16 @@ func absPathify(inPath string) string {
p, err := filepath.Abs(inPath) p, err := filepath.Abs(inPath)
if err == nil { if err == nil {
return filepath.Clean(p) return filepath.Clean(p)
} else { }
jww.ERROR.Println("Couldn't discover absolute path") jww.ERROR.Println("Couldn't discover absolute path")
jww.ERROR.Println(err) jww.ERROR.Println(err)
}
return "" return ""
} }
// Check if File / Directory Exists // Check if File / Directory Exists
func exists(path string) (bool, error) { func exists(path string) (bool, error) {
_, err := os.Stat(path) _, err := v.fs.Stat(path)
if err == nil { if err == nil {
return true, nil return true, nil
} }
@ -107,29 +111,6 @@ func userHomeDir() string {
return os.Getenv("HOME") return os.Getenv("HOME")
} }
func findCWD() (string, error) {
serverFile, err := filepath.Abs(os.Args[0])
if err != nil {
return "", fmt.Errorf("Can't get absolute path for executable: %v", err)
}
path := filepath.Dir(serverFile)
realFile, err := filepath.EvalSymlinks(serverFile)
if err != nil {
if _, err = os.Stat(serverFile + ".exe"); err == nil {
realFile = filepath.Clean(serverFile + ".exe")
}
}
if err == nil && realFile != serverFile {
path = filepath.Dir(realFile)
}
return path, nil
}
func unmarshallConfigReader(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)
@ -155,9 +136,14 @@ func unmarshallConfigReader(in io.Reader, c map[string]interface{}, configType s
} }
case "toml": case "toml":
if _, err := toml.Decode(buf.String(), &c); err != nil { tree, err := toml.LoadReader(buf)
if err != nil {
return ConfigParseError{err} return ConfigParseError{err}
} }
tmap := tree.ToMap()
for k, v := range tmap {
c[k] = v
}
case "properties", "props", "prop": case "properties", "props", "prop":
var p *properties.Properties var p *properties.Properties
@ -167,7 +153,12 @@ func unmarshallConfigReader(in io.Reader, c map[string]interface{}, configType s
} }
for _, key := range p.Keys() { for _, key := range p.Keys() {
value, _ := p.Get(key) value, _ := p.Get(key)
c[key] = value // 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
} }
} }
@ -217,3 +208,34 @@ func parseSizeInBytes(sizeStr string) uint {
return safeMul(uint(size), multiplier) return safeMul(uint(size), multiplier)
} }
// deepSearch scans deep maps, following the key indexes listed in the
// sequence "path".
// The last value is expected to be another map, and is returned.
//
// In case intermediate keys do not exist, or map to a non-map value,
// a new map is created and inserted, and the search continues from there:
// the initial map "m" may be modified!
func deepSearch(m map[string]interface{}, path []string) map[string]interface{} {
for _, k := range path {
m2, ok := m[k]
if !ok {
// intermediate key does not exist
// => create it and continue from there
m3 := make(map[string]interface{})
m[k] = m3
m = m3
continue
}
m3, ok := m2.(map[string]interface{})
if !ok {
// intermediate key is a value
// => replace with a new map
m3 = make(map[string]interface{})
m[k] = m3
}
// continue search from here
m = m3
}
return m
}

752
viper.go

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,7 @@ package viper
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
@ -17,6 +18,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/spf13/cast"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -37,6 +40,14 @@ eyes : brown
beard: true beard: true
`) `)
var yamlExampleWithExtras = []byte(`Existing: true
Bogus: true
`)
type testUnmarshalExtra struct {
Existing bool
}
var tomlExample = []byte(` var tomlExample = []byte(`
title = "TOML Example" title = "TOML Example"
@ -96,8 +107,9 @@ var remoteExample = []byte(`{
func initConfigs() { func initConfigs() {
Reset() Reset()
var r io.Reader
SetConfigType("yaml") SetConfigType("yaml")
r := bytes.NewReader(yamlExample) r = bytes.NewReader(yamlExample)
unmarshalReader(r, v.config) unmarshalReader(r, v.config)
SetConfigType("json") SetConfigType("json")
@ -121,12 +133,18 @@ func initConfigs() {
unmarshalReader(remote, v.kvstore) unmarshalReader(remote, v.kvstore)
} }
func initYAML() { func initConfig(typ, config string) {
Reset() Reset()
SetConfigType("yaml") SetConfigType(typ)
r := bytes.NewReader(yamlExample) r := strings.NewReader(config)
unmarshalReader(r, v.config) if err := unmarshalReader(r, v.config); err != nil {
panic(err)
}
}
func initYAML() {
initConfig("yaml", string(yamlExample))
} }
func initJSON() { func initJSON() {
@ -253,10 +271,22 @@ func TestUnmarshalling(t *testing.T) {
assert.False(t, InConfig("state")) assert.False(t, InConfig("state"))
assert.Equal(t, "steve", Get("name")) assert.Equal(t, "steve", Get("name"))
assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, Get("hobbies")) assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, Get("hobbies"))
assert.Equal(t, map[interface{}]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[interface{}]interface{}{"size": "large"}}, Get("clothing")) assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[interface{}]interface{}{"size": "large"}}, Get("clothing"))
assert.Equal(t, 35, Get("age")) assert.Equal(t, 35, Get("age"))
} }
func TestUnmarshalExact(t *testing.T) {
vip := New()
target := &testUnmarshalExtra{}
vip.SetConfigType("yaml")
r := bytes.NewReader(yamlExampleWithExtras)
vip.ReadConfig(r)
err := vip.UnmarshalExact(target)
if err == nil {
t.Fatal("UnmarshalExact should error when populating a struct from a conf that contains unused fields")
}
}
func TestOverrides(t *testing.T) { func TestOverrides(t *testing.T) {
Set("age", 40) Set("age", 40)
assert.Equal(t, 40, Get("age")) assert.Equal(t, 40, Get("age"))
@ -402,9 +432,9 @@ func TestSetEnvReplacer(t *testing.T) {
func TestAllKeys(t *testing.T) { func TestAllKeys(t *testing.T) {
initConfigs() initConfigs()
ks := sort.StringSlice{"title", "newkey", "owner", "name", "beard", "ppu", "batters", "hobbies", "clothing", "age", "hacker", "id", "type", "eyes", "p_id", "p_ppu", "p_batters.batter.type", "p_type", "p_name", "foos"} ks := sort.StringSlice{"title", "newkey", "owner.organization", "owner.dob", "owner.bio", "name", "beard", "ppu", "batters.batter", "hobbies", "clothing.jacket", "clothing.trousers", "clothing.pants.size", "age", "hacker", "id", "type", "eyes", "p_id", "p_ppu", "p_batters.batter.type", "p_type", "p_name", "foos"}
dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z")
all := map[string]interface{}{"owner": map[string]interface{}{"organization": "MongoDB", "Bio": "MongoDB Chief Developer Advocate & Hacker at Large", "dob": dob}, "title": "TOML Example", "ppu": 0.55, "eyes": "brown", "clothing": map[interface{}]interface{}{"trousers": "denim", "jacket": "leather", "pants": map[interface{}]interface{}{"size": "large"}}, "id": "0001", "batters": map[string]interface{}{"batter": []interface{}{map[string]interface{}{"type": "Regular"}, map[string]interface{}{"type": "Chocolate"}, map[string]interface{}{"type": "Blueberry"}, map[string]interface{}{"type": "Devil's Food"}}}, "hacker": true, "beard": true, "hobbies": []interface{}{"skateboarding", "snowboarding", "go"}, "age": 35, "type": "donut", "newkey": "remote", "name": "Cake", "p_id": "0001", "p_ppu": "0.55", "p_name": "Cake", "p_batters.batter.type": "Regular", "p_type": "donut", "foos": []map[string]interface{}{map[string]interface{}{"foo": []map[string]interface{}{map[string]interface{}{"key": 1}, map[string]interface{}{"key": 2}, map[string]interface{}{"key": 3}, map[string]interface{}{"key": 4}}}}} all := map[string]interface{}{"owner": map[string]interface{}{"organization": "MongoDB", "bio": "MongoDB Chief Developer Advocate & Hacker at Large", "dob": dob}, "title": "TOML Example", "ppu": 0.55, "eyes": "brown", "clothing": map[string]interface{}{"trousers": "denim", "jacket": "leather", "pants": map[string]interface{}{"size": "large"}}, "id": "0001", "batters": map[string]interface{}{"batter": []interface{}{map[string]interface{}{"type": "Regular"}, map[string]interface{}{"type": "Chocolate"}, map[string]interface{}{"type": "Blueberry"}, map[string]interface{}{"type": "Devil's Food"}}}, "hacker": true, "beard": true, "hobbies": []interface{}{"skateboarding", "snowboarding", "go"}, "age": 35, "type": "donut", "newkey": "remote", "name": "Cake", "p_id": "0001", "p_ppu": "0.55", "p_name": "Cake", "p_batters": map[string]interface{}{"batter": map[string]interface{}{"type": "Regular"}}, "p_type": "donut", "foos": []map[string]interface{}{map[string]interface{}{"foo": []map[string]interface{}{map[string]interface{}{"key": 1}, map[string]interface{}{"key": 2}, map[string]interface{}{"key": 3}, map[string]interface{}{"key": 4}}}}}
var allkeys sort.StringSlice var allkeys sort.StringSlice
allkeys = AllKeys() allkeys = AllKeys()
@ -415,13 +445,8 @@ func TestAllKeys(t *testing.T) {
assert.Equal(t, all, AllSettings()) assert.Equal(t, all, AllSettings())
} }
func TestCaseInSensitive(t *testing.T) {
assert.Equal(t, true, Get("hacker"))
Set("Title", "Checking Case")
assert.Equal(t, "Checking Case", Get("tItle"))
}
func TestAliasesOfAliases(t *testing.T) { func TestAliasesOfAliases(t *testing.T) {
Set("Title", "Checking Case")
RegisterAlias("Foo", "Bar") RegisterAlias("Foo", "Bar")
RegisterAlias("Bar", "Title") RegisterAlias("Bar", "Title")
assert.Equal(t, "Checking Case", Get("FOO")) assert.Equal(t, "Checking Case", Get("FOO"))
@ -435,10 +460,12 @@ func TestRecursiveAliases(t *testing.T) {
func TestUnmarshal(t *testing.T) { func TestUnmarshal(t *testing.T) {
SetDefault("port", 1313) SetDefault("port", 1313)
Set("name", "Steve") Set("name", "Steve")
Set("duration", "1s1ms")
type config struct { type config struct {
Port int Port int
Name string Name string
Duration time.Duration
} }
var C config var C config
@ -448,17 +475,18 @@ func TestUnmarshal(t *testing.T) {
t.Fatalf("unable to decode into struct, %v", err) t.Fatalf("unable to decode into struct, %v", err)
} }
assert.Equal(t, &C, &config{Name: "Steve", Port: 1313}) assert.Equal(t, &config{Name: "Steve", Port: 1313, Duration: time.Second + time.Millisecond}, &C)
Set("port", 1234) Set("port", 1234)
err = Unmarshal(&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)
} }
assert.Equal(t, &C, &config{Name: "Steve", Port: 1234}) assert.Equal(t, &config{Name: "Steve", Port: 1234, Duration: time.Second + time.Millisecond}, &C)
} }
func TestBindPFlags(t *testing.T) { func TestBindPFlags(t *testing.T) {
v := New() // create independent Viper object
flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
var testValues = map[string]*string{ var testValues = map[string]*string{
@ -473,11 +501,11 @@ func TestBindPFlags(t *testing.T) {
"endpoint": "/public", "endpoint": "/public",
} }
for name, _ := range testValues { for name := range testValues {
testValues[name] = flagSet.String(name, "", "test") testValues[name] = flagSet.String(name, "", "test")
} }
err := BindPFlags(flagSet) err := v.BindPFlags(flagSet)
if err != nil { if err != nil {
t.Fatalf("error binding flag set, %v", err) t.Fatalf("error binding flag set, %v", err)
} }
@ -488,7 +516,7 @@ func TestBindPFlags(t *testing.T) {
}) })
for name, expected := range mutatedTestValues { for name, expected := range mutatedTestValues {
assert.Equal(t, Get(name), expected) assert.Equal(t, expected, v.Get(name))
} }
} }
@ -515,7 +543,6 @@ func TestBindPFlag(t *testing.T) {
} }
func TestBoundCaseSensitivity(t *testing.T) { func TestBoundCaseSensitivity(t *testing.T) {
assert.Equal(t, "brown", Get("eyes")) assert.Equal(t, "brown", Get("eyes"))
BindEnv("eYEs", "TURTLE_EYES") BindEnv("eYEs", "TURTLE_EYES")
@ -621,7 +648,7 @@ func TestFindsNestedKeys(t *testing.T) {
"name": "Cake", "name": "Cake",
"hacker": true, "hacker": true,
"ppu": 0.55, "ppu": 0.55,
"clothing": map[interface{}]interface{}{ "clothing": map[string]interface{}{
"jacket": "leather", "jacket": "leather",
"trousers": "denim", "trousers": "denim",
"pants": map[interface{}]interface{}{ "pants": map[interface{}]interface{}{
@ -670,7 +697,7 @@ func TestReadBufConfig(t *testing.T) {
assert.False(t, v.InConfig("state")) assert.False(t, v.InConfig("state"))
assert.Equal(t, "steve", v.Get("name")) assert.Equal(t, "steve", v.Get("name"))
assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, v.Get("hobbies")) assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, v.Get("hobbies"))
assert.Equal(t, map[interface{}]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[interface{}]interface{}{"size": "large"}}, v.Get("clothing")) assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[interface{}]interface{}{"size": "large"}}, v.Get("clothing"))
assert.Equal(t, 35, v.Get("age")) assert.Equal(t, 35, v.Get("age"))
} }
@ -739,12 +766,16 @@ func TestSub(t *testing.T) {
assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("size")) assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("size"))
subv = v.Sub("clothing.pants.size") subv = v.Sub("clothing.pants.size")
assert.Equal(t, subv, (*Viper)(nil)) assert.Equal(t, (*Viper)(nil), subv)
subv = v.Sub("missing.key")
assert.Equal(t, (*Viper)(nil), subv)
} }
var yamlMergeExampleTgt = []byte(` var yamlMergeExampleTgt = []byte(`
hello: hello:
pop: 37890 pop: 37890
lagrenum: 765432101234567
world: world:
- us - us
- uk - uk
@ -755,6 +786,7 @@ hello:
var yamlMergeExampleSrc = []byte(` var yamlMergeExampleSrc = []byte(`
hello: hello:
pop: 45000 pop: 45000
lagrenum: 7654321001234567
universe: universe:
- mw - mw
- ad - ad
@ -772,6 +804,14 @@ func TestMergeConfig(t *testing.T) {
t.Fatalf("pop != 37890, = %d", pop) t.Fatalf("pop != 37890, = %d", pop)
} }
if pop := v.GetInt("hello.lagrenum"); pop != 765432101234567 {
t.Fatalf("lagrenum != 765432101234567, = %d", pop)
}
if pop := v.GetInt64("hello.lagrenum"); pop != int64(765432101234567) {
t.Fatalf("int64 lagrenum != 765432101234567, = %d", pop)
}
if world := v.GetStringSlice("hello.world"); len(world) != 4 { if world := v.GetStringSlice("hello.world"); len(world) != 4 {
t.Fatalf("len(world) != 4, = %d", len(world)) t.Fatalf("len(world) != 4, = %d", len(world))
} }
@ -788,6 +828,14 @@ func TestMergeConfig(t *testing.T) {
t.Fatalf("pop != 45000, = %d", pop) t.Fatalf("pop != 45000, = %d", pop)
} }
if pop := v.GetInt("hello.lagrenum"); pop != 7654321001234567 {
t.Fatalf("lagrenum != 7654321001234567, = %d", pop)
}
if pop := v.GetInt64("hello.lagrenum"); pop != int64(7654321001234567) {
t.Fatalf("int64 lagrenum != 7654321001234567, = %d", pop)
}
if world := v.GetStringSlice("hello.world"); len(world) != 4 { if world := v.GetStringSlice("hello.world"); len(world) != 4 {
t.Fatalf("len(world) != 4, = %d", len(world)) t.Fatalf("len(world) != 4, = %d", len(world))
} }
@ -840,3 +888,171 @@ func TestMergeConfigNoMerge(t *testing.T) {
t.Fatalf("fu != \"bar\", = %s", fu) t.Fatalf("fu != \"bar\", = %s", fu)
} }
} }
func TestUnmarshalingWithAliases(t *testing.T) {
v := New()
v.SetDefault("ID", 1)
v.Set("name", "Steve")
v.Set("lastname", "Owen")
v.RegisterAlias("UserID", "ID")
v.RegisterAlias("Firstname", "name")
v.RegisterAlias("Surname", "lastname")
type config struct {
ID int
FirstName string
Surname string
}
var C config
err := v.Unmarshal(&C)
if err != nil {
t.Fatalf("unable to decode into struct, %v", err)
}
assert.Equal(t, &config{ID: 1, FirstName: "Steve", Surname: "Owen"}, &C)
}
func TestSetConfigNameClearsFileCache(t *testing.T) {
SetConfigFile("/tmp/config.yaml")
SetConfigName("default")
f, err := v.getConfigFile()
if err == nil {
t.Fatalf("config file cache should have been cleared")
}
assert.Empty(t, f)
}
func TestShadowedNestedValue(t *testing.T) {
config := `name: steve
clothing:
jacket: leather
trousers: denim
pants:
size: large
`
initConfig("yaml", config)
assert.Equal(t, "steve", GetString("name"))
polyester := "polyester"
SetDefault("clothing.shirt", polyester)
SetDefault("clothing.jacket.price", 100)
assert.Equal(t, "leather", GetString("clothing.jacket"))
assert.Nil(t, Get("clothing.jacket.price"))
assert.Equal(t, polyester, GetString("clothing.shirt"))
clothingSettings := AllSettings()["clothing"].(map[string]interface{})
assert.Equal(t, "leather", clothingSettings["jacket"])
assert.Equal(t, polyester, clothingSettings["shirt"])
}
func TestDotParameter(t *testing.T) {
initJSON()
// shoud take precedence over batters defined in jsonExample
r := bytes.NewReader([]byte(`{ "batters.batter": [ { "type": "Small" } ] }`))
unmarshalReader(r, v.config)
actual := Get("batters.batter")
expected := []interface{}{map[string]interface{}{"type": "Small"}}
assert.Equal(t, expected, actual)
}
func TestCaseInSensitive(t *testing.T) {
for _, config := range []struct {
typ string
content string
}{
{"yaml", `
aBcD: 1
eF:
gH: 2
iJk: 3
Lm:
nO: 4
P:
Q: 5
R: 6
`},
{"json", `{
"aBcD": 1,
"eF": {
"iJk": 3,
"Lm": {
"P": {
"Q": 5,
"R": 6
},
"nO": 4
},
"gH": 2
}
}`},
{"toml", `aBcD = 1
[eF]
gH = 2
iJk = 3
[eF.Lm]
nO = 4
[eF.Lm.P]
Q = 5
R = 6
`},
} {
doTestCaseInSensitive(t, config.typ, config.content)
}
}
func doTestCaseInSensitive(t *testing.T, typ, config string) {
initConfig(typ, config)
Set("RfD", true)
assert.Equal(t, true, Get("rfd"))
assert.Equal(t, true, Get("rFD"))
assert.Equal(t, 1, cast.ToInt(Get("abcd")))
assert.Equal(t, 1, cast.ToInt(Get("Abcd")))
assert.Equal(t, 2, cast.ToInt(Get("ef.gh")))
assert.Equal(t, 3, cast.ToInt(Get("ef.ijk")))
assert.Equal(t, 4, cast.ToInt(Get("ef.lm.no")))
assert.Equal(t, 5, cast.ToInt(Get("ef.lm.p.q")))
}
func BenchmarkGetBool(b *testing.B) {
key := "BenchmarkGetBool"
v = New()
v.Set(key, true)
for i := 0; i < b.N; i++ {
if !v.GetBool(key) {
b.Fatal("GetBool returned false")
}
}
}
func BenchmarkGet(b *testing.B) {
key := "BenchmarkGet"
v = New()
v.Set(key, true)
for i := 0; i < b.N; i++ {
if !v.Get(key).(bool) {
b.Fatal("Get returned false")
}
}
}
// This is the "perfect result" for the above.
func BenchmarkGetBoolFromMap(b *testing.B) {
m := make(map[string]bool)
key := "BenchmarkGetBool"
m[key] = true
for i := 0; i < b.N; i++ {
if !m[key] {
b.Fatal("Map value was false")
}
}
}