mirror of
https://github.com/spf13/viper
synced 2025-05-06 20:27:17 +00:00
Merge upstream
This commit is contained in:
commit
4339546753
9 changed files with 1037 additions and 333 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -21,3 +21,4 @@ _testmain.go
|
||||||
|
|
||||||
*.exe
|
*.exe
|
||||||
*.test
|
*.test
|
||||||
|
*.bench
|
22
.travis.yml
22
.travis.yml
|
@ -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
|
||||||
|
|
49
README.md
49
README.md
|
@ -2,7 +2,17 @@
|
||||||
|
|
||||||
Go configuration with fangs!
|
Go configuration with fangs!
|
||||||
|
|
||||||
[](https://travis-ci.org/spf13/viper) [](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)
|
||||||
|
|
||||||
|
[](https://travis-ci.org/spf13/viper) [](https://gitter.im/spf13/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](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.
|
||||||
|
|
|
@ -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
1
nohup.out
Normal file
|
@ -0,0 +1 @@
|
||||||
|
QProcess::start: Process is already running
|
173
overrides_test.go
Normal file
173
overrides_test.go
Normal 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
84
util.go
|
@ -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
|
||||||
|
}
|
||||||
|
|
262
viper_test.go
262
viper_test.go
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue