Simple table array

This commit is contained in:
Thomas Pelletier
2021-02-08 09:08:42 -05:00
parent bd8df24646
commit a197513ce7
3 changed files with 203 additions and 31 deletions
-19
View File
@@ -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
View File
@@ -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)
+123
View File
@@ -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"
//`
//
//}