diff --git a/.travis.yml b/.travis.yml index 55960d1..fa39805 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,8 @@ go_import_path: github.com/spf13/viper language: go go: - - 1.7.x - - 1.8.x - 1.9.x + - 1.10.x - tip os: diff --git a/README.md b/README.md index 64bf474..d752822 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,7 @@ _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. @@ -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 diff --git a/nohup.out b/nohup.out deleted file mode 100644 index 8973bf2..0000000 --- a/nohup.out +++ /dev/null @@ -1 +0,0 @@ -QProcess::start: Process is already running diff --git a/viper.go b/viper.go index 8284149..bda3ea8 100644 --- a/viper.go +++ b/viper.go @@ -114,6 +114,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 @@ -709,6 +726,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 { @@ -766,9 +789,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 @@ -781,9 +806,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 @@ -796,8 +823,8 @@ func (v *Viper) Unmarshal(rawVal interface{}) error { // defaultDecoderConfig returns default mapsstructure.DecoderConfig with suppot // of time.Duration values & string slices -func defaultDecoderConfig(output interface{}) *mapstructure.DecoderConfig { - return &mapstructure.DecoderConfig{ +func defaultDecoderConfig(output interface{}, opts ...DecoderConfigOption) *mapstructure.DecoderConfig { + c := &mapstructure.DecoderConfig{ Metadata: nil, Result: output, WeaklyTypedInput: true, @@ -806,6 +833,10 @@ func defaultDecoderConfig(output interface{}) *mapstructure.DecoderConfig { mapstructure.StringToSliceHookFunc(","), ), } + for _, opt := range opts { + opt(c) + } + return c } // A wrapper around mapstructure.Decode that mimics the WeakDecode functionality @@ -1747,18 +1778,14 @@ 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) { diff --git a/viper_test.go b/viper_test.go index 0985821..7acd910 100644 --- a/viper_test.go +++ b/viper_test.go @@ -7,6 +7,7 @@ package viper import ( "bytes" + "encoding/json" "fmt" "io" "io/ioutil" @@ -22,6 +23,7 @@ import ( "time" "github.com/fsnotify/fsnotify" + "github.com/mitchellh/mapstructure" "github.com/spf13/afero" "github.com/spf13/cast" @@ -508,6 +510,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) @@ -1073,6 +1111,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) } @@ -1097,6 +1139,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) }