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
 | |
| }
 |