Unmarshal slices of strings
This commit is contained in:
@@ -218,8 +218,9 @@ func (p *parser) parseVal(b []byte) (ast.Node, []byte, error) {
|
||||
// TODO
|
||||
return node, b[5:], nil
|
||||
case '[':
|
||||
// TODO
|
||||
//return p.parseValArray(b)
|
||||
node.Kind = ast.Array
|
||||
b, err := p.parseValArray(&node, b)
|
||||
return node, b, err
|
||||
case '{':
|
||||
// TODO
|
||||
//return p.parseInlineTable(b)
|
||||
@@ -275,7 +276,7 @@ func (p *parser) parseInlineTable(b []byte) ([]byte, error) {
|
||||
return expect('}', b)
|
||||
}
|
||||
|
||||
func (p *parser) parseValArray(b []byte) ([]byte, error) {
|
||||
func (p *parser) parseValArray(node *ast.Node, b []byte) ([]byte, error) {
|
||||
//array = array-open [ array-values ] ws-comment-newline array-close
|
||||
//array-open = %x5B ; [
|
||||
//array-close = %x5D ; ]
|
||||
@@ -284,45 +285,46 @@ func (p *parser) parseValArray(b []byte) ([]byte, error) {
|
||||
//array-sep = %x2C ; , Comma
|
||||
//ws-comment-newline = *( wschar / [ comment ] newline )
|
||||
|
||||
// TODO
|
||||
//b = b[1:]
|
||||
//
|
||||
//first := true
|
||||
//var err error
|
||||
//for len(b) > 0 {
|
||||
// b, err = p.parseOptionalWhitespaceCommentNewline(b)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// if len(b) == 0 {
|
||||
// return nil, unexpectedCharacter{b: b}
|
||||
// }
|
||||
//
|
||||
// if b[0] == ']' {
|
||||
// break
|
||||
// }
|
||||
// if b[0] == ',' {
|
||||
// if first {
|
||||
// return nil, fmt.Errorf("array cannot start with comma")
|
||||
// }
|
||||
// b = b[1:]
|
||||
// b, err = p.parseOptionalWhitespaceCommentNewline(b)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// b, err = p.parseVal(b)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// b, err = p.parseOptionalWhitespaceCommentNewline(b)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// first = false
|
||||
//}
|
||||
b = b[1:]
|
||||
|
||||
first := true
|
||||
var err error
|
||||
for len(b) > 0 {
|
||||
b, err = p.parseOptionalWhitespaceCommentNewline(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(b) == 0 {
|
||||
return nil, unexpectedCharacter{b: b}
|
||||
}
|
||||
|
||||
if b[0] == ']' {
|
||||
break
|
||||
}
|
||||
if b[0] == ',' {
|
||||
if first {
|
||||
return nil, fmt.Errorf("array cannot start with comma")
|
||||
}
|
||||
b = b[1:]
|
||||
b, err = p.parseOptionalWhitespaceCommentNewline(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var valueNode ast.Node
|
||||
valueNode, b, err = p.parseVal(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node.Children = append(node.Children, valueNode)
|
||||
b, err = p.parseOptionalWhitespaceCommentNewline(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
first = false
|
||||
}
|
||||
|
||||
return expect(']', b)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParser_Simple(t *testing.T) {
|
||||
func TestParser_AST(t *testing.T) {
|
||||
examples := []struct {
|
||||
desc string
|
||||
input string
|
||||
@@ -33,6 +33,71 @@ func TestParser_Simple(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "array of strings",
|
||||
input: `A = ["hello", ["world", "again"]]`,
|
||||
ast: ast.Root{
|
||||
ast.Node{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.Key,
|
||||
Data: []byte(`A`),
|
||||
},
|
||||
{
|
||||
Kind: ast.Array,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`hello`),
|
||||
},
|
||||
{
|
||||
Kind: ast.Array,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`world`),
|
||||
},
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`again`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "array of arrays of strings",
|
||||
input: `A = ["hello", "world"]`,
|
||||
ast: ast.Root{
|
||||
ast.Node{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.Key,
|
||||
Data: []byte(`A`),
|
||||
},
|
||||
{
|
||||
Kind: ast.Array,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`hello`),
|
||||
},
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`world`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range examples {
|
||||
|
||||
@@ -6,15 +6,12 @@ import (
|
||||
)
|
||||
|
||||
type target interface {
|
||||
// Ensure the target's reflect value is not nil.
|
||||
ensure()
|
||||
// Ensure the target's value is compatible with a slice and initialized.
|
||||
ensureSlice() error
|
||||
|
||||
// Store a string at the target.
|
||||
setString(v string) error
|
||||
|
||||
// Appends an arbitrary value to the container.
|
||||
pushValue(v reflect.Value) error
|
||||
|
||||
// Creates a new value of the container's element type, and returns a
|
||||
// target to it.
|
||||
pushNew() (target, error)
|
||||
@@ -31,38 +28,38 @@ func (t valueTarget) get() reflect.Value {
|
||||
return reflect.Value(t)
|
||||
}
|
||||
|
||||
func (t valueTarget) ensure() {
|
||||
func (t valueTarget) ensureSlice() error {
|
||||
f := t.get()
|
||||
if !f.IsNil() {
|
||||
return
|
||||
}
|
||||
|
||||
switch f.Kind() {
|
||||
switch f.Type().Kind() {
|
||||
case reflect.Slice:
|
||||
f.Set(reflect.MakeSlice(f.Type(), 0, 0))
|
||||
if f.IsNil() {
|
||||
f.Set(reflect.MakeSlice(f.Type(), 0, 0))
|
||||
}
|
||||
case reflect.Interface:
|
||||
if f.IsNil() {
|
||||
f.Set(reflect.MakeSlice(reflect.TypeOf([]interface{}{}), 0, 0))
|
||||
} else {
|
||||
if f.Type().Elem().Kind() != reflect.Slice {
|
||||
return fmt.Errorf("interface is pointing to a %s, not a slice", f.Kind())
|
||||
}
|
||||
}
|
||||
default:
|
||||
panic(fmt.Errorf("don't know how to ensure %s", f.Kind()))
|
||||
return fmt.Errorf("cannot initialize a slice in %s", f.Kind())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t valueTarget) setString(v string) error {
|
||||
f := t.get()
|
||||
if f.Kind() != reflect.String {
|
||||
return fmt.Errorf("cannot assign string to a %s", f.String())
|
||||
}
|
||||
f.SetString(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t valueTarget) pushValue(v reflect.Value) error {
|
||||
f := t.get()
|
||||
|
||||
switch f.Kind() {
|
||||
case reflect.Slice:
|
||||
t.ensure()
|
||||
f.Set(reflect.Append(f, v))
|
||||
case reflect.String:
|
||||
f.SetString(v)
|
||||
case reflect.Interface:
|
||||
f.Set(reflect.ValueOf(v))
|
||||
default:
|
||||
return fmt.Errorf("cannot push %s on a %s", v.Kind(), f.Kind())
|
||||
return fmt.Errorf("cannot assign string to a %s", f.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -73,11 +70,22 @@ func (t valueTarget) pushNew() (target, error) {
|
||||
|
||||
switch f.Kind() {
|
||||
case reflect.Slice:
|
||||
t.ensure()
|
||||
f = t.get()
|
||||
idx := f.Len()
|
||||
f.Set(reflect.Append(f, reflect.New(f.Type().Elem()).Elem()))
|
||||
return valueTarget(f.Index(idx)), nil
|
||||
case reflect.Interface:
|
||||
if f.IsNil() {
|
||||
panic("interface should have been initialized")
|
||||
}
|
||||
ifaceElem := f.Elem()
|
||||
if ifaceElem.Kind() != reflect.Slice {
|
||||
return nil, fmt.Errorf("cannot pushNew on a %s", f.Kind())
|
||||
}
|
||||
idx := ifaceElem.Len()
|
||||
newElem := reflect.New(ifaceElem.Type().Elem()).Elem()
|
||||
newSlice := reflect.Append(ifaceElem, newElem)
|
||||
f.Set(newSlice)
|
||||
return valueTarget(f.Elem().Index(idx)), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot pushNew on a %s", f.Kind())
|
||||
}
|
||||
|
||||
@@ -13,13 +13,14 @@ func TestStructTarget_Ensure(t *testing.T) {
|
||||
desc string
|
||||
input reflect.Value
|
||||
name string
|
||||
test func(v reflect.Value)
|
||||
test func(v reflect.Value, err error)
|
||||
}{
|
||||
{
|
||||
desc: "handle a nil slice of string",
|
||||
input: reflect.ValueOf(&struct{ A []string }{}).Elem(),
|
||||
name: "A",
|
||||
test: func(v reflect.Value) {
|
||||
test: func(v reflect.Value, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, v.IsNil())
|
||||
},
|
||||
},
|
||||
@@ -27,7 +28,8 @@ func TestStructTarget_Ensure(t *testing.T) {
|
||||
desc: "handle an existing slice of string",
|
||||
input: reflect.ValueOf(&struct{ A []string }{A: []string{"foo"}}).Elem(),
|
||||
name: "A",
|
||||
test: func(v reflect.Value) {
|
||||
test: func(v reflect.Value, err error) {
|
||||
assert.NoError(t, err)
|
||||
require.False(t, v.IsNil())
|
||||
s := v.Interface().([]string)
|
||||
assert.Equal(t, []string{"foo"}, s)
|
||||
@@ -39,9 +41,9 @@ func TestStructTarget_Ensure(t *testing.T) {
|
||||
t.Run(e.desc, func(t *testing.T) {
|
||||
target, err := scope(e.input, e.name)
|
||||
require.NoError(t, err)
|
||||
target.ensure()
|
||||
err = target.ensureSlice()
|
||||
v := target.get()
|
||||
e.test(v)
|
||||
e.test(v, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -93,42 +95,6 @@ func TestStructTarget_SetString(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushValue_Struct(t *testing.T) {
|
||||
examples := []struct {
|
||||
desc string
|
||||
input reflect.Value
|
||||
expected []string
|
||||
error bool
|
||||
}{
|
||||
{
|
||||
desc: "push to nil slice",
|
||||
input: reflect.ValueOf(&struct{ A []string }{}).Elem(),
|
||||
expected: []string{"hello"},
|
||||
},
|
||||
{
|
||||
desc: "push to string",
|
||||
input: reflect.ValueOf(&struct{ A string }{}).Elem(),
|
||||
error: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range examples {
|
||||
t.Run(e.desc, func(t *testing.T) {
|
||||
target, err := scope(e.input, "A")
|
||||
require.NoError(t, err)
|
||||
v := reflect.ValueOf("hello")
|
||||
err = target.pushValue(v)
|
||||
if e.error {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
x := target.get().Interface().([]string)
|
||||
assert.Equal(t, e.expected, x)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushNew(t *testing.T) {
|
||||
t.Run("slice of strings", func(t *testing.T) {
|
||||
type Doc struct {
|
||||
@@ -149,6 +115,26 @@ func TestPushNew(t *testing.T) {
|
||||
require.NoError(t, n.setString("world"))
|
||||
require.Equal(t, []string{"hello", "world"}, d.A)
|
||||
})
|
||||
|
||||
t.Run("slice of interfaces", func(t *testing.T) {
|
||||
type Doc struct {
|
||||
A []interface{}
|
||||
}
|
||||
d := Doc{}
|
||||
|
||||
x, err := scope(reflect.ValueOf(&d).Elem(), "A")
|
||||
require.NoError(t, err)
|
||||
|
||||
n, err := x.pushNew()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, n.setString("hello"))
|
||||
require.Equal(t, []interface{}{"hello"}, d.A)
|
||||
|
||||
n, err = x.pushNew()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, n.setString("world"))
|
||||
require.Equal(t, []interface{}{"hello", "world"}, d.A)
|
||||
})
|
||||
}
|
||||
|
||||
func TestScope_Struct(t *testing.T) {
|
||||
|
||||
@@ -77,7 +77,10 @@ func unmarshalString(x target, node *ast.Node) error {
|
||||
func unmarshalArray(x target, node *ast.Node) error {
|
||||
assertNode(ast.Array, node)
|
||||
|
||||
x.ensure()
|
||||
err := x.ensureSlice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, n := range node.Children {
|
||||
v, err := x.pushNew()
|
||||
|
||||
@@ -38,37 +38,120 @@ func TestFromAst_KV(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFromAst_Slice(t *testing.T) {
|
||||
root := ast.Root{
|
||||
ast.Node{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.Key,
|
||||
Data: []byte(`Foo`),
|
||||
},
|
||||
{
|
||||
Kind: ast.Array,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`hello`),
|
||||
},
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`world`),
|
||||
t.Run("slice of string", func(t *testing.T) {
|
||||
root := ast.Root{
|
||||
ast.Node{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.Key,
|
||||
Data: []byte(`Foo`),
|
||||
},
|
||||
{
|
||||
Kind: ast.Array,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`hello`),
|
||||
},
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`world`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type Doc struct {
|
||||
Foo []string
|
||||
}
|
||||
type Doc struct {
|
||||
Foo []string
|
||||
}
|
||||
|
||||
x := Doc{}
|
||||
err := unmarshaler.FromAst(root, &x)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, Doc{Foo: []string{"hello", "world"}}, x)
|
||||
x := Doc{}
|
||||
err := unmarshaler.FromAst(root, &x)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, Doc{Foo: []string{"hello", "world"}}, x)
|
||||
})
|
||||
|
||||
t.Run("slice of interfaces for strings", func(t *testing.T) {
|
||||
root := ast.Root{
|
||||
ast.Node{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.Key,
|
||||
Data: []byte(`Foo`),
|
||||
},
|
||||
{
|
||||
Kind: ast.Array,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`hello`),
|
||||
},
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`world`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type Doc struct {
|
||||
Foo []interface{}
|
||||
}
|
||||
|
||||
x := Doc{}
|
||||
err := unmarshaler.FromAst(root, &x)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, Doc{Foo: []interface{}{"hello", "world"}}, x)
|
||||
})
|
||||
|
||||
t.Run("slice of interfaces with slices", func(t *testing.T) {
|
||||
root := ast.Root{
|
||||
ast.Node{
|
||||
Kind: ast.KeyValue,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.Key,
|
||||
Data: []byte(`Foo`),
|
||||
},
|
||||
{
|
||||
Kind: ast.Array,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`hello`),
|
||||
},
|
||||
{
|
||||
Kind: ast.Array,
|
||||
Children: []ast.Node{
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`inner1`),
|
||||
},
|
||||
{
|
||||
Kind: ast.String,
|
||||
Data: []byte(`inner2`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type Doc struct {
|
||||
Foo []interface{}
|
||||
}
|
||||
|
||||
x := Doc{}
|
||||
err := unmarshaler.FromAst(root, &x)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, Doc{Foo: []interface{}{"hello", []interface{}{"inner1", "inner2"}}}, x)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user