Config has been changed from map to struct type

This commit is contained in:
crusader 2017-09-19 14:44:45 +09:00
parent d7879b0149
commit 89da001e7d
4 changed files with 205 additions and 1336 deletions

1112
config.go

File diff suppressed because it is too large Load Diff

View File

@ -1,57 +0,0 @@
package config
import "github.com/spf13/pflag"
// FlagValueSet is an interface that users can implement
// to bind a set of flags to viper.
type FlagValueSet interface {
VisitAll(fn func(FlagValue))
}
// FlagValue is an interface that users can implement
// to bind different flags to viper.
type FlagValue interface {
HasChanged() bool
Name() string
ValueString() string
ValueType() string
}
// pflagValueSet is a wrapper around *pflag.ValueSet
// that implements FlagValueSet.
type pflagValueSet struct {
flags *pflag.FlagSet
}
// VisitAll iterates over all *pflag.Flag inside the *pflag.FlagSet.
func (p pflagValueSet) VisitAll(fn func(flag FlagValue)) {
p.flags.VisitAll(func(flag *pflag.Flag) {
fn(pflagValue{flag})
})
}
// pflagValue is a wrapper aroung *pflag.flag
// that implements FlagValue
type pflagValue struct {
flag *pflag.Flag
}
// HasChanges returns whether the flag has changes or not.
func (p pflagValue) HasChanged() bool {
return p.flag.Changed
}
// Name returns the name of the flag.
func (p pflagValue) Name() string {
return p.flag.Name
}
// ValueString returns the value of the flag as a string.
func (p pflagValue) ValueString() string {
return p.flag.Value.String()
}
// ValueType returns the type of the flag as a string.
func (p pflagValue) ValueType() string {
return p.flag.Value.Type()
}

View File

@ -1,16 +1,4 @@
package: git.loafle.net/commons_go/config package: git.loafle.net/commons_go/config
import: import:
- package: github.com/spf13/afero - package: github.com/BurntSushi/toml
- package: gopkg.in/yaml.v2 - package: gopkg.in/yaml.v2
- package: github.com/hashicorp/hcl
- package: github.com/pelletier/go-toml
version: v1.0.0
- package: github.com/magiconair/properties
version: v1.7.3
- package: github.com/spf13/cast
version: v1.1.0
- package: github.com/spf13/pflag
version: v1.0.0
- package: github.com/mitchellh/mapstructure
- package: github.com/fsnotify/fsnotify
version: v1.4.2

328
util.go
View File

@ -3,41 +3,19 @@ package config
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"runtime" "runtime"
"strings" "strings"
"unicode"
"github.com/hashicorp/hcl" "github.com/BurntSushi/toml"
"github.com/magiconair/properties"
toml "github.com/pelletier/go-toml"
"github.com/spf13/cast"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
) )
// ConfigParseError denotes failing to parse configuration file.
type ConfigParseError struct {
err error
}
// Error returns the formatted configuration error.
func (pe ConfigParseError) Error() string {
return fmt.Sprintf("While parsing config: %s", pe.err.Error())
}
// ConfigMarshalError denotes failing to marshal configuration.
type ConfigMarshalError struct {
err error
}
// Error returns the formatted marshal error.
func (me ConfigMarshalError) Error() string {
return fmt.Sprintf("While marshaling config: %s", me.err.Error())
}
func absPathify(inPath string) (string, error) { func absPathify(inPath string) (string, error) {
if strings.HasPrefix(inPath, "$HOME") { if strings.HasPrefix(inPath, "$HOME") {
inPath = userHomeDir() + inPath[5:] inPath = userHomeDir() + inPath[5:]
@ -71,221 +49,157 @@ func userHomeDir() string {
return os.Getenv("HOME") return os.Getenv("HOME")
} }
func stringInSlice(a string, list []string) bool { func exists(path string) bool {
for _, b := range list { if fileInfo, err := os.Stat(path); err == nil && fileInfo.Mode().IsRegular() {
if b == a {
return true return true
} }
}
return false return false
} }
func exists(path string) (bool, error) { func unmarshalFile(target interface{}, file string) error {
_, err := _c.fs.Stat(path) data, err := ioutil.ReadFile(file)
if err == nil { if err != nil {
return true, nil return err
} }
if os.IsNotExist(err) {
return false, nil return unmarshalData(target, filepath.Ext(file), data)
}
return false, err
} }
func marshallConfig(c map[string]interface{}, configType string) ([]byte, error) { func unmarshalData(target interface{}, ext string, data []byte) error {
switch ext {
case ".yaml", ".yml":
return yaml.Unmarshal(data, target)
case ".toml":
return toml.Unmarshal(data, target)
case ".json":
return json.Unmarshal(data, target)
default:
if toml.Unmarshal(data, target) != nil {
if json.Unmarshal(data, target) != nil {
if yaml.Unmarshal(data, target) != nil {
return errors.New("failed to decode config")
}
}
}
return nil
}
}
func unmarshalTags(target interface{}, prefixes ...string) error {
targetValue := reflect.Indirect(reflect.ValueOf(target))
if targetValue.Kind() != reflect.Struct {
return errors.New("invalid config, should be struct")
}
targetType := targetValue.Type()
for i := 0; i < targetType.NumField(); i++ {
var ( var (
buf []byte envNames []string
err error fieldStruct = targetType.Field(i)
field = targetValue.Field(i)
envName = fieldStruct.Tag.Get(ConfigTagEnv) // read configuration from shell env
) )
if !field.CanAddr() || !field.CanInterface() {
switch strings.ToLower(configType) { continue
case "yaml", "yml":
if buf, err = yaml.Marshal(c); err != nil {
return nil, ConfigMarshalError{err}
} }
if envName == "" {
case "json": envNames = append(envNames, strings.Join(append(prefixes, fieldStruct.Name), "_")) // Configor_DB_Name
if buf, err = json.Marshal(c); err != nil { envNames = append(envNames, strings.ToUpper(strings.Join(append(prefixes, fieldStruct.Name), "_"))) // CONFIGOR_DB_NAME
return nil, ConfigMarshalError{err} } else {
envNames = []string{envName}
} }
// Load From Shell ENV
case "toml": for _, env := range envNames {
if buf, err = toml.Marshal(c); err != nil { if value := os.Getenv(env); value != "" {
return nil, ConfigMarshalError{err} if err := yaml.Unmarshal([]byte(value), field.Addr().Interface()); err != nil {
return err
} }
break
} }
return buf, nil
}
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}
} }
if isBlank := reflect.DeepEqual(field.Interface(), reflect.Zero(field.Type()).Interface()); isBlank {
case "json": // Set default configuration if blank
if err := json.Unmarshal(buf.Bytes(), &c); err != nil { if value := fieldStruct.Tag.Get("default"); value != "" {
return ConfigParseError{err} if err := yaml.Unmarshal([]byte(value), field.Addr().Interface()); err != nil {
return err
} }
} else if fieldStruct.Tag.Get("required") == "true" {
case "hcl": // return error if it is required but blank
obj, err := hcl.Parse(string(buf.Bytes())) return fmt.Errorf("Field[%s] is required", fieldStruct.Name)
if err != nil { }
return ConfigParseError{err} }
for field.Kind() == reflect.Ptr {
field = field.Elem()
}
if field.Kind() == reflect.Struct {
if err := unmarshalTags(field.Addr().Interface(), getPrefixForStruct(prefixes, &fieldStruct)...); err != nil {
return err
}
}
if field.Kind() == reflect.Slice {
for i := 0; i < field.Len(); i++ {
if reflect.Indirect(field.Index(i)).Kind() == reflect.Struct {
if err := unmarshalTags(field.Index(i).Addr().Interface(), append(getPrefixForStruct(prefixes, &fieldStruct), fmt.Sprint(i))...); err != nil {
return 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 return nil
} }
func insensitiviseMap(m map[string]interface{}) { func getPrefixForStruct(prefixes []string, fieldStruct *reflect.StructField) []string {
for key, val := range m { if fieldStruct.Anonymous && fieldStruct.Tag.Get("anonymous") == "true" {
switch val.(type) { return prefixes
case map[interface{}]interface{}:
// nested map: cast and recursively insensitivise
val = cast.ToStringMap(val)
insensitiviseMap(val.(map[string]interface{}))
case map[string]interface{}:
// nested map: recursively insensitivise
insensitiviseMap(val.(map[string]interface{}))
}
lower := strings.ToLower(key)
if key != lower {
// remove old key (not lower-cased)
delete(m, key)
}
// update map
m[lower] = val
} }
return append(prefixes, fieldStruct.Name)
} }
func deepSearch(m map[string]interface{}, path []string) map[string]interface{} { func marshalFile(target interface{}, file string, overWrite bool) error {
for _, k := range path { var f *os.File
m2, ok := m[k] var err error
if !ok { if exists(file) {
// intermediate key does not exist if !overWrite {
// => create it and continue from there return fmt.Errorf("Config: File[%s] is exist", file)
m3 := make(map[string]interface{})
m[k] = m3
m = m3
continue
} }
m3, ok := m2.(map[string]interface{}) if f, err = os.Open(file); nil != err {
if !ok { return err
// intermediate key is a value
// => replace with a new map
m3 = make(map[string]interface{})
m[k] = m3
} }
// continue search from here } else {
m = m3 if f, err = os.Create(file); nil != err {
return err
} }
return m }
var b []byte
if b, err = marshal(target, filepath.Ext(file)); nil != err {
return err
}
if _, err = f.Write(b); nil != err {
return err
}
return nil
} }
func toCaseInsensitiveValue(value interface{}) interface{} { func marshal(target interface{}, ext string) ([]byte, error) {
switch v := value.(type) { switch ext {
case map[interface{}]interface{}: case ".yaml", ".yml":
value = copyAndInsensitiviseMap(cast.ToStringMap(v)) return yaml.Marshal(target)
case map[string]interface{}: case ".toml":
value = copyAndInsensitiviseMap(v) var buf bytes.Buffer
enc := toml.NewEncoder(&buf)
if err := enc.Encode(target); nil != err {
return nil, err
} }
return buf.Bytes(), nil
return value case ".json":
} return json.Marshal(target)
func copyAndInsensitiviseMap(m map[string]interface{}) map[string]interface{} {
nm := make(map[string]interface{})
for key, val := range m {
lkey := strings.ToLower(key)
switch v := val.(type) {
case map[interface{}]interface{}:
nm[lkey] = copyAndInsensitiviseMap(cast.ToStringMap(v))
case map[string]interface{}:
nm[lkey] = copyAndInsensitiviseMap(v)
default: default:
nm[lkey] = v return nil, fmt.Errorf("Config: Not supported extention[%s]", ext)
} }
}
return nm
}
func safeMul(a, b uint) uint {
c := a * b
if a > 1 && b > 1 && c/b != a {
return 0
}
return c
}
func parseSizeInBytes(sizeStr string) uint {
sizeStr = strings.TrimSpace(sizeStr)
lastChar := len(sizeStr) - 1
multiplier := uint(1)
if lastChar > 0 {
if sizeStr[lastChar] == 'b' || sizeStr[lastChar] == 'B' {
if lastChar > 1 {
switch unicode.ToLower(rune(sizeStr[lastChar-1])) {
case 'k':
multiplier = 1 << 10
sizeStr = strings.TrimSpace(sizeStr[:lastChar-1])
case 'm':
multiplier = 1 << 20
sizeStr = strings.TrimSpace(sizeStr[:lastChar-1])
case 'g':
multiplier = 1 << 30
sizeStr = strings.TrimSpace(sizeStr[:lastChar-1])
default:
multiplier = 1
sizeStr = strings.TrimSpace(sizeStr[:lastChar])
}
}
}
}
size := cast.ToInt(sizeStr)
if size < 0 {
size = 0
}
return safeMul(uint(size), multiplier)
} }