Simple table array
This commit is contained in:
-19
@@ -21,25 +21,6 @@ var scanFollowsMultilineLiteralStringDelimiter = scanFollows([]byte{'\'', '\'',
|
||||
var scanFollowsTrue = scanFollows([]byte{'t', 'r', 'u', 'e'})
|
||||
var scanFollowsFalse = scanFollows([]byte{'f', 'a', 'l', 's', 'e'})
|
||||
|
||||
func scanNewline(b []byte) ([]byte, []byte, error) {
|
||||
if len(b) == 0 {
|
||||
return nil, nil, fmt.Errorf("not enough bytes for new line")
|
||||
}
|
||||
if b[0] == '\n' {
|
||||
return b[:1], b[1:], nil
|
||||
}
|
||||
if b[0] == '\r' {
|
||||
if len(b) < 2 {
|
||||
return nil, nil, fmt.Errorf("not enough bytes for windows newline")
|
||||
}
|
||||
if b[1] == '\n' {
|
||||
return b[:2], b[2:], nil
|
||||
}
|
||||
return nil, nil, unexpectedCharacter{r: '\n', b: b[2:]}
|
||||
}
|
||||
return nil, nil, unexpectedCharacter{b: b}
|
||||
}
|
||||
|
||||
func scanUnquotedKey(b []byte) ([]byte, []byte, error) {
|
||||
//unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _
|
||||
for i := 0; i < len(b); i++ {
|
||||
|
||||
+80
-12
@@ -34,9 +34,63 @@ type unmarshaler struct {
|
||||
// When set all callbacks are no-ops.
|
||||
err error
|
||||
|
||||
// State that indicates the parser is processing a [table] name. If false
|
||||
// keys are interpreted as part of a key-value.
|
||||
parsingTable bool
|
||||
// State that indicates the parser is processing a [[table-array]] name.
|
||||
// If false keys are interpreted as part of a key-value or [table].
|
||||
parsingTableArray bool
|
||||
|
||||
// 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.
|
||||
arrayTableKey [][]byte
|
||||
}
|
||||
|
||||
func (u *unmarshaler) ArrayTableBegin() {
|
||||
if u.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
u.parsingTableArray = true
|
||||
}
|
||||
|
||||
func (u *unmarshaler) ArrayTableEnd() {
|
||||
if u.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
u.parsingTableArray = false
|
||||
|
||||
u.stack = u.stack[:1]
|
||||
|
||||
parent := u.top()
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
u.err = fmt.Errorf("array table key is not a slice")
|
||||
return
|
||||
}
|
||||
|
||||
n := reflect.New(parent.Type().Elem())
|
||||
parent.Set(reflect.Append(parent, n.Elem()))
|
||||
last := parent.Index(parent.Len() - 1)
|
||||
u.push(last)
|
||||
u.arrayTableKey = u.arrayTableKey[:0]
|
||||
}
|
||||
|
||||
func (u *unmarshaler) KeyValBegin() {
|
||||
@@ -47,10 +101,17 @@ func (u *unmarshaler) KeyValEnd() {
|
||||
u.pop()
|
||||
}
|
||||
|
||||
func getOrCreateChild(parent reflect.Value, key string) (reflect.Value, error) {
|
||||
if parent.Type().Kind() != reflect.Struct {
|
||||
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
|
||||
@@ -88,13 +149,16 @@ func (u *unmarshaler) SimpleKey(v []byte) {
|
||||
return
|
||||
}
|
||||
|
||||
target, err := getOrCreateChild(u.top(), string(v))
|
||||
if err != nil {
|
||||
u.err = err
|
||||
return
|
||||
if u.parsingTableArray {
|
||||
u.arrayTableKey = append(u.arrayTableKey, v)
|
||||
} else {
|
||||
target, err := u.getOrCreateChild(string(v))
|
||||
if err != nil {
|
||||
u.err = err
|
||||
return
|
||||
}
|
||||
u.replace(target)
|
||||
}
|
||||
|
||||
u.replace(target)
|
||||
}
|
||||
|
||||
func (u *unmarshaler) StandardTableBegin() {
|
||||
@@ -119,7 +183,8 @@ type builder interface {
|
||||
|
||||
StandardTableBegin()
|
||||
StandardTableEnd()
|
||||
|
||||
ArrayTableBegin()
|
||||
ArrayTableEnd()
|
||||
KeyValBegin()
|
||||
KeyValEnd()
|
||||
|
||||
@@ -212,6 +277,9 @@ func (p parser) parseArrayTable(b []byte) ([]byte, error) {
|
||||
//array-table-open = %x5B.5B ws ; [[ Double left square bracket
|
||||
//array-table-close = ws %x5D.5D ; ]] Double right square bracket
|
||||
|
||||
p.builder.ArrayTableBegin()
|
||||
defer p.builder.ArrayTableEnd()
|
||||
|
||||
b = b[2:]
|
||||
b = p.parseWhitespace(b)
|
||||
b, err := p.parseKey(b)
|
||||
|
||||
@@ -65,3 +65,126 @@ D = "D"
|
||||
assert.Equal(t, "D", x.Bar.D)
|
||||
assert.Equal(t, "E", x.E)
|
||||
}
|
||||
|
||||
func TestUnmarshalDoesNotEraseBaseStruct(t *testing.T) {
|
||||
x := struct {
|
||||
A string
|
||||
B string
|
||||
}{
|
||||
A: "preset",
|
||||
}
|
||||
err := Unmarshal([]byte(`B = "data"`), &x)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "preset", x.A)
|
||||
assert.Equal(t, "data", x.B)
|
||||
}
|
||||
|
||||
func TestArrayTableSimple(t *testing.T) {
|
||||
doc := `
|
||||
[[Products]]
|
||||
Name = "Hammer"
|
||||
|
||||
[[Products]]
|
||||
Name = "Nail"
|
||||
`
|
||||
|
||||
type Product struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type Data struct {
|
||||
Products []Product
|
||||
}
|
||||
|
||||
x := Data{}
|
||||
err := Unmarshal([]byte(doc), &x)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := Data{
|
||||
Products: []Product{
|
||||
{
|
||||
Name: "Hammer",
|
||||
},
|
||||
{
|
||||
Name: "Nail",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, x)
|
||||
}
|
||||
|
||||
//func TestUnmarshalArrayTablesMultiple(t *testing.T) {
|
||||
// doc := `
|
||||
//[[Products]]
|
||||
//Name = "Hammer"
|
||||
//Sku = "738594937"
|
||||
//
|
||||
//[[Products]] # empty table within the array
|
||||
//
|
||||
//[[Products]]
|
||||
//Name = "Nail"
|
||||
//Sku = "284758393"
|
||||
//
|
||||
//Color = "gray"
|
||||
//`
|
||||
//
|
||||
// type Product struct {
|
||||
// Name string
|
||||
// Sku string
|
||||
// Color string
|
||||
// }
|
||||
//
|
||||
// type Data struct {
|
||||
// Products []Product
|
||||
// }
|
||||
//
|
||||
// x := Data{}
|
||||
// err := Unmarshal([]byte(doc), &x)
|
||||
//
|
||||
// require.NoError(t, err)
|
||||
//
|
||||
// expected := Data{
|
||||
// Products: []Product{
|
||||
// {
|
||||
// Name: "Hammer",
|
||||
// Sku: "738594937",
|
||||
// },
|
||||
// {},
|
||||
// {
|
||||
// Name: "Nail",
|
||||
// Sku: "284758393",
|
||||
// Color: "gray",
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// assert.Equal(t, expected, x)
|
||||
//}
|
||||
|
||||
//func TestUnmarshalArrayTablesNested(t *testing.T) {
|
||||
// doc := `
|
||||
//[[Fruits]]
|
||||
//Name = "apple"
|
||||
//
|
||||
//[Fruits.Physical] # subtable
|
||||
//Color = "red"
|
||||
//Shape = "round"
|
||||
//
|
||||
//[[Fruits.Varieties]] # nested array of tables
|
||||
//Name = "red delicious"
|
||||
//
|
||||
//[[fruits.varieties]]
|
||||
//Name = "granny smith"
|
||||
//
|
||||
//
|
||||
//[[fruits]]
|
||||
//Name = "banana"
|
||||
//
|
||||
//[[fruits.varieties]]
|
||||
//Name = "plantain"
|
||||
//`
|
||||
//
|
||||
//}
|
||||
|
||||
Reference in New Issue
Block a user