cfg/cfg.go

373 lines
6.6 KiB
Go
Raw Permalink Normal View History

2018-03-25 17:18:30 +02:00
package cfg
import (
"bufio"
"bytes"
"errors"
2018-05-01 21:33:13 +02:00
"fmt"
2018-03-25 17:18:30 +02:00
"io"
2019-03-07 17:23:03 +01:00
"math"
2018-03-25 17:18:30 +02:00
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
2018-03-25 17:18:30 +02:00
"time"
"unicode"
)
2018-05-01 21:33:13 +02:00
const (
tagCfg = "cfg"
tagDefault = "default"
)
2018-06-24 23:01:01 +02:00
// ConfigFiles represents multiple file containing the config keys and values
2018-05-02 16:34:33 +02:00
type ConfigFiles struct {
2018-03-25 17:18:30 +02:00
Files []File
}
2018-06-24 23:01:01 +02:00
// File represents a file
// Required if an error should be thrown if file is absent
2018-03-25 17:18:30 +02:00
type File struct {
2018-06-24 23:01:01 +02:00
Name string
Path string
Required bool
2018-03-25 17:18:30 +02:00
}
2018-06-24 23:01:01 +02:00
// Default represents a default value for a field
2018-05-04 22:47:11 +02:00
type Default struct {
2018-05-01 21:33:13 +02:00
Value string
field reflect.Value
}
2018-06-24 23:01:01 +02:00
// CustomType can be implemented to unmarshal in a custom format
2018-03-29 14:11:41 +02:00
type CustomType interface {
Unmarshal(value string) error
}
2018-06-24 23:01:01 +02:00
// FileSize implements Unmarshal for parsing a file size config value.
// e.g. 10MB
type FileSize uint64
2019-03-07 17:23:03 +01:00
const (
B = 1 << (iota * 10)
KB
MB
GB
TB
)
var sizes = []string{"B", "KB", "MB", "GB", "TB"}
func (fs FileSize) HumanReadable() string {
2019-03-07 18:37:07 +01:00
exp := math.Floor(math.Log(float64(fs)) / math.Log(1024))
2019-03-07 17:23:03 +01:00
2019-03-07 18:37:07 +01:00
if exp > 4 {
exp = 4
}
2019-03-07 17:23:03 +01:00
s := sizes[int(exp)]
if exp == 0 {
return fmt.Sprintf("%d %s", fs, s)
}
val := float64(fs) / float64(math.Pow(1024, exp))
2019-03-07 18:37:07 +01:00
return fmt.Sprintf("%s %s", strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.2f", val), "0"), "."), s)
2019-03-07 17:23:03 +01:00
}
func (fs *FileSize) Unmarshal(value string) error {
size := FileSize(0)
if len(value) == 0 {
fs = &size
return nil
}
2018-06-24 23:01:01 +02:00
value = strings.TrimSpace(strings.ToLower(value))
last := len(value) - 1
mp := uint64(1)
if value[last] == 'b' {
switch value[last-1] {
case 't':
mp = mp << 40
value = strings.TrimSpace(value[:last-1])
case 'g':
mp = mp << 30
value = strings.TrimSpace(value[:last-1])
case 'm':
mp = mp << 20
value = strings.TrimSpace(value[:last-1])
case 'k':
mp = mp << 10
value = strings.TrimSpace(value[:last-1])
default:
value = strings.TrimSpace(value[:last])
}
}
ps, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return err
}
*fs = FileSize(uint64(ps) * mp)
return nil
}
2018-05-01 21:33:13 +02:00
//AddConfig adds a config file
2018-06-24 23:01:01 +02:00
func (c *ConfigFiles) AddConfig(path, name string, required bool) {
2018-03-25 17:18:30 +02:00
f := File{
2018-06-24 23:01:01 +02:00
Path: path,
Name: name,
Required: required,
2018-03-25 17:18:30 +02:00
}
2018-03-27 22:11:26 +02:00
c.Files = append(c.Files, f)
2018-03-25 17:18:30 +02:00
}
2018-05-01 21:33:13 +02:00
//MergeConfigsInto merges multiple configs files into a struct
//returns the applied default values
2018-05-04 22:47:11 +02:00
func (c ConfigFiles) MergeConfigsInto(dest interface{}) (map[string]Default, error) {
2018-05-01 21:33:13 +02:00
kvs := make(map[string]string)
2018-03-25 17:18:30 +02:00
for _, v := range c.Files {
f, err := os.Open(filepath.Join(v.Path, v.Name))
if err != nil {
2018-06-24 23:01:01 +02:00
if !v.Required {
continue
}
2018-05-01 21:33:13 +02:00
return nil, err
2018-03-25 17:18:30 +02:00
}
defer f.Close()
2018-05-01 21:33:13 +02:00
kv, err := parse(f, dest)
2018-03-25 17:18:30 +02:00
if err != nil {
2018-05-01 21:33:13 +02:00
return nil, err
2018-03-25 17:18:30 +02:00
}
2018-04-18 15:16:39 +02:00
2018-05-01 21:33:13 +02:00
for k, v := range kv {
kvs[k] = v
}
2018-03-25 17:18:30 +02:00
}
2018-05-04 22:47:11 +02:00
defaults := make(map[string]Default)
2018-05-01 21:33:13 +02:00
err := setFields(kvs, defaults, dest)
if err != nil {
return nil, err
}
return defaults, nil
2018-03-25 17:18:30 +02:00
}
2018-05-01 21:33:13 +02:00
//LoadConfigInto loads a single config into struct
//returns the applied default values
2018-05-04 22:47:11 +02:00
func LoadConfigInto(file string, dest interface{}) (map[string]Default, error) {
f, err := os.Open(file)
if err != nil {
2018-05-01 21:33:13 +02:00
return nil, err
}
defer f.Close()
2018-05-01 21:33:13 +02:00
kvs, err := parse(f, dest)
if err != nil {
2018-05-01 21:33:13 +02:00
return nil, err
}
2018-05-04 22:47:11 +02:00
defaults := make(map[string]Default)
2018-05-01 21:33:13 +02:00
err = setFields(kvs, defaults, dest)
if err != nil {
return nil, err
}
return defaults, nil
}
2018-05-01 21:33:13 +02:00
func parse(file *os.File, dest interface{}) (map[string]string, error) {
2018-03-25 17:18:30 +02:00
reader := bufio.NewReader(file)
kvmap := make(map[string]string)
for {
line, err := reader.ReadBytes('\n')
if err != nil {
if err == io.EOF {
break
} else {
2018-05-01 21:33:13 +02:00
return nil, err
2018-03-25 17:18:30 +02:00
}
}
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
if len(line) == 0 {
2018-03-25 18:12:04 +02:00
continue
2018-03-25 17:18:30 +02:00
}
if line[0] == '#' {
2018-03-25 18:12:04 +02:00
continue
2018-03-25 17:18:30 +02:00
}
kv := bytes.SplitN(line, []byte("="), 2)
if len(kv) < 2 {
2018-03-25 18:12:04 +02:00
continue
2018-03-25 17:18:30 +02:00
}
key := string(bytes.TrimRightFunc(kv[0], unicode.IsSpace))
value := string(bytes.TrimSpace(bytes.TrimRight(kv[1], "\r\n")))
kvmap[key] = value
}
2018-05-01 21:33:13 +02:00
return kvmap, nil
2018-03-25 17:18:30 +02:00
}
2018-05-04 22:47:11 +02:00
func setFields(kv map[string]string, defaults map[string]Default, dest interface{}) error {
2018-03-25 17:18:30 +02:00
v := reflect.ValueOf(dest)
if v.Kind() != reflect.Ptr {
return errors.New("struct must be a pointer")
}
el := v.Elem()
for i := 0; i < el.NumField(); i++ {
2018-03-25 18:12:04 +02:00
if el.Field(i).Kind() == reflect.Struct {
2018-05-01 21:33:13 +02:00
err := setFields(kv, defaults, el.Field(i).Addr().Interface())
2018-04-18 15:16:39 +02:00
if err != nil {
return err
}
2018-03-25 18:12:04 +02:00
continue
}
2018-03-25 17:18:30 +02:00
if el.Field(i).CanSet() {
sKey := el.Type().Field(i).Tag.Get(tagCfg)
2018-05-01 21:33:13 +02:00
defValue := el.Type().Field(i).Tag.Get(tagDefault)
2018-03-25 17:18:30 +02:00
2018-03-28 00:34:49 +02:00
if sKey == "-" {
continue
}
2018-03-28 00:46:21 +02:00
2018-03-25 17:18:30 +02:00
if len(sKey) == 0 {
sKey = el.Type().Field(i).Name
}
2018-05-04 22:47:11 +02:00
def := Default{}
2018-05-01 21:33:13 +02:00
if len(defValue) > 0 {
2018-05-04 22:47:11 +02:00
def = Default{
2018-05-01 21:33:13 +02:00
Value: defValue,
field: el.Field(i),
}
defaults[sKey] = def
}
2018-03-25 17:18:30 +02:00
value, ok := kv[sKey]
if ok {
2018-03-25 18:12:04 +02:00
err := setField(el.Field(i), value)
2018-03-25 17:18:30 +02:00
if err != nil {
2018-05-04 22:47:11 +02:00
if def != (Default{}) {
2018-05-01 22:01:44 +02:00
//ignore error here if key has a default
2018-05-01 21:33:13 +02:00
continue
}
return fmt.Errorf("error while setting value [%s] for key [%s] error %v", value, sKey, err)
2018-03-25 17:18:30 +02:00
}
2018-05-01 21:33:13 +02:00
delete(defaults, sKey)
2018-03-25 17:18:30 +02:00
}
}
}
2018-05-01 21:33:13 +02:00
for k, d := range defaults {
err := setField(d.field, d.Value)
2018-03-25 17:18:30 +02:00
if err != nil {
2018-05-01 21:33:13 +02:00
return fmt.Errorf("error while setting default value [%s] for key [%s] error %v", d.Value, k, err)
2018-03-25 17:18:30 +02:00
}
}
return nil
}
2018-03-25 18:12:04 +02:00
func setField(field reflect.Value, value string) error {
2018-03-29 14:11:41 +02:00
customType := reflect.TypeOf((*CustomType)(nil)).Elem()
if reflect.PtrTo(field.Type()).Implements(customType) {
if c, ok := field.Addr().Interface().(CustomType); ok {
err := c.Unmarshal(value)
if err != nil {
return err
}
}
return nil
}
2018-03-25 17:18:30 +02:00
switch field.Kind() {
case reflect.String:
field.SetString(value)
case reflect.Int8, reflect.Int16, reflect.Int, reflect.Int64:
d, err := time.ParseDuration(value)
if err == nil {
field.Set(reflect.ValueOf(d))
return nil
}
iVal, err := strconv.ParseInt(value, 10, 64)
if err != nil {
2018-03-28 00:46:21 +02:00
return err
2018-03-25 17:18:30 +02:00
}
field.SetInt(int64(iVal))
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
iVal, err := strconv.ParseUint(value, 10, 64)
if err != nil {
2018-03-28 00:46:21 +02:00
return err
2018-03-25 17:18:30 +02:00
}
field.SetUint(uint64(iVal))
case reflect.Bool:
b := false
if value == "yes" || value == "YES" || value == "Yes" {
b = true
} else if value == "no" || value == "NO" || value == "No" {
b = false
} else {
var err error
b, err = strconv.ParseBool(value)
if err != nil {
2018-03-28 00:46:21 +02:00
return err
2018-03-25 17:18:30 +02:00
}
}
field.SetBool(b)
case reflect.Float32, reflect.Float64:
f, err := strconv.ParseFloat(value, 64)
if err != nil {
2018-03-28 00:46:21 +02:00
return err
2018-03-25 17:18:30 +02:00
}
field.SetFloat(float64(f))
}
return nil
}