diff --git a/internal/ast/ast.go b/internal/ast/ast.go index eed8675..0ec989e 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -147,7 +147,7 @@ func (n *Node) Key() []Node { panic(fmt.Errorf("KeyValue should have at least two children, not %d", len(n.Children))) } return n.Children[:len(n.Children)-1] - case Table: + case Table, ArrayTable: return n.Children default: panic(fmt.Errorf("Key() is not supported on a %s", n.Kind)) diff --git a/parser.go b/parser.go index 9459500..803e48a 100644 --- a/parser.go +++ b/parser.go @@ -73,6 +73,8 @@ func (p *parser) parseExpression(b []byte) ([]byte, error) { return nil, err } + p.tree = append(p.tree, node) + b = p.parseWhitespace(b) if len(b) > 0 && b[0] == '#' { @@ -80,8 +82,6 @@ func (p *parser) parseExpression(b []byte) ([]byte, error) { return rest, err } - p.tree = append(p.tree, node) - return b, nil } diff --git a/targets.go b/targets.go index bff9970..417cdb5 100644 --- a/targets.go +++ b/targets.go @@ -201,6 +201,19 @@ func scopeTarget(t target, name string) (target, error) { return scope(x, name) } +func scopeTableTarget(append bool, t target, name string) (target, error) { + x := t.get() + t, err := scope(x, name) + if err != nil { + return t, err + } + x = t.get() + if x.Kind() == reflect.Slice { + return scopeSlice(t, append) + } + return t, nil +} + func scope(v reflect.Value, name string) (target, error) { switch v.Kind() { case reflect.Struct: @@ -218,6 +231,20 @@ func scope(v reflect.Value, name string) (target, error) { } } +func scopeSlice(t target, append bool) (target, error) { + v := t.get() + if append { + newElem := reflect.New(v.Type().Elem()) + newSlice := reflect.Append(v, newElem.Elem()) + err := t.set(newSlice) + if err != nil { + return t, err + } + v = t.get() + } + return valueTarget(v.Index(v.Len() - 1)), nil +} + func scopeMap(v reflect.Value, name string) (target, error) { if v.IsNil() { v.Set(reflect.MakeMap(v.Type())) diff --git a/unmarshaler.go b/unmarshaler.go index 2859c53..309fd38 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -25,10 +25,11 @@ func fromAst(tree ast.Root, v interface{}) error { return fmt.Errorf("target pointer must be non-nil") } - var x target = valueTarget(r.Elem()) var err error + var root target = valueTarget(r.Elem()) + current := root for _, node := range tree { - x, err = unmarshalTopLevelNode(x, &node) + current, err = unmarshalTopLevelNode(root, current, &node) if err != nil { return err } @@ -39,12 +40,12 @@ func fromAst(tree ast.Root, v interface{}) error { // The target return value is the target for the next top-level node. Mostly // unchanged, except by table and array table. -func unmarshalTopLevelNode(x target, node *ast.Node) (target, error) { +func unmarshalTopLevelNode(root target, x target, node *ast.Node) (target, error) { switch node.Kind { case ast.Table: - return scopeWithKey(x, node.Key()) + return scopeWithTable(root, node.Key()) case ast.ArrayTable: - panic("TODO") + return scopeWithArrayTable(root, node.Key()) case ast.KeyValue: return x, unmarshalKeyValue(x, node) default: @@ -52,6 +53,30 @@ func unmarshalTopLevelNode(x target, node *ast.Node) (target, error) { } } +func scopeWithTable(x target, key []ast.Node) (target, error) { + var err error + for _, n := range key { + x, err = scopeTableTarget(false, x, string(n.Data)) + if err != nil { + return nil, err + } + } + return x, nil +} + +func scopeWithArrayTable(x target, key []ast.Node) (target, error) { + var err error + if len(key) > 1 { + for _, n := range key[:len(key)-1] { + x, err = scopeTableTarget(false, x, string(n.Data)) + if err != nil { + return nil, err + } + } + } + return scopeTableTarget(true, x, string(key[len(key)-1].Data)) +} + func scopeWithKey(x target, key []ast.Node) (target, error) { var err error for _, n := range key { diff --git a/unmarshaler_test.go b/unmarshaler_test.go index deb0532..8c682ed 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -175,6 +175,7 @@ func TestUnmarshal(t *testing.T) { err bool } examples := []struct { + skip bool desc string input string gen func() test @@ -312,7 +313,7 @@ B = "data"`, { desc: "multi keys of different types into map[string]interface{}", input: `A = "foo" -B = 42`, + B = 42`, gen: func() test { doc := map[string]interface{}{} return test{ @@ -361,10 +362,104 @@ B = 42`, } }, }, + { + desc: "one-level one-element array table", + input: `[[First]] + Second = "hello"`, + gen: func() test { + type First struct { + Second string + } + type Doc struct { + First []First + } + return test{ + target: &Doc{}, + expected: &Doc{ + First: []First{ + { + Second: "hello", + }, + }, + }, + } + }, + }, + { + desc: "one-level multi-element array table", + input: `[[Products]] + Name = "Hammer" + Sku = 738594937 + + [[Products]] # empty table within the array + + [[Products]] + Name = "Nail" + Sku = 284758393 + + Color = "gray"`, + gen: func() test { + type Product struct { + Name string + Sku int64 + Color string + } + type Doc struct { + Products []Product + } + return test{ + target: &Doc{}, + expected: &Doc{ + Products: []Product{ + {Name: "Hammer", Sku: 738594937}, + {}, + {Name: "Nail", Sku: 284758393, Color: "gray"}, + }, + }, + } + }, + }, + { + skip: true, // TODO + desc: "one-level multi-element array table to map", + input: `[[Products]] + Name = "Hammer" + Sku = 738594937 + + [[Products]] # empty table within the array + + [[Products]] + Name = "Nail" + Sku = 284758393 + + Color = "gray"`, + gen: func() test { + return test{ + target: &map[string]interface{}{}, + expected: &map[string]interface{}{ + "Products": []interface{}{ + map[string]interface{}{ + "Name": "Hammer", + "Sku": 738594937, + }, + nil, + map[string]interface{}{ + "Name": "Nail", + "Sku": 284758393, + "Color": "gray", + }, + }, + }, + } + }, + }, } for _, e := range examples { t.Run(e.desc, func(t *testing.T) { + if e.skip { + t.Skip() + } test := e.gen() if test.err && test.expected != nil { panic("invalid test: cannot expect both an error and a value")