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
This commit is contained in:
eanderton
2014-09-09 22:31:41 -04:00
parent 7f30fba1e6
commit 2811a1a3c9
6 changed files with 101 additions and 87 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ func (f *terminatingFn) SetNext(next PathFn) {
}
func (f *terminatingFn) Call(node interface{}, ctx *queryContext) {
ctx.appendResult(node)
ctx.result.appendResult(node)
}
// shim to ease functor writing
+48 -51
View File
@@ -4,8 +4,55 @@ import (
"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 @@ func assertQuery(t *testing.T, toml, query string, ref []interface{}) {
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,
+42 -14
View File
@@ -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 @@ func Compile(path string) *Query {
return parse(flow)
}
func (q *Query) Execute(node interface{}) interface{} {
if q.root == nil {
return []interface{}{node} // identity query for no predicates
func (q *Query) Execute(tree *TomlTree) *QueryResult {
result := &QueryResult {
items: []interface{}{},
positions: []Position{},
}
if q.root == nil {
result.appendResult(tree)
} else {
ctx := &queryContext{
result: result,
filters: q.filters,
scripts: q.scripts,
results: []interface{}{},
}
q.root.Call(node, ctx)
return ctx.results
q.root.Call(tree, ctx)
}
return result
}
func (q *Query) SetFilter(name string, fn nodeFilterFn) {
+1 -3
View File
@@ -70,9 +70,6 @@ func (p *parser) getToken() *token {
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 parseArray(p *parser) []interface{} {
func parse(flow chan token) *TomlTree {
result := newTomlTree()
result.position = Position{1,1}
parser := &parser{
flow: flow,
tree: result,
+3 -12
View File
@@ -396,6 +396,7 @@ func TestDocumentPositions(t *testing.T) {
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 @@ func TestDocumentPositionsWithSpaces(t *testing.T) {
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 @@ func TestDocumentPositionsWithGroupArray(t *testing.T) {
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")
}
}
+2 -2
View File
@@ -28,7 +28,7 @@ type TomlTree struct {
func newTomlTree() *TomlTree {
return &TomlTree{
values: make(map[string]interface{}),
position: Position{0, 0},
position: Position{},
}
}
@@ -103,7 +103,7 @@ func (t *TomlTree) GetPath(keys []string) interface{} {
// 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, "."))
}