forked from toolshed/abra
		
	
		
			
				
	
	
		
			335 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			335 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package gcfg
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"encoding"
 | 
						|
	"encoding/gob"
 | 
						|
	"fmt"
 | 
						|
	"math/big"
 | 
						|
	"reflect"
 | 
						|
	"strings"
 | 
						|
	"unicode"
 | 
						|
	"unicode/utf8"
 | 
						|
 | 
						|
	"gopkg.in/warnings.v0"
 | 
						|
 | 
						|
	"github.com/go-git/gcfg/types"
 | 
						|
)
 | 
						|
 | 
						|
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.(encoding.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
 | 
						|
}
 |