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:
+1
-1
@@ -28,7 +28,7 @@ func (f *terminatingFn) SetNext(next PathFn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *terminatingFn) Call(node interface{}, ctx *queryContext) {
|
func (f *terminatingFn) Call(node interface{}, ctx *queryContext) {
|
||||||
ctx.appendResult(node)
|
ctx.result.appendResult(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
// shim to ease functor writing
|
// shim to ease functor writing
|
||||||
|
|||||||
+48
-51
@@ -4,8 +4,55 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
. "github.com/pelletier/go-toml"
|
. "github.com/pelletier/go-toml"
|
||||||
"testing"
|
"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{}) {
|
func assertQuery(t *testing.T, toml, query string, ref []interface{}) {
|
||||||
tree, err := Load(toml)
|
tree, err := Load(toml)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -13,59 +60,9 @@ func assertQuery(t *testing.T, toml, query string, ref []interface{}) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
results := Compile(query).Execute(tree)
|
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) {
|
func TestQueryRoot(t *testing.T) {
|
||||||
assertQuery(t,
|
assertQuery(t,
|
||||||
|
|||||||
+42
-14
@@ -1,21 +1,44 @@
|
|||||||
package jpath
|
package jpath
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "github.com/pelletier/go-toml"
|
. "github.com/pelletier/go-toml"
|
||||||
)
|
)
|
||||||
|
|
||||||
type nodeFilterFn func(node interface{}) bool
|
type nodeFilterFn func(node interface{}) bool
|
||||||
type nodeFn func(node interface{}) interface{}
|
type nodeFn func(node interface{}) interface{}
|
||||||
|
|
||||||
// runtime context for executing query paths
|
type QueryResult struct {
|
||||||
type queryContext struct {
|
items []interface{}
|
||||||
filters *map[string]nodeFilterFn
|
positions []Position
|
||||||
scripts *map[string]nodeFn
|
|
||||||
results []interface{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *queryContext) appendResult(value interface{}) {
|
// TODO: modify after merging with rest of lib
|
||||||
c.results = append(c.results, value)
|
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
|
// generic path functor interface
|
||||||
@@ -56,17 +79,22 @@ func Compile(path string) *Query {
|
|||||||
return parse(flow)
|
return parse(flow)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Query) Execute(node interface{}) interface{} {
|
func (q *Query) Execute(tree *TomlTree) *QueryResult {
|
||||||
if q.root == nil {
|
result := &QueryResult {
|
||||||
return []interface{}{node} // identity query for no predicates
|
items: []interface{}{},
|
||||||
|
positions: []Position{},
|
||||||
}
|
}
|
||||||
|
if q.root == nil {
|
||||||
|
result.appendResult(tree)
|
||||||
|
} else {
|
||||||
ctx := &queryContext{
|
ctx := &queryContext{
|
||||||
|
result: result,
|
||||||
filters: q.filters,
|
filters: q.filters,
|
||||||
scripts: q.scripts,
|
scripts: q.scripts,
|
||||||
results: []interface{}{},
|
|
||||||
}
|
}
|
||||||
q.root.Call(node, ctx)
|
q.root.Call(tree, ctx)
|
||||||
return ctx.results
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Query) SetFilter(name string, fn nodeFilterFn) {
|
func (q *Query) SetFilter(name string, fn nodeFilterFn) {
|
||||||
|
|||||||
@@ -70,9 +70,6 @@ func (p *parser) getToken() *token {
|
|||||||
func parseStart(p *parser) parserStateFn {
|
func parseStart(p *parser) parserStateFn {
|
||||||
tok := p.peek()
|
tok := p.peek()
|
||||||
|
|
||||||
// prime position data with root tree instance
|
|
||||||
p.tree.position = tok.Position
|
|
||||||
|
|
||||||
// end of stream, parsing is finished
|
// end of stream, parsing is finished
|
||||||
if tok == nil {
|
if tok == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -279,6 +276,7 @@ func parseArray(p *parser) []interface{} {
|
|||||||
|
|
||||||
func parse(flow chan token) *TomlTree {
|
func parse(flow chan token) *TomlTree {
|
||||||
result := newTomlTree()
|
result := newTomlTree()
|
||||||
|
result.position = Position{1,1}
|
||||||
parser := &parser{
|
parser := &parser{
|
||||||
flow: flow,
|
flow: flow,
|
||||||
tree: result,
|
tree: result,
|
||||||
|
|||||||
+3
-12
@@ -396,6 +396,7 @@ func TestDocumentPositions(t *testing.T) {
|
|||||||
assertPosition(t,
|
assertPosition(t,
|
||||||
"[foo]\nbar=42\nbaz=69",
|
"[foo]\nbar=42\nbaz=69",
|
||||||
map[string]Position{
|
map[string]Position{
|
||||||
|
"": Position{1, 1},
|
||||||
"foo": Position{1, 1},
|
"foo": Position{1, 1},
|
||||||
"foo.bar": Position{2, 1},
|
"foo.bar": Position{2, 1},
|
||||||
"foo.baz": Position{3, 1},
|
"foo.baz": Position{3, 1},
|
||||||
@@ -406,6 +407,7 @@ func TestDocumentPositionsWithSpaces(t *testing.T) {
|
|||||||
assertPosition(t,
|
assertPosition(t,
|
||||||
" [foo]\n bar=42\n baz=69",
|
" [foo]\n bar=42\n baz=69",
|
||||||
map[string]Position{
|
map[string]Position{
|
||||||
|
"": Position{1, 1},
|
||||||
"foo": Position{1, 3},
|
"foo": Position{1, 3},
|
||||||
"foo.bar": Position{2, 3},
|
"foo.bar": Position{2, 3},
|
||||||
"foo.baz": Position{3, 3},
|
"foo.baz": Position{3, 3},
|
||||||
@@ -416,20 +418,9 @@ func TestDocumentPositionsWithGroupArray(t *testing.T) {
|
|||||||
assertPosition(t,
|
assertPosition(t,
|
||||||
"[[foo]]\nbar=42\nbaz=69",
|
"[[foo]]\nbar=42\nbaz=69",
|
||||||
map[string]Position{
|
map[string]Position{
|
||||||
|
"": Position{1, 1},
|
||||||
"foo": Position{1, 1},
|
"foo": Position{1, 1},
|
||||||
"foo.bar": Position{2, 1},
|
"foo.bar": Position{2, 1},
|
||||||
"foo.baz": Position{3, 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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ type TomlTree struct {
|
|||||||
func newTomlTree() *TomlTree {
|
func newTomlTree() *TomlTree {
|
||||||
return &TomlTree{
|
return &TomlTree{
|
||||||
values: make(map[string]interface{}),
|
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.
|
// GetPosition returns the position of the given key.
|
||||||
func (t *TomlTree) GetPosition(key string) Position {
|
func (t *TomlTree) GetPosition(key string) Position {
|
||||||
if key == "" {
|
if key == "" {
|
||||||
return Position{0, 0}
|
return t.position
|
||||||
}
|
}
|
||||||
return t.GetPositionPath(strings.Split(key, "."))
|
return t.GetPositionPath(strings.Split(key, "."))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user