From e1357b574b74299c64f435495d48069bd3d14446 Mon Sep 17 00:00:00 2001 From: Galaco Date: Mon, 24 Dec 2018 17:36:45 +0000 Subject: [PATCH] Allow bi-directional to node hierarchy; Allow multiple trees to merge Each node now has a parent member A KeyValue tree can now be merged into another tree. --- keyvalue.go | 73 ++++++++++++++++++++++++++++++++++++++++ keyvalue_test.go | 86 +++++++++++++++++++++++++++++++++++++++++++++++- reader.go | 6 +++- 3 files changed, 163 insertions(+), 2 deletions(-) diff --git a/keyvalue.go b/keyvalue.go index f5f8f98..559b0e1 100644 --- a/keyvalue.go +++ b/keyvalue.go @@ -10,6 +10,7 @@ type KeyValue struct { key string valueType ValueType value []interface{} + parent *KeyValue } // key is the identifier for a stored value @@ -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 +} \ No newline at end of file diff --git a/keyvalue_test.go b/keyvalue_test.go index 7c2d7c9..befd4e2 100644 --- a/keyvalue_test.go +++ b/keyvalue_test.go @@ -1,6 +1,9 @@ package keyvalues -import "testing" +import ( + "log" + "testing" +) func TestKeyValue_Key(t *testing.T) { key := "foo" @@ -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) + } +} diff --git a/reader.go b/reader.go index 370783e..f02d7fd 100644 --- a/reader.go +++ b/reader.go @@ -39,6 +39,7 @@ func (reader *Reader) Read() (keyvalue KeyValue, err error) { rootNode := KeyValue{ key: tokenRootNodeKey, valueType: ValueArray, + parent: nil, } readScope(bufReader, &rootNode) @@ -92,6 +93,7 @@ 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) @@ -99,7 +101,9 @@ func readScope(reader *bufio.Reader, scope *KeyValue) *KeyValue { } // 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