Nested arrays
This commit is contained in:
@@ -0,0 +1,239 @@
|
|||||||
|
// reflectbuild is a package that provides utility functions to build Go
|
||||||
|
// objects using reflection.
|
||||||
|
package reflectbuild
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Builder wraps a value and provides method to modify its structure.
|
||||||
|
// It is a stateful object that keeps a cursor of what part of the object is
|
||||||
|
// being modified.
|
||||||
|
// Create a Builder with NewBuilder.
|
||||||
|
type Builder struct {
|
||||||
|
root reflect.Value
|
||||||
|
// Root is always a pointer to a non-nil value.
|
||||||
|
// Cursor is the top of the stack.
|
||||||
|
stack []reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuilder creates a Builder to construct v.
|
||||||
|
// If v is nil or not a pointer, an error will be returned.
|
||||||
|
func NewBuilder(v interface{}) (Builder, error) {
|
||||||
|
if v == nil {
|
||||||
|
return Builder{}, fmt.Errorf("cannot build a nil value")
|
||||||
|
}
|
||||||
|
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
if rv.Type().Kind() != reflect.Ptr {
|
||||||
|
return Builder{}, fmt.Errorf("cannot build a %s: need a pointer", rv.Type().Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
return Builder{
|
||||||
|
root: rv.Elem(),
|
||||||
|
stack: []reflect.Value{rv.Elem()},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) top() reflect.Value {
|
||||||
|
return b.stack[len(b.stack)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) push(v reflect.Value) {
|
||||||
|
b.stack = append(b.stack, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) pop() {
|
||||||
|
b.stack = b.stack[:len(b.stack)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) len() int {
|
||||||
|
return len(b.stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Dump() string {
|
||||||
|
str := strings.Builder{}
|
||||||
|
str.WriteByte('[')
|
||||||
|
|
||||||
|
for i, x := range b.stack {
|
||||||
|
if i > 0 {
|
||||||
|
str.WriteString(" | ")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&str, "%s (%s)", x.Type(), x)
|
||||||
|
}
|
||||||
|
|
||||||
|
str.WriteByte(']')
|
||||||
|
return str.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) replace(v reflect.Value) {
|
||||||
|
b.stack[len(b.stack)-1] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// DigField pushes the cursor into a field of the current struct.
|
||||||
|
// Errors if the current value is not a struct, or the field does not exist.
|
||||||
|
func (b *Builder) DigField(s string) error {
|
||||||
|
t := b.top()
|
||||||
|
|
||||||
|
err := checkKind(t.Type(), reflect.Struct)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
f := t.FieldByName(s)
|
||||||
|
if !f.IsValid() {
|
||||||
|
return FieldNotFoundError{FieldName: s, Struct: t}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.replace(f)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save stores a copy of the current cursor position.
|
||||||
|
// It can be restored using Back().
|
||||||
|
// Save points are stored as a stack.
|
||||||
|
func (b *Builder) Save() {
|
||||||
|
b.push(b.top())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset brings the cursor back to the root object.
|
||||||
|
func (b *Builder) Reset() {
|
||||||
|
b.stack = b.stack[:1]
|
||||||
|
b.stack[0] = b.root
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load is the opposite of Save. It discards the current cursor and loads the
|
||||||
|
// last saved cursor.
|
||||||
|
// Panics if no cursor has been saved.
|
||||||
|
func (b *Builder) Load() {
|
||||||
|
if b.len() < 2 {
|
||||||
|
panic(fmt.Errorf("tried to Back() when cursor was already at root"))
|
||||||
|
}
|
||||||
|
b.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cursor returns the value pointed at by the cursor.
|
||||||
|
func (b *Builder) Cursor() reflect.Value {
|
||||||
|
return b.top()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) IsSlice() bool {
|
||||||
|
return b.top().Kind() == reflect.Slice
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last moves the cursor to the last value of the current value.
|
||||||
|
// For a slice or an array, it is the last element they contain, if any.
|
||||||
|
// For anything else, it's a no-op.
|
||||||
|
func (b *Builder) Last() {
|
||||||
|
switch b.Cursor().Kind() {
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
length := b.Cursor().Len()
|
||||||
|
if length > 0 {
|
||||||
|
x := b.Cursor().Index(length - 1)
|
||||||
|
b.replace(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceLastOrCreate moves the cursor to the last element of the slice if any.
|
||||||
|
// Otherwise creates a new element in that slice and moves to it.
|
||||||
|
func (b *Builder) SliceLastOrCreate() error {
|
||||||
|
t := b.top()
|
||||||
|
err := checkKind(t.Type(), reflect.Slice)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Len() == 0 {
|
||||||
|
return b.SliceNewElem()
|
||||||
|
}
|
||||||
|
b.Last()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceNewElem operates on a slice. It creates a new object (of type contained
|
||||||
|
// by the slice), append it to the slice, and moves the cursor to the new
|
||||||
|
// object.
|
||||||
|
func (b *Builder) SliceNewElem() error {
|
||||||
|
t := b.top()
|
||||||
|
err := checkKind(t.Type(), reflect.Slice)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
elem := reflect.New(t.Type().Elem())
|
||||||
|
newSlice := reflect.Append(t, elem.Elem())
|
||||||
|
t.Set(newSlice)
|
||||||
|
b.replace(t.Index(t.Len() - 1))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) SliceAppend(v reflect.Value) error {
|
||||||
|
t := b.top()
|
||||||
|
err := checkKind(t.Type(), reflect.Slice)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newSlice := reflect.Append(t, v)
|
||||||
|
t.Set(newSlice)
|
||||||
|
b.replace(t.Index(t.Len() - 1))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the value at the cursor to the given string.
|
||||||
|
// Errors if a string cannot be assigned to the current value.
|
||||||
|
func (b *Builder) SetString(s string) error {
|
||||||
|
t := b.top()
|
||||||
|
|
||||||
|
err := checkKind(t.Type(), reflect.String)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.SetString(s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the value at the cursor to the given boolean.
|
||||||
|
// Errors if a boolean cannot be assigned to the current value.
|
||||||
|
func (b *Builder) SetBool(v bool) error {
|
||||||
|
t := b.top()
|
||||||
|
|
||||||
|
err := checkKind(t.Type(), reflect.Bool)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.SetBool(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkKind(rt reflect.Type, expected reflect.Kind) error {
|
||||||
|
if rt.Kind() != expected {
|
||||||
|
return IncorrectKindError{
|
||||||
|
Actual: rt.Kind(),
|
||||||
|
Expected: expected,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type IncorrectKindError struct {
|
||||||
|
Actual reflect.Kind
|
||||||
|
Expected reflect.Kind
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e IncorrectKindError) Error() string {
|
||||||
|
return fmt.Sprintf("incorrect kind: expected '%s', got '%s'", e.Expected, e.Actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
type FieldNotFoundError struct {
|
||||||
|
Struct reflect.Value
|
||||||
|
FieldName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e FieldNotFoundError) Error() string {
|
||||||
|
return fmt.Sprintf("field not found: '%s' on '%s'", e.FieldName, e.Struct.Type())
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package reflectbuild_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pelletier/go-toml/v2/internal/reflectbuild"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewBuilderSuccess(t *testing.T) {
|
||||||
|
x := struct{}{}
|
||||||
|
_, err := reflectbuild.NewBuilder(&x)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewBuilderNil(t *testing.T) {
|
||||||
|
_, err := reflectbuild.NewBuilder(nil)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewBuilderNonPtr(t *testing.T) {
|
||||||
|
_, err := reflectbuild.NewBuilder(struct{}{})
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDigField(t *testing.T) {
|
||||||
|
x := struct {
|
||||||
|
Field string
|
||||||
|
}{}
|
||||||
|
b, err := reflectbuild.NewBuilder(&x)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Error(t, b.DigField("oops"))
|
||||||
|
assert.NoError(t, b.DigField("Field"))
|
||||||
|
assert.Error(t, b.DigField("does not work on strings"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBack(t *testing.T) {
|
||||||
|
x := struct {
|
||||||
|
A string
|
||||||
|
B string
|
||||||
|
}{}
|
||||||
|
b, err := reflectbuild.NewBuilder(&x)
|
||||||
|
require.NoError(t, err)
|
||||||
|
b.Save()
|
||||||
|
assert.NoError(t, b.DigField("A"))
|
||||||
|
assert.NoError(t, b.SetString("A"))
|
||||||
|
b.Load()
|
||||||
|
b.Save()
|
||||||
|
assert.NoError(t, b.DigField("B"))
|
||||||
|
assert.NoError(t, b.SetString("B"))
|
||||||
|
assert.Equal(t, "A", x.A)
|
||||||
|
assert.Equal(t, "B", x.B)
|
||||||
|
b.Load() // back to root
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
b.Load() // oops
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReset(t *testing.T) {
|
||||||
|
x := struct {
|
||||||
|
A []string
|
||||||
|
B string
|
||||||
|
}{}
|
||||||
|
b, err := reflectbuild.NewBuilder(&x)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, b.DigField("A"))
|
||||||
|
require.NoError(t, b.SliceNewElem())
|
||||||
|
require.NoError(t, b.SetString("hello"))
|
||||||
|
b.Reset()
|
||||||
|
require.NoError(t, b.DigField("B"))
|
||||||
|
require.NoError(t, b.SetString("world"))
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"hello"}, x.A)
|
||||||
|
assert.Equal(t, "world", x.B)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetString(t *testing.T) {
|
||||||
|
x := struct {
|
||||||
|
Field string
|
||||||
|
}{}
|
||||||
|
b, err := reflectbuild.NewBuilder(&x)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Error(t, b.SetString("oops"))
|
||||||
|
require.NoError(t, b.DigField("Field"))
|
||||||
|
require.NoError(t, b.SetString("hello!"))
|
||||||
|
assert.Equal(t, "hello!", x.Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSliceNewElem(t *testing.T) {
|
||||||
|
x := struct {
|
||||||
|
Field []string
|
||||||
|
}{}
|
||||||
|
b, err := reflectbuild.NewBuilder(&x)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, b.DigField("Field"))
|
||||||
|
b.Save()
|
||||||
|
|
||||||
|
require.NoError(t, b.SliceNewElem())
|
||||||
|
require.NoError(t, b.SetString("Val1"))
|
||||||
|
b.Load()
|
||||||
|
require.NoError(t, b.SliceNewElem())
|
||||||
|
require.NoError(t, b.SetString("Val2"))
|
||||||
|
|
||||||
|
require.Error(t, b.SliceNewElem())
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"Val1", "Val2"}, x.Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSliceNewElemNested(t *testing.T) {
|
||||||
|
x := struct {
|
||||||
|
Field [][]string
|
||||||
|
}{}
|
||||||
|
b, err := reflectbuild.NewBuilder(&x)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, b.DigField("Field"))
|
||||||
|
|
||||||
|
b.Save()
|
||||||
|
|
||||||
|
require.NoError(t, b.SliceNewElem())
|
||||||
|
require.NoError(t, b.SliceNewElem())
|
||||||
|
require.NoError(t, b.SetString("Val1.1"))
|
||||||
|
b.Load()
|
||||||
|
b.Save()
|
||||||
|
|
||||||
|
require.NoError(t, b.SliceNewElem())
|
||||||
|
b.Save()
|
||||||
|
require.NoError(t, b.SliceNewElem())
|
||||||
|
require.NoError(t, b.SetString("Val2.1"))
|
||||||
|
b.Load()
|
||||||
|
require.NoError(t, b.SliceNewElem())
|
||||||
|
require.NoError(t, b.SetString("Val2.2"))
|
||||||
|
b.Load()
|
||||||
|
require.NoError(t, b.SliceNewElem())
|
||||||
|
|
||||||
|
assert.Equal(t, [][]string{{"Val1.1"}, {"Val2.1", "Val2.2"}, nil}, x.Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIncorrectKindError(t *testing.T) {
|
||||||
|
err := reflectbuild.IncorrectKindError{
|
||||||
|
Actual: reflect.String,
|
||||||
|
Expected: reflect.Struct,
|
||||||
|
}
|
||||||
|
assert.NotEmpty(t, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFieldNotFoundError(t *testing.T) {
|
||||||
|
err := reflectbuild.FieldNotFoundError{
|
||||||
|
Struct: reflect.ValueOf(struct {
|
||||||
|
Blah string
|
||||||
|
}{}),
|
||||||
|
FieldName: "Foo",
|
||||||
|
}
|
||||||
|
assert.NotEmpty(t, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCursor(t *testing.T) {
|
||||||
|
x := struct {
|
||||||
|
Field string
|
||||||
|
}{}
|
||||||
|
b, err := reflectbuild.NewBuilder(&x)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, b.Cursor().Kind(), reflect.Struct)
|
||||||
|
require.NoError(t, b.DigField("Field"))
|
||||||
|
assert.Equal(t, b.Cursor().Kind(), reflect.String)
|
||||||
|
}
|
||||||
+81
-102
@@ -5,30 +5,24 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/pelletier/go-toml/v2/internal/reflectbuild"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Unmarshal(data []byte, v interface{}) error {
|
func Unmarshal(data []byte, v interface{}) error {
|
||||||
if v == nil {
|
u := &unmarshaler{}
|
||||||
return fmt.Errorf("cannot unmarshal to nil target")
|
u.builder, u.err = reflectbuild.NewBuilder(v)
|
||||||
}
|
if u.err == nil {
|
||||||
rv := reflect.ValueOf(v)
|
parseErr := parser{builder: u}.parse(data)
|
||||||
if rv.Kind() != reflect.Ptr {
|
if parseErr != nil {
|
||||||
return fmt.Errorf("can only marshal to pointer, not %s", rv.Kind())
|
return parseErr
|
||||||
}
|
}
|
||||||
|
|
||||||
u := &unmarshaler{stack: []reflect.Value{rv.Elem()}}
|
|
||||||
parseErr := parser{builder: u}.parse(data)
|
|
||||||
if parseErr != nil {
|
|
||||||
return parseErr
|
|
||||||
}
|
}
|
||||||
return u.err
|
return u.err
|
||||||
}
|
}
|
||||||
|
|
||||||
type unmarshaler struct {
|
type unmarshaler struct {
|
||||||
// Each stack frame is a pointer to the root object that should be
|
builder reflectbuild.Builder
|
||||||
// considered when settings values.
|
|
||||||
// It at least contains the root object passed to Unmarshal.
|
|
||||||
stack []reflect.Value
|
|
||||||
|
|
||||||
// First error that appeared during the construction of the object.
|
// First error that appeared during the construction of the object.
|
||||||
// When set all callbacks are no-ops.
|
// When set all callbacks are no-ops.
|
||||||
@@ -41,6 +35,35 @@ type unmarshaler struct {
|
|||||||
// Table Arrays need a buffer of keys because we need to know which one is
|
// Table Arrays need a buffer of keys because we need to know which one is
|
||||||
// the last one, as it may result in creating a new element in the array.
|
// the last one, as it may result in creating a new element in the array.
|
||||||
arrayTableKey [][]byte
|
arrayTableKey [][]byte
|
||||||
|
|
||||||
|
// Flag to indicate that the next value is an an assignment.
|
||||||
|
// Assignments are when the builder already points to the value, and should
|
||||||
|
// be directly assigned. This is used to distinguish between assigning or
|
||||||
|
// appending to arrays.
|
||||||
|
assign bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *unmarshaler) Assignation() {
|
||||||
|
u.assign = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *unmarshaler) ArrayBegin() {
|
||||||
|
if u.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
u.builder.Save()
|
||||||
|
if u.assign {
|
||||||
|
u.assign = false
|
||||||
|
} else {
|
||||||
|
u.builder.SliceNewElem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *unmarshaler) ArrayEnd() {
|
||||||
|
if u.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
u.builder.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *unmarshaler) ArrayTableBegin() {
|
func (u *unmarshaler) ArrayTableBegin() {
|
||||||
@@ -56,99 +79,48 @@ func (u *unmarshaler) ArrayTableEnd() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
u.parsingTableArray = false
|
u.builder.Reset()
|
||||||
|
|
||||||
u.stack = u.stack[:1]
|
for _, v := range u.arrayTableKey[:len(u.arrayTableKey)-1] {
|
||||||
|
u.err = u.builder.DigField(string(v))
|
||||||
parent := u.top()
|
if u.err != nil {
|
||||||
for _, k := range u.arrayTableKey {
|
|
||||||
switch parent.Type().Kind() {
|
|
||||||
case reflect.Slice:
|
|
||||||
l := parent.Len()
|
|
||||||
parent = parent.Index(l - 1)
|
|
||||||
case reflect.Struct:
|
|
||||||
default:
|
|
||||||
u.err = fmt.Errorf("value of type '%s' cannot have children", parent)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
u.err = u.builder.SliceLastOrCreate()
|
||||||
f := parent.FieldByName(string(k))
|
|
||||||
if !f.IsValid() {
|
|
||||||
// TODO: implement alternative names
|
|
||||||
u.err = fmt.Errorf("field '%s' not found", string(k))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
parent = f
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if parent.Type().Kind() != reflect.Slice {
|
v := u.arrayTableKey[len(u.arrayTableKey)-1]
|
||||||
u.err = fmt.Errorf("array table key is not a slice")
|
u.err = u.builder.DigField(string(v))
|
||||||
|
if u.err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
u.err = u.builder.SliceNewElem()
|
||||||
|
|
||||||
n := reflect.New(parent.Type().Elem())
|
u.parsingTableArray = false
|
||||||
parent.Set(reflect.Append(parent, n.Elem()))
|
|
||||||
last := parent.Index(parent.Len() - 1)
|
|
||||||
u.push(last)
|
|
||||||
u.arrayTableKey = u.arrayTableKey[:0]
|
u.arrayTableKey = u.arrayTableKey[:0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *unmarshaler) KeyValBegin() {
|
func (u *unmarshaler) KeyValBegin() {
|
||||||
u.push(u.top())
|
u.builder.Save()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *unmarshaler) KeyValEnd() {
|
func (u *unmarshaler) KeyValEnd() {
|
||||||
u.pop()
|
u.builder.Load()
|
||||||
}
|
|
||||||
|
|
||||||
func (u *unmarshaler) getOrCreateChild(key string) (reflect.Value, error) {
|
|
||||||
parent := u.top()
|
|
||||||
switch parent.Type().Kind() {
|
|
||||||
case reflect.Slice:
|
|
||||||
l := parent.Len()
|
|
||||||
parent = parent.Index(l - 1)
|
|
||||||
case reflect.Struct:
|
|
||||||
default:
|
|
||||||
return reflect.Value{}, fmt.Errorf("value of type '%s' cannot have children", parent)
|
|
||||||
}
|
|
||||||
|
|
||||||
f := parent.FieldByName(key)
|
|
||||||
if !f.IsValid() {
|
|
||||||
// TODO: implement alternative names
|
|
||||||
return reflect.Value{}, fmt.Errorf("field '%s' not found", key)
|
|
||||||
}
|
|
||||||
// TODO create things
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *unmarshaler) top() reflect.Value {
|
|
||||||
return u.stack[len(u.stack)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *unmarshaler) push(v reflect.Value) {
|
|
||||||
u.stack = append(u.stack, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *unmarshaler) pop() {
|
|
||||||
u.stack = u.stack[:len(u.stack)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *unmarshaler) replace(v reflect.Value) {
|
|
||||||
u.stack[len(u.stack)-1] = v
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *unmarshaler) StringValue(v []byte) {
|
func (u *unmarshaler) StringValue(v []byte) {
|
||||||
if u.err != nil {
|
if u.err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if u.builder.IsSlice() {
|
||||||
t := u.top()
|
u.builder.Save()
|
||||||
if t.Type().Kind() == reflect.Slice {
|
u.err = u.builder.SliceAppend(reflect.ValueOf(string(v)))
|
||||||
s := reflect.ValueOf(string(v))
|
if u.err != nil {
|
||||||
n := reflect.Append(t, s)
|
return
|
||||||
t.Set(n)
|
}
|
||||||
|
u.builder.Load()
|
||||||
} else {
|
} else {
|
||||||
u.top().SetString(string(v))
|
u.err = u.builder.SetString(string(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,14 +128,15 @@ func (u *unmarshaler) BoolValue(b bool) {
|
|||||||
if u.err != nil {
|
if u.err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if u.builder.IsSlice() {
|
||||||
t := u.top()
|
u.builder.Save()
|
||||||
if t.Type().Kind() == reflect.Slice {
|
u.err = u.builder.SliceAppend(reflect.ValueOf(b))
|
||||||
s := reflect.ValueOf(b)
|
if u.err != nil {
|
||||||
n := reflect.Append(t, s)
|
return
|
||||||
t.Set(n)
|
}
|
||||||
|
u.builder.Load()
|
||||||
} else {
|
} else {
|
||||||
u.top().SetBool(b)
|
u.err = u.builder.SetBool(b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,12 +148,13 @@ func (u *unmarshaler) SimpleKey(v []byte) {
|
|||||||
if u.parsingTableArray {
|
if u.parsingTableArray {
|
||||||
u.arrayTableKey = append(u.arrayTableKey, v)
|
u.arrayTableKey = append(u.arrayTableKey, v)
|
||||||
} else {
|
} else {
|
||||||
target, err := u.getOrCreateChild(string(v))
|
if u.builder.Cursor().Kind() == reflect.Slice {
|
||||||
if err != nil {
|
u.err = u.builder.SliceLastOrCreate()
|
||||||
u.err = err
|
if u.err != nil {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
u.replace(target)
|
u.err = u.builder.DigField(string(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,9 +164,7 @@ func (u *unmarshaler) StandardTableBegin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// tables are only top-level
|
// tables are only top-level
|
||||||
u.stack = u.stack[:1]
|
u.builder.Reset()
|
||||||
|
|
||||||
u.push(u.top())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *unmarshaler) StandardTableEnd() {
|
func (u *unmarshaler) StandardTableEnd() {
|
||||||
@@ -210,6 +182,9 @@ type builder interface {
|
|||||||
ArrayTableEnd()
|
ArrayTableEnd()
|
||||||
KeyValBegin()
|
KeyValBegin()
|
||||||
KeyValEnd()
|
KeyValEnd()
|
||||||
|
ArrayBegin()
|
||||||
|
ArrayEnd()
|
||||||
|
Assignation()
|
||||||
|
|
||||||
StringValue(v []byte)
|
StringValue(v []byte)
|
||||||
BoolValue(b bool)
|
BoolValue(b bool)
|
||||||
@@ -355,6 +330,7 @@ func (p parser) parseKeyval(b []byte) ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
p.builder.Assignation()
|
||||||
b = p.parseWhitespace(b)
|
b = p.parseWhitespace(b)
|
||||||
|
|
||||||
return p.parseVal(b)
|
return p.parseVal(b)
|
||||||
@@ -471,6 +447,9 @@ func (p parser) parseValArray(b []byte) ([]byte, error) {
|
|||||||
//array-sep = %x2C ; , Comma
|
//array-sep = %x2C ; , Comma
|
||||||
//ws-comment-newline = *( wschar / [ comment ] newline )
|
//ws-comment-newline = *( wschar / [ comment ] newline )
|
||||||
|
|
||||||
|
p.builder.ArrayBegin()
|
||||||
|
defer p.builder.ArrayEnd()
|
||||||
|
|
||||||
b = b[1:]
|
b = b[1:]
|
||||||
|
|
||||||
first := true
|
first := true
|
||||||
|
|||||||
@@ -245,6 +245,42 @@ func TestArraySimple(t *testing.T) {
|
|||||||
assert.Equal(t, []string{"hello", "world"}, x.Foo)
|
assert.Equal(t, []string{"hello", "world"}, x.Foo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestArrayNestedInTable(t *testing.T) {
|
||||||
|
x := struct {
|
||||||
|
Wrapper struct {
|
||||||
|
Foo []string
|
||||||
|
}
|
||||||
|
}{}
|
||||||
|
doc := `[Wrapper]
|
||||||
|
Foo = ["hello", "world"]`
|
||||||
|
err := toml.Unmarshal([]byte(doc), &x)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{"hello", "world"}, x.Wrapper.Foo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayMixed(t *testing.T) {
|
||||||
|
x := struct {
|
||||||
|
Wrapper struct {
|
||||||
|
Foo []interface{}
|
||||||
|
}
|
||||||
|
}{}
|
||||||
|
doc := `[Wrapper]
|
||||||
|
Foo = ["hello", true]`
|
||||||
|
err := toml.Unmarshal([]byte(doc), &x)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, []interface{}{"hello", true}, x.Wrapper.Foo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayNested(t *testing.T) {
|
||||||
|
x := struct {
|
||||||
|
Foo [][]string
|
||||||
|
}{}
|
||||||
|
doc := `Foo = [["hello", "world"], ["a"], []]`
|
||||||
|
err := toml.Unmarshal([]byte(doc), &x)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, [][]string{{"hello", "world"}, {"a"}, nil}, x.Foo)
|
||||||
|
}
|
||||||
|
|
||||||
func TestBool(t *testing.T) {
|
func TestBool(t *testing.T) {
|
||||||
x := struct {
|
x := struct {
|
||||||
Truthy bool
|
Truthy bool
|
||||||
|
|||||||
Reference in New Issue
Block a user