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
import:
- package: github.com/spf13/afero
- package: github.com/BurntSushi/toml
- 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 (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"unicode"
"github.com/hashicorp/hcl"
"github.com/magiconair/properties"
toml "github.com/pelletier/go-toml"
"github.com/spf13/cast"
"github.com/BurntSushi/toml"
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) {
if strings.HasPrefix(inPath, "$HOME") {
inPath = userHomeDir() + inPath[5:]
@ -71,221 +49,157 @@ func userHomeDir() string {
return os.Getenv("HOME")
}
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
func exists(path string) bool {
if fileInfo, err := os.Stat(path); err == nil && fileInfo.Mode().IsRegular() {
return true
}
}
return false
}
func exists(path string) (bool, error) {
_, err := _c.fs.Stat(path)
if err == nil {
return true, nil
func unmarshalFile(target interface{}, file string) error {
data, err := ioutil.ReadFile(file)
if err != nil {
return err
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
return unmarshalData(target, filepath.Ext(file), data)
}
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 (
buf []byte
err error
envNames []string
fieldStruct = targetType.Field(i)
field = targetValue.Field(i)
envName = fieldStruct.Tag.Get(ConfigTagEnv) // read configuration from shell env
)
switch strings.ToLower(configType) {
case "yaml", "yml":
if buf, err = yaml.Marshal(c); err != nil {
return nil, ConfigMarshalError{err}
if !field.CanAddr() || !field.CanInterface() {
continue
}
case "json":
if buf, err = json.Marshal(c); err != nil {
return nil, ConfigMarshalError{err}
if envName == "" {
envNames = append(envNames, strings.Join(append(prefixes, fieldStruct.Name), "_")) // Configor_DB_Name
envNames = append(envNames, strings.ToUpper(strings.Join(append(prefixes, fieldStruct.Name), "_"))) // CONFIGOR_DB_NAME
} else {
envNames = []string{envName}
}
case "toml":
if buf, err = toml.Marshal(c); err != nil {
return nil, ConfigMarshalError{err}
// Load From Shell ENV
for _, env := range envNames {
if value := os.Getenv(env); value != "" {
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}
}
case "json":
if err := json.Unmarshal(buf.Bytes(), &c); err != nil {
return ConfigParseError{err}
if isBlank := reflect.DeepEqual(field.Interface(), reflect.Zero(field.Type()).Interface()); isBlank {
// Set default configuration if blank
if value := fieldStruct.Tag.Get("default"); value != "" {
if err := yaml.Unmarshal([]byte(value), field.Addr().Interface()); err != nil {
return err
}
case "hcl":
obj, err := hcl.Parse(string(buf.Bytes()))
if err != nil {
return ConfigParseError{err}
} else if fieldStruct.Tag.Get("required") == "true" {
// return error if it is required but blank
return fmt.Errorf("Field[%s] is required", fieldStruct.Name)
}
}
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
}
func insensitiviseMap(m map[string]interface{}) {
for key, val := range m {
switch val.(type) {
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
func getPrefixForStruct(prefixes []string, fieldStruct *reflect.StructField) []string {
if fieldStruct.Anonymous && fieldStruct.Tag.Get("anonymous") == "true" {
return prefixes
}
return append(prefixes, fieldStruct.Name)
}
func deepSearch(m map[string]interface{}, path []string) map[string]interface{} {
for _, k := range path {
m2, ok := m[k]
if !ok {
// intermediate key does not exist
// => create it and continue from there
m3 := make(map[string]interface{})
m[k] = m3
m = m3
continue
func marshalFile(target interface{}, file string, overWrite bool) error {
var f *os.File
var err error
if exists(file) {
if !overWrite {
return fmt.Errorf("Config: File[%s] is exist", file)
}
m3, ok := m2.(map[string]interface{})
if !ok {
// intermediate key is a value
// => replace with a new map
m3 = make(map[string]interface{})
m[k] = m3
if f, err = os.Open(file); nil != err {
return err
}
// continue search from here
m = m3
} else {
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{} {
switch v := value.(type) {
case map[interface{}]interface{}:
value = copyAndInsensitiviseMap(cast.ToStringMap(v))
case map[string]interface{}:
value = copyAndInsensitiviseMap(v)
func marshal(target interface{}, ext string) ([]byte, error) {
switch ext {
case ".yaml", ".yml":
return yaml.Marshal(target)
case ".toml":
var buf bytes.Buffer
enc := toml.NewEncoder(&buf)
if err := enc.Encode(target); nil != err {
return nil, err
}
return value
}
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)
return buf.Bytes(), nil
case ".json":
return json.Marshal(target)
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)
}