mirror of
https://github.com/spf13/viper
synced 2025-05-06 12:17:18 +00:00
Merge b477d087b3
into 2578450e4a
This commit is contained in:
commit
a141461e4f
4 changed files with 276 additions and 9 deletions
|
@ -237,6 +237,12 @@ Example:
|
||||||
fmt.Println("verbose enabled")
|
fmt.Println("verbose enabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
You can get deeply nested values by providing the expected key path.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
viper.Get("log.verbose") // case-insensitive path <root key>.<subkey1>.<subkey2>...etc
|
||||||
|
|
||||||
### Marshaling
|
### Marshaling
|
||||||
|
|
||||||
You also have the option of Marshaling all or a specific value to a struct, map, etc.
|
You also have the option of Marshaling all or a specific value to a struct, map, etc.
|
||||||
|
|
21
util.go
21
util.go
|
@ -17,6 +17,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
@ -196,3 +197,23 @@ func parseSizeInBytes(sizeStr string) uint {
|
||||||
|
|
||||||
return safeMul(uint(size), multiplier)
|
return safeMul(uint(size), multiplier)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recursively walks through the source map, populating the index with keys representing
|
||||||
|
// the nesting of the value and the value itself.
|
||||||
|
func indexMap(source map[string]interface{}, prefix string, index map[string]interface{}) {
|
||||||
|
if len(prefix) > 0 {
|
||||||
|
prefix = prefix + INDEX_DELIM
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, val := range source {
|
||||||
|
|
||||||
|
indexPath := strings.ToLower(prefix + key)
|
||||||
|
|
||||||
|
v.index[indexPath] = val
|
||||||
|
|
||||||
|
if reflect.TypeOf(val).Kind() == reflect.Map {
|
||||||
|
indexMap(cast.ToStringMap(val), indexPath, index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
78
viper.go
78
viper.go
|
@ -38,6 +38,9 @@ import (
|
||||||
crypt "github.com/xordataexchange/crypt/config"
|
crypt "github.com/xordataexchange/crypt/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const INDEX_DELIM = "."
|
||||||
|
const ENV_INDEX_DELIM = "__"
|
||||||
|
|
||||||
var v *Viper
|
var v *Viper
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -121,7 +124,10 @@ type Viper struct {
|
||||||
|
|
||||||
automaticEnvApplied bool
|
automaticEnvApplied bool
|
||||||
envKeyReplacer *strings.Replacer
|
envKeyReplacer *strings.Replacer
|
||||||
|
envToIndex *strings.Replacer
|
||||||
|
indexToEnv *strings.Replacer
|
||||||
|
|
||||||
|
index map[string]interface{}
|
||||||
config map[string]interface{}
|
config map[string]interface{}
|
||||||
override map[string]interface{}
|
override map[string]interface{}
|
||||||
defaults map[string]interface{}
|
defaults map[string]interface{}
|
||||||
|
@ -135,6 +141,7 @@ type Viper struct {
|
||||||
func New() *Viper {
|
func New() *Viper {
|
||||||
v := new(Viper)
|
v := new(Viper)
|
||||||
v.configName = "config"
|
v.configName = "config"
|
||||||
|
v.index = make(map[string]interface{})
|
||||||
v.config = make(map[string]interface{})
|
v.config = make(map[string]interface{})
|
||||||
v.override = make(map[string]interface{})
|
v.override = make(map[string]interface{})
|
||||||
v.defaults = make(map[string]interface{})
|
v.defaults = make(map[string]interface{})
|
||||||
|
@ -149,6 +156,8 @@ func New() *Viper {
|
||||||
} else {
|
} else {
|
||||||
v.AddConfigPath(wd)
|
v.AddConfigPath(wd)
|
||||||
}
|
}
|
||||||
|
v.indexToEnv = strings.NewReplacer(ENV_INDEX_DELIM, INDEX_DELIM)
|
||||||
|
v.envToIndex = strings.NewReplacer(INDEX_DELIM, ENV_INDEX_DELIM)
|
||||||
|
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
@ -214,6 +223,8 @@ func (v *Viper) mergeWithEnvPrefix(in string) string {
|
||||||
// key. This allows env vars which have different keys then the config object
|
// key. This allows env vars which have different keys then the config object
|
||||||
// keys
|
// keys
|
||||||
func (v *Viper) getEnv(key string) string {
|
func (v *Viper) getEnv(key string) string {
|
||||||
|
key = v.indexToEnv.Replace(key)
|
||||||
|
|
||||||
if v.envKeyReplacer != nil {
|
if v.envKeyReplacer != nil {
|
||||||
key = v.envKeyReplacer.Replace(key)
|
key = v.envKeyReplacer.Replace(key)
|
||||||
}
|
}
|
||||||
|
@ -317,12 +328,23 @@ func (v *Viper) providerPathExists(p *remoteProvider) bool {
|
||||||
func Get(key string) interface{} { return v.Get(key) }
|
func Get(key string) interface{} { return v.Get(key) }
|
||||||
func (v *Viper) Get(key string) interface{} {
|
func (v *Viper) Get(key string) interface{} {
|
||||||
key = strings.ToLower(key)
|
key = strings.ToLower(key)
|
||||||
val := v.find(key)
|
var val interface{}
|
||||||
|
|
||||||
|
if val = v.find(key); val == nil {
|
||||||
|
v.buildIndex()
|
||||||
|
val = v.findIndex(key)
|
||||||
|
}
|
||||||
|
|
||||||
if val == nil {
|
if val == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val = castVal(val)
|
||||||
|
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func castVal(val interface{}) interface{} {
|
||||||
switch val.(type) {
|
switch val.(type) {
|
||||||
case bool:
|
case bool:
|
||||||
return cast.ToBool(val)
|
return cast.ToBool(val)
|
||||||
|
@ -339,6 +361,7 @@ func (v *Viper) Get(key string) interface{} {
|
||||||
case []string:
|
case []string:
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -483,7 +506,7 @@ func (v *Viper) BindEnv(input ...string) (err error) {
|
||||||
return fmt.Errorf("BindEnv missing key to bind to")
|
return fmt.Errorf("BindEnv missing key to bind to")
|
||||||
}
|
}
|
||||||
|
|
||||||
key = strings.ToLower(input[0])
|
key = v.envToIndex.Replace(strings.ToLower(input[0]))
|
||||||
|
|
||||||
if len(input) == 1 {
|
if len(input) == 1 {
|
||||||
envkey = v.mergeWithEnvPrefix(key)
|
envkey = v.mergeWithEnvPrefix(key)
|
||||||
|
@ -496,6 +519,18 @@ func (v *Viper) BindEnv(input ...string) (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Viper) findIndex(key string) interface{} {
|
||||||
|
// Check index - it is safe ot check it first
|
||||||
|
// as the paths there should be materialized
|
||||||
|
// according to their priority
|
||||||
|
val, exists := v.index[key]
|
||||||
|
if exists {
|
||||||
|
jww.TRACE.Println(key, "found in index:", val)
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Given a key, find the value
|
// Given a key, find the value
|
||||||
// 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
|
||||||
|
@ -507,6 +542,12 @@ func (v *Viper) find(key string) interface{} {
|
||||||
// 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)
|
key = v.realKey(key)
|
||||||
|
|
||||||
|
val, exists = v.override[key]
|
||||||
|
if exists {
|
||||||
|
jww.TRACE.Println(key, "found in override:", val)
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
// PFlag Override first
|
// PFlag Override first
|
||||||
flag, exists := v.pflags[key]
|
flag, exists := v.pflags[key]
|
||||||
if exists {
|
if exists {
|
||||||
|
@ -516,12 +557,6 @@ func (v *Viper) find(key string) interface{} {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val, exists = v.override[key]
|
|
||||||
if exists {
|
|
||||||
jww.TRACE.Println(key, "found in override:", val)
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
|
@ -563,6 +598,15 @@ func (v *Viper) find(key string) interface{} {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recursively walks through the structure returned by
|
||||||
|
// AllSettings, indexing deeply nested values.
|
||||||
|
// It uses AllSettings in order to get a properly
|
||||||
|
// prioritized config structure.
|
||||||
|
func (v *Viper) buildIndex() {
|
||||||
|
v.index = make(map[string]interface{})
|
||||||
|
indexMap(v.AllSettings(), "", v.index)
|
||||||
|
}
|
||||||
|
|
||||||
// Check to see if the key has been set in any of the data locations
|
// Check to see if the key has been set in any of the data locations
|
||||||
func IsSet(key string) bool { return v.IsSet(key) }
|
func IsSet(key string) bool { return v.IsSet(key) }
|
||||||
func (v *Viper) IsSet(key string) bool {
|
func (v *Viper) IsSet(key string) bool {
|
||||||
|
@ -784,12 +828,26 @@ func (v *Viper) AllKeys() []string {
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return all keys found in the index, including the deeply
|
||||||
|
// nested keys.
|
||||||
|
func AllIndexes() []string { return v.AllIndexes() }
|
||||||
|
func (v *Viper) AllIndexes() []string {
|
||||||
|
v.buildIndex()
|
||||||
|
|
||||||
|
var indexes []string
|
||||||
|
for key, _ := range v.index {
|
||||||
|
indexes = append(indexes, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return indexes
|
||||||
|
}
|
||||||
|
|
||||||
// Return all settings as a map[string]interface{}
|
// Return all settings as a map[string]interface{}
|
||||||
func AllSettings() map[string]interface{} { return v.AllSettings() }
|
func AllSettings() map[string]interface{} { return v.AllSettings() }
|
||||||
func (v *Viper) AllSettings() map[string]interface{} {
|
func (v *Viper) AllSettings() map[string]interface{} {
|
||||||
m := map[string]interface{}{}
|
m := map[string]interface{}{}
|
||||||
for _, x := range v.AllKeys() {
|
for _, x := range v.AllKeys() {
|
||||||
m[x] = v.Get(x)
|
m[x] = castVal(v.find(x))
|
||||||
}
|
}
|
||||||
|
|
||||||
return m
|
return m
|
||||||
|
@ -881,6 +939,8 @@ func (v *Viper) findConfigFile() (string, error) {
|
||||||
// purposes.
|
// purposes.
|
||||||
func Debug() { v.Debug() }
|
func Debug() { v.Debug() }
|
||||||
func (v *Viper) Debug() {
|
func (v *Viper) Debug() {
|
||||||
|
fmt.Println("Index:")
|
||||||
|
pretty.Print(v.index)
|
||||||
fmt.Println("Config:")
|
fmt.Println("Config:")
|
||||||
pretty.Println(v.config)
|
pretty.Println(v.config)
|
||||||
fmt.Println("Key/Value Store:")
|
fmt.Println("Key/Value Store:")
|
||||||
|
|
180
viper_test.go
180
viper_test.go
|
@ -315,6 +315,184 @@ func TestAllKeys(t *testing.T) {
|
||||||
assert.Equal(t, all, AllSettings())
|
assert.Equal(t, all, AllSettings())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAllIndexes(t *testing.T) {
|
||||||
|
initConfigs()
|
||||||
|
expected := sort.StringSlice{"owner.organization", "name", "ppu", "clothing.trousers", "type", "batters.batter", "clothing.jacket", "hacker", "beard", "newkey", "owner.bio", "batters", "clothing", "owner", "eyes", "hobbies", "owner.dob", "title", "age", "id"}
|
||||||
|
expected.Sort()
|
||||||
|
|
||||||
|
var actual sort.StringSlice
|
||||||
|
actual = AllIndexes()
|
||||||
|
actual.Sort()
|
||||||
|
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildsIndex(t *testing.T) {
|
||||||
|
initConfigs()
|
||||||
|
dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z")
|
||||||
|
|
||||||
|
Set("super", map[string]interface{}{
|
||||||
|
"deep": map[string]interface{}{
|
||||||
|
"nested": "value",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expected := map[string]interface{}{
|
||||||
|
"super": map[string]interface{}{
|
||||||
|
"deep": map[string]interface{}{
|
||||||
|
"nested": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"super.deep": map[string]interface{}{
|
||||||
|
"nested": "value",
|
||||||
|
},
|
||||||
|
"super.deep.nested": "value",
|
||||||
|
"owner.organization": "MongoDB",
|
||||||
|
"batters.batter": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"type": "Regular",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"type": "Chocolate",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"type": "Blueberry",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"type": "Devil's Food",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"hobbies": []interface{}{
|
||||||
|
"skateboarding", "snowboarding", "go",
|
||||||
|
},
|
||||||
|
"title": "TOML Example",
|
||||||
|
"newkey": "remote",
|
||||||
|
"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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"eyes": "brown",
|
||||||
|
"age": 35,
|
||||||
|
"owner": map[string]interface{}{
|
||||||
|
"organization": "MongoDB",
|
||||||
|
"Bio": "MongoDB Chief Developer Advocate & Hacker at Large",
|
||||||
|
"dob": dob,
|
||||||
|
},
|
||||||
|
"owner.bio": "MongoDB Chief Developer Advocate & Hacker at Large",
|
||||||
|
"type": "donut",
|
||||||
|
"id": "0001",
|
||||||
|
"name": "Cake",
|
||||||
|
"hacker": true,
|
||||||
|
"ppu": 0.55,
|
||||||
|
"clothing": map[interface{}]interface{}{
|
||||||
|
"jacket": "leather",
|
||||||
|
"trousers": "denim",
|
||||||
|
},
|
||||||
|
"clothing.jacket": "leather",
|
||||||
|
"clothing.trousers": "denim",
|
||||||
|
"owner.dob": dob,
|
||||||
|
"beard": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
v.buildIndex()
|
||||||
|
|
||||||
|
assert.Equal(t, expected, v.index)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRetrievesIndex(t *testing.T) {
|
||||||
|
initConfigs()
|
||||||
|
|
||||||
|
dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z")
|
||||||
|
|
||||||
|
Set("super", map[string]interface{}{
|
||||||
|
"deep": map[string]interface{}{
|
||||||
|
"nested": "value",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expected := map[string]interface{}{
|
||||||
|
"super": map[string]interface{}{
|
||||||
|
"deep": map[string]interface{}{
|
||||||
|
"nested": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"super.deep": map[string]interface{}{
|
||||||
|
"nested": "value",
|
||||||
|
},
|
||||||
|
"super.deep.nested": "value",
|
||||||
|
"owner.organization": "MongoDB",
|
||||||
|
"batters.batter": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"type": "Regular",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"type": "Chocolate",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"type": "Blueberry",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"type": "Devil's Food",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"hobbies": []interface{}{
|
||||||
|
"skateboarding", "snowboarding", "go",
|
||||||
|
},
|
||||||
|
"title": "TOML Example",
|
||||||
|
"newkey": "remote",
|
||||||
|
"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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"eyes": "brown",
|
||||||
|
"age": 35,
|
||||||
|
"owner": map[string]interface{}{
|
||||||
|
"organization": "MongoDB",
|
||||||
|
"Bio": "MongoDB Chief Developer Advocate & Hacker at Large",
|
||||||
|
"dob": dob,
|
||||||
|
},
|
||||||
|
"owner.bio": "MongoDB Chief Developer Advocate & Hacker at Large",
|
||||||
|
"type": "donut",
|
||||||
|
"id": "0001",
|
||||||
|
"name": "Cake",
|
||||||
|
"hacker": true,
|
||||||
|
"ppu": 0.55,
|
||||||
|
"clothing": map[interface{}]interface{}{
|
||||||
|
"jacket": "leather",
|
||||||
|
"trousers": "denim",
|
||||||
|
},
|
||||||
|
"clothing.jacket": "leather",
|
||||||
|
"clothing.trousers": "denim",
|
||||||
|
"owner.dob": dob,
|
||||||
|
"beard": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
v.buildIndex()
|
||||||
|
|
||||||
|
for key, value := range expected {
|
||||||
|
assert.Equal(t, value, v.Get(key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCaseInSensitive(t *testing.T) {
|
func TestCaseInSensitive(t *testing.T) {
|
||||||
assert.Equal(t, true, Get("hacker"))
|
assert.Equal(t, true, Get("hacker"))
|
||||||
Set("Title", "Checking Case")
|
Set("Title", "Checking Case")
|
||||||
|
@ -359,6 +537,7 @@ func TestMarshal(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindPFlags(t *testing.T) {
|
func TestBindPFlags(t *testing.T) {
|
||||||
|
Reset()
|
||||||
flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||||
|
|
||||||
var testValues = map[string]*string{
|
var testValues = map[string]*string{
|
||||||
|
@ -415,6 +594,7 @@ func TestBindPFlag(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBoundCaseSensitivity(t *testing.T) {
|
func TestBoundCaseSensitivity(t *testing.T) {
|
||||||
|
initConfigs()
|
||||||
|
|
||||||
assert.Equal(t, "brown", Get("eyes"))
|
assert.Equal(t, "brown", Get("eyes"))
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue