mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-28 19:30:30 +00:00
c28f7cb29f
Initial part of #435
511 lines
14 KiB
Go
511 lines
14 KiB
Go
package toml
|
||
|
||
import (
|
||
"encoding"
|
||
"fmt"
|
||
"io"
|
||
"io/ioutil"
|
||
"math"
|
||
"os"
|
||
"reflect"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
// Unmarshaler is the interface implemented by objects that can unmarshal a
|
||
// TOML description of themselves.
|
||
type Unmarshaler interface {
|
||
UnmarshalTOML(interface{}) error
|
||
}
|
||
|
||
// Unmarshal decodes the contents of `p` in TOML format into a pointer `v`.
|
||
func Unmarshal(p []byte, v interface{}) error {
|
||
_, err := Decode(string(p), v)
|
||
return err
|
||
}
|
||
|
||
// Primitive is a TOML value that hasn't been decoded into a Go value.
|
||
//
|
||
// This type can be used for any value, which will cause decoding to be delayed.
|
||
// You can use the PrimitiveDecode() function to "manually" decode these values.
|
||
//
|
||
// NOTE: The underlying representation of a `Primitive` value is subject to
|
||
// change. Do not rely on it.
|
||
//
|
||
// NOTE: Primitive values are still parsed, so using them will only avoid the
|
||
// overhead of reflection. They can be useful when you don't know the exact type
|
||
// of TOML data until runtime.
|
||
type Primitive struct {
|
||
undecoded interface{}
|
||
context Key
|
||
}
|
||
|
||
// PrimitiveDecode is just like the other `Decode*` functions, except it
|
||
// decodes a TOML value that has already been parsed. Valid primitive values
|
||
// can *only* be obtained from values filled by the decoder functions,
|
||
// including this method. (i.e., `v` may contain more `Primitive`
|
||
// values.)
|
||
//
|
||
// Meta data for primitive values is included in the meta data returned by
|
||
// the `Decode*` functions with one exception: keys returned by the Undecoded
|
||
// method will only reflect keys that were decoded. Namely, any keys hidden
|
||
// behind a Primitive will be considered undecoded. Executing this method will
|
||
// update the undecoded keys in the meta data. (See the example.)
|
||
func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error {
|
||
md.context = primValue.context
|
||
defer func() { md.context = nil }()
|
||
return md.unify(primValue.undecoded, rvalue(v))
|
||
}
|
||
|
||
// Decoder decodes TOML data.
|
||
//
|
||
// TOML tables correspond to Go structs or maps (dealer's choice – they can be
|
||
// used interchangeably).
|
||
//
|
||
// TOML table arrays correspond to either a slice of structs or a slice of maps.
|
||
//
|
||
// TOML datetimes correspond to Go time.Time values. Local datetimes are parsed
|
||
// in the local timezone.
|
||
//
|
||
// All other TOML types (float, string, int, bool and array) correspond to the
|
||
// obvious Go types.
|
||
//
|
||
// An exception to the above rules is if a type implements the TextUnmarshaler
|
||
// interface, in which case any primitive TOML value (floats, strings, integers,
|
||
// booleans, datetimes) will be converted to a []byte and given to the value's
|
||
// UnmarshalText method. See the Unmarshaler example for a demonstration with
|
||
// time duration strings.
|
||
//
|
||
// Key mapping
|
||
//
|
||
// TOML keys can map to either keys in a Go map or field names in a Go struct.
|
||
// The special `toml` struct tag can be used to map TOML keys to struct fields
|
||
// that don't match the key name exactly (see the example). A case insensitive
|
||
// match to struct names will be tried if an exact match can't be found.
|
||
//
|
||
// The mapping between TOML values and Go values is loose. That is, there may
|
||
// exist TOML values that cannot be placed into your representation, and there
|
||
// may be parts of your representation that do not correspond to TOML values.
|
||
// This loose mapping can be made stricter by using the IsDefined and/or
|
||
// Undecoded methods on the MetaData returned.
|
||
//
|
||
// This decoder does not handle cyclic types. Decode will not terminate if a
|
||
// cyclic type is passed.
|
||
type Decoder struct {
|
||
r io.Reader
|
||
}
|
||
|
||
// NewDecoder creates a new Decoder.
|
||
func NewDecoder(r io.Reader) *Decoder {
|
||
return &Decoder{r: r}
|
||
}
|
||
|
||
// Decode TOML data in to the pointer `v`.
|
||
func (dec *Decoder) Decode(v interface{}) (MetaData, error) {
|
||
rv := reflect.ValueOf(v)
|
||
if rv.Kind() != reflect.Ptr {
|
||
return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v))
|
||
}
|
||
if rv.IsNil() {
|
||
return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v))
|
||
}
|
||
|
||
// TODO: have parser should read from io.Reader? Or at the very least, make
|
||
// it read from []byte rather than string
|
||
data, err := ioutil.ReadAll(dec.r)
|
||
if err != nil {
|
||
return MetaData{}, err
|
||
}
|
||
|
||
p, err := parse(string(data))
|
||
if err != nil {
|
||
return MetaData{}, err
|
||
}
|
||
md := MetaData{
|
||
p.mapping, p.types, p.ordered,
|
||
make(map[string]bool, len(p.ordered)), nil,
|
||
}
|
||
return md, md.unify(p.mapping, indirect(rv))
|
||
}
|
||
|
||
// Decode the TOML data in to the pointer v.
|
||
//
|
||
// See the documentation on Decoder for a description of the decoding process.
|
||
func Decode(data string, v interface{}) (MetaData, error) {
|
||
return NewDecoder(strings.NewReader(data)).Decode(v)
|
||
}
|
||
|
||
// DecodeFile is just like Decode, except it will automatically read the
|
||
// contents of the file at path and decode it for you.
|
||
func DecodeFile(path string, v interface{}) (MetaData, error) {
|
||
fp, err := os.Open(path)
|
||
if err != nil {
|
||
return MetaData{}, err
|
||
}
|
||
defer fp.Close()
|
||
return NewDecoder(fp).Decode(v)
|
||
}
|
||
|
||
// unify performs a sort of type unification based on the structure of `rv`,
|
||
// which is the client representation.
|
||
//
|
||
// Any type mismatch produces an error. Finding a type that we don't know
|
||
// how to handle produces an unsupported type error.
|
||
func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
|
||
// Special case. Look for a `Primitive` value.
|
||
// TODO: #76 would make this superfluous after implemented.
|
||
if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() {
|
||
// Save the undecoded data and the key context into the primitive
|
||
// value.
|
||
context := make(Key, len(md.context))
|
||
copy(context, md.context)
|
||
rv.Set(reflect.ValueOf(Primitive{
|
||
undecoded: data,
|
||
context: context,
|
||
}))
|
||
return nil
|
||
}
|
||
|
||
// Special case. Unmarshaler Interface support.
|
||
if rv.CanAddr() {
|
||
if v, ok := rv.Addr().Interface().(Unmarshaler); ok {
|
||
return v.UnmarshalTOML(data)
|
||
}
|
||
}
|
||
|
||
// Special case. Look for a value satisfying the TextUnmarshaler interface.
|
||
if v, ok := rv.Interface().(encoding.TextUnmarshaler); ok {
|
||
return md.unifyText(data, v)
|
||
}
|
||
// TODO:
|
||
// The behavior here is incorrect whenever a Go type satisfies the
|
||
// encoding.TextUnmarshaler interface but also corresponds to a TOML hash or
|
||
// array. In particular, the unmarshaler should only be applied to primitive
|
||
// TOML values. But at this point, it will be applied to all kinds of values
|
||
// and produce an incorrect error whenever those values are hashes or arrays
|
||
// (including arrays of tables).
|
||
|
||
k := rv.Kind()
|
||
|
||
// laziness
|
||
if k >= reflect.Int && k <= reflect.Uint64 {
|
||
return md.unifyInt(data, rv)
|
||
}
|
||
switch k {
|
||
case reflect.Ptr:
|
||
elem := reflect.New(rv.Type().Elem())
|
||
err := md.unify(data, reflect.Indirect(elem))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
rv.Set(elem)
|
||
return nil
|
||
case reflect.Struct:
|
||
return md.unifyStruct(data, rv)
|
||
case reflect.Map:
|
||
return md.unifyMap(data, rv)
|
||
case reflect.Array:
|
||
return md.unifyArray(data, rv)
|
||
case reflect.Slice:
|
||
return md.unifySlice(data, rv)
|
||
case reflect.String:
|
||
return md.unifyString(data, rv)
|
||
case reflect.Bool:
|
||
return md.unifyBool(data, rv)
|
||
case reflect.Interface:
|
||
// we only support empty interfaces.
|
||
if rv.NumMethod() > 0 {
|
||
return e("unsupported type %s", rv.Type())
|
||
}
|
||
return md.unifyAnything(data, rv)
|
||
case reflect.Float32:
|
||
fallthrough
|
||
case reflect.Float64:
|
||
return md.unifyFloat64(data, rv)
|
||
}
|
||
return e("unsupported type %s", rv.Kind())
|
||
}
|
||
|
||
func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
|
||
tmap, ok := mapping.(map[string]interface{})
|
||
if !ok {
|
||
if mapping == nil {
|
||
return nil
|
||
}
|
||
return e("type mismatch for %s: expected table but found %T",
|
||
rv.Type().String(), mapping)
|
||
}
|
||
|
||
for key, datum := range tmap {
|
||
var f *field
|
||
fields := cachedTypeFields(rv.Type())
|
||
for i := range fields {
|
||
ff := &fields[i]
|
||
if ff.name == key {
|
||
f = ff
|
||
break
|
||
}
|
||
if f == nil && strings.EqualFold(ff.name, key) {
|
||
f = ff
|
||
}
|
||
}
|
||
if f != nil {
|
||
subv := rv
|
||
for _, i := range f.index {
|
||
subv = indirect(subv.Field(i))
|
||
}
|
||
if isUnifiable(subv) {
|
||
md.decoded[md.context.add(key).String()] = true
|
||
md.context = append(md.context, key)
|
||
if err := md.unify(datum, subv); err != nil {
|
||
return err
|
||
}
|
||
md.context = md.context[0 : len(md.context)-1]
|
||
} else if f.name != "" {
|
||
// Bad user! No soup for you!
|
||
return e("cannot write unexported field %s.%s",
|
||
rv.Type().String(), f.name)
|
||
}
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
|
||
if k := rv.Type().Key().Kind(); k != reflect.String {
|
||
return fmt.Errorf(
|
||
"toml: cannot decode to a map with non-string key type (%s in %q)",
|
||
k, rv.Type())
|
||
}
|
||
|
||
tmap, ok := mapping.(map[string]interface{})
|
||
if !ok {
|
||
if tmap == nil {
|
||
return nil
|
||
}
|
||
return badtype("map", mapping)
|
||
}
|
||
if rv.IsNil() {
|
||
rv.Set(reflect.MakeMap(rv.Type()))
|
||
}
|
||
for k, v := range tmap {
|
||
md.decoded[md.context.add(k).String()] = true
|
||
md.context = append(md.context, k)
|
||
|
||
rvkey := indirect(reflect.New(rv.Type().Key()))
|
||
rvval := reflect.Indirect(reflect.New(rv.Type().Elem()))
|
||
if err := md.unify(v, rvval); err != nil {
|
||
return err
|
||
}
|
||
md.context = md.context[0 : len(md.context)-1]
|
||
|
||
rvkey.SetString(k)
|
||
rv.SetMapIndex(rvkey, rvval)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
|
||
datav := reflect.ValueOf(data)
|
||
if datav.Kind() != reflect.Slice {
|
||
if !datav.IsValid() {
|
||
return nil
|
||
}
|
||
return badtype("slice", data)
|
||
}
|
||
if l := datav.Len(); l != rv.Len() {
|
||
return e("expected array length %d; got TOML array of length %d", rv.Len(), l)
|
||
}
|
||
return md.unifySliceArray(datav, rv)
|
||
}
|
||
|
||
func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
|
||
datav := reflect.ValueOf(data)
|
||
if datav.Kind() != reflect.Slice {
|
||
if !datav.IsValid() {
|
||
return nil
|
||
}
|
||
return badtype("slice", data)
|
||
}
|
||
n := datav.Len()
|
||
if rv.IsNil() || rv.Cap() < n {
|
||
rv.Set(reflect.MakeSlice(rv.Type(), n, n))
|
||
}
|
||
rv.SetLen(n)
|
||
return md.unifySliceArray(datav, rv)
|
||
}
|
||
|
||
func (md *MetaData) unifySliceArray(data, rv reflect.Value) error {
|
||
l := data.Len()
|
||
for i := 0; i < l; i++ {
|
||
err := md.unify(data.Index(i).Interface(), indirect(rv.Index(i)))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (md *MetaData) unifyDatetime(data interface{}, rv reflect.Value) error {
|
||
if _, ok := data.(time.Time); ok {
|
||
rv.Set(reflect.ValueOf(data))
|
||
return nil
|
||
}
|
||
return badtype("time.Time", data)
|
||
}
|
||
|
||
func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error {
|
||
if s, ok := data.(string); ok {
|
||
rv.SetString(s)
|
||
return nil
|
||
}
|
||
return badtype("string", data)
|
||
}
|
||
|
||
func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error {
|
||
if num, ok := data.(float64); ok {
|
||
switch rv.Kind() {
|
||
case reflect.Float32:
|
||
fallthrough
|
||
case reflect.Float64:
|
||
rv.SetFloat(num)
|
||
default:
|
||
panic("bug")
|
||
}
|
||
return nil
|
||
}
|
||
return badtype("float", data)
|
||
}
|
||
|
||
func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error {
|
||
if num, ok := data.(int64); ok {
|
||
if rv.Kind() >= reflect.Int && rv.Kind() <= reflect.Int64 {
|
||
switch rv.Kind() {
|
||
case reflect.Int, reflect.Int64:
|
||
// No bounds checking necessary.
|
||
case reflect.Int8:
|
||
if num < math.MinInt8 || num > math.MaxInt8 {
|
||
return e("value %d is out of range for int8", num)
|
||
}
|
||
case reflect.Int16:
|
||
if num < math.MinInt16 || num > math.MaxInt16 {
|
||
return e("value %d is out of range for int16", num)
|
||
}
|
||
case reflect.Int32:
|
||
if num < math.MinInt32 || num > math.MaxInt32 {
|
||
return e("value %d is out of range for int32", num)
|
||
}
|
||
}
|
||
rv.SetInt(num)
|
||
} else if rv.Kind() >= reflect.Uint && rv.Kind() <= reflect.Uint64 {
|
||
unum := uint64(num)
|
||
switch rv.Kind() {
|
||
case reflect.Uint, reflect.Uint64:
|
||
// No bounds checking necessary.
|
||
case reflect.Uint8:
|
||
if num < 0 || unum > math.MaxUint8 {
|
||
return e("value %d is out of range for uint8", num)
|
||
}
|
||
case reflect.Uint16:
|
||
if num < 0 || unum > math.MaxUint16 {
|
||
return e("value %d is out of range for uint16", num)
|
||
}
|
||
case reflect.Uint32:
|
||
if num < 0 || unum > math.MaxUint32 {
|
||
return e("value %d is out of range for uint32", num)
|
||
}
|
||
}
|
||
rv.SetUint(unum)
|
||
} else {
|
||
panic("unreachable")
|
||
}
|
||
return nil
|
||
}
|
||
return badtype("integer", data)
|
||
}
|
||
|
||
func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error {
|
||
if b, ok := data.(bool); ok {
|
||
rv.SetBool(b)
|
||
return nil
|
||
}
|
||
return badtype("boolean", data)
|
||
}
|
||
|
||
func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error {
|
||
rv.Set(reflect.ValueOf(data))
|
||
return nil
|
||
}
|
||
|
||
func (md *MetaData) unifyText(data interface{}, v encoding.TextUnmarshaler) error {
|
||
var s string
|
||
switch sdata := data.(type) {
|
||
case TextMarshaler:
|
||
text, err := sdata.MarshalText()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
s = string(text)
|
||
case fmt.Stringer:
|
||
s = sdata.String()
|
||
case string:
|
||
s = sdata
|
||
case bool:
|
||
s = fmt.Sprintf("%v", sdata)
|
||
case int64:
|
||
s = fmt.Sprintf("%d", sdata)
|
||
case float64:
|
||
s = fmt.Sprintf("%f", sdata)
|
||
default:
|
||
return badtype("primitive (string-like)", data)
|
||
}
|
||
if err := v.UnmarshalText([]byte(s)); err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// rvalue returns a reflect.Value of `v`. All pointers are resolved.
|
||
func rvalue(v interface{}) reflect.Value {
|
||
return indirect(reflect.ValueOf(v))
|
||
}
|
||
|
||
// indirect returns the value pointed to by a pointer.
|
||
// Pointers are followed until the value is not a pointer.
|
||
// New values are allocated for each nil pointer.
|
||
//
|
||
// An exception to this rule is if the value satisfies an interface of
|
||
// interest to us (like encoding.TextUnmarshaler).
|
||
func indirect(v reflect.Value) reflect.Value {
|
||
if v.Kind() != reflect.Ptr {
|
||
if v.CanSet() {
|
||
pv := v.Addr()
|
||
if _, ok := pv.Interface().(encoding.TextUnmarshaler); ok {
|
||
return pv
|
||
}
|
||
}
|
||
return v
|
||
}
|
||
if v.IsNil() {
|
||
v.Set(reflect.New(v.Type().Elem()))
|
||
}
|
||
return indirect(reflect.Indirect(v))
|
||
}
|
||
|
||
func isUnifiable(rv reflect.Value) bool {
|
||
if rv.CanSet() {
|
||
return true
|
||
}
|
||
if _, ok := rv.Interface().(encoding.TextUnmarshaler); ok {
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
func e(format string, args ...interface{}) error {
|
||
return fmt.Errorf("toml: "+format, args...)
|
||
}
|
||
|
||
func badtype(expected string, data interface{}) error {
|
||
return e("cannot load TOML value of type %T into a Go %s", data, expected)
|
||
}
|