mirror of
https://github.com/spf13/viper
synced 2025-05-10 22:27: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:
|
env:
|
||||||
global:
|
global:
|
||||||
- GO111MODULE="on"
|
- GO111MODULE="on"
|
||||||
|
- GOFLAGS="-mod=readonly"
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.11.x
|
- 1.11.x
|
||||||
|
- 1.12.x
|
||||||
|
- 1.13.x
|
||||||
- tip
|
- tip
|
||||||
|
|
||||||
os:
|
os:
|
||||||
|
@ -27,5 +30,3 @@ script:
|
||||||
after_success:
|
after_success:
|
||||||
- go get -u -d github.com/spf13/hugo
|
- go get -u -d github.com/spf13/hugo
|
||||||
- cd $GOPATH/src/github.com/spf13/hugo && make && ./hugo -s docs && cd -
|
- 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:
|
and formats. It supports:
|
||||||
|
|
||||||
* setting defaults
|
* 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)
|
* 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
|
||||||
|
@ -46,7 +46,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, 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
|
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
|
||||||
|
@ -87,7 +87,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, 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.
|
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.
|
||||||
|
@ -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`
|
*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
|
### Writing Config Files
|
||||||
|
@ -227,9 +241,9 @@ prefix.
|
||||||
`BindEnv` takes one or two parameters. The first parameter is the key name, the
|
`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
|
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
|
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,
|
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),
|
||||||
but the ENV variable is IN ALL CAPS. When you explicitly provide the ENV
|
it **does not** automatically add the prefix. For example if the second parameter is "id",
|
||||||
variable name, it **does not** automatically add the prefix.
|
Viper will look for the ENV variable "ID".
|
||||||
|
|
||||||
One important thing to recognize when working with ENV variables is that the
|
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
|
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"`
|
`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
|
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,
|
default values, but are overridden by configuration values retrieved from disk,
|
||||||
flags, or environment variables.
|
flags, or environment variables.
|
||||||
|
@ -409,7 +423,7 @@ how to use Consul.
|
||||||
#### etcd
|
#### etcd
|
||||||
```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, 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()
|
err := viper.ReadRemoteConfig()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -437,7 +451,7 @@ fmt.Println(viper.Get("hostname")) // myhostname.com
|
||||||
|
|
||||||
```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, 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()
|
err := viper.ReadRemoteConfig()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -448,7 +462,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, 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.
|
// read from remote config the first time.
|
||||||
err := runtime_viper.ReadRemoteConfig()
|
err := runtime_viper.ReadRemoteConfig()
|
||||||
|
@ -484,6 +498,7 @@ The following functions and methods exist:
|
||||||
* `GetBool(key string) : bool`
|
* `GetBool(key string) : bool`
|
||||||
* `GetFloat64(key string) : float64`
|
* `GetFloat64(key string) : float64`
|
||||||
* `GetInt(key string) : int`
|
* `GetInt(key string) : int`
|
||||||
|
* `GetIntSlice(key string) : []int`
|
||||||
* `GetString(key string) : string`
|
* `GetString(key string) : string`
|
||||||
* `GetStringMap(key string) : map[string]interface{}`
|
* `GetStringMap(key string) : map[string]interface{}`
|
||||||
* `GetStringMapString(key string) : map[string]string`
|
* `GetStringMapString(key string) : map[string]string`
|
||||||
|
|
2
flags.go
2
flags.go
|
@ -36,7 +36,7 @@ type pflagValue struct {
|
||||||
flag *pflag.Flag
|
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 {
|
func (p pflagValue) HasChanged() bool {
|
||||||
return p.flag.Changed
|
return p.flag.Changed
|
||||||
}
|
}
|
||||||
|
|
5
go.mod
5
go.mod
|
@ -1,5 +1,7 @@
|
||||||
module github.com/spf13/viper
|
module github.com/spf13/viper
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect
|
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect
|
||||||
github.com/coreos/bbolt v1.3.2 // indirect
|
github.com/coreos/bbolt v1.3.2 // indirect
|
||||||
|
@ -28,6 +30,7 @@ require (
|
||||||
github.com/spf13/jwalterweatherman v1.0.0
|
github.com/spf13/jwalterweatherman v1.0.0
|
||||||
github.com/spf13/pflag v1.0.3
|
github.com/spf13/pflag v1.0.3
|
||||||
github.com/stretchr/testify v1.2.2
|
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/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect
|
||||||
github.com/ugorji/go v1.1.4 // indirect
|
github.com/ugorji/go v1.1.4 // indirect
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // 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/net v0.0.0-20190522155817-f3200d17e092 // indirect
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
|
||||||
google.golang.org/grpc v1.21.0 // 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/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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
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 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
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/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 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
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 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
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=
|
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/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.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.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
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=
|
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
|
var err error
|
||||||
|
|
||||||
if rp.SecretKeyring() != "" {
|
if rp.SecretKeyring() != "" {
|
||||||
kr, err := os.Open(rp.SecretKeyring())
|
var kr *os.File
|
||||||
defer kr.Close()
|
kr, err = os.Open(rp.SecretKeyring())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer kr.Close()
|
||||||
if rp.Provider() == "etcd" {
|
if rp.Provider() == "etcd" {
|
||||||
cm, err = crypt.NewEtcdConfigManager([]string{rp.Endpoint()}, kr)
|
cm, err = crypt.NewEtcdConfigManager([]string{rp.Endpoint()}, kr)
|
||||||
} else {
|
} else {
|
||||||
|
|
46
viper.go
46
viper.go
|
@ -3,7 +3,7 @@
|
||||||
// Use of this source code is governed by an MIT-style
|
// Use of this source code is governed by an MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// 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
|
// It believes that applications can be configured a variety of ways
|
||||||
// via flags, ENVIRONMENT variables, configuration files retrieved
|
// via flags, ENVIRONMENT variables, configuration files retrieved
|
||||||
// from the file system, or a remote key/value store.
|
// from the file system, or a remote key/value store.
|
||||||
|
@ -226,7 +226,7 @@ func New() *Viper {
|
||||||
return v
|
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
|
// In the public interface for the viper package so applications
|
||||||
// can use it in their testing as well.
|
// can use it in their testing as well.
|
||||||
func Reset() {
|
func Reset() {
|
||||||
|
@ -295,6 +295,7 @@ func (v *Viper) WatchConfig() {
|
||||||
filename, err := v.getConfigFile()
|
filename, err := v.getConfigFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error: %v\n", err)
|
log.Printf("error: %v\n", err)
|
||||||
|
initWG.Done()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,7 +345,7 @@ func (v *Viper) WatchConfig() {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
watcher.Add(configDir)
|
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...
|
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
|
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))
|
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.
|
// GetStringSlice returns the value associated with the key as a slice of strings.
|
||||||
func GetStringSlice(key string) []string { return v.GetStringSlice(key) }
|
func GetStringSlice(key string) []string { return v.GetStringSlice(key) }
|
||||||
func (v *Viper) GetStringSlice(key string) []string {
|
func (v *Viper) GetStringSlice(key string) []string {
|
||||||
|
@ -1136,8 +1143,8 @@ func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) {
|
||||||
v.envKeyReplacer = r
|
v.envKeyReplacer = r
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aliases provide another accessor for the same key.
|
// RegisterAlias creates an alias that provides another accessor for the same key.
|
||||||
// This enables one to change a name without breaking the application
|
// This enables one to change a name without breaking the application.
|
||||||
func RegisterAlias(alias string, key string) { v.RegisterAlias(alias, key) }
|
func RegisterAlias(alias string, key string) { v.RegisterAlias(alias, key) }
|
||||||
func (v *Viper) RegisterAlias(alias string, key string) {
|
func (v *Viper) RegisterAlias(alias string, key string) {
|
||||||
v.registerAlias(alias, strings.ToLower(key))
|
v.registerAlias(alias, strings.ToLower(key))
|
||||||
|
@ -1357,21 +1364,21 @@ func (v *Viper) writeConfig(filename string, force bool) error {
|
||||||
if v.config == nil {
|
if v.config == nil {
|
||||||
v.config = make(map[string]interface{})
|
v.config = make(map[string]interface{})
|
||||||
}
|
}
|
||||||
var flags int
|
flags := os.O_CREATE | os.O_TRUNC | os.O_WRONLY
|
||||||
if force == true {
|
if !force {
|
||||||
flags = os.O_CREATE | os.O_TRUNC | os.O_WRONLY
|
flags |= os.O_EXCL
|
||||||
} 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
f, err := v.fs.OpenFile(filename, flags, v.configPermissions)
|
f, err := v.fs.OpenFile(filename, flags, v.configPermissions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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.
|
// Unmarshal a Reader into a map.
|
||||||
|
@ -1395,7 +1402,7 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
case "hcl":
|
case "hcl":
|
||||||
obj, err := hcl.Parse(string(buf.Bytes()))
|
obj, err := hcl.Parse(buf.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ConfigParseError{err}
|
return ConfigParseError{err}
|
||||||
}
|
}
|
||||||
|
@ -1462,6 +1469,9 @@ func (v *Viper) marshalWriter(f afero.File, configType string) error {
|
||||||
|
|
||||||
case "hcl":
|
case "hcl":
|
||||||
b, err := json.Marshal(c)
|
b, err := json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
return ConfigMarshalError{err}
|
||||||
|
}
|
||||||
ast, err := hcl.Parse(string(b))
|
ast, err := hcl.Parse(string(b))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ConfigMarshalError{err}
|
return ConfigMarshalError{err}
|
||||||
|
@ -1713,7 +1723,7 @@ func (v *Viper) AllKeys() []string {
|
||||||
m = v.flattenAndMergeMap(m, v.defaults, "")
|
m = v.flattenAndMergeMap(m, v.defaults, "")
|
||||||
|
|
||||||
// convert set of paths to list
|
// convert set of paths to list
|
||||||
a := []string{}
|
a := make([]string, 0, len(m))
|
||||||
for x := range m {
|
for x := range m {
|
||||||
a = append(a, x)
|
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 {
|
func (v *Viper) mergeFlatMap(shadow map[string]bool, m map[string]interface{}) map[string]bool {
|
||||||
// scan keys
|
// scan keys
|
||||||
outer:
|
outer:
|
||||||
for k, _ := range m {
|
for k := range m {
|
||||||
path := strings.Split(k, v.keyDelim)
|
path := strings.Split(k, v.keyDelim)
|
||||||
// scan intermediate paths
|
// scan intermediate paths
|
||||||
var parentKey string
|
var parentKey string
|
||||||
|
|
147
viper_test.go
147
viper_test.go
|
@ -8,7 +8,6 @@ package viper
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -209,11 +208,16 @@ func initHcl() {
|
||||||
func initDirs(t *testing.T) (string, string, func()) {
|
func initDirs(t *testing.T) (string, string, func()) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
testDirs = []string{`a a`, `b`, `c\c`, `D_`}
|
testDirs = []string{`a a`, `b`, `C_`}
|
||||||
config = `improbable`
|
config = `improbable`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
testDirs = append(testDirs, `d\d`)
|
||||||
|
}
|
||||||
|
|
||||||
root, err := ioutil.TempDir("", "")
|
root, err := ioutil.TempDir("", "")
|
||||||
|
require.NoError(t, err, "Failed to create temporary directory")
|
||||||
|
|
||||||
cleanup := true
|
cleanup := true
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -226,7 +230,7 @@ func initDirs(t *testing.T) (string, string, func()) {
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
err = os.Chdir(root)
|
err = os.Chdir(root)
|
||||||
assert.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
for _, dir := range testDirs {
|
for _, dir := range testDirs {
|
||||||
err = os.Mkdir(dir, 0750)
|
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
|
type stringValue string
|
||||||
|
|
||||||
func newStringValue(val string, p *string) *stringValue {
|
func newStringValue(val string, p *string) *stringValue {
|
||||||
|
@ -264,7 +268,7 @@ func (s *stringValue) Type() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stringValue) String() string {
|
func (s *stringValue) String() string {
|
||||||
return fmt.Sprintf("%s", *s)
|
return string(*s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBasics(t *testing.T) {
|
func TestBasics(t *testing.T) {
|
||||||
|
@ -433,7 +437,10 @@ func TestEmptyEnv(t *testing.T) {
|
||||||
BindEnv("type") // Empty environment variable
|
BindEnv("type") // Empty environment variable
|
||||||
BindEnv("name") // Bound, but not set 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", "")
|
os.Setenv("TYPE", "")
|
||||||
|
|
||||||
|
@ -449,7 +456,10 @@ func TestEmptyEnv_Allowed(t *testing.T) {
|
||||||
BindEnv("type") // Empty environment variable
|
BindEnv("type") // Empty environment variable
|
||||||
BindEnv("name") // Bound, but not set 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", "")
|
os.Setenv("TYPE", "")
|
||||||
|
|
||||||
|
@ -509,14 +519,92 @@ func TestSetEnvKeyReplacer(t *testing.T) {
|
||||||
func TestAllKeys(t *testing.T) {
|
func TestAllKeys(t *testing.T) {
|
||||||
initConfigs()
|
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",
|
ks := sort.StringSlice{
|
||||||
"title_dotenv", "type_dotenv", "name_dotenv",
|
"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")
|
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 := sort.StringSlice(AllKeys())
|
||||||
allkeys = AllKeys()
|
|
||||||
allkeys.Sort()
|
allkeys.Sort()
|
||||||
ks.Sort()
|
ks.Sort()
|
||||||
|
|
||||||
|
@ -780,7 +868,7 @@ func TestBindPFlag(t *testing.T) {
|
||||||
assert.Equal(t, testString, Get("testvalue"))
|
assert.Equal(t, testString, Get("testvalue"))
|
||||||
|
|
||||||
flag.Value.Set("testing_mutate")
|
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"))
|
assert.Equal(t, "testing_mutate", Get("testvalue"))
|
||||||
|
|
||||||
|
@ -969,6 +1057,7 @@ func TestDirsSearch(t *testing.T) {
|
||||||
v.SetDefault(`key`, `default`)
|
v.SetDefault(`key`, `default`)
|
||||||
|
|
||||||
entries, err := ioutil.ReadDir(root)
|
entries, err := ioutil.ReadDir(root)
|
||||||
|
assert.Nil(t, err)
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
if e.IsDir() {
|
if e.IsDir() {
|
||||||
v.AddConfigPath(e.Name())
|
v.AddConfigPath(e.Name())
|
||||||
|
@ -978,7 +1067,7 @@ func TestDirsSearch(t *testing.T) {
|
||||||
err = v.ReadInConfig()
|
err = v.ReadInConfig()
|
||||||
assert.Nil(t, err)
|
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) {
|
func TestWrongDirsSearchNotFound(t *testing.T) {
|
||||||
|
@ -1279,6 +1368,9 @@ hello:
|
||||||
universe:
|
universe:
|
||||||
- mw
|
- mw
|
||||||
- ad
|
- ad
|
||||||
|
ints:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
fu: bar
|
fu: bar
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
@ -1345,6 +1437,10 @@ func TestMergeConfig(t *testing.T) {
|
||||||
t.Fatalf("len(universe) != 2, = %d", len(universe))
|
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" {
|
if fu := v.GetString("fu"); fu != "bar" {
|
||||||
t.Fatalf("fu != \"bar\", = %s", fu)
|
t.Fatalf("fu != \"bar\", = %s", fu)
|
||||||
}
|
}
|
||||||
|
@ -1385,6 +1481,10 @@ func TestMergeConfigNoMerge(t *testing.T) {
|
||||||
t.Fatalf("len(universe) != 2, = %d", len(universe))
|
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" {
|
if fu := v.GetString("fu"); fu != "bar" {
|
||||||
t.Fatalf("fu != \"bar\", = %s", fu)
|
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) {
|
func BenchmarkGetBool(b *testing.B) {
|
||||||
key := "BenchmarkGetBool"
|
key := "BenchmarkGetBool"
|
||||||
v = New()
|
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) {
|
func BenchmarkGetBoolFromMap(b *testing.B) {
|
||||||
m := make(map[string]bool)
|
m := make(map[string]bool)
|
||||||
key := "BenchmarkGetBool"
|
key := "BenchmarkGetBool"
|
||||||
|
|
Loading…
Add table
Reference in a new issue