Added QueryResult and patched bugs
QueryResult now stores result items and position data, which aligns more
strongly with the rest of the library features than a plain
[]interface[}. The design of the parser_test unittest was revised to
use array/map/scalar serialization (like match_test), since Go 1.3
redesigned maps to randomly order their keys. Since naive comparisons of
map data is now no longer possible, the unittest now sorts map
keys:value combinations.
* Patched a bug where getPosition("") was returning an invalid Position
* Revised parser_test to use serialization for comparisons for Go 1.3
diff --git a/jpath/match.go b/jpath/match.go
index c517157..bdc3db5 100644
--- a/jpath/match.go
+++ b/jpath/match.go
@@ -28,7 +28,7 @@
}
func (f *terminatingFn) Call(node interface{}, ctx *queryContext) {
- ctx.appendResult(node)
+ ctx.result.appendResult(node)
}
// shim to ease functor writing
diff --git a/jpath/parser_test.go b/jpath/parser_test.go
index ec05f07..61908de 100644
--- a/jpath/parser_test.go
+++ b/jpath/parser_test.go
@@ -4,8 +4,55 @@
"fmt"
. "github.com/pelletier/go-toml"
"testing"
+ "sort"
+ "strings"
)
+func valueString(root interface{}) string {
+ result := "" //fmt.Sprintf("%T:", root)
+ switch node := root.(type) {
+ case []interface{}:
+ items := []string{}
+ for _, v := range node {
+ items = append(items, valueString(v))
+ }
+ sort.Strings(items)
+ result = "[" + strings.Join(items, ", ") + "]"
+ case *TomlTree:
+ // workaround for unreliable map key ordering
+ items := []string{}
+ for _, k := range node.Keys() {
+ v := node.GetPath([]string{k})
+ items = append(items, k + ":" + valueString(v))
+ }
+ sort.Strings(items)
+ result = "{" + strings.Join(items, ", ") + "}"
+ case map[string]interface{}:
+ // workaround for unreliable map key ordering
+ items := []string{}
+ for k, v := range node {
+ items = append(items, k + ":" + valueString(v))
+ }
+ sort.Strings(items)
+ result = "{" + strings.Join(items, ", ") + "}"
+ case int64:
+ result += fmt.Sprintf("%d", node)
+ case string:
+ result += "'" + node + "'"
+ }
+ return result
+}
+
+func assertValue(t *testing.T, result, ref interface{}) {
+ pathStr := valueString(result)
+ refStr := valueString(ref)
+ if pathStr != refStr {
+ t.Errorf("values do not match")
+ t.Log("test:", pathStr)
+ t.Log("ref: ", refStr)
+ }
+}
+
func assertQuery(t *testing.T, toml, query string, ref []interface{}) {
tree, err := Load(toml)
if err != nil {
@@ -13,59 +60,9 @@
return
}
results := Compile(query).Execute(tree)
- assertValue(t, results, ref, "(("+query+")) -> ")
+ assertValue(t, results.Values(), ref)
}
-func assertValue(t *testing.T, result, ref interface{}, location string) {
- switch node := ref.(type) {
- case []interface{}:
- if resultNode, ok := result.([]interface{}); !ok {
- t.Errorf("{%s} result value not of type %T: %T",
- location, node, resultNode)
- } else {
- if len(node) != len(resultNode) {
- t.Errorf("{%s} lengths do not match: %v vs %v",
- location, node, resultNode)
- } else {
- for i, v := range node {
- assertValue(t, resultNode[i], v, fmt.Sprintf("%s[%d]", location, i))
- }
- }
- }
- case map[string]interface{}:
- if resultNode, ok := result.(*TomlTree); !ok {
- t.Errorf("{%s} result value not of type %T: %T",
- location, node, resultNode)
- } else {
- for k, v := range node {
- assertValue(t, resultNode.GetPath([]string{k}), v, location+"."+k)
- }
- }
- case int64:
- if resultNode, ok := result.(int64); !ok {
- t.Errorf("{%s} result value not of type %T: %T",
- location, node, resultNode)
- } else {
- if node != resultNode {
- t.Errorf("{%s} result value does not match", location)
- }
- }
- case string:
- if resultNode, ok := result.(string); !ok {
- t.Errorf("{%s} result value not of type %T: %T",
- location, node, resultNode)
- } else {
- if node != resultNode {
- t.Errorf("{%s} result value does not match", location)
- }
- }
- default:
- if fmt.Sprintf("%v", node) != fmt.Sprintf("%v", ref) {
- t.Errorf("{%s} result value does not match: %v != %v",
- location, node, ref)
- }
- }
-}
func TestQueryRoot(t *testing.T) {
assertQuery(t,
diff --git a/jpath/query.go b/jpath/query.go
index 3bf306c..3435515 100644
--- a/jpath/query.go
+++ b/jpath/query.go
@@ -1,21 +1,44 @@
package jpath
import (
- _ "github.com/pelletier/go-toml"
+ . "github.com/pelletier/go-toml"
)
type nodeFilterFn func(node interface{}) bool
type nodeFn func(node interface{}) interface{}
-// runtime context for executing query paths
-type queryContext struct {
- filters *map[string]nodeFilterFn
- scripts *map[string]nodeFn
- results []interface{}
+type QueryResult struct {
+ items []interface{}
+ positions []Position
}
-func (c *queryContext) appendResult(value interface{}) {
- c.results = append(c.results, value)
+// TODO: modify after merging with rest of lib
+func (r *QueryResult) appendResult(node interface{}) {
+ r.items = append(r.items, node)
+ switch castNode := node.(type) {
+ case *TomlTree:
+ r.positions = append(r.positions, castNode.GetPosition(""))
+ //r.positions = append(r.positions, castNode.position)
+ //case *tomlValue:
+ //r.positions = append(r.positions, castNode.position)
+ default:
+ r.positions = append(r.positions, Position{})
+ }
+}
+
+func (r *QueryResult) Values() []interface{} {
+ return r.items
+}
+
+func (r *QueryResult) Positions() []Position {
+ return r.positions
+}
+
+// runtime context for executing query paths
+type queryContext struct {
+ result *QueryResult
+ filters *map[string]nodeFilterFn
+ scripts *map[string]nodeFn
}
// generic path functor interface
@@ -56,17 +79,22 @@
return parse(flow)
}
-func (q *Query) Execute(node interface{}) interface{} {
+func (q *Query) Execute(tree *TomlTree) *QueryResult {
+ result := &QueryResult {
+ items: []interface{}{},
+ positions: []Position{},
+ }
if q.root == nil {
- return []interface{}{node} // identity query for no predicates
- }
- ctx := &queryContext{
- filters: q.filters,
- scripts: q.scripts,
- results: []interface{}{},
- }
- q.root.Call(node, ctx)
- return ctx.results
+ result.appendResult(tree)
+ } else {
+ ctx := &queryContext{
+ result: result,
+ filters: q.filters,
+ scripts: q.scripts,
+ }
+ q.root.Call(tree, ctx)
+ }
+ return result
}
func (q *Query) SetFilter(name string, fn nodeFilterFn) {
diff --git a/parser.go b/parser.go
index c9f42a3..dcab890 100644
--- a/parser.go
+++ b/parser.go
@@ -70,9 +70,6 @@
func parseStart(p *parser) parserStateFn {
tok := p.peek()
- // prime position data with root tree instance
- p.tree.position = tok.Position
-
// end of stream, parsing is finished
if tok == nil {
return nil
@@ -279,6 +276,7 @@
func parse(flow chan token) *TomlTree {
result := newTomlTree()
+ result.position = Position{1,1}
parser := &parser{
flow: flow,
tree: result,
diff --git a/parser_test.go b/parser_test.go
index 761a181..10ac6d7 100644
--- a/parser_test.go
+++ b/parser_test.go
@@ -396,6 +396,7 @@
assertPosition(t,
"[foo]\nbar=42\nbaz=69",
map[string]Position{
+ "": Position{1, 1},
"foo": Position{1, 1},
"foo.bar": Position{2, 1},
"foo.baz": Position{3, 1},
@@ -406,6 +407,7 @@
assertPosition(t,
" [foo]\n bar=42\n baz=69",
map[string]Position{
+ "": Position{1, 1},
"foo": Position{1, 3},
"foo.bar": Position{2, 3},
"foo.baz": Position{3, 3},
@@ -416,20 +418,9 @@
assertPosition(t,
"[[foo]]\nbar=42\nbaz=69",
map[string]Position{
+ "": Position{1, 1},
"foo": Position{1, 1},
"foo.bar": Position{2, 1},
"foo.baz": Position{3, 1},
})
}
-
-func TestDocumentPositionsEmptyPath(t *testing.T) {
- text := "[foo]\nbar=42\nbaz=69"
- tree, err := Load(text)
- if err != nil {
- t.Errorf("Error loading document text: `%v`", text)
- t.Errorf("Error: %v", err)
- }
- if pos := tree.GetPosition(""); !pos.Invalid() {
- t.Errorf("Valid position was returned for empty path")
- }
-}
diff --git a/toml.go b/toml.go
index 7cd74b0..678959a 100644
--- a/toml.go
+++ b/toml.go
@@ -28,7 +28,7 @@
func newTomlTree() *TomlTree {
return &TomlTree{
values: make(map[string]interface{}),
- position: Position{0, 0},
+ position: Position{},
}
}
@@ -103,7 +103,7 @@
// GetPosition returns the position of the given key.
func (t *TomlTree) GetPosition(key string) Position {
if key == "" {
- return Position{0, 0}
+ return t.position
}
return t.GetPositionPath(strings.Split(key, "."))
}