333 lines
8.2 KiB
Go
333 lines
8.2 KiB
Go
|
package gcfg
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/gob"
|
||
|
"fmt"
|
||
|
"math/big"
|
||
|
"reflect"
|
||
|
"strings"
|
||
|
"unicode"
|
||
|
"unicode/utf8"
|
||
|
|
||
|
"github.com/src-d/gcfg/types"
|
||
|
"gopkg.in/warnings.v0"
|
||
|
)
|
||
|
|
||
|
type tag struct {
|
||
|
ident string
|
||
|
intMode string
|
||
|
}
|
||
|
|
||
|
func newTag(ts string) tag {
|
||
|
t := tag{}
|
||
|
s := strings.Split(ts, ",")
|
||
|
t.ident = s[0]
|
||
|
for _, tse := range s[1:] {
|
||
|
if strings.HasPrefix(tse, "int=") {
|
||
|
t.intMode = tse[len("int="):]
|
||
|
}
|
||
|
}
|
||
|
return t
|
||
|
}
|
||
|
|
||
|
func fieldFold(v reflect.Value, name string) (reflect.Value, tag) {
|
||
|
var n string
|
||
|
r0, _ := utf8.DecodeRuneInString(name)
|
||
|
if unicode.IsLetter(r0) && !unicode.IsLower(r0) && !unicode.IsUpper(r0) {
|
||
|
n = "X"
|
||
|
}
|
||
|
n += strings.Replace(name, "-", "_", -1)
|
||
|
f, ok := v.Type().FieldByNameFunc(func(fieldName string) bool {
|
||
|
if !v.FieldByName(fieldName).CanSet() {
|
||
|
return false
|
||
|
}
|
||
|
f, _ := v.Type().FieldByName(fieldName)
|
||
|
t := newTag(f.Tag.Get("gcfg"))
|
||
|
if t.ident != "" {
|
||
|
return strings.EqualFold(t.ident, name)
|
||
|
}
|
||
|
return strings.EqualFold(n, fieldName)
|
||
|
})
|
||
|
if !ok {
|
||
|
return reflect.Value{}, tag{}
|
||
|
}
|
||
|
return v.FieldByName(f.Name), newTag(f.Tag.Get("gcfg"))
|
||
|
}
|
||
|
|
||
|
type setter func(destp interface{}, blank bool, val string, t tag) error
|
||
|
|
||
|
var errUnsupportedType = fmt.Errorf("unsupported type")
|
||
|
var errBlankUnsupported = fmt.Errorf("blank value not supported for type")
|
||
|
|
||
|
var setters = []setter{
|
||
|
typeSetter, textUnmarshalerSetter, kindSetter, scanSetter,
|
||
|
}
|
||
|
|
||
|
func textUnmarshalerSetter(d interface{}, blank bool, val string, t tag) error {
|
||
|
dtu, ok := d.(textUnmarshaler)
|
||
|
if !ok {
|
||
|
return errUnsupportedType
|
||
|
}
|
||
|
if blank {
|
||
|
return errBlankUnsupported
|
||
|
}
|
||
|
return dtu.UnmarshalText([]byte(val))
|
||
|
}
|
||
|
|
||
|
func boolSetter(d interface{}, blank bool, val string, t tag) error {
|
||
|
if blank {
|
||
|
reflect.ValueOf(d).Elem().Set(reflect.ValueOf(true))
|
||
|
return nil
|
||
|
}
|
||
|
b, err := types.ParseBool(val)
|
||
|
if err == nil {
|
||
|
reflect.ValueOf(d).Elem().Set(reflect.ValueOf(b))
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func intMode(mode string) types.IntMode {
|
||
|
var m types.IntMode
|
||
|
if strings.ContainsAny(mode, "dD") {
|
||
|
m |= types.Dec
|
||
|
}
|
||
|
if strings.ContainsAny(mode, "hH") {
|
||
|
m |= types.Hex
|
||
|
}
|
||
|
if strings.ContainsAny(mode, "oO") {
|
||
|
m |= types.Oct
|
||
|
}
|
||
|
return m
|
||
|
}
|
||
|
|
||
|
var typeModes = map[reflect.Type]types.IntMode{
|
||
|
reflect.TypeOf(int(0)): types.Dec | types.Hex,
|
||
|
reflect.TypeOf(int8(0)): types.Dec | types.Hex,
|
||
|
reflect.TypeOf(int16(0)): types.Dec | types.Hex,
|
||
|
reflect.TypeOf(int32(0)): types.Dec | types.Hex,
|
||
|
reflect.TypeOf(int64(0)): types.Dec | types.Hex,
|
||
|
reflect.TypeOf(uint(0)): types.Dec | types.Hex,
|
||
|
reflect.TypeOf(uint8(0)): types.Dec | types.Hex,
|
||
|
reflect.TypeOf(uint16(0)): types.Dec | types.Hex,
|
||
|
reflect.TypeOf(uint32(0)): types.Dec | types.Hex,
|
||
|
reflect.TypeOf(uint64(0)): types.Dec | types.Hex,
|
||
|
// use default mode (allow dec/hex/oct) for uintptr type
|
||
|
reflect.TypeOf(big.Int{}): types.Dec | types.Hex,
|
||
|
}
|
||
|
|
||
|
func intModeDefault(t reflect.Type) types.IntMode {
|
||
|
m, ok := typeModes[t]
|
||
|
if !ok {
|
||
|
m = types.Dec | types.Hex | types.Oct
|
||
|
}
|
||
|
return m
|
||
|
}
|
||
|
|
||
|
func intSetter(d interface{}, blank bool, val string, t tag) error {
|
||
|
if blank {
|
||
|
return errBlankUnsupported
|
||
|
}
|
||
|
mode := intMode(t.intMode)
|
||
|
if mode == 0 {
|
||
|
mode = intModeDefault(reflect.TypeOf(d).Elem())
|
||
|
}
|
||
|
return types.ParseInt(d, val, mode)
|
||
|
}
|
||
|
|
||
|
func stringSetter(d interface{}, blank bool, val string, t tag) error {
|
||
|
if blank {
|
||
|
return errBlankUnsupported
|
||
|
}
|
||
|
dsp, ok := d.(*string)
|
||
|
if !ok {
|
||
|
return errUnsupportedType
|
||
|
}
|
||
|
*dsp = val
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
var kindSetters = map[reflect.Kind]setter{
|
||
|
reflect.String: stringSetter,
|
||
|
reflect.Bool: boolSetter,
|
||
|
reflect.Int: intSetter,
|
||
|
reflect.Int8: intSetter,
|
||
|
reflect.Int16: intSetter,
|
||
|
reflect.Int32: intSetter,
|
||
|
reflect.Int64: intSetter,
|
||
|
reflect.Uint: intSetter,
|
||
|
reflect.Uint8: intSetter,
|
||
|
reflect.Uint16: intSetter,
|
||
|
reflect.Uint32: intSetter,
|
||
|
reflect.Uint64: intSetter,
|
||
|
reflect.Uintptr: intSetter,
|
||
|
}
|
||
|
|
||
|
var typeSetters = map[reflect.Type]setter{
|
||
|
reflect.TypeOf(big.Int{}): intSetter,
|
||
|
}
|
||
|
|
||
|
func typeSetter(d interface{}, blank bool, val string, tt tag) error {
|
||
|
t := reflect.ValueOf(d).Type().Elem()
|
||
|
setter, ok := typeSetters[t]
|
||
|
if !ok {
|
||
|
return errUnsupportedType
|
||
|
}
|
||
|
return setter(d, blank, val, tt)
|
||
|
}
|
||
|
|
||
|
func kindSetter(d interface{}, blank bool, val string, tt tag) error {
|
||
|
k := reflect.ValueOf(d).Type().Elem().Kind()
|
||
|
setter, ok := kindSetters[k]
|
||
|
if !ok {
|
||
|
return errUnsupportedType
|
||
|
}
|
||
|
return setter(d, blank, val, tt)
|
||
|
}
|
||
|
|
||
|
func scanSetter(d interface{}, blank bool, val string, tt tag) error {
|
||
|
if blank {
|
||
|
return errBlankUnsupported
|
||
|
}
|
||
|
return types.ScanFully(d, val, 'v')
|
||
|
}
|
||
|
|
||
|
func newValue(c *warnings.Collector, sect string, vCfg reflect.Value,
|
||
|
vType reflect.Type) (reflect.Value, error) {
|
||
|
//
|
||
|
pv := reflect.New(vType)
|
||
|
dfltName := "default-" + sect
|
||
|
dfltField, _ := fieldFold(vCfg, dfltName)
|
||
|
var err error
|
||
|
if dfltField.IsValid() {
|
||
|
b := bytes.NewBuffer(nil)
|
||
|
ge := gob.NewEncoder(b)
|
||
|
if err = c.Collect(ge.EncodeValue(dfltField)); err != nil {
|
||
|
return pv, err
|
||
|
}
|
||
|
gd := gob.NewDecoder(bytes.NewReader(b.Bytes()))
|
||
|
if err = c.Collect(gd.DecodeValue(pv.Elem())); err != nil {
|
||
|
return pv, err
|
||
|
}
|
||
|
}
|
||
|
return pv, nil
|
||
|
}
|
||
|
|
||
|
func set(c *warnings.Collector, cfg interface{}, sect, sub, name string,
|
||
|
value string, blankValue bool, subsectPass bool) error {
|
||
|
//
|
||
|
vPCfg := reflect.ValueOf(cfg)
|
||
|
if vPCfg.Kind() != reflect.Ptr || vPCfg.Elem().Kind() != reflect.Struct {
|
||
|
panic(fmt.Errorf("config must be a pointer to a struct"))
|
||
|
}
|
||
|
vCfg := vPCfg.Elem()
|
||
|
vSect, _ := fieldFold(vCfg, sect)
|
||
|
if !vSect.IsValid() {
|
||
|
err := extraData{section: sect}
|
||
|
return c.Collect(err)
|
||
|
}
|
||
|
isSubsect := vSect.Kind() == reflect.Map
|
||
|
if subsectPass != isSubsect {
|
||
|
return nil
|
||
|
}
|
||
|
if isSubsect {
|
||
|
vst := vSect.Type()
|
||
|
if vst.Key().Kind() != reflect.String ||
|
||
|
vst.Elem().Kind() != reflect.Ptr ||
|
||
|
vst.Elem().Elem().Kind() != reflect.Struct {
|
||
|
panic(fmt.Errorf("map field for section must have string keys and "+
|
||
|
" pointer-to-struct values: section %q", sect))
|
||
|
}
|
||
|
if vSect.IsNil() {
|
||
|
vSect.Set(reflect.MakeMap(vst))
|
||
|
}
|
||
|
k := reflect.ValueOf(sub)
|
||
|
pv := vSect.MapIndex(k)
|
||
|
if !pv.IsValid() {
|
||
|
vType := vSect.Type().Elem().Elem()
|
||
|
var err error
|
||
|
if pv, err = newValue(c, sect, vCfg, vType); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
vSect.SetMapIndex(k, pv)
|
||
|
}
|
||
|
vSect = pv.Elem()
|
||
|
} else if vSect.Kind() != reflect.Struct {
|
||
|
panic(fmt.Errorf("field for section must be a map or a struct: "+
|
||
|
"section %q", sect))
|
||
|
} else if sub != "" {
|
||
|
err := extraData{section: sect, subsection: &sub}
|
||
|
return c.Collect(err)
|
||
|
}
|
||
|
// Empty name is a special value, meaning that only the
|
||
|
// section/subsection object is to be created, with no values set.
|
||
|
if name == "" {
|
||
|
return nil
|
||
|
}
|
||
|
vVar, t := fieldFold(vSect, name)
|
||
|
if !vVar.IsValid() {
|
||
|
var err error
|
||
|
if isSubsect {
|
||
|
err = extraData{section: sect, subsection: &sub, variable: &name}
|
||
|
} else {
|
||
|
err = extraData{section: sect, variable: &name}
|
||
|
}
|
||
|
return c.Collect(err)
|
||
|
}
|
||
|
// vVal is either single-valued var, or newly allocated value within multi-valued var
|
||
|
var vVal reflect.Value
|
||
|
// multi-value if unnamed slice type
|
||
|
isMulti := vVar.Type().Name() == "" && vVar.Kind() == reflect.Slice ||
|
||
|
vVar.Type().Name() == "" && vVar.Kind() == reflect.Ptr && vVar.Type().Elem().Name() == "" && vVar.Type().Elem().Kind() == reflect.Slice
|
||
|
if isMulti && vVar.Kind() == reflect.Ptr {
|
||
|
if vVar.IsNil() {
|
||
|
vVar.Set(reflect.New(vVar.Type().Elem()))
|
||
|
}
|
||
|
vVar = vVar.Elem()
|
||
|
}
|
||
|
if isMulti && blankValue {
|
||
|
vVar.Set(reflect.Zero(vVar.Type()))
|
||
|
return nil
|
||
|
}
|
||
|
if isMulti {
|
||
|
vVal = reflect.New(vVar.Type().Elem()).Elem()
|
||
|
} else {
|
||
|
vVal = vVar
|
||
|
}
|
||
|
isDeref := vVal.Type().Name() == "" && vVal.Type().Kind() == reflect.Ptr
|
||
|
isNew := isDeref && vVal.IsNil()
|
||
|
// vAddr is address of value to set (dereferenced & allocated as needed)
|
||
|
var vAddr reflect.Value
|
||
|
switch {
|
||
|
case isNew:
|
||
|
vAddr = reflect.New(vVal.Type().Elem())
|
||
|
case isDeref && !isNew:
|
||
|
vAddr = vVal
|
||
|
default:
|
||
|
vAddr = vVal.Addr()
|
||
|
}
|
||
|
vAddrI := vAddr.Interface()
|
||
|
err, ok := error(nil), false
|
||
|
for _, s := range setters {
|
||
|
err = s(vAddrI, blankValue, value, t)
|
||
|
if err == nil {
|
||
|
ok = true
|
||
|
break
|
||
|
}
|
||
|
if err != errUnsupportedType {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
if !ok {
|
||
|
// in case all setters returned errUnsupportedType
|
||
|
return err
|
||
|
}
|
||
|
if isNew { // set reference if it was dereferenced and newly allocated
|
||
|
vVal.Set(vAddr)
|
||
|
}
|
||
|
if isMulti { // append if multi-valued
|
||
|
vVar.Set(reflect.Append(vVar, vVal))
|
||
|
}
|
||
|
return nil
|
||
|
}
|