mirror of
https://github.com/spf13/viper
synced 2025-05-07 20:57:18 +00:00
Merge branch 'master' into support-config-file-with-no-extension
This commit is contained in:
commit
895aadc1d8
8 changed files with 201 additions and 54 deletions
|
@ -4,10 +4,13 @@ language: go
|
|||
|
||||
env:
|
||||
global:
|
||||
- GO111MODULE="on"
|
||||
- GO111MODULE="on"
|
||||
- GOFLAGS="-mod=readonly"
|
||||
|
||||
go:
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
- tip
|
||||
|
||||
os:
|
||||
|
@ -27,5 +30,3 @@ script:
|
|||
after_success:
|
||||
- go get -u -d github.com/spf13/hugo
|
||||
- cd $GOPATH/src/github.com/spf13/hugo && make && ./hugo -s docs && cd -
|
||||
|
||||
sudo: false
|
||||
|
|
35
README.md
35
README.md
|
@ -27,7 +27,7 @@ to work within an application, and can handle all types of configuration needs
|
|||
and formats. It supports:
|
||||
|
||||
* setting defaults
|
||||
* reading from JSON, TOML, YAML, HCL, and Java properties config files
|
||||
* reading from JSON, TOML, YAML, HCL, envfile and Java properties config files
|
||||
* live watching and re-reading of config files (optional)
|
||||
* reading from environment variables
|
||||
* reading from remote config systems (etcd or Consul), and watching changes
|
||||
|
@ -46,7 +46,7 @@ Viper is here to help with that.
|
|||
|
||||
Viper does the following for you:
|
||||
|
||||
1. Find, load, and unmarshal a configuration file in JSON, TOML, YAML, HCL, or Java properties formats.
|
||||
1. Find, load, and unmarshal a configuration file in JSON, TOML, YAML, HCL, envfile or Java properties formats.
|
||||
2. Provide a mechanism to set default values for your different
|
||||
configuration options.
|
||||
3. Provide a mechanism to set override values for options specified through
|
||||
|
@ -87,7 +87,7 @@ viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "cat
|
|||
### Reading Config Files
|
||||
|
||||
Viper requires minimal configuration so it knows where to look for config files.
|
||||
Viper supports JSON, TOML, YAML, HCL, and Java Properties files. Viper can search multiple paths, but
|
||||
Viper supports JSON, TOML, YAML, HCL, envfile and Java Properties files. Viper can search multiple paths, but
|
||||
currently a single Viper instance only supports a single configuration file.
|
||||
Viper does not default to any configuration search paths leaving defaults decision
|
||||
to an application.
|
||||
|
@ -107,6 +107,20 @@ if err != nil { // Handle errors reading the config file
|
|||
}
|
||||
```
|
||||
|
||||
You can handle the specific case where no config file is found like this:
|
||||
|
||||
```go
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
||||
// Config file not found; ignore error if desired
|
||||
} else {
|
||||
// Config file was found but another error was produced
|
||||
}
|
||||
}
|
||||
|
||||
// Config file found and successfully parsed
|
||||
```
|
||||
|
||||
*NOTE:* You can also have a file without an extension and specify the format programmaticaly. For those configuration files that lie in the home of the user without any extension like `.bashrc`
|
||||
|
||||
### Writing Config Files
|
||||
|
@ -227,9 +241,9 @@ prefix.
|
|||
`BindEnv` takes one or two parameters. The first parameter is the key name, the
|
||||
second is the name of the environment variable. The name of the environment
|
||||
variable is case sensitive. If the ENV variable name is not provided, then
|
||||
Viper will automatically assume that the key name matches the ENV variable name,
|
||||
but the ENV variable is IN ALL CAPS. When you explicitly provide the ENV
|
||||
variable name, it **does not** automatically add the prefix.
|
||||
Viper will automatically assume that the ENV variable matches the following format: prefix + "_" + the key name in ALL CAPS. When you explicitly provide the ENV variable name (the second parameter),
|
||||
it **does not** automatically add the prefix. For example if the second parameter is "id",
|
||||
Viper will look for the ENV variable "ID".
|
||||
|
||||
One important thing to recognize when working with ENV variables is that the
|
||||
value will be read each time it is accessed. Viper does not fix the value when
|
||||
|
@ -374,7 +388,7 @@ package:
|
|||
|
||||
`import _ "github.com/spf13/viper/remote"`
|
||||
|
||||
Viper will read a config string (as JSON, TOML, YAML or HCL) retrieved from a path
|
||||
Viper will read a config string (as JSON, TOML, YAML, HCL or envfile) retrieved from a path
|
||||
in a Key/Value store such as etcd or Consul. These values take precedence over
|
||||
default values, but are overridden by configuration values retrieved from disk,
|
||||
flags, or environment variables.
|
||||
|
@ -409,7 +423,7 @@ how to use Consul.
|
|||
#### etcd
|
||||
```go
|
||||
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, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop"
|
||||
viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
|
||||
err := viper.ReadRemoteConfig()
|
||||
```
|
||||
|
||||
|
@ -437,7 +451,7 @@ fmt.Println(viper.Get("hostname")) // myhostname.com
|
|||
|
||||
```go
|
||||
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, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop"
|
||||
viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
|
||||
err := viper.ReadRemoteConfig()
|
||||
```
|
||||
|
||||
|
@ -448,7 +462,7 @@ err := viper.ReadRemoteConfig()
|
|||
var runtime_viper = viper.New()
|
||||
|
||||
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, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop"
|
||||
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", "env", "dotenv"
|
||||
|
||||
// read from remote config the first time.
|
||||
err := runtime_viper.ReadRemoteConfig()
|
||||
|
@ -484,6 +498,7 @@ The following functions and methods exist:
|
|||
* `GetBool(key string) : bool`
|
||||
* `GetFloat64(key string) : float64`
|
||||
* `GetInt(key string) : int`
|
||||
* `GetIntSlice(key string) : []int`
|
||||
* `GetString(key string) : string`
|
||||
* `GetStringMap(key string) : map[string]interface{}`
|
||||
* `GetStringMapString(key string) : map[string]string`
|
||||
|
|
2
flags.go
2
flags.go
|
@ -36,7 +36,7 @@ type pflagValue struct {
|
|||
flag *pflag.Flag
|
||||
}
|
||||
|
||||
// HasChanges returns whether the flag has changes or not.
|
||||
// HasChanged returns whether the flag has changes or not.
|
||||
func (p pflagValue) HasChanged() bool {
|
||||
return p.flag.Changed
|
||||
}
|
||||
|
|
5
go.mod
5
go.mod
|
@ -1,5 +1,7 @@
|
|||
module github.com/spf13/viper
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect
|
||||
github.com/coreos/bbolt v1.3.2 // indirect
|
||||
|
@ -28,6 +30,7 @@ require (
|
|||
github.com/spf13/jwalterweatherman v1.0.0
|
||||
github.com/spf13/pflag v1.0.3
|
||||
github.com/stretchr/testify v1.2.2
|
||||
github.com/subosito/gotenv v1.2.0
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect
|
||||
github.com/ugorji/go v1.1.4 // indirect
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
|
||||
|
@ -39,5 +42,5 @@ require (
|
|||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 // indirect
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
|
||||
google.golang.org/grpc v1.21.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
gopkg.in/yaml.v2 v2.2.4
|
||||
)
|
||||
|
|
8
go.sum
8
go.sum
|
@ -71,8 +71,6 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
|||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
|
@ -117,6 +115,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
|||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
|
||||
|
@ -175,6 +175,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
|
|||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
|
@ -77,11 +77,12 @@ func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) {
|
|||
var err error
|
||||
|
||||
if rp.SecretKeyring() != "" {
|
||||
kr, err := os.Open(rp.SecretKeyring())
|
||||
defer kr.Close()
|
||||
var kr *os.File
|
||||
kr, err = os.Open(rp.SecretKeyring())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer kr.Close()
|
||||
if rp.Provider() == "etcd" {
|
||||
cm, err = crypt.NewEtcdConfigManager([]string{rp.Endpoint()}, kr)
|
||||
} else {
|
||||
|
|
46
viper.go
46
viper.go
|
@ -3,7 +3,7 @@
|
|||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Viper is a application configuration system.
|
||||
// Viper is an application configuration system.
|
||||
// It believes that applications can be configured a variety of ways
|
||||
// via flags, ENVIRONMENT variables, configuration files retrieved
|
||||
// from the file system, or a remote key/value store.
|
||||
|
@ -226,7 +226,7 @@ func New() *Viper {
|
|||
return v
|
||||
}
|
||||
|
||||
// Intended for testing, will reset all to default settings.
|
||||
// Reset is intended for testing, will reset all to default settings.
|
||||
// In the public interface for the viper package so applications
|
||||
// can use it in their testing as well.
|
||||
func Reset() {
|
||||
|
@ -295,6 +295,7 @@ func (v *Viper) WatchConfig() {
|
|||
filename, err := v.getConfigFile()
|
||||
if err != nil {
|
||||
log.Printf("error: %v\n", err)
|
||||
initWG.Done()
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -344,7 +345,7 @@ func (v *Viper) WatchConfig() {
|
|||
}
|
||||
}()
|
||||
watcher.Add(configDir)
|
||||
initWG.Done() // done initalizing the watch in this go routine, so the parent routine can move on...
|
||||
initWG.Done() // done initializing the watch in this go routine, so the parent routine can move on...
|
||||
eventsWG.Wait() // now, wait for event loop to end in this go-routine...
|
||||
}()
|
||||
initWG.Wait() // make sure that the go routine above fully ended before returning
|
||||
|
@ -797,6 +798,12 @@ func (v *Viper) GetDuration(key string) time.Duration {
|
|||
return cast.ToDuration(v.Get(key))
|
||||
}
|
||||
|
||||
// GetIntSlice returns the value associated with the key as a slice of int values.
|
||||
func GetIntSlice(key string) []int { return v.GetIntSlice(key) }
|
||||
func (v *Viper) GetIntSlice(key string) []int {
|
||||
return cast.ToIntSlice(v.Get(key))
|
||||
}
|
||||
|
||||
// GetStringSlice returns the value associated with the key as a slice of strings.
|
||||
func GetStringSlice(key string) []string { return v.GetStringSlice(key) }
|
||||
func (v *Viper) GetStringSlice(key string) []string {
|
||||
|
@ -1136,8 +1143,8 @@ func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) {
|
|||
v.envKeyReplacer = r
|
||||
}
|
||||
|
||||
// Aliases provide another accessor for the same key.
|
||||
// This enables one to change a name without breaking the application
|
||||
// RegisterAlias creates an alias that provides another accessor for the same key.
|
||||
// This enables one to change a name without breaking the application.
|
||||
func RegisterAlias(alias string, key string) { v.RegisterAlias(alias, key) }
|
||||
func (v *Viper) RegisterAlias(alias string, key string) {
|
||||
v.registerAlias(alias, strings.ToLower(key))
|
||||
|
@ -1357,21 +1364,21 @@ func (v *Viper) writeConfig(filename string, force bool) error {
|
|||
if v.config == nil {
|
||||
v.config = make(map[string]interface{})
|
||||
}
|
||||
var flags int
|
||||
if force == true {
|
||||
flags = os.O_CREATE | os.O_TRUNC | os.O_WRONLY
|
||||
} else {
|
||||
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||
flags = os.O_WRONLY
|
||||
} else {
|
||||
return fmt.Errorf("File: %s exists. Use WriteConfig to overwrite.", filename)
|
||||
}
|
||||
flags := os.O_CREATE | os.O_TRUNC | os.O_WRONLY
|
||||
if !force {
|
||||
flags |= os.O_EXCL
|
||||
}
|
||||
f, err := v.fs.OpenFile(filename, flags, v.configPermissions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return v.marshalWriter(f, configType)
|
||||
defer f.Close()
|
||||
|
||||
if err := v.marshalWriter(f, configType); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return f.Sync()
|
||||
}
|
||||
|
||||
// Unmarshal a Reader into a map.
|
||||
|
@ -1395,7 +1402,7 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
|
|||
}
|
||||
|
||||
case "hcl":
|
||||
obj, err := hcl.Parse(string(buf.Bytes()))
|
||||
obj, err := hcl.Parse(buf.String())
|
||||
if err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
|
@ -1462,6 +1469,9 @@ func (v *Viper) marshalWriter(f afero.File, configType string) error {
|
|||
|
||||
case "hcl":
|
||||
b, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
ast, err := hcl.Parse(string(b))
|
||||
if err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
|
@ -1713,7 +1723,7 @@ func (v *Viper) AllKeys() []string {
|
|||
m = v.flattenAndMergeMap(m, v.defaults, "")
|
||||
|
||||
// convert set of paths to list
|
||||
a := []string{}
|
||||
a := make([]string, 0, len(m))
|
||||
for x := range m {
|
||||
a = append(a, x)
|
||||
}
|
||||
|
@ -1762,7 +1772,7 @@ func (v *Viper) flattenAndMergeMap(shadow map[string]bool, m map[string]interfac
|
|||
func (v *Viper) mergeFlatMap(shadow map[string]bool, m map[string]interface{}) map[string]bool {
|
||||
// scan keys
|
||||
outer:
|
||||
for k, _ := range m {
|
||||
for k := range m {
|
||||
path := strings.Split(k, v.keyDelim)
|
||||
// scan intermediate paths
|
||||
var parentKey string
|
||||
|
|
147
viper_test.go
147
viper_test.go
|
@ -8,7 +8,6 @@ package viper
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
@ -209,11 +208,16 @@ func initHcl() {
|
|||
func initDirs(t *testing.T) (string, string, func()) {
|
||||
|
||||
var (
|
||||
testDirs = []string{`a a`, `b`, `c\c`, `D_`}
|
||||
testDirs = []string{`a a`, `b`, `C_`}
|
||||
config = `improbable`
|
||||
)
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
testDirs = append(testDirs, `d\d`)
|
||||
}
|
||||
|
||||
root, err := ioutil.TempDir("", "")
|
||||
require.NoError(t, err, "Failed to create temporary directory")
|
||||
|
||||
cleanup := true
|
||||
defer func() {
|
||||
|
@ -226,7 +230,7 @@ func initDirs(t *testing.T) (string, string, func()) {
|
|||
assert.Nil(t, err)
|
||||
|
||||
err = os.Chdir(root)
|
||||
assert.Nil(t, err)
|
||||
require.Nil(t, err)
|
||||
|
||||
for _, dir := range testDirs {
|
||||
err = os.Mkdir(dir, 0750)
|
||||
|
@ -246,7 +250,7 @@ func initDirs(t *testing.T) (string, string, func()) {
|
|||
}
|
||||
}
|
||||
|
||||
//stubs for PFlag Values
|
||||
// stubs for PFlag Values
|
||||
type stringValue string
|
||||
|
||||
func newStringValue(val string, p *string) *stringValue {
|
||||
|
@ -264,7 +268,7 @@ func (s *stringValue) Type() string {
|
|||
}
|
||||
|
||||
func (s *stringValue) String() string {
|
||||
return fmt.Sprintf("%s", *s)
|
||||
return string(*s)
|
||||
}
|
||||
|
||||
func TestBasics(t *testing.T) {
|
||||
|
@ -433,7 +437,10 @@ func TestEmptyEnv(t *testing.T) {
|
|||
BindEnv("type") // Empty environment variable
|
||||
BindEnv("name") // Bound, but not set environment variable
|
||||
|
||||
os.Clearenv()
|
||||
os.Unsetenv("type")
|
||||
os.Unsetenv("TYPE")
|
||||
os.Unsetenv("name")
|
||||
os.Unsetenv("NAME")
|
||||
|
||||
os.Setenv("TYPE", "")
|
||||
|
||||
|
@ -449,7 +456,10 @@ func TestEmptyEnv_Allowed(t *testing.T) {
|
|||
BindEnv("type") // Empty environment variable
|
||||
BindEnv("name") // Bound, but not set environment variable
|
||||
|
||||
os.Clearenv()
|
||||
os.Unsetenv("type")
|
||||
os.Unsetenv("TYPE")
|
||||
os.Unsetenv("name")
|
||||
os.Unsetenv("NAME")
|
||||
|
||||
os.Setenv("TYPE", "")
|
||||
|
||||
|
@ -509,14 +519,92 @@ func TestSetEnvKeyReplacer(t *testing.T) {
|
|||
func TestAllKeys(t *testing.T) {
|
||||
initConfigs()
|
||||
|
||||
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",
|
||||
"title_dotenv", "type_dotenv", "name_dotenv",
|
||||
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",
|
||||
"title_dotenv",
|
||||
"type_dotenv",
|
||||
"name_dotenv",
|
||||
}
|
||||
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[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}}}}, "title_dotenv": "DotEnv Example", "type_dotenv": "donut", "name_dotenv": "Cake"}
|
||||
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{}{
|
||||
{
|
||||
"foo": []map[string]interface{}{
|
||||
{"key": 1},
|
||||
{"key": 2},
|
||||
{"key": 3},
|
||||
{"key": 4}},
|
||||
},
|
||||
},
|
||||
"title_dotenv": "DotEnv Example",
|
||||
"type_dotenv": "donut",
|
||||
"name_dotenv": "Cake",
|
||||
}
|
||||
|
||||
var allkeys sort.StringSlice
|
||||
allkeys = AllKeys()
|
||||
allkeys := sort.StringSlice(AllKeys())
|
||||
allkeys.Sort()
|
||||
ks.Sort()
|
||||
|
||||
|
@ -780,7 +868,7 @@ func TestBindPFlag(t *testing.T) {
|
|||
assert.Equal(t, testString, Get("testvalue"))
|
||||
|
||||
flag.Value.Set("testing_mutate")
|
||||
flag.Changed = true //hack for pflag usage
|
||||
flag.Changed = true // hack for pflag usage
|
||||
|
||||
assert.Equal(t, "testing_mutate", Get("testvalue"))
|
||||
|
||||
|
@ -969,6 +1057,7 @@ func TestDirsSearch(t *testing.T) {
|
|||
v.SetDefault(`key`, `default`)
|
||||
|
||||
entries, err := ioutil.ReadDir(root)
|
||||
assert.Nil(t, err)
|
||||
for _, e := range entries {
|
||||
if e.IsDir() {
|
||||
v.AddConfigPath(e.Name())
|
||||
|
@ -978,7 +1067,7 @@ func TestDirsSearch(t *testing.T) {
|
|||
err = v.ReadInConfig()
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, `value is `+path.Base(v.configPaths[0]), v.GetString(`key`))
|
||||
assert.Equal(t, `value is `+filepath.Base(v.configPaths[0]), v.GetString(`key`))
|
||||
}
|
||||
|
||||
func TestWrongDirsSearchNotFound(t *testing.T) {
|
||||
|
@ -1279,6 +1368,9 @@ hello:
|
|||
universe:
|
||||
- mw
|
||||
- ad
|
||||
ints:
|
||||
- 1
|
||||
- 2
|
||||
fu: bar
|
||||
`)
|
||||
|
||||
|
@ -1345,6 +1437,10 @@ func TestMergeConfig(t *testing.T) {
|
|||
t.Fatalf("len(universe) != 2, = %d", len(universe))
|
||||
}
|
||||
|
||||
if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 {
|
||||
t.Fatalf("len(ints) != 2, = %d", len(ints))
|
||||
}
|
||||
|
||||
if fu := v.GetString("fu"); fu != "bar" {
|
||||
t.Fatalf("fu != \"bar\", = %s", fu)
|
||||
}
|
||||
|
@ -1385,6 +1481,10 @@ func TestMergeConfigNoMerge(t *testing.T) {
|
|||
t.Fatalf("len(universe) != 2, = %d", len(universe))
|
||||
}
|
||||
|
||||
if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 {
|
||||
t.Fatalf("len(ints) != 2, = %d", len(ints))
|
||||
}
|
||||
|
||||
if fu := v.GetString("fu"); fu != "bar" {
|
||||
t.Fatalf("fu != \"bar\", = %s", fu)
|
||||
}
|
||||
|
@ -1756,6 +1856,23 @@ func TestWatchFile(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
func TestUnmarshal_DotSeparatorBackwardCompatibility(t *testing.T) {
|
||||
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||
flags.String("foo.bar", "cobra_flag", "")
|
||||
|
||||
v := New()
|
||||
assert.NoError(t, v.BindPFlags(flags))
|
||||
|
||||
config := &struct {
|
||||
Foo struct {
|
||||
Bar string
|
||||
}
|
||||
}{}
|
||||
|
||||
assert.NoError(t, v.Unmarshal(config))
|
||||
assert.Equal(t, "cobra_flag", config.Foo.Bar)
|
||||
}
|
||||
|
||||
func BenchmarkGetBool(b *testing.B) {
|
||||
key := "BenchmarkGetBool"
|
||||
v = New()
|
||||
|
@ -1780,7 +1897,7 @@ func BenchmarkGet(b *testing.B) {
|
|||
}
|
||||
}
|
||||
|
||||
// This is the "perfect result" for the above.
|
||||
// BenchmarkGetBoolFromMap is the "perfect result" for the above.
|
||||
func BenchmarkGetBoolFromMap(b *testing.B) {
|
||||
m := make(map[string]bool)
|
||||
key := "BenchmarkGetBool"
|
||||
|
|
Loading…
Add table
Reference in a new issue