diff --git a/internal/unmarshaler/targets.go b/internal/unmarshaler/targets.go index 1c9fd25..17c4ad9 100644 --- a/internal/unmarshaler/targets.go +++ b/internal/unmarshaler/targets.go @@ -15,18 +15,23 @@ type target interface { // 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) + // Dereferences the target. get() reflect.Value } -// struct target just contain the reflect.Value of the target field. -type structTarget reflect.Value +// valueTarget just contains a reflect.Value that can be set. +// It is used for struct fields. +type valueTarget reflect.Value -func (t structTarget) get() reflect.Value { +func (t valueTarget) get() reflect.Value { return reflect.Value(t) } -func (t structTarget) ensure() { +func (t valueTarget) ensure() { f := t.get() if !f.IsNil() { return @@ -40,7 +45,7 @@ func (t structTarget) ensure() { } } -func (t structTarget) setString(v string) error { +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()) @@ -49,7 +54,7 @@ func (t structTarget) setString(v string) error { return nil } -func (t structTarget) pushValue(v reflect.Value) error { +func (t valueTarget) pushValue(v reflect.Value) error { f := t.get() switch f.Kind() { @@ -63,6 +68,26 @@ func (t structTarget) pushValue(v reflect.Value) error { return nil } +func (t valueTarget) pushNew() (target, error) { + f := t.get() + + 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 + default: + return nil, fmt.Errorf("cannot pushNew on a %s", f.Kind()) + } +} + +func scopeTarget(t target, name string) (target, error) { + x := t.get() + return scope(x, name) +} + func scope(v reflect.Value, name string) (target, error) { switch v.Kind() { case reflect.Struct: @@ -86,7 +111,7 @@ func scopeStruct(v reflect.Value, name string) (target, error) { } else { // TODO: handle names variations if f.Name == name { - return structTarget(v.Field(i)), nil + return valueTarget(v.Field(i)), nil } } } diff --git a/internal/unmarshaler/targets_test.go b/internal/unmarshaler/targets_test.go index 7e14d25..598384b 100644 --- a/internal/unmarshaler/targets_test.go +++ b/internal/unmarshaler/targets_test.go @@ -129,6 +129,28 @@ func TestPushValue_Struct(t *testing.T) { } } +func TestPushNew(t *testing.T) { + t.Run("slice of strings", func(t *testing.T) { + type Doc struct { + A []string + } + 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, []string{"hello"}, d.A) + + n, err = x.pushNew() + require.NoError(t, err) + require.NoError(t, n.setString("world")) + require.Equal(t, []string{"hello", "world"}, d.A) + }) +} + func TestScope_Struct(t *testing.T) { examples := []struct { desc string @@ -157,7 +179,7 @@ func TestScope_Struct(t *testing.T) { if e.err { require.Error(t, err) } else { - x2, ok := x.(structTarget) + x2, ok := x.(valueTarget) require.True(t, ok) x2.get() } diff --git a/internal/unmarshaler/unmarshaler.go b/internal/unmarshaler/unmarshaler.go index 82227b3..d2a35f6 100644 --- a/internal/unmarshaler/unmarshaler.go +++ b/internal/unmarshaler/unmarshaler.go @@ -8,16 +8,18 @@ import ( ) func FromAst(tree ast.Root, target interface{}) error { - x := reflect.ValueOf(target) - if x.Kind() != reflect.Ptr { - return fmt.Errorf("need to target a pointer, not %s", x.Kind()) + v := reflect.ValueOf(target) + if v.Kind() != reflect.Ptr { + return fmt.Errorf("need to target a pointer, not %s", v.Kind()) } - if x.IsNil() { + if v.IsNil() { return fmt.Errorf("target pointer must be non-nil") } + x := valueTarget(v.Elem()) + for _, node := range tree { - err := topLevelNode(x, &node) + err := unmarshalTopLevelNode(x, &node) if err != nil { return err } @@ -26,33 +28,67 @@ func FromAst(tree ast.Root, target interface{}) error { return nil } -func topLevelNode(x reflect.Value, node *ast.Node) error { - if x.Kind() != reflect.Ptr { - panic("topLevelNode should receive target, which should be a pointer") - } - if x.IsNil() { - panic("topLevelNode should receive target, which should not be a nil pointer") - } - +func unmarshalTopLevelNode(x target, node *ast.Node) error { switch node.Kind { case ast.Table: panic("TODO") case ast.ArrayTable: panic("TODO") case ast.KeyValue: - return keyValue(x, node) + return unmarshalKeyValue(x, node) default: panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind)) } } -func keyValue(x reflect.Value, node *ast.Node) error { +func unmarshalKeyValue(x target, node *ast.Node) error { assertNode(ast.KeyValue, node) - assertPtr(x) key := node.Key() - key = key - // TODO + + var err error + for _, n := range key { + x, err = scopeTarget(x, string(n.Data)) + if err != nil { + return err + } + } + + return unmarshalValue(x, node.Value()) +} + +func unmarshalValue(x target, node *ast.Node) error { + switch node.Kind { + case ast.String: + return unmarshalString(x, node) + case ast.Array: + return unmarshalArray(x, node) + default: + panic(fmt.Errorf("unhandled unmarshalValue kind %s", node.Kind)) + } +} + +func unmarshalString(x target, node *ast.Node) error { + assertNode(ast.String, node) + + return x.setString(string(node.Data)) +} + +func unmarshalArray(x target, node *ast.Node) error { + assertNode(ast.Array, node) + + x.ensure() + + for _, n := range node.Children { + v, err := x.pushNew() + if err != nil { + return err + } + err = unmarshalValue(v, &n) + if err != nil { + return err + } + } return nil } @@ -61,9 +97,3 @@ func assertNode(expected ast.Kind, node *ast.Node) { panic(fmt.Errorf("expected node of kind %s, not %s", expected, node.Kind)) } } - -func assertPtr(x reflect.Value) { - if x.Kind() != reflect.Ptr { - panic(fmt.Errorf("should be a pointer, not a %s", x.Kind())) - } -} diff --git a/internal/unmarshaler/unmarshaler_test.go b/internal/unmarshaler/unmarshaler_test.go index da71743..d99b62c 100644 --- a/internal/unmarshaler/unmarshaler_test.go +++ b/internal/unmarshaler/unmarshaler_test.go @@ -11,7 +11,6 @@ import ( ) func TestFromAst_KV(t *testing.T) { - t.Skipf("later") root := ast.Root{ ast.Node{ Kind: ast.KeyValue, @@ -37,3 +36,39 @@ func TestFromAst_KV(t *testing.T) { require.NoError(t, err) assert.Equal(t, Doc{Foo: "hello"}, x) } + +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`), + }, + }, + }, + }, + }, + } + + 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) +}