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