Skip to content

Commit

Permalink
Allow bi-directional to node hierarchy; Allow multiple trees to merge
Browse files Browse the repository at this point in the history
Each node now has a parent member
A KeyValue tree can now be merged into another tree.
  • Loading branch information
Galaco committed Dec 24, 2018
1 parent 3edcfa1 commit e1357b5
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 2 deletions.
73 changes: 73 additions & 0 deletions keyvalue.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type KeyValue struct {
key string
valueType ValueType
value []interface{}
parent *KeyValue
}

// key is the identifier for a stored value
Expand Down Expand Up @@ -108,6 +109,78 @@ func (node *KeyValue) AddChild(value *KeyValue) error {
if !node.HasChildren() {
return errors.New("parent does not accept child keys")
}
value.parent = node
node.value = append(node.value, value)
return nil
}

// Parent returns this node's parent.
// Parent can be nil
func (node *KeyValue) Parent() *KeyValue {
return node.parent
}

// MergeInto merges this KeyValue tree into another.
// The resultant tree will contain all nodes in the same tree from both
// this and the target.
// In the case where a key exists in both trees, this key's value will
// replace the parent's value
func (node *KeyValue) MergeInto(parent *KeyValue) (merged KeyValue, err error) {
merged = *parent
if node.Key() != merged.Key() {
return merged,errors.New("cannot merge mismatched root nodes")
}

err = recursiveMerge(node, &merged)

return merged,err
}

// recursiveMerge merge a into b
// if a.Key() == b.Key(), a will replace b
func recursiveMerge(a *KeyValue, b *KeyValue) (err error) {
// Bottem level node on parent tree
if b.HasChildren() == false {
// only option is to replace b with a, and types must match
if a.Key() != b.Key() {
return errors.New("mismatched types on keyvalue")
}
b.valueType = a.valueType
b.value = a.value
return nil
}
// a has a new key to add to b
if a.Key() != b.Key() {
err = b.parent.AddChild(a)
return err
}

// a and b have the same key, and b has children
// a and b must be of the same types for matching keys
if a.HasChildren() == false {
return errors.New("mismatched types for keyvalue")
}

// see if every child of A appears in B
children,err := a.Children()
if err != nil {
return err
}
for idx,child := range children {
childB,err := b.Find(child.Key())
// a is not in B
if err != nil {
err = b.AddChild(children[idx])
if err != nil {
return err
}
} else {
err = recursiveMerge(children[idx], childB)
if err != nil {
return err
}
}
}

return err
}
86 changes: 85 additions & 1 deletion keyvalue_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package keyvalues

import "testing"
import (
"log"
"testing"
)

func TestKeyValue_Key(t *testing.T) {
key := "foo"
Expand Down Expand Up @@ -160,3 +163,84 @@ func TestKeyValue_AsFloat(t *testing.T) {
}
}
}

func TestKeyValue_MergeInto(t *testing.T) {
a := &KeyValue{
key: "foo",
valueType: ValueArray,
value: []interface{}{
&KeyValue{
key: "bar",
valueType: ValueString,
value: []interface{}{
"bar",
},
},
&KeyValue{
key: "baz",
valueType: ValueString,
value: []interface{}{
"baz",
},
},
&KeyValue{
key: "bat",
valueType: ValueString,
value: []interface{}{
"bat",
},
},
},
}
b := &KeyValue{
key: "foo",
valueType: ValueArray,
value: []interface{}{
&KeyValue{
key: "bar",
valueType: ValueString,
value: []interface{}{
"cart",
},
},
&KeyValue{
key: "egg",
valueType: ValueString,
value: []interface{}{
"bat",
},
},
},
}

result,err := a.MergeInto(b)
if err != nil {
t.Error(err)
}
log.Println(result.Children())

actual,err := result.Find("bar")
if actual == nil {
t.Error(err)
}
actualVal,err := actual.AsString()
if actualVal != "bar" {
if actualVal == "cart" {
t.Error("keyvalue was not overwritten during merge")
} else {
t.Errorf("unexpected value for associated key. expected bar, received: %s", actualVal)
}
}
actual,err = result.Find("baz")
if actual == nil {
t.Error(err)
}
actual,err = result.Find("bat")
if actual == nil {
t.Error(err)
}
actual,err = result.Find("egg")
if actual == nil {
t.Error(err)
}
}
6 changes: 5 additions & 1 deletion reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func (reader *Reader) Read() (keyvalue KeyValue, err error) {
rootNode := KeyValue{
key: tokenRootNodeKey,
valueType: ValueArray,
parent: nil,
}

readScope(bufReader, &rootNode)
Expand Down Expand Up @@ -92,14 +93,17 @@ func readScope(reader *bufio.Reader, scope *KeyValue) *KeyValue {
kv := &KeyValue{
key: strings.Trim(prop[0], tokenEscape),
valueType: ValueArray,
parent: scope,
}

scope.value = append(scope.value, kv)
continue
}

// Read keyvalue & append to current scope
scope.value = append(scope.value, parseKV(line))
result := parseKV(line)
result.parent = scope
scope.value = append(scope.value, result)
}

return scope
Expand Down

0 comments on commit e1357b5

Please sign in to comment.