Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
332 lines
8.2 KiB
332 lines
8.2 KiB
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 |
|
}
|
|
|