config/config.go

1093 lines
29 KiB
Go
Raw Normal View History

2017-08-30 09:24:22 +00:00
package config
import (
"bytes"
"encoding/csv"
"fmt"
"io"
"log"
"os"
"path/filepath"
"reflect"
"strings"
"time"
"github.com/fsnotify/fsnotify"
"github.com/mitchellh/mapstructure"
"github.com/spf13/afero"
"github.com/spf13/cast"
"github.com/spf13/pflag"
)
// SupportedExts are universally supported extensions.
var SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl"}
type UnsupportedConfigError string
// Error returns the formatted configuration error.
func (str UnsupportedConfigError) Error() string {
return fmt.Sprintf("Unsupported Config Type %q", string(str))
}
// ConfigFileNotFoundError denotes failing to find configuration file.
type ConfigFileNotFoundError struct {
name, locations string
}
// Error returns the formatted configuration error.
func (fnfe ConfigFileNotFoundError) Error() string {
return fmt.Sprintf("Config File %q Not Found in %q", fnfe.name, fnfe.locations)
}
type CircularReferenceAliasError struct {
alias, key, realKey string
}
// Error returns the formatted configuration error.
func (crae CircularReferenceAliasError) Error() string {
return fmt.Sprintf("Creating circular reference alias[%q] for key[%q]:realKey[%q]", crae.alias, crae.key, crae.realKey)
}
type Configurator interface {
2017-08-30 10:41:51 +00:00
OnConfigChange(run func(in fsnotify.Event))
WatchConfig()
SetConfigType(in string)
SetConfigFile(in string)
SetEnvPrefix(in string)
2017-08-30 09:24:22 +00:00
SetConfigName(in string)
AddConfigPath(in string) error
2017-08-30 10:41:51 +00:00
ReadConfig(in io.Reader) error
2017-08-30 09:24:22 +00:00
ReadInConfig() error
2017-08-30 10:41:51 +00:00
AutomaticEnv()
SetEnvKeyReplacer(r *strings.Replacer)
RegisterAlias(alias string, key string) error
SetDefault(key string, value interface{})
Set(key string, value interface{})
IsSet(key string) bool
Get(key string) interface{}
GetString(key string) string
GetBool(key string) bool
GetInt(key string) int
GetInt64(key string) int64
GetFloat64(key string) float64
GetTime(key string) time.Time
GetDuration(key string) time.Duration
GetStringSlice(key string) []string
GetStringMap(key string) map[string]interface{}
GetStringMapString(key string) map[string]string
GetStringMapStringSlice(key string) map[string][]string
GetSizeInBytes(key string) uint
Sub(key string) Configurator
UnmarshalKey(key string, rawVal interface{}) error
Unmarshal(rawVal interface{}) error
UnmarshalExact(rawVal interface{}) error
BindPFlags(flags *pflag.FlagSet) error
BindPFlag(key string, flag *pflag.Flag) error
BindFlagValues(flags FlagValueSet) (err error)
BindFlagValue(key string, flag FlagValue) error
BindEnv(input ...string) error
AllSettings() map[string]interface{}
AllKeys() []string
2017-08-30 09:24:22 +00:00
}
type configurator struct {
keyDelim string
configPaths []string
// The filesystem to read config from.
fs afero.Fs
// Name of file to look for inside the path
configName string
configFile string
configType string
envPrefix string
automaticEnvApplied bool
envKeyReplacer *strings.Replacer
config map[string]interface{}
override map[string]interface{}
defaults map[string]interface{}
kvstore map[string]interface{}
pflags map[string]FlagValue
env map[string]string
aliases map[string]string
typeByDefValue bool
onConfigChange func(fsnotify.Event)
}
var _c *configurator
func init() {
_c = New().(*configurator)
}
func New() Configurator {
c := new(configurator)
c.keyDelim = "."
c.configName = "config"
c.fs = afero.NewOsFs()
c.config = make(map[string]interface{})
c.override = make(map[string]interface{})
c.defaults = make(map[string]interface{})
c.kvstore = make(map[string]interface{})
c.pflags = make(map[string]FlagValue)
c.env = make(map[string]string)
c.aliases = make(map[string]string)
c.typeByDefValue = false
return c
}
func OnConfigChange(run func(in fsnotify.Event)) { _c.OnConfigChange(run) }
func (c *configurator) OnConfigChange(run func(in fsnotify.Event)) {
c.onConfigChange = run
}
func WatchConfig() { _c.WatchConfig() }
func (c *configurator) WatchConfig() {
go func() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
// we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way
filename, err := c.getConfigFile()
if err != nil {
log.Println("error:", err)
return
}
configFile := filepath.Clean(filename)
configDir, _ := filepath.Split(configFile)
done := make(chan bool)
go func() {
for {
select {
case event := <-watcher.Events:
// we only care about the config file
if filepath.Clean(event.Name) == configFile {
if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create {
err := c.ReadInConfig()
if err != nil {
log.Println("error:", err)
}
c.onConfigChange(event)
}
}
case err := <-watcher.Errors:
log.Println("error:", err)
}
}
}()
watcher.Add(configDir)
<-done
}()
}
func SetConfigType(in string) { _c.SetConfigType(in) }
func (c *configurator) SetConfigType(in string) {
if in != "" {
c.configType = in
}
}
func SetConfigFile(in string) { _c.SetConfigFile(in) }
func (c *configurator) SetConfigFile(in string) {
if in != "" {
c.configFile = in
}
}
func SetEnvPrefix(in string) { _c.SetEnvPrefix(in) }
func (c *configurator) SetEnvPrefix(in string) {
if in != "" {
c.envPrefix = in
}
}
// SetConfigName sets name for the config file.
// Does not include extension.
func SetConfigName(in string) { _c.SetConfigName(in) }
func (c *configurator) SetConfigName(in string) {
if in != "" {
c.configName = in
c.configFile = ""
}
}
// AddConfigPath adds a path for Viper to search for the config file in.
// Can be called multiple times to define multiple search paths.
func AddConfigPath(in string) error { return _c.AddConfigPath(in) }
func (c *configurator) AddConfigPath(in string) error {
if in != "" {
absin, err := absPathify(in)
if nil != err {
return err
}
if !stringInSlice(absin, c.configPaths) {
c.configPaths = append(c.configPaths, absin)
}
}
return nil
}
func ReadConfig(in io.Reader) error { return _c.ReadConfig(in) }
func (c *configurator) ReadConfig(in io.Reader) error {
c.config = make(map[string]interface{})
return c.unmarshalReader(in, c.config)
}
// ReadInConfig will discover and load the configuration file from disk
// and key/value stores, searching in one of the defined paths.
func ReadInConfig() error { return _c.ReadInConfig() }
func (c *configurator) ReadInConfig() error {
filename, err := c.getConfigFile()
if err != nil {
return err
}
if !stringInSlice(c.getConfigType(), SupportedExts) {
return UnsupportedConfigError(c.getConfigType())
}
file, err := afero.ReadFile(c.fs, filename)
if err != nil {
return err
}
config := make(map[string]interface{})
err = c.unmarshalReader(bytes.NewReader(file), config)
if err != nil {
return err
}
c.config = config
return nil
}
// AutomaticEnv has Viper check ENV variables for all.
// keys set in config, default & flags
func AutomaticEnv() { _c.AutomaticEnv() }
func (c *configurator) AutomaticEnv() {
c.automaticEnvApplied = true
}
// SetEnvKeyReplacer sets the strings.Replacer on the viper object
// Useful for mapping an environmental variable to a key that does
// not match it.
func SetEnvKeyReplacer(r *strings.Replacer) { _c.SetEnvKeyReplacer(r) }
func (c *configurator) SetEnvKeyReplacer(r *strings.Replacer) {
c.envKeyReplacer = r
}
func RegisterAlias(alias string, key string) error { return _c.RegisterAlias(alias, key) }
func (c *configurator) RegisterAlias(alias string, key string) error {
return c.registerAlias(alias, strings.ToLower(key))
}
func (c *configurator) registerAlias(alias string, key string) error {
alias = strings.ToLower(alias)
if alias != key && alias != c.realKey(key) {
_, exists := c.aliases[alias]
if !exists {
// if we alias something that exists in one of the maps to another
// name, we'll never be able to get that value using the original
// name, so move the config value to the new realkey.
if val, ok := c.config[alias]; ok {
delete(c.config, alias)
c.config[key] = val
}
if val, ok := c.kvstore[alias]; ok {
delete(c.kvstore, alias)
c.kvstore[key] = val
}
if val, ok := c.defaults[alias]; ok {
delete(c.defaults, alias)
c.defaults[key] = val
}
if val, ok := c.override[alias]; ok {
delete(c.override, alias)
c.override[key] = val
}
c.aliases[alias] = key
}
} else {
return CircularReferenceAliasError{alias: alias, key: key, realKey: c.realKey(key)}
}
return nil
}
func SetDefault(key string, value interface{}) { _c.SetDefault(key, value) }
func (c *configurator) SetDefault(key string, value interface{}) {
// If alias passed in, then set the proper default
key = c.realKey(strings.ToLower(key))
value = toCaseInsensitiveValue(value)
path := strings.Split(key, c.keyDelim)
lastKey := strings.ToLower(path[len(path)-1])
deepestMap := deepSearch(c.defaults, path[0:len(path)-1])
// set innermost value
deepestMap[lastKey] = value
}
func Set(key string, value interface{}) { _c.Set(key, value) }
func (c *configurator) Set(key string, value interface{}) {
// If alias passed in, then set the proper override
key = c.realKey(strings.ToLower(key))
value = toCaseInsensitiveValue(value)
path := strings.Split(key, c.keyDelim)
lastKey := strings.ToLower(path[len(path)-1])
deepestMap := deepSearch(c.override, path[0:len(path)-1])
// set innermost value
deepestMap[lastKey] = value
}
func IsSet(key string) bool { return _c.IsSet(key) }
func (c *configurator) IsSet(key string) bool {
lcaseKey := strings.ToLower(key)
val := c.find(lcaseKey)
return val != nil
}
func Get(key string) interface{} { return _c.Get(key) }
func (c *configurator) Get(key string) interface{} {
lcaseKey := strings.ToLower(key)
val := c.find(lcaseKey)
if val == nil {
return nil
}
if c.typeByDefValue {
// TODO(bep) this branch isn't covered by a single test.
valType := val
path := strings.Split(lcaseKey, c.keyDelim)
defVal := c.searchMap(c.defaults, path)
if defVal != nil {
valType = defVal
}
switch valType.(type) {
case bool:
return cast.ToBool(val)
case string:
return cast.ToString(val)
case int64, int32, int16, int8, int:
return cast.ToInt(val)
case float64, float32:
return cast.ToFloat64(val)
case time.Time:
return cast.ToTime(val)
case time.Duration:
return cast.ToDuration(val)
case []string:
return cast.ToStringSlice(val)
}
}
return val
}
// GetString returns the value associated with the key as a string.
func GetString(key string) string { return _c.GetString(key) }
func (c *configurator) GetString(key string) string {
return cast.ToString(c.Get(key))
}
// GetBool returns the value associated with the key as a boolean.
func GetBool(key string) bool { return _c.GetBool(key) }
func (c *configurator) GetBool(key string) bool {
return cast.ToBool(c.Get(key))
}
// GetInt returns the value associated with the key as an integer.
func GetInt(key string) int { return _c.GetInt(key) }
func (c *configurator) GetInt(key string) int {
return cast.ToInt(c.Get(key))
}
// GetInt64 returns the value associated with the key as an integer.
func GetInt64(key string) int64 { return _c.GetInt64(key) }
func (c *configurator) GetInt64(key string) int64 {
return cast.ToInt64(c.Get(key))
}
// GetFloat64 returns the value associated with the key as a float64.
func GetFloat64(key string) float64 { return _c.GetFloat64(key) }
func (c *configurator) GetFloat64(key string) float64 {
return cast.ToFloat64(c.Get(key))
}
// GetTime returns the value associated with the key as time.
func GetTime(key string) time.Time { return _c.GetTime(key) }
func (c *configurator) GetTime(key string) time.Time {
return cast.ToTime(c.Get(key))
}
// GetDuration returns the value associated with the key as a duration.
func GetDuration(key string) time.Duration { return _c.GetDuration(key) }
func (c *configurator) GetDuration(key string) time.Duration {
return cast.ToDuration(c.Get(key))
}
// GetStringSlice returns the value associated with the key as a slice of strings.
func GetStringSlice(key string) []string { return _c.GetStringSlice(key) }
func (c *configurator) GetStringSlice(key string) []string {
return cast.ToStringSlice(c.Get(key))
}
// GetStringMap returns the value associated with the key as a map of interfaces.
func GetStringMap(key string) map[string]interface{} { return _c.GetStringMap(key) }
func (c *configurator) GetStringMap(key string) map[string]interface{} {
return cast.ToStringMap(c.Get(key))
}
// GetStringMapString returns the value associated with the key as a map of strings.
func GetStringMapString(key string) map[string]string { return _c.GetStringMapString(key) }
func (c *configurator) GetStringMapString(key string) map[string]string {
return cast.ToStringMapString(c.Get(key))
}
// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.
func GetStringMapStringSlice(key string) map[string][]string { return _c.GetStringMapStringSlice(key) }
func (c *configurator) GetStringMapStringSlice(key string) map[string][]string {
return cast.ToStringMapStringSlice(c.Get(key))
}
// GetSizeInBytes returns the size of the value associated with the given key
// in bytes.
func GetSizeInBytes(key string) uint { return _c.GetSizeInBytes(key) }
func (c *configurator) GetSizeInBytes(key string) uint {
sizeStr := cast.ToString(c.Get(key))
return parseSizeInBytes(sizeStr)
}
// Sub returns new Viper instance representing a sub tree of this instance.
// Sub is case-insensitive for a key.
func Sub(key string) Configurator { return _c.Sub(key) }
func (c *configurator) Sub(key string) Configurator {
subv := New()
data := c.Get(key)
if data == nil {
return nil
}
if reflect.TypeOf(data).Kind() == reflect.Map {
subv.(*configurator).config = cast.ToStringMap(data)
return subv
}
return nil
}
func (c *configurator) getConfigFile() (string, error) {
// if explicitly set, then use it
if c.configFile != "" {
return c.configFile, nil
}
cf, err := c.findConfigFile()
if err != nil {
return "", err
}
c.configFile = cf
return c.getConfigFile()
}
func (c *configurator) getConfigType() string {
if c.configType != "" {
return c.configType
}
cf, err := c.getConfigFile()
if err != nil {
return ""
}
ext := filepath.Ext(cf)
if len(ext) > 1 {
return ext[1:]
}
return ""
}
func (c *configurator) findConfigFile() (string, error) {
for _, cp := range c.configPaths {
file := c.searchInPath(cp)
if file != "" {
return file, nil
}
}
return "", ConfigFileNotFoundError{c.configName, fmt.Sprintf("%s", c.configPaths)}
}
func (c *configurator) searchInPath(in string) (filename string) {
for _, ext := range SupportedExts {
if b, _ := exists(filepath.Join(in, c.configName+"."+ext)); b {
return filepath.Join(in, c.configName+"."+ext)
}
}
return ""
}
func unmarshalReader(in io.Reader, config map[string]interface{}) error {
return _c.unmarshalReader(in, config)
}
func (c *configurator) unmarshalReader(in io.Reader, config map[string]interface{}) error {
return unmarshallConfigReader(in, config, c.getConfigType())
}
func (c *configurator) insensitiviseMaps() {
insensitiviseMap(c.config)
insensitiviseMap(c.defaults)
insensitiviseMap(c.override)
insensitiviseMap(c.kvstore)
}
func (c *configurator) realKey(key string) string {
newkey, exists := c.aliases[key]
if exists {
return c.realKey(newkey)
}
return key
}
func (c *configurator) searchMap(source map[string]interface{}, path []string) interface{} {
if len(path) == 0 {
return source
}
next, ok := source[path[0]]
if ok {
// Fast path
if len(path) == 1 {
return next
}
// Nested case
switch next.(type) {
case map[interface{}]interface{}:
return c.searchMap(cast.ToStringMap(next), path[1:])
case map[string]interface{}:
// Type assertion is safe here since it is only reached
// if the type of `next` is the same as the type being asserted
return c.searchMap(next.(map[string]interface{}), path[1:])
default:
// got a value but nested key expected, return "nil" for not found
return nil
}
}
return nil
}
// UnmarshalKey takes a single key and unmarshals it into a Struct.
func UnmarshalKey(key string, rawVal interface{}) error { return _c.UnmarshalKey(key, rawVal) }
func (c *configurator) UnmarshalKey(key string, rawVal interface{}) error {
err := decode(c.Get(key), defaultDecoderConfig(rawVal))
if err != nil {
return err
}
c.insensitiviseMaps()
return nil
}
// 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 _c.Unmarshal(rawVal) }
func (c *configurator) Unmarshal(rawVal interface{}) error {
err := decode(c.AllSettings(), defaultDecoderConfig(rawVal))
if err != nil {
return err
}
c.insensitiviseMaps()
return nil
}
// defaultDecoderConfig returns default mapsstructure.DecoderConfig with suppot
// of time.Duration values
func defaultDecoderConfig(output interface{}) *mapstructure.DecoderConfig {
return &mapstructure.DecoderConfig{
Metadata: nil,
Result: output,
WeaklyTypedInput: true,
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
}
}
// A wrapper around mapstructure.Decode that mimics the WeakDecode functionality
func decode(input interface{}, config *mapstructure.DecoderConfig) error {
decoder, err := mapstructure.NewDecoder(config)
if err != nil {
return err
}
return decoder.Decode(input)
}
// UnmarshalExact unmarshals the config into a Struct, erroring if a field is nonexistent
// in the destination struct.
func (c *configurator) UnmarshalExact(rawVal interface{}) error {
config := defaultDecoderConfig(rawVal)
config.ErrorUnused = true
err := decode(c.AllSettings(), config)
if err != nil {
return err
}
c.insensitiviseMaps()
return nil
}
// BindPFlags binds a full flag set to the configuration, using each flag's long
// name as the config key.
func BindPFlags(flags *pflag.FlagSet) error { return _c.BindPFlags(flags) }
func (c *configurator) BindPFlags(flags *pflag.FlagSet) error {
return c.BindFlagValues(pflagValueSet{flags})
}
// BindPFlag binds a specific key to a pflag (as used by cobra).
// Example (where serverCmd is a Cobra instance):
//
// serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
// Viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
//
func BindPFlag(key string, flag *pflag.Flag) error { return _c.BindPFlag(key, flag) }
func (c *configurator) BindPFlag(key string, flag *pflag.Flag) error {
return c.BindFlagValue(key, pflagValue{flag})
}
// BindFlagValues binds a full FlagValue set to the configuration, using each flag's long
// name as the config key.
func BindFlagValues(flags FlagValueSet) error { return _c.BindFlagValues(flags) }
func (c *configurator) BindFlagValues(flags FlagValueSet) (err error) {
flags.VisitAll(func(flag FlagValue) {
if err = c.BindFlagValue(flag.Name(), flag); err != nil {
return
}
})
return nil
}
// BindFlagValue binds a specific key to a FlagValue.
// Example (where serverCmd is a Cobra instance):
//
// serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
// Viper.BindFlagValue("port", serverCmd.Flags().Lookup("port"))
//
func BindFlagValue(key string, flag FlagValue) error { return _c.BindFlagValue(key, flag) }
func (c *configurator) BindFlagValue(key string, flag FlagValue) error {
if flag == nil {
return fmt.Errorf("flag for %q is nil", key)
}
c.pflags[strings.ToLower(key)] = flag
return nil
}
// BindEnv binds a Viper key to a ENV variable.
// ENV variables are case sensitive.
// If only a key is provided, it will use the env key matching the key, uppercased.
// EnvPrefix will be used when set when env name is not provided.
func BindEnv(input ...string) error { return _c.BindEnv(input...) }
func (c *configurator) BindEnv(input ...string) error {
var key, envkey string
if len(input) == 0 {
return fmt.Errorf("BindEnv missing key to bind to")
}
key = strings.ToLower(input[0])
if len(input) == 1 {
envkey = c.mergeWithEnvPrefix(key)
} else {
envkey = input[1]
}
c.env[key] = envkey
return nil
}
func (c *configurator) find(lcaseKey string) interface{} {
var (
val interface{}
exists bool
path = strings.Split(lcaseKey, c.keyDelim)
nested = len(path) > 1
)
// compute the path through the nested maps to the nested value
if nested && c.isPathShadowedInDeepMap(path, castMapStringToMapInterface(c.aliases)) != "" {
return nil
}
// if the requested key is an alias, then return the proper key
lcaseKey = c.realKey(lcaseKey)
path = strings.Split(lcaseKey, c.keyDelim)
nested = len(path) > 1
// Set() override first
val = c.searchMap(c.override, path)
if val != nil {
return val
}
if nested && c.isPathShadowedInDeepMap(path, c.override) != "" {
return nil
}
// PFlag override next
flag, exists := c.pflags[lcaseKey]
if exists && flag.HasChanged() {
switch flag.ValueType() {
case "int", "int8", "int16", "int32", "int64":
return cast.ToInt(flag.ValueString())
case "bool":
return cast.ToBool(flag.ValueString())
case "stringSlice":
s := strings.TrimPrefix(flag.ValueString(), "[")
s = strings.TrimSuffix(s, "]")
res, _ := readAsCSV(s)
return res
default:
return flag.ValueString()
}
}
if nested && c.isPathShadowedInFlatMap(path, c.pflags) != "" {
return nil
}
// Env override next
if c.automaticEnvApplied {
// even if it hasn't been registered, if automaticEnv is used,
// check any Get request
if val = c.getEnv(c.mergeWithEnvPrefix(lcaseKey)); val != "" {
return val
}
if nested && c.isPathShadowedInAutoEnv(path) != "" {
return nil
}
}
envkey, exists := c.env[lcaseKey]
if exists {
if val = c.getEnv(envkey); val != "" {
return val
}
}
if nested && c.isPathShadowedInFlatMap(path, c.env) != "" {
return nil
}
// Config file next
val = c.searchMapWithPathPrefixes(c.config, path)
if val != nil {
return val
}
if nested && c.isPathShadowedInDeepMap(path, c.config) != "" {
return nil
}
// K/V store next
val = c.searchMap(c.kvstore, path)
if val != nil {
return val
}
if nested && c.isPathShadowedInDeepMap(path, c.kvstore) != "" {
return nil
}
// Default next
val = c.searchMap(c.defaults, path)
if val != nil {
return val
}
if nested && c.isPathShadowedInDeepMap(path, c.defaults) != "" {
return nil
}
// last chance: if no other value is returned and a flag does exist for the value,
// get the flag's value even if the flag's value has not changed
if flag, exists := c.pflags[lcaseKey]; exists {
switch flag.ValueType() {
case "int", "int8", "int16", "int32", "int64":
return cast.ToInt(flag.ValueString())
case "bool":
return cast.ToBool(flag.ValueString())
case "stringSlice":
s := strings.TrimPrefix(flag.ValueString(), "[")
s = strings.TrimSuffix(s, "]")
res, _ := readAsCSV(s)
return res
default:
return flag.ValueString()
}
}
// last item, no need to check shadowing
return nil
}
func (c *configurator) searchMapWithPathPrefixes(source map[string]interface{}, path []string) interface{} {
if len(path) == 0 {
return source
}
// search for path prefixes, starting from the longest one
for i := len(path); i > 0; i-- {
prefixKey := strings.ToLower(strings.Join(path[0:i], c.keyDelim))
next, ok := source[prefixKey]
if ok {
// Fast path
if i == len(path) {
return next
}
// Nested case
var val interface{}
switch next.(type) {
case map[interface{}]interface{}:
val = c.searchMapWithPathPrefixes(cast.ToStringMap(next), path[i:])
case map[string]interface{}:
// Type assertion is safe here since it is only reached
// if the type of `next` is the same as the type being asserted
val = c.searchMapWithPathPrefixes(next.(map[string]interface{}), path[i:])
default:
// got a value but nested key expected, do nothing and look for next prefix
}
if val != nil {
return val
}
}
}
// not found
return nil
}
func (c *configurator) isPathShadowedInDeepMap(path []string, m map[string]interface{}) string {
var parentVal interface{}
for i := 1; i < len(path); i++ {
parentVal = c.searchMap(m, path[0:i])
if parentVal == nil {
// not found, no need to add more path elements
return ""
}
switch parentVal.(type) {
case map[interface{}]interface{}:
continue
case map[string]interface{}:
continue
default:
// parentVal is a regular value which shadows "path"
return strings.Join(path[0:i], c.keyDelim)
}
}
return ""
}
func (c *configurator) isPathShadowedInFlatMap(path []string, mi interface{}) string {
// unify input map
var m map[string]interface{}
switch mi.(type) {
case map[string]string, map[string]FlagValue:
m = cast.ToStringMap(mi)
default:
return ""
}
// scan paths
var parentKey string
for i := 1; i < len(path); i++ {
parentKey = strings.Join(path[0:i], c.keyDelim)
if _, ok := m[parentKey]; ok {
return parentKey
}
}
return ""
}
func (c *configurator) isPathShadowedInAutoEnv(path []string) string {
var parentKey string
var val string
for i := 1; i < len(path); i++ {
parentKey = strings.Join(path[0:i], c.keyDelim)
if val = c.getEnv(c.mergeWithEnvPrefix(parentKey)); val != "" {
return parentKey
}
}
return ""
}
func (c *configurator) getEnv(key string) string {
if c.envKeyReplacer != nil {
key = c.envKeyReplacer.Replace(key)
}
return os.Getenv(key)
}
func (c *configurator) mergeWithEnvPrefix(in string) string {
if c.envPrefix != "" {
return strings.ToUpper(c.envPrefix + "_" + in)
}
return strings.ToUpper(in)
}
func castToMapStringInterface(
src map[interface{}]interface{}) map[string]interface{} {
tgt := map[string]interface{}{}
for k, v := range src {
tgt[fmt.Sprintf("%v", k)] = v
}
return tgt
}
func castMapStringToMapInterface(src map[string]string) map[string]interface{} {
tgt := map[string]interface{}{}
for k, v := range src {
tgt[k] = v
}
return tgt
}
func castMapFlagToMapInterface(src map[string]FlagValue) map[string]interface{} {
tgt := map[string]interface{}{}
for k, v := range src {
tgt[k] = v
}
return tgt
}
func readAsCSV(val string) ([]string, error) {
if val == "" {
return []string{}, nil
}
stringReader := strings.NewReader(val)
csvReader := csv.NewReader(stringReader)
return csvReader.Read()
}
// AllSettings merges all settings and returns them as a map[string]interface{}.
func AllSettings() map[string]interface{} { return _c.AllSettings() }
func (c *configurator) AllSettings() map[string]interface{} {
m := map[string]interface{}{}
// start from the list of keys, and construct the map one value at a time
for _, k := range c.AllKeys() {
value := c.Get(k)
if value == nil {
// should not happen, since AllKeys() returns only keys holding a value,
// check just in case anything changes
continue
}
path := strings.Split(k, c.keyDelim)
lastKey := strings.ToLower(path[len(path)-1])
deepestMap := deepSearch(m, path[0:len(path)-1])
// set innermost value
deepestMap[lastKey] = value
}
return m
}
// AllKeys returns all keys holding a value, regardless of where they are set.
// Nested keys are returned with a v.keyDelim (= ".") separator
func AllKeys() []string { return _c.AllKeys() }
func (c *configurator) AllKeys() []string {
m := map[string]bool{}
// add all paths, by order of descending priority to ensure correct shadowing
m = c.flattenAndMergeMap(m, castMapStringToMapInterface(c.aliases), "")
m = c.flattenAndMergeMap(m, c.override, "")
m = c.mergeFlatMap(m, castMapFlagToMapInterface(c.pflags))
m = c.mergeFlatMap(m, castMapStringToMapInterface(c.env))
m = c.flattenAndMergeMap(m, c.config, "")
m = c.flattenAndMergeMap(m, c.kvstore, "")
m = c.flattenAndMergeMap(m, c.defaults, "")
// convert set of paths to list
a := []string{}
for x := range m {
a = append(a, x)
}
return a
}
// flattenAndMergeMap recursively flattens the given map into a map[string]bool
// of key paths (used as a set, easier to manipulate than a []string):
// - each path is merged into a single key string, delimited with v.keyDelim (= ".")
// - if a path is shadowed by an earlier value in the initial shadow map,
// it is skipped.
// The resulting set of paths is merged to the given shadow set at the same time.
func (c *configurator) flattenAndMergeMap(shadow map[string]bool, m map[string]interface{}, prefix string) map[string]bool {
if shadow != nil && prefix != "" && shadow[prefix] {
// prefix is shadowed => nothing more to flatten
return shadow
}
if shadow == nil {
shadow = make(map[string]bool)
}
var m2 map[string]interface{}
if prefix != "" {
prefix += c.keyDelim
}
for k, val := range m {
fullKey := prefix + k
switch val.(type) {
case map[string]interface{}:
m2 = val.(map[string]interface{})
case map[interface{}]interface{}:
m2 = cast.ToStringMap(val)
default:
// immediate value
shadow[strings.ToLower(fullKey)] = true
continue
}
// recursively merge to shadow map
shadow = c.flattenAndMergeMap(shadow, m2, fullKey)
}
return shadow
}
// mergeFlatMap merges the given maps, excluding values of the second map
// shadowed by values from the first map.
func (c *configurator) mergeFlatMap(shadow map[string]bool, m map[string]interface{}) map[string]bool {
// scan keys
outer:
for k, _ := range m {
path := strings.Split(k, c.keyDelim)
// scan intermediate paths
var parentKey string
for i := 1; i < len(path); i++ {
parentKey = strings.Join(path[0:i], c.keyDelim)
if shadow[parentKey] {
// path is shadowed, continue
continue outer
}
}
// add key
shadow[strings.ToLower(k)] = true
}
return shadow
}