mirror of
https://github.com/spf13/viper.git
synced 2024-04-21 12:31:38 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f69c91abd3 | ||
|
|
907c19d40d | ||
|
|
d493c32b69 | ||
|
|
fb7a06477f | ||
|
|
15738813a0 | ||
|
|
8dc2790b02 | ||
|
|
b5e8006cbe | ||
|
|
00ed41cdba | ||
|
|
aafc9e6bc7 | ||
|
|
1a0c4a370c | ||
|
|
4dddf7c62e | ||
|
|
8ef37cbca7 | ||
|
|
d9cca5ef33 | ||
|
|
266e588e9e |
+2
-2
@@ -2,8 +2,8 @@ go_import_path: github.com/spf13/viper
|
||||
|
||||
language: go
|
||||
go:
|
||||
- 1.7.5
|
||||
- 1.8
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- tip
|
||||
|
||||
os:
|
||||
|
||||
@@ -185,13 +185,13 @@ with ENV:
|
||||
* `AutomaticEnv()`
|
||||
* `BindEnv(string...) : error`
|
||||
* `SetEnvPrefix(string)`
|
||||
* `SetEnvReplacer(string...) *strings.Replacer`
|
||||
* `SetEnvKeyReplacer(string...) *strings.Replacer`
|
||||
|
||||
_When working with ENV variables, it’s important to recognize that Viper
|
||||
treats ENV variables as case sensitive._
|
||||
|
||||
Viper provides a mechanism to try to ensure that ENV variables are unique. By
|
||||
using `SetEnvPrefix`, you can tell Viper to use add a prefix while reading from
|
||||
using `SetEnvPrefix`, you can tell Viper to use a prefix while reading from
|
||||
the environment variables. Both `BindEnv` and `AutomaticEnv` will use this
|
||||
prefix.
|
||||
|
||||
@@ -212,7 +212,7 @@ time a `viper.Get` request is made. It will apply the following rules. It will
|
||||
check for a environment variable with a name matching the key uppercased and
|
||||
prefixed with the `EnvPrefix` if set.
|
||||
|
||||
`SetEnvReplacer` allows you to use a `strings.Replacer` object to rewrite Env
|
||||
`SetEnvKeyReplacer` allows you to use a `strings.Replacer` object to rewrite Env
|
||||
keys to an extent. This is useful if you want to use `-` or something in your
|
||||
`Get()` calls, but want your environmental variables to use `_` delimiters. An
|
||||
example of using it can be found in `viper_test.go`.
|
||||
@@ -437,6 +437,7 @@ The following functions and methods exist:
|
||||
* `GetTime(key string) : time.Time`
|
||||
* `GetDuration(key string) : time.Duration`
|
||||
* `IsSet(key string) : bool`
|
||||
* `AllSettings() : map[string]interface{}`
|
||||
|
||||
One important thing to recognize is that each Get function will return a zero
|
||||
value if it’s not found. To check if a given key exists, the `IsSet()` method
|
||||
@@ -590,6 +591,27 @@ if err != nil {
|
||||
}
|
||||
```
|
||||
|
||||
### Marshalling to string
|
||||
|
||||
You may need to marhsal all the settings held in viper into a string rather than write them to a file.
|
||||
You can use your favorite format's marshaller with the config returned by `AllSettings()`.
|
||||
|
||||
```go
|
||||
import (
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
// ...
|
||||
)
|
||||
|
||||
func yamlStringSettings() string {
|
||||
c := viper.AllSettings()
|
||||
bs, err := yaml.Marshal(c)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to marshal config to YAML: %v", err)
|
||||
}
|
||||
return string(bs)
|
||||
}
|
||||
```
|
||||
|
||||
## Viper or Vipers?
|
||||
|
||||
Viper comes ready to use out of the box. There is no configuration or
|
||||
|
||||
+3
-2
@@ -8,10 +8,11 @@ package remote
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/spf13/viper"
|
||||
crypt "github.com/xordataexchange/crypt/config"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
crypt "github.com/xordataexchange/crypt/config"
|
||||
)
|
||||
|
||||
type remoteConfigProvider struct{}
|
||||
|
||||
@@ -11,22 +11,16 @@
|
||||
package viper
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/hashicorp/hcl"
|
||||
"github.com/magiconair/properties"
|
||||
toml "github.com/pelletier/go-toml"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cast"
|
||||
jww "github.com/spf13/jwalterweatherman"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// ConfigParseError denotes failing to parse configuration file.
|
||||
@@ -121,8 +115,8 @@ func absPathify(inPath string) string {
|
||||
}
|
||||
|
||||
// Check if File / Directory Exists
|
||||
func exists(path string) (bool, error) {
|
||||
_, err := v.fs.Stat(path)
|
||||
func exists(fs afero.Fs, path string) (bool, error) {
|
||||
_, err := fs.Stat(path)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
@@ -152,61 +146,6 @@ func userHomeDir() string {
|
||||
return os.Getenv("HOME")
|
||||
}
|
||||
|
||||
func unmarshallConfigReader(in io.Reader, c map[string]interface{}, configType string) error {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(in)
|
||||
|
||||
switch strings.ToLower(configType) {
|
||||
case "yaml", "yml":
|
||||
if err := yaml.Unmarshal(buf.Bytes(), &c); err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
|
||||
case "json":
|
||||
if err := json.Unmarshal(buf.Bytes(), &c); err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
|
||||
case "hcl":
|
||||
obj, err := hcl.Parse(string(buf.Bytes()))
|
||||
if err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
if err = hcl.DecodeObject(&c, obj); err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
|
||||
case "toml":
|
||||
tree, err := toml.LoadReader(buf)
|
||||
if err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
tmap := tree.ToMap()
|
||||
for k, v := range tmap {
|
||||
c[k] = v
|
||||
}
|
||||
|
||||
case "properties", "props", "prop":
|
||||
var p *properties.Properties
|
||||
var err error
|
||||
if p, err = properties.Load(buf.Bytes(), properties.UTF8); err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
for _, key := range p.Keys() {
|
||||
value, _ := p.Get(key)
|
||||
// recursively build nested maps
|
||||
path := strings.Split(key, ".")
|
||||
lastKey := strings.ToLower(path[len(path)-1])
|
||||
deepestMap := deepSearch(c, path[0:len(path)-1])
|
||||
// set innermost value
|
||||
deepestMap[lastKey] = value
|
||||
}
|
||||
}
|
||||
|
||||
insensitiviseMap(c)
|
||||
return nil
|
||||
}
|
||||
|
||||
func safeMul(a, b uint) uint {
|
||||
c := a * b
|
||||
if a > 1 && b > 1 && c/b != a {
|
||||
|
||||
@@ -22,6 +22,7 @@ package viper
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
@@ -31,14 +32,30 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/hashicorp/hcl"
|
||||
"github.com/hashicorp/hcl/hcl/printer"
|
||||
"github.com/magiconair/properties"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
toml "github.com/pelletier/go-toml"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cast"
|
||||
jww "github.com/spf13/jwalterweatherman"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// ConfigMarshalError happens when failing to marshal the configuration.
|
||||
type ConfigMarshalError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
// Error returns the formatted configuration error.
|
||||
func (e ConfigMarshalError) Error() string {
|
||||
return fmt.Sprintf("While marshaling config: %s", e.err.Error())
|
||||
}
|
||||
|
||||
var v *Viper
|
||||
|
||||
type RemoteResponse struct {
|
||||
@@ -96,6 +113,23 @@ func (fnfe ConfigFileNotFoundError) Error() string {
|
||||
return fmt.Sprintf("Config File %q Not Found in %q", fnfe.name, fnfe.locations)
|
||||
}
|
||||
|
||||
// A DecoderConfigOption can be passed to viper.Unmarshal to configure
|
||||
// mapstructure.DecoderConfig options
|
||||
type DecoderConfigOption func(*mapstructure.DecoderConfig)
|
||||
|
||||
// DecodeHook returns a DecoderConfigOption which overrides the default
|
||||
// DecoderConfig.DecodeHook value, the default is:
|
||||
//
|
||||
// mapstructure.ComposeDecodeHookFunc(
|
||||
// mapstructure.StringToTimeDurationHookFunc(),
|
||||
// mapstructure.StringToSliceHookFunc(","),
|
||||
// )
|
||||
func DecodeHook(hook mapstructure.DecodeHookFunc) DecoderConfigOption {
|
||||
return func(c *mapstructure.DecoderConfig) {
|
||||
c.DecodeHook = hook
|
||||
}
|
||||
}
|
||||
|
||||
// Viper is a prioritized configuration registry. It
|
||||
// maintains a set of configuration sources, fetches
|
||||
// values to populate those, and provides them according
|
||||
@@ -162,6 +196,10 @@ type Viper struct {
|
||||
aliases map[string]string
|
||||
typeByDefValue bool
|
||||
|
||||
// Store read properties on the object so that we can write back in order with comments.
|
||||
// This will only be used if the configuration read is a properties file.
|
||||
properties *properties.Properties
|
||||
|
||||
onConfigChange func(fsnotify.Event)
|
||||
}
|
||||
|
||||
@@ -188,7 +226,7 @@ func New() *Viper {
|
||||
// can use it in their testing as well.
|
||||
func Reset() {
|
||||
v = New()
|
||||
SupportedExts = []string{"json", "toml", "yaml", "yml", "hcl"}
|
||||
SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl"}
|
||||
SupportedRemoteProviders = []string{"etcd", "consul"}
|
||||
}
|
||||
|
||||
@@ -268,7 +306,9 @@ func (v *Viper) WatchConfig() {
|
||||
if err != nil {
|
||||
log.Println("error:", err)
|
||||
}
|
||||
v.onConfigChange(event)
|
||||
if v.onConfigChange != nil {
|
||||
v.onConfigChange(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
case err := <-watcher.Errors:
|
||||
@@ -661,6 +701,12 @@ func (v *Viper) GetInt(key string) int {
|
||||
return cast.ToInt(v.Get(key))
|
||||
}
|
||||
|
||||
// GetInt32 returns the value associated with the key as an integer.
|
||||
func GetInt32(key string) int32 { return v.GetInt32(key) }
|
||||
func (v *Viper) GetInt32(key string) int32 {
|
||||
return cast.ToInt32(v.Get(key))
|
||||
}
|
||||
|
||||
// GetInt64 returns the value associated with the key as an integer.
|
||||
func GetInt64(key string) int64 { return v.GetInt64(key) }
|
||||
func (v *Viper) GetInt64(key string) int64 {
|
||||
@@ -718,9 +764,11 @@ func (v *Viper) GetSizeInBytes(key string) uint {
|
||||
}
|
||||
|
||||
// UnmarshalKey takes a single key and unmarshals it into a Struct.
|
||||
func UnmarshalKey(key string, rawVal interface{}) error { return v.UnmarshalKey(key, rawVal) }
|
||||
func (v *Viper) UnmarshalKey(key string, rawVal interface{}) error {
|
||||
err := decode(v.Get(key), defaultDecoderConfig(rawVal))
|
||||
func UnmarshalKey(key string, rawVal interface{}, opts ...DecoderConfigOption) error {
|
||||
return v.UnmarshalKey(key, rawVal, opts...)
|
||||
}
|
||||
func (v *Viper) UnmarshalKey(key string, rawVal interface{}, opts ...DecoderConfigOption) error {
|
||||
err := decode(v.Get(key), defaultDecoderConfig(rawVal, opts...))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -733,9 +781,11 @@ func (v *Viper) UnmarshalKey(key string, rawVal interface{}) error {
|
||||
|
||||
// Unmarshal unmarshals the config into a Struct. Make sure that the tags
|
||||
// on the fields of the structure are properly set.
|
||||
func Unmarshal(rawVal interface{}) error { return v.Unmarshal(rawVal) }
|
||||
func (v *Viper) Unmarshal(rawVal interface{}) error {
|
||||
err := decode(v.AllSettings(), defaultDecoderConfig(rawVal))
|
||||
func Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error {
|
||||
return v.Unmarshal(rawVal, opts...)
|
||||
}
|
||||
func (v *Viper) Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error {
|
||||
err := decode(v.AllSettings(), defaultDecoderConfig(rawVal, opts...))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -747,14 +797,21 @@ func (v *Viper) Unmarshal(rawVal interface{}) error {
|
||||
}
|
||||
|
||||
// defaultDecoderConfig returns default mapsstructure.DecoderConfig with suppot
|
||||
// of time.Duration values
|
||||
func defaultDecoderConfig(output interface{}) *mapstructure.DecoderConfig {
|
||||
return &mapstructure.DecoderConfig{
|
||||
// of time.Duration values & string slices
|
||||
func defaultDecoderConfig(output interface{}, opts ...DecoderConfigOption) *mapstructure.DecoderConfig {
|
||||
c := &mapstructure.DecoderConfig{
|
||||
Metadata: nil,
|
||||
Result: output,
|
||||
WeaklyTypedInput: true,
|
||||
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
||||
DecodeHook: mapstructure.ComposeDecodeHookFunc(
|
||||
mapstructure.StringToTimeDurationHookFunc(),
|
||||
mapstructure.StringToSliceHookFunc(","),
|
||||
),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(c)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// A wrapper around mapstructure.Decode that mimics the WeakDecode functionality
|
||||
@@ -1116,6 +1173,7 @@ func (v *Viper) ReadInConfig() error {
|
||||
return UnsupportedConfigError(v.getConfigType())
|
||||
}
|
||||
|
||||
jww.DEBUG.Println("Reading file: ", filename)
|
||||
file, err := afero.ReadFile(v.fs, filename)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1175,6 +1233,195 @@ func (v *Viper) MergeConfig(in io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteConfig writes the current configuration to a file.
|
||||
func WriteConfig() error { return v.WriteConfig() }
|
||||
func (v *Viper) WriteConfig() error {
|
||||
filename, err := v.getConfigFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return v.writeConfig(filename, true)
|
||||
}
|
||||
|
||||
// SafeWriteConfig writes current configuration to file only if the file does not exist.
|
||||
func SafeWriteConfig() error { return v.SafeWriteConfig() }
|
||||
func (v *Viper) SafeWriteConfig() error {
|
||||
filename, err := v.getConfigFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return v.writeConfig(filename, false)
|
||||
}
|
||||
|
||||
// WriteConfigAs writes current configuration to a given filename.
|
||||
func WriteConfigAs(filename string) error { return v.WriteConfigAs(filename) }
|
||||
func (v *Viper) WriteConfigAs(filename string) error {
|
||||
return v.writeConfig(filename, true)
|
||||
}
|
||||
|
||||
// SafeWriteConfigAs writes current configuration to a given filename if it does not exist.
|
||||
func SafeWriteConfigAs(filename string) error { return v.SafeWriteConfigAs(filename) }
|
||||
func (v *Viper) SafeWriteConfigAs(filename string) error {
|
||||
return v.writeConfig(filename, false)
|
||||
}
|
||||
|
||||
func writeConfig(filename string, force bool) error { return v.writeConfig(filename, force) }
|
||||
func (v *Viper) writeConfig(filename string, force bool) error {
|
||||
jww.INFO.Println("Attempting to write configuration to file.")
|
||||
ext := filepath.Ext(filename)
|
||||
if len(ext) <= 1 {
|
||||
return fmt.Errorf("Filename: %s requires valid extension.", filename)
|
||||
}
|
||||
configType := ext[1:]
|
||||
if !stringInSlice(configType, SupportedExts) {
|
||||
return UnsupportedConfigError(configType)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
f, err := v.fs.OpenFile(filename, flags, os.FileMode(0644))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return v.marshalWriter(f, configType)
|
||||
}
|
||||
|
||||
// Unmarshal a Reader into a map.
|
||||
// Should probably be an unexported function.
|
||||
func unmarshalReader(in io.Reader, c map[string]interface{}) error {
|
||||
return v.unmarshalReader(in, c)
|
||||
}
|
||||
func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(in)
|
||||
|
||||
switch strings.ToLower(v.getConfigType()) {
|
||||
case "yaml", "yml":
|
||||
if err := yaml.Unmarshal(buf.Bytes(), &c); err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
|
||||
case "json":
|
||||
if err := json.Unmarshal(buf.Bytes(), &c); err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
|
||||
case "hcl":
|
||||
obj, err := hcl.Parse(string(buf.Bytes()))
|
||||
if err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
if err = hcl.DecodeObject(&c, obj); err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
|
||||
case "toml":
|
||||
tree, err := toml.LoadReader(buf)
|
||||
if err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
tmap := tree.ToMap()
|
||||
for k, v := range tmap {
|
||||
c[k] = v
|
||||
}
|
||||
|
||||
case "properties", "props", "prop":
|
||||
v.properties = properties.NewProperties()
|
||||
var err error
|
||||
if v.properties, err = properties.Load(buf.Bytes(), properties.UTF8); err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
for _, key := range v.properties.Keys() {
|
||||
value, _ := v.properties.Get(key)
|
||||
// recursively build nested maps
|
||||
path := strings.Split(key, ".")
|
||||
lastKey := strings.ToLower(path[len(path)-1])
|
||||
deepestMap := deepSearch(c, path[0:len(path)-1])
|
||||
// set innermost value
|
||||
deepestMap[lastKey] = value
|
||||
}
|
||||
}
|
||||
|
||||
insensitiviseMap(c)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal a map into Writer.
|
||||
func marshalWriter(f afero.File, configType string) error {
|
||||
return v.marshalWriter(f, configType)
|
||||
}
|
||||
func (v *Viper) marshalWriter(f afero.File, configType string) error {
|
||||
c := v.AllSettings()
|
||||
switch configType {
|
||||
case "json":
|
||||
b, err := json.MarshalIndent(c, "", " ")
|
||||
if err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
_, err = f.WriteString(string(b))
|
||||
if err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
|
||||
case "hcl":
|
||||
b, err := json.Marshal(c)
|
||||
ast, err := hcl.Parse(string(b))
|
||||
if err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
err = printer.Fprint(f, ast.Node)
|
||||
if err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
|
||||
case "prop", "props", "properties":
|
||||
if v.properties == nil {
|
||||
v.properties = properties.NewProperties()
|
||||
}
|
||||
p := v.properties
|
||||
for _, key := range v.AllKeys() {
|
||||
_, _, err := p.Set(key, v.GetString(key))
|
||||
if err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
}
|
||||
_, err := p.WriteComment(f, "#", properties.UTF8)
|
||||
if err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
|
||||
case "toml":
|
||||
t, err := toml.TreeFromMap(c)
|
||||
if err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
s := t.String()
|
||||
if _, err := f.WriteString(s); err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
|
||||
case "yaml", "yml":
|
||||
b, err := yaml.Marshal(c)
|
||||
if err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
if _, err = f.WriteString(string(b)); err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func keyExists(k string, m map[string]interface{}) string {
|
||||
lk := strings.ToLower(k)
|
||||
for mk := range m {
|
||||
@@ -1287,16 +1534,6 @@ func (v *Viper) WatchRemoteConfigOnChannel() error {
|
||||
return v.watchKeyValueConfigOnChannel()
|
||||
}
|
||||
|
||||
// Unmarshal a Reader into a map.
|
||||
// Should probably be an unexported function.
|
||||
func unmarshalReader(in io.Reader, c map[string]interface{}) error {
|
||||
return v.unmarshalReader(in, c)
|
||||
}
|
||||
|
||||
func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
|
||||
return unmarshallConfigReader(in, c, v.getConfigType())
|
||||
}
|
||||
|
||||
func (v *Viper) insensitiviseMaps() {
|
||||
insensitiviseMap(v.config)
|
||||
insensitiviseMap(v.defaults)
|
||||
@@ -1516,25 +1753,21 @@ func (v *Viper) getConfigType() string {
|
||||
}
|
||||
|
||||
func (v *Viper) getConfigFile() (string, error) {
|
||||
// if explicitly set, then use it
|
||||
if v.configFile != "" {
|
||||
return v.configFile, nil
|
||||
if v.configFile == "" {
|
||||
cf, err := v.findConfigFile()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
v.configFile = cf
|
||||
}
|
||||
|
||||
cf, err := v.findConfigFile()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
v.configFile = cf
|
||||
return v.getConfigFile()
|
||||
return v.configFile, nil
|
||||
}
|
||||
|
||||
func (v *Viper) searchInPath(in string) (filename string) {
|
||||
jww.DEBUG.Println("Searching for config in ", in)
|
||||
for _, ext := range SupportedExts {
|
||||
jww.DEBUG.Println("Checking for", filepath.Join(in, v.configName+"."+ext))
|
||||
if b, _ := exists(filepath.Join(in, v.configName+"."+ext)); b {
|
||||
if b, _ := exists(v.fs, filepath.Join(in, v.configName+"."+ext)); b {
|
||||
jww.DEBUG.Println("Found: ", filepath.Join(in, v.configName+"."+ext))
|
||||
return filepath.Join(in, v.configName+"."+ext)
|
||||
}
|
||||
|
||||
+233
-2
@@ -7,6 +7,7 @@ package viper
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -18,6 +19,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
@@ -262,7 +265,7 @@ func TestDefault(t *testing.T) {
|
||||
assert.Equal(t, "leather", Get("clothing.jacket"))
|
||||
}
|
||||
|
||||
func TestUnmarshalling(t *testing.T) {
|
||||
func TestUnmarshaling(t *testing.T) {
|
||||
SetConfigType("yaml")
|
||||
r := bytes.NewReader(yamlExample)
|
||||
|
||||
@@ -417,7 +420,7 @@ func TestAutoEnvWithPrefix(t *testing.T) {
|
||||
assert.Equal(t, "13", Get("bar"))
|
||||
}
|
||||
|
||||
func TestSetEnvReplacer(t *testing.T) {
|
||||
func TestSetEnvKeyReplacer(t *testing.T) {
|
||||
Reset()
|
||||
|
||||
AutomaticEnv()
|
||||
@@ -502,6 +505,42 @@ func TestUnmarshal(t *testing.T) {
|
||||
assert.Equal(t, &config{Name: "Steve", Port: 1234, Duration: time.Second + time.Millisecond}, &C)
|
||||
}
|
||||
|
||||
func TestUnmarshalWithDecoderOptions(t *testing.T) {
|
||||
Set("credentials", "{\"foo\":\"bar\"}")
|
||||
|
||||
opt := DecodeHook(mapstructure.ComposeDecodeHookFunc(
|
||||
mapstructure.StringToTimeDurationHookFunc(),
|
||||
mapstructure.StringToSliceHookFunc(","),
|
||||
// Custom Decode Hook Function
|
||||
func(rf reflect.Kind, rt reflect.Kind, data interface{}) (interface{}, error) {
|
||||
if rf != reflect.String || rt != reflect.Map {
|
||||
return data, nil
|
||||
}
|
||||
m := map[string]string{}
|
||||
raw := data.(string)
|
||||
if raw == "" {
|
||||
return m, nil
|
||||
}
|
||||
return m, json.Unmarshal([]byte(raw), &m)
|
||||
},
|
||||
))
|
||||
|
||||
type config struct {
|
||||
Credentials map[string]string
|
||||
}
|
||||
|
||||
var C config
|
||||
|
||||
err := Unmarshal(&C, opt)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to decode into struct, %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, &config{
|
||||
Credentials: map[string]string{"foo": "bar"},
|
||||
}, &C)
|
||||
}
|
||||
|
||||
func TestBindPFlags(t *testing.T) {
|
||||
v := New() // create independent Viper object
|
||||
flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||
@@ -847,6 +886,190 @@ func TestSub(t *testing.T) {
|
||||
assert.Equal(t, (*Viper)(nil), subv)
|
||||
}
|
||||
|
||||
var hclWriteExpected = []byte(`"foos" = {
|
||||
"foo" = {
|
||||
"key" = 1
|
||||
}
|
||||
|
||||
"foo" = {
|
||||
"key" = 2
|
||||
}
|
||||
|
||||
"foo" = {
|
||||
"key" = 3
|
||||
}
|
||||
|
||||
"foo" = {
|
||||
"key" = 4
|
||||
}
|
||||
}
|
||||
|
||||
"id" = "0001"
|
||||
|
||||
"name" = "Cake"
|
||||
|
||||
"ppu" = 0.55
|
||||
|
||||
"type" = "donut"`)
|
||||
|
||||
func TestWriteConfigHCL(t *testing.T) {
|
||||
v := New()
|
||||
fs := afero.NewMemMapFs()
|
||||
v.SetFs(fs)
|
||||
v.SetConfigName("c")
|
||||
v.SetConfigType("hcl")
|
||||
err := v.ReadConfig(bytes.NewBuffer(hclExample))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := v.WriteConfigAs("c.hcl"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
read, err := afero.ReadFile(fs, "c.hcl")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, hclWriteExpected, read)
|
||||
}
|
||||
|
||||
var jsonWriteExpected = []byte(`{
|
||||
"batters": {
|
||||
"batter": [
|
||||
{
|
||||
"type": "Regular"
|
||||
},
|
||||
{
|
||||
"type": "Chocolate"
|
||||
},
|
||||
{
|
||||
"type": "Blueberry"
|
||||
},
|
||||
{
|
||||
"type": "Devil's Food"
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": "0001",
|
||||
"name": "Cake",
|
||||
"ppu": 0.55,
|
||||
"type": "donut"
|
||||
}`)
|
||||
|
||||
func TestWriteConfigJson(t *testing.T) {
|
||||
v := New()
|
||||
fs := afero.NewMemMapFs()
|
||||
v.SetFs(fs)
|
||||
v.SetConfigName("c")
|
||||
v.SetConfigType("json")
|
||||
err := v.ReadConfig(bytes.NewBuffer(jsonExample))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := v.WriteConfigAs("c.json"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
read, err := afero.ReadFile(fs, "c.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, jsonWriteExpected, read)
|
||||
}
|
||||
|
||||
var propertiesWriteExpected = []byte(`p_id = 0001
|
||||
p_type = donut
|
||||
p_name = Cake
|
||||
p_ppu = 0.55
|
||||
p_batters.batter.type = Regular
|
||||
`)
|
||||
|
||||
func TestWriteConfigProperties(t *testing.T) {
|
||||
v := New()
|
||||
fs := afero.NewMemMapFs()
|
||||
v.SetFs(fs)
|
||||
v.SetConfigName("c")
|
||||
v.SetConfigType("properties")
|
||||
err := v.ReadConfig(bytes.NewBuffer(propertiesExample))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := v.WriteConfigAs("c.properties"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
read, err := afero.ReadFile(fs, "c.properties")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, propertiesWriteExpected, read)
|
||||
}
|
||||
|
||||
func TestWriteConfigTOML(t *testing.T) {
|
||||
fs := afero.NewMemMapFs()
|
||||
v := New()
|
||||
v.SetFs(fs)
|
||||
v.SetConfigName("c")
|
||||
v.SetConfigType("toml")
|
||||
err := v.ReadConfig(bytes.NewBuffer(tomlExample))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := v.WriteConfigAs("c.toml"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// The TOML String method does not order the contents.
|
||||
// Therefore, we must read the generated file and compare the data.
|
||||
v2 := New()
|
||||
v2.SetFs(fs)
|
||||
v2.SetConfigName("c")
|
||||
v2.SetConfigType("toml")
|
||||
v2.SetConfigFile("c.toml")
|
||||
err = v2.ReadInConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, v.GetString("title"), v2.GetString("title"))
|
||||
assert.Equal(t, v.GetString("owner.bio"), v2.GetString("owner.bio"))
|
||||
assert.Equal(t, v.GetString("owner.dob"), v2.GetString("owner.dob"))
|
||||
assert.Equal(t, v.GetString("owner.organization"), v2.GetString("owner.organization"))
|
||||
}
|
||||
|
||||
var yamlWriteExpected = []byte(`age: 35
|
||||
beard: true
|
||||
clothing:
|
||||
jacket: leather
|
||||
pants:
|
||||
size: large
|
||||
trousers: denim
|
||||
eyes: brown
|
||||
hacker: true
|
||||
hobbies:
|
||||
- skateboarding
|
||||
- snowboarding
|
||||
- go
|
||||
name: steve
|
||||
`)
|
||||
|
||||
func TestWriteConfigYAML(t *testing.T) {
|
||||
v := New()
|
||||
fs := afero.NewMemMapFs()
|
||||
v.SetFs(fs)
|
||||
v.SetConfigName("c")
|
||||
v.SetConfigType("yaml")
|
||||
err := v.ReadConfig(bytes.NewBuffer(yamlExample))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := v.WriteConfigAs("c.yaml"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
read, err := afero.ReadFile(fs, "c.yaml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, yamlWriteExpected, read)
|
||||
}
|
||||
|
||||
var yamlMergeExampleTgt = []byte(`
|
||||
hello:
|
||||
pop: 37890
|
||||
@@ -883,6 +1106,10 @@ func TestMergeConfig(t *testing.T) {
|
||||
t.Fatalf("lagrenum != 765432101234567, = %d", pop)
|
||||
}
|
||||
|
||||
if pop := v.GetInt32("hello.pop"); pop != int32(37890) {
|
||||
t.Fatalf("pop != 37890, = %d", pop)
|
||||
}
|
||||
|
||||
if pop := v.GetInt64("hello.lagrenum"); pop != int64(765432101234567) {
|
||||
t.Fatalf("int64 lagrenum != 765432101234567, = %d", pop)
|
||||
}
|
||||
@@ -907,6 +1134,10 @@ func TestMergeConfig(t *testing.T) {
|
||||
t.Fatalf("lagrenum != 7654321001234567, = %d", pop)
|
||||
}
|
||||
|
||||
if pop := v.GetInt32("hello.pop"); pop != int32(45000) {
|
||||
t.Fatalf("pop != 45000, = %d", pop)
|
||||
}
|
||||
|
||||
if pop := v.GetInt64("hello.lagrenum"); pop != int64(7654321001234567) {
|
||||
t.Fatalf("int64 lagrenum != 7654321001234567, = %d", pop)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user