mirror of https://github.com/spf13/viper.git
Restore performance for the simple case
``` BenchmarkGetBool-4 1021 481 -52.89% BenchmarkGet-4 879 403 -54.15% BenchmarkGetBoolFromMap-4 6.56 6.40 -2.44% benchmark old allocs new allocs delta BenchmarkGetBool-4 6 4 -33.33% BenchmarkGet-4 6 4 -33.33% BenchmarkGetBoolFromMap-4 0 0 +0.00% benchmark old bytes new bytes delta BenchmarkGetBool-4 113 49 -56.64% BenchmarkGet-4 112 48 -57.14% BenchmarkGetBoolFromMap-4 0 0 +0.00% ``` Fixes #249 Fixes https://github.com/spf13/hugo/issues/2536
This commit is contained in:
parent
54b81535af
commit
51f23d1f1c
71
viper.go
71
viper.go
|
@ -399,17 +399,42 @@ func (v *Viper) providerPathExists(p *defaultRemoteProvider) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// searchMapForKey may end up traversing the map if the key references a nested
|
||||||
|
// item (foo.bar), but will use a fast path for the common case.
|
||||||
|
// Note: This assumes that the key given is already lowercase.
|
||||||
|
func (v *Viper) searchMapForKey(source map[string]interface{}, lcaseKey string) interface{} {
|
||||||
|
if !strings.Contains(lcaseKey, v.keyDelim) {
|
||||||
|
v, ok := source[lcaseKey]
|
||||||
|
if ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
path := strings.Split(lcaseKey, v.keyDelim)
|
||||||
|
return v.searchMap(source, path)
|
||||||
|
}
|
||||||
|
|
||||||
// searchMap recursively searches for a value for path in source map.
|
// searchMap recursively searches for a value for path in source map.
|
||||||
// Returns nil if not found.
|
// Returns nil if not found.
|
||||||
|
// Note: This assumes that the path entries are lower cased.
|
||||||
func (v *Viper) searchMap(source map[string]interface{}, path []string) interface{} {
|
func (v *Viper) searchMap(source map[string]interface{}, path []string) interface{} {
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
return source
|
return source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fast path
|
||||||
|
if len(path) == 1 {
|
||||||
|
if v, ok := source[path[0]]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var ok bool
|
var ok bool
|
||||||
var next interface{}
|
var next interface{}
|
||||||
for k, v := range source {
|
for k, v := range source {
|
||||||
if strings.ToLower(k) == strings.ToLower(path[0]) {
|
if k == path[0] {
|
||||||
ok = true
|
ok = true
|
||||||
next = v
|
next = v
|
||||||
break
|
break
|
||||||
|
@ -594,8 +619,8 @@ func (v *Viper) Get(key string) interface{} {
|
||||||
|
|
||||||
valType := val
|
valType := val
|
||||||
if v.typeByDefValue {
|
if v.typeByDefValue {
|
||||||
path := strings.Split(lcaseKey, v.keyDelim)
|
// TODO(bep) this branch isn't covered by a single test.
|
||||||
defVal := v.searchMap(v.defaults, path)
|
defVal := v.searchMapForKey(v.defaults, lcaseKey)
|
||||||
if defVal != nil {
|
if defVal != nil {
|
||||||
valType = defVal
|
valType = defVal
|
||||||
}
|
}
|
||||||
|
@ -841,32 +866,39 @@ func (v *Viper) BindEnv(input ...string) error {
|
||||||
// Viper will check in the following order:
|
// Viper will check in the following order:
|
||||||
// flag, env, config file, key/value store, default.
|
// flag, env, config file, key/value store, default.
|
||||||
// Viper will check to see if an alias exists first.
|
// Viper will check to see if an alias exists first.
|
||||||
func (v *Viper) find(key string) interface{} {
|
// Note: this assumes a lower-cased key given.
|
||||||
var val interface{}
|
func (v *Viper) find(lcaseKey string) interface{} {
|
||||||
var exists bool
|
|
||||||
|
var (
|
||||||
|
val interface{}
|
||||||
|
exists bool
|
||||||
|
path = strings.Split(lcaseKey, v.keyDelim)
|
||||||
|
nested = len(path) > 1
|
||||||
|
)
|
||||||
|
|
||||||
// compute the path through the nested maps to the nested value
|
// compute the path through the nested maps to the nested value
|
||||||
path := strings.Split(key, v.keyDelim)
|
if nested && v.isPathShadowedInDeepMap(path, castMapStringToMapInterface(v.aliases)) != "" {
|
||||||
if shadow := v.isPathShadowedInDeepMap(path, castMapStringToMapInterface(v.aliases)); shadow != "" {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the requested key is an alias, then return the proper key
|
// if the requested key is an alias, then return the proper key
|
||||||
key = v.realKey(key)
|
lcaseKey = v.realKey(lcaseKey)
|
||||||
// re-compute the path
|
|
||||||
path = strings.Split(key, v.keyDelim)
|
|
||||||
|
|
||||||
// Set() override first
|
// Set() override first
|
||||||
val = v.searchMap(v.override, path)
|
val = v.searchMapForKey(v.override, lcaseKey)
|
||||||
if val != nil {
|
if val != nil {
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
if shadow := v.isPathShadowedInDeepMap(path, v.override); shadow != "" {
|
|
||||||
|
path = strings.Split(lcaseKey, v.keyDelim)
|
||||||
|
nested = len(path) > 1
|
||||||
|
|
||||||
|
if nested && v.isPathShadowedInDeepMap(path, v.override) != "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PFlag override next
|
// PFlag override next
|
||||||
flag, exists := v.pflags[key]
|
flag, exists := v.pflags[lcaseKey]
|
||||||
if exists && flag.HasChanged() {
|
if exists && flag.HasChanged() {
|
||||||
switch flag.ValueType() {
|
switch flag.ValueType() {
|
||||||
case "int", "int8", "int16", "int32", "int64":
|
case "int", "int8", "int16", "int32", "int64":
|
||||||
|
@ -880,7 +912,8 @@ func (v *Viper) find(key string) interface{} {
|
||||||
return flag.ValueString()
|
return flag.ValueString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if shadow := v.isPathShadowedInFlatMap(path, v.pflags); shadow != "" {
|
|
||||||
|
if nested && v.isPathShadowedInFlatMap(path, v.pflags) != "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -888,14 +921,14 @@ func (v *Viper) find(key string) interface{} {
|
||||||
if v.automaticEnvApplied {
|
if v.automaticEnvApplied {
|
||||||
// even if it hasn't been registered, if automaticEnv is used,
|
// even if it hasn't been registered, if automaticEnv is used,
|
||||||
// check any Get request
|
// check any Get request
|
||||||
if val = v.getEnv(v.mergeWithEnvPrefix(key)); val != "" {
|
if val = v.getEnv(v.mergeWithEnvPrefix(lcaseKey)); val != "" {
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
if shadow := v.isPathShadowedInAutoEnv(path); shadow != "" {
|
if nested && v.isPathShadowedInAutoEnv(path) != "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
envkey, exists := v.env[key]
|
envkey, exists := v.env[lcaseKey]
|
||||||
if exists {
|
if exists {
|
||||||
if val = v.getEnv(envkey); val != "" {
|
if val = v.getEnv(envkey); val != "" {
|
||||||
return val
|
return val
|
||||||
|
@ -934,7 +967,7 @@ func (v *Viper) find(key string) interface{} {
|
||||||
|
|
||||||
// last chance: if no other value is returned and a flag does exist for the value,
|
// last chance: if no other value is returned and a flag does exist for the value,
|
||||||
// get the flag's value even if the flag's value has not changed
|
// get the flag's value even if the flag's value has not changed
|
||||||
if flag, exists := v.pflags[key]; exists {
|
if flag, exists := v.pflags[lcaseKey]; exists {
|
||||||
switch flag.ValueType() {
|
switch flag.ValueType() {
|
||||||
case "int", "int8", "int16", "int32", "int64":
|
case "int", "int8", "int16", "int32", "int64":
|
||||||
return cast.ToInt(flag.ValueString())
|
return cast.ToInt(flag.ValueString())
|
||||||
|
|
102
viper_test.go
102
viper_test.go
|
@ -18,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"
|
||||||
)
|
)
|
||||||
|
@ -131,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() {
|
||||||
|
@ -435,13 +443,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"))
|
||||||
|
@ -538,7 +541,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")
|
||||||
|
@ -917,8 +919,19 @@ func TestSetConfigNameClearsFileCache(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShadowedNestedValue(t *testing.T) {
|
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"
|
polyester := "polyester"
|
||||||
initYAML()
|
|
||||||
SetDefault("clothing.shirt", polyester)
|
SetDefault("clothing.shirt", polyester)
|
||||||
SetDefault("clothing.jacket.price", 100)
|
SetDefault("clothing.jacket.price", 100)
|
||||||
|
|
||||||
|
@ -942,18 +955,65 @@ func TestDotParameter(t *testing.T) {
|
||||||
assert.Equal(t, expected, actual)
|
assert.Equal(t, expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetBool(t *testing.T) {
|
func TestCaseInSensitive(t *testing.T) {
|
||||||
key := "BooleanKey"
|
for _, config := range []struct {
|
||||||
v = New()
|
typ string
|
||||||
v.Set(key, true)
|
content string
|
||||||
if !v.GetBool(key) {
|
}{
|
||||||
t.Fatal("GetBool returned false")
|
{"yaml", `
|
||||||
}
|
aBcD: 1
|
||||||
if v.GetBool("NotFound") {
|
eF:
|
||||||
t.Fatal("GetBool returned true")
|
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) {
|
func BenchmarkGetBool(b *testing.B) {
|
||||||
key := "BenchmarkGetBool"
|
key := "BenchmarkGetBool"
|
||||||
v = New()
|
v = New()
|
||||||
|
|
Loading…
Reference in New Issue