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.
478 lines
12 KiB
478 lines
12 KiB
// Package toml encodes and decodes the TOML configuration format using reflection. |
|
// |
|
// This library is compatible with TOML version v0.4.0. |
|
package toml |
|
|
|
import ( |
|
"encoding" |
|
"fmt" |
|
"io" |
|
"io/ioutil" |
|
"reflect" |
|
"strconv" |
|
"strings" |
|
"time" |
|
|
|
"github.com/naoina/toml/ast" |
|
) |
|
|
|
const ( |
|
tableSeparator = '.' |
|
) |
|
|
|
var ( |
|
escapeReplacer = strings.NewReplacer( |
|
"\b", "\\n", |
|
"\f", "\\f", |
|
"\n", "\\n", |
|
"\r", "\\r", |
|
"\t", "\\t", |
|
) |
|
underscoreReplacer = strings.NewReplacer( |
|
"_", "", |
|
) |
|
) |
|
|
|
var timeType = reflect.TypeOf(time.Time{}) |
|
|
|
// Unmarshal parses the TOML data and stores the result in the value pointed to by v. |
|
// |
|
// Unmarshal will mapped to v that according to following rules: |
|
// |
|
// TOML strings to string |
|
// TOML integers to any int type |
|
// TOML floats to float32 or float64 |
|
// TOML booleans to bool |
|
// TOML datetimes to time.Time |
|
// TOML arrays to any type of slice |
|
// TOML tables to struct or map |
|
// TOML array tables to slice of struct or map |
|
func (cfg *Config) Unmarshal(data []byte, v interface{}) error { |
|
table, err := Parse(data) |
|
if err != nil { |
|
return err |
|
} |
|
if err := cfg.UnmarshalTable(table, v); err != nil { |
|
return err |
|
} |
|
return nil |
|
} |
|
|
|
// A Decoder reads and decodes TOML from an input stream. |
|
type Decoder struct { |
|
r io.Reader |
|
cfg *Config |
|
} |
|
|
|
// NewDecoder returns a new Decoder that reads from r. |
|
// Note that it reads all from r before parsing it. |
|
func (cfg *Config) NewDecoder(r io.Reader) *Decoder { |
|
return &Decoder{r, cfg} |
|
} |
|
|
|
// Decode parses the TOML data from its input and stores it in the value pointed to by v. |
|
// See the documentation for Unmarshal for details about the conversion of TOML into a Go value. |
|
func (d *Decoder) Decode(v interface{}) error { |
|
b, err := ioutil.ReadAll(d.r) |
|
if err != nil { |
|
return err |
|
} |
|
return d.cfg.Unmarshal(b, v) |
|
} |
|
|
|
// UnmarshalerRec may be implemented by types to customize their behavior when being |
|
// unmarshaled from TOML. You can use it to implement custom validation or to set |
|
// unexported fields. |
|
// |
|
// UnmarshalTOML receives a function that can be called to unmarshal the original TOML |
|
// value into a field or variable. It is safe to call the function more than once if |
|
// necessary. |
|
type UnmarshalerRec interface { |
|
UnmarshalTOML(fn func(interface{}) error) error |
|
} |
|
|
|
// Unmarshaler can be used to capture and process raw TOML source of a table or value. |
|
// UnmarshalTOML must copy the input if it wishes to retain it after returning. |
|
// |
|
// Note: this interface is retained for backwards compatibility. You probably want |
|
// to implement encoding.TextUnmarshaler or UnmarshalerRec instead. |
|
type Unmarshaler interface { |
|
UnmarshalTOML(input []byte) error |
|
} |
|
|
|
// UnmarshalTable applies the contents of an ast.Table to the value pointed at by v. |
|
// |
|
// UnmarshalTable will mapped to v that according to following rules: |
|
// |
|
// TOML strings to string |
|
// TOML integers to any int type |
|
// TOML floats to float32 or float64 |
|
// TOML booleans to bool |
|
// TOML datetimes to time.Time |
|
// TOML arrays to any type of slice |
|
// TOML tables to struct or map |
|
// TOML array tables to slice of struct or map |
|
func (cfg *Config) UnmarshalTable(t *ast.Table, v interface{}) error { |
|
rv := reflect.ValueOf(v) |
|
toplevelMap := rv.Kind() == reflect.Map |
|
if (!toplevelMap && rv.Kind() != reflect.Ptr) || rv.IsNil() { |
|
return &invalidUnmarshalError{reflect.TypeOf(v)} |
|
} |
|
return unmarshalTable(cfg, rv, t, toplevelMap) |
|
} |
|
|
|
// used for UnmarshalerRec. |
|
func unmarshalTableOrValue(cfg *Config, rv reflect.Value, av interface{}) error { |
|
if (rv.Kind() != reflect.Ptr && rv.Kind() != reflect.Map) || rv.IsNil() { |
|
return &invalidUnmarshalError{rv.Type()} |
|
} |
|
rv = indirect(rv) |
|
|
|
switch av.(type) { |
|
case *ast.KeyValue, *ast.Table, []*ast.Table: |
|
if err := unmarshalField(cfg, rv, av); err != nil { |
|
return lineError(fieldLineNumber(av), err) |
|
} |
|
return nil |
|
case ast.Value: |
|
return setValue(cfg, rv, av.(ast.Value)) |
|
default: |
|
panic(fmt.Sprintf("BUG: unhandled AST node type %T", av)) |
|
} |
|
} |
|
|
|
// unmarshalTable unmarshals the fields of a table into a struct or map. |
|
// |
|
// toplevelMap is true when rv is an (unadressable) map given to UnmarshalTable. In this |
|
// (special) case, the map is used as-is instead of creating a new map. |
|
func unmarshalTable(cfg *Config, rv reflect.Value, t *ast.Table, toplevelMap bool) error { |
|
rv = indirect(rv) |
|
if err, ok := setUnmarshaler(cfg, rv, t); ok { |
|
return lineError(t.Line, err) |
|
} |
|
switch { |
|
case rv.Kind() == reflect.Struct: |
|
fc := makeFieldCache(cfg, rv.Type()) |
|
for key, fieldAst := range t.Fields { |
|
fv, fieldName, err := fc.findField(cfg, rv, key) |
|
if err != nil { |
|
return lineError(fieldLineNumber(fieldAst), err) |
|
} |
|
if fv.IsValid() { |
|
if err := unmarshalField(cfg, fv, fieldAst); err != nil { |
|
return lineErrorField(fieldLineNumber(fieldAst), rv.Type().String()+"."+fieldName, err) |
|
} |
|
} |
|
} |
|
case rv.Kind() == reflect.Map || isEface(rv): |
|
m := rv |
|
if !toplevelMap { |
|
if rv.Kind() == reflect.Interface { |
|
m = reflect.ValueOf(make(map[string]interface{})) |
|
} else { |
|
m = reflect.MakeMap(rv.Type()) |
|
} |
|
} |
|
elemtyp := m.Type().Elem() |
|
for key, fieldAst := range t.Fields { |
|
kv, err := unmarshalMapKey(m.Type().Key(), key) |
|
if err != nil { |
|
return lineError(fieldLineNumber(fieldAst), err) |
|
} |
|
fv := reflect.New(elemtyp).Elem() |
|
if err := unmarshalField(cfg, fv, fieldAst); err != nil { |
|
return lineError(fieldLineNumber(fieldAst), err) |
|
} |
|
m.SetMapIndex(kv, fv) |
|
} |
|
if !toplevelMap { |
|
rv.Set(m) |
|
} |
|
default: |
|
return lineError(t.Line, &unmarshalTypeError{"table", "struct or map", rv.Type()}) |
|
} |
|
return nil |
|
} |
|
|
|
func fieldLineNumber(fieldAst interface{}) int { |
|
switch av := fieldAst.(type) { |
|
case *ast.KeyValue: |
|
return av.Line |
|
case *ast.Table: |
|
return av.Line |
|
case []*ast.Table: |
|
return av[0].Line |
|
default: |
|
panic(fmt.Sprintf("BUG: unhandled node type %T", fieldAst)) |
|
} |
|
} |
|
|
|
func unmarshalField(cfg *Config, rv reflect.Value, fieldAst interface{}) error { |
|
switch av := fieldAst.(type) { |
|
case *ast.KeyValue: |
|
return setValue(cfg, rv, av.Value) |
|
case *ast.Table: |
|
return unmarshalTable(cfg, rv, av, false) |
|
case []*ast.Table: |
|
rv = indirect(rv) |
|
if err, ok := setUnmarshaler(cfg, rv, fieldAst); ok { |
|
return err |
|
} |
|
var slice reflect.Value |
|
switch { |
|
case rv.Kind() == reflect.Slice: |
|
slice = reflect.MakeSlice(rv.Type(), len(av), len(av)) |
|
case isEface(rv): |
|
slice = reflect.ValueOf(make([]interface{}, len(av))) |
|
default: |
|
return &unmarshalTypeError{"array table", "slice", rv.Type()} |
|
} |
|
for i, tbl := range av { |
|
vv := reflect.New(slice.Type().Elem()).Elem() |
|
if err := unmarshalTable(cfg, vv, tbl, false); err != nil { |
|
return err |
|
} |
|
slice.Index(i).Set(vv) |
|
} |
|
rv.Set(slice) |
|
default: |
|
panic(fmt.Sprintf("BUG: unhandled AST node type %T", av)) |
|
} |
|
return nil |
|
} |
|
|
|
func unmarshalMapKey(typ reflect.Type, key string) (reflect.Value, error) { |
|
rv := reflect.New(typ).Elem() |
|
if u, ok := rv.Addr().Interface().(encoding.TextUnmarshaler); ok { |
|
return rv, u.UnmarshalText([]byte(key)) |
|
} |
|
switch typ.Kind() { |
|
case reflect.String: |
|
rv.SetString(key) |
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
|
i, err := strconv.ParseInt(key, 10, int(typ.Size()*8)) |
|
if err != nil { |
|
return rv, convertNumError(typ.Kind(), err) |
|
} |
|
rv.SetInt(i) |
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
|
i, err := strconv.ParseUint(key, 10, int(typ.Size()*8)) |
|
if err != nil { |
|
return rv, convertNumError(typ.Kind(), err) |
|
} |
|
rv.SetUint(i) |
|
default: |
|
return rv, fmt.Errorf("invalid map key type %s", typ) |
|
} |
|
return rv, nil |
|
} |
|
|
|
func setValue(cfg *Config, lhs reflect.Value, val ast.Value) error { |
|
lhs = indirect(lhs) |
|
if err, ok := setUnmarshaler(cfg, lhs, val); ok { |
|
return err |
|
} |
|
if err, ok := setTextUnmarshaler(lhs, val); ok { |
|
return err |
|
} |
|
switch v := val.(type) { |
|
case *ast.Integer: |
|
return setInt(lhs, v) |
|
case *ast.Float: |
|
return setFloat(lhs, v) |
|
case *ast.String: |
|
return setString(lhs, v) |
|
case *ast.Boolean: |
|
return setBoolean(lhs, v) |
|
case *ast.Datetime: |
|
return setDatetime(lhs, v) |
|
case *ast.Array: |
|
return setArray(cfg, lhs, v) |
|
default: |
|
panic(fmt.Sprintf("BUG: unhandled node type %T", v)) |
|
} |
|
} |
|
|
|
func indirect(rv reflect.Value) reflect.Value { |
|
for rv.Kind() == reflect.Ptr { |
|
if rv.IsNil() { |
|
rv.Set(reflect.New(rv.Type().Elem())) |
|
} |
|
rv = rv.Elem() |
|
} |
|
return rv |
|
} |
|
|
|
func setUnmarshaler(cfg *Config, lhs reflect.Value, av interface{}) (error, bool) { |
|
if lhs.CanAddr() { |
|
if u, ok := lhs.Addr().Interface().(UnmarshalerRec); ok { |
|
err := u.UnmarshalTOML(func(v interface{}) error { |
|
return unmarshalTableOrValue(cfg, reflect.ValueOf(v), av) |
|
}) |
|
return err, true |
|
} |
|
if u, ok := lhs.Addr().Interface().(Unmarshaler); ok { |
|
return u.UnmarshalTOML(unmarshalerSource(av)), true |
|
} |
|
} |
|
return nil, false |
|
} |
|
|
|
func unmarshalerSource(av interface{}) []byte { |
|
var source []byte |
|
switch av := av.(type) { |
|
case []*ast.Table: |
|
for i, tab := range av { |
|
source = append(source, tab.Source()...) |
|
if i != len(av)-1 { |
|
source = append(source, '\n') |
|
} |
|
} |
|
case ast.Value: |
|
source = []byte(av.Source()) |
|
default: |
|
panic(fmt.Sprintf("BUG: unhandled node type %T", av)) |
|
} |
|
return source |
|
} |
|
|
|
func setTextUnmarshaler(lhs reflect.Value, val ast.Value) (error, bool) { |
|
if !lhs.CanAddr() { |
|
return nil, false |
|
} |
|
u, ok := lhs.Addr().Interface().(encoding.TextUnmarshaler) |
|
if !ok || lhs.Type() == timeType { |
|
return nil, false |
|
} |
|
var data string |
|
switch val := val.(type) { |
|
case *ast.Array: |
|
return &unmarshalTypeError{"array", "", lhs.Type()}, true |
|
case *ast.String: |
|
data = val.Value |
|
default: |
|
data = val.Source() |
|
} |
|
return u.UnmarshalText([]byte(data)), true |
|
} |
|
|
|
func setInt(fv reflect.Value, v *ast.Integer) error { |
|
k := fv.Kind() |
|
switch { |
|
case k >= reflect.Int && k <= reflect.Int64: |
|
i, err := strconv.ParseInt(v.Value, 10, int(fv.Type().Size()*8)) |
|
if err != nil { |
|
return convertNumError(fv.Kind(), err) |
|
} |
|
fv.SetInt(i) |
|
case k >= reflect.Uint && k <= reflect.Uintptr: |
|
i, err := strconv.ParseUint(v.Value, 10, int(fv.Type().Size()*8)) |
|
if err != nil { |
|
return convertNumError(fv.Kind(), err) |
|
} |
|
fv.SetUint(i) |
|
case isEface(fv): |
|
i, err := strconv.ParseInt(v.Value, 10, 64) |
|
if err != nil { |
|
return convertNumError(reflect.Int64, err) |
|
} |
|
fv.Set(reflect.ValueOf(i)) |
|
default: |
|
return &unmarshalTypeError{"integer", "", fv.Type()} |
|
} |
|
return nil |
|
} |
|
|
|
func setFloat(fv reflect.Value, v *ast.Float) error { |
|
f, err := v.Float() |
|
if err != nil { |
|
return err |
|
} |
|
switch { |
|
case fv.Kind() == reflect.Float32 || fv.Kind() == reflect.Float64: |
|
if fv.OverflowFloat(f) { |
|
return &overflowError{fv.Kind(), v.Value} |
|
} |
|
fv.SetFloat(f) |
|
case isEface(fv): |
|
fv.Set(reflect.ValueOf(f)) |
|
default: |
|
return &unmarshalTypeError{"float", "", fv.Type()} |
|
} |
|
return nil |
|
} |
|
|
|
func setString(fv reflect.Value, v *ast.String) error { |
|
switch { |
|
case fv.Kind() == reflect.String: |
|
fv.SetString(v.Value) |
|
case isEface(fv): |
|
fv.Set(reflect.ValueOf(v.Value)) |
|
default: |
|
return &unmarshalTypeError{"string", "", fv.Type()} |
|
} |
|
return nil |
|
} |
|
|
|
func setBoolean(fv reflect.Value, v *ast.Boolean) error { |
|
b, _ := v.Boolean() |
|
switch { |
|
case fv.Kind() == reflect.Bool: |
|
fv.SetBool(b) |
|
case isEface(fv): |
|
fv.Set(reflect.ValueOf(b)) |
|
default: |
|
return &unmarshalTypeError{"boolean", "", fv.Type()} |
|
} |
|
return nil |
|
} |
|
|
|
func setDatetime(rv reflect.Value, v *ast.Datetime) error { |
|
t, err := v.Time() |
|
if err != nil { |
|
return err |
|
} |
|
if !timeType.AssignableTo(rv.Type()) { |
|
return &unmarshalTypeError{"datetime", "", rv.Type()} |
|
} |
|
rv.Set(reflect.ValueOf(t)) |
|
return nil |
|
} |
|
|
|
func setArray(cfg *Config, rv reflect.Value, v *ast.Array) error { |
|
var slicetyp reflect.Type |
|
switch { |
|
case rv.Kind() == reflect.Slice: |
|
slicetyp = rv.Type() |
|
case isEface(rv): |
|
slicetyp = reflect.SliceOf(rv.Type()) |
|
default: |
|
return &unmarshalTypeError{"array", "slice", rv.Type()} |
|
} |
|
|
|
if len(v.Value) == 0 { |
|
// Ensure defined slices are always set to a non-nil value. |
|
rv.Set(reflect.MakeSlice(slicetyp, 0, 0)) |
|
return nil |
|
} |
|
|
|
tomltyp := reflect.TypeOf(v.Value[0]) |
|
slice := reflect.MakeSlice(slicetyp, len(v.Value), len(v.Value)) |
|
typ := slicetyp.Elem() |
|
for i, vv := range v.Value { |
|
if i > 0 && tomltyp != reflect.TypeOf(vv) { |
|
return errArrayMultiType |
|
} |
|
tmp := reflect.New(typ).Elem() |
|
if err := setValue(cfg, tmp, vv); err != nil { |
|
return err |
|
} |
|
slice.Index(i).Set(tmp) |
|
} |
|
rv.Set(slice) |
|
return nil |
|
} |
|
|
|
func isEface(rv reflect.Value) bool { |
|
return rv.Kind() == reflect.Interface && rv.Type().NumMethod() == 0 |
|
}
|
|
|