Changed match func strategy; added tests

As it turns out, closures are very hard to validate without running them.
Since table-driven tests tend to rely on value types that can be
compared directly, using structs that adhere to a generic callback
interface is more work, but is more easily tested.

* Changed jsonpath match functions to structs with Call() methods
* Added tests to verify the generation of jsonpath QueryPath data
* Added tests to verify jsonpath lexer
* Fixed jsonpath whitespace handling bug
* Fixed numerous flaws in jsonpath parser
This commit is contained in:
eanderton
2014-09-04 23:17:29 -04:00
parent 27416cc1b9
commit b74544d345
6 changed files with 447 additions and 156 deletions
+112 -84
View File
@@ -4,111 +4,139 @@ import (
. "github.com/pelletier/go-toml"
)
type QueryPath []PathFn
type PathFn func(context interface{}, next QueryPath)
func (path QueryPath) Fn(context interface{}) {
path[0](context, path[1:])
type PathFn interface{
Call(context interface{}, next QueryPath)
}
type QueryPath []PathFn
func (path QueryPath) Fn(context interface{}) {
path[0].Call(context, path[1:])
}
// shim to ease functor writing
func treeValue(tree *TomlTree, key string) interface{} {
return tree.GetPath([]string{key})
}
func matchKeyFn(name string) PathFn {
return func(context interface{}, next QueryPath) {
if tree, ok := context.(*TomlTree); ok {
item := treeValue(tree, name)
if item != nil {
next.Fn(item)
}
}
}
// match single key
type matchKeyFn struct {
Name string
}
func matchIndexFn(idx int) PathFn {
return func(context interface{}, next QueryPath) {
if arr, ok := context.([]interface{}); ok {
if idx < len(arr) && idx >= 0 {
next.Fn(arr[idx])
}
}
}
func (f *matchKeyFn) Call(context interface{}, next QueryPath) {
if tree, ok := context.(*TomlTree); ok {
item := treeValue(tree, f.Name)
if item != nil {
next.Fn(item)
}
}
}
func matchSliceFn(start, end, step int) PathFn {
return func(context interface{}, next QueryPath) {
if arr, ok := context.([]interface{}); ok {
// adjust indexes for negative values, reverse ordering
realStart, realEnd := start, end
if realStart < 0 {
realStart = len(arr) + realStart
}
if realEnd < 0 {
realEnd = len(arr) + realEnd
}
if realEnd < realStart {
realEnd, realStart = realStart, realEnd // swap
}
// loop and gather
for idx := realStart; idx < realEnd; idx += step {
next.Fn(arr[idx])
}
}
}
// match single index
type matchIndexFn struct {
Idx int
}
func matchAnyFn() PathFn {
return func(context interface{}, next QueryPath) {
if tree, ok := context.(*TomlTree); ok {
for _, key := range tree.Keys() {
item := treeValue(tree, key)
next.Fn(item)
}
}
}
func (f *matchIndexFn) Call(context interface{}, next QueryPath) {
if arr, ok := context.([]interface{}); ok {
if f.Idx < len(arr) && f.Idx >= 0 {
next.Fn(arr[f.Idx])
}
}
}
func matchUnionFn(union QueryPath) PathFn {
return func(context interface{}, next QueryPath) {
for _, fn := range union {
fn(context, next)
}
}
// filter by slicing
type matchSliceFn struct {
Start, End, Step int
}
func matchRecurseFn() PathFn {
return func(context interface{}, next QueryPath) {
if tree, ok := context.(*TomlTree); ok {
var visit func(tree *TomlTree)
visit = func(tree *TomlTree) {
for _, key := range tree.Keys() {
item := treeValue(tree, key)
next.Fn(item)
switch node := item.(type) {
case *TomlTree:
visit(node)
case []*TomlTree:
for _, subtree := range node {
visit(subtree)
}
}
}
}
visit(tree)
}
}
func (f *matchSliceFn) Call(context interface{}, next QueryPath) {
if arr, ok := context.([]interface{}); ok {
// adjust indexes for negative values, reverse ordering
realStart, realEnd := f.Start, f.End
if realStart < 0 {
realStart = len(arr) + realStart
}
if realEnd < 0 {
realEnd = len(arr) + realEnd
}
if realEnd < realStart {
realEnd, realStart = realStart, realEnd // swap
}
// loop and gather
for idx := realStart; idx < realEnd; idx += f.Step {
next.Fn(arr[idx])
}
}
}
// match anything
type matchAnyFn struct {
// empty
}
func (f *matchAnyFn) Call(context interface{}, next QueryPath) {
if tree, ok := context.(*TomlTree); ok {
for _, key := range tree.Keys() {
item := treeValue(tree, key)
next.Fn(item)
}
}
}
// filter through union
type matchUnionFn struct {
Union QueryPath
}
func (f *matchUnionFn) Call(context interface{}, next QueryPath) {
for _, fn := range f.Union {
fn.Call(context, next)
}
}
// match every single last node in the tree
type matchRecursiveFn struct {
// empty
}
func (f *matchRecursiveFn) Call(context interface{}, next QueryPath) {
if tree, ok := context.(*TomlTree); ok {
var visit func(tree *TomlTree)
visit = func(tree *TomlTree) {
for _, key := range tree.Keys() {
item := treeValue(tree, key)
next.Fn(item)
switch node := item.(type) {
case *TomlTree:
visit(node)
case []*TomlTree:
for _, subtree := range node {
visit(subtree)
}
}
}
}
visit(tree)
}
}
// terminating expression
type matchEndFn struct {
Result []interface{}
}
func (f *matchEndFn) Call(context interface{}, next QueryPath) {
f.Result = append(f.Result, context)
}
func processPath(path QueryPath, context interface{}) []interface{} {
// terminate the path with a collection funciton
result := []interface{}{}
newPath := append(path, func(context interface{}, next QueryPath) {
result = append(result, context)
})
endFn := &matchEndFn{ []interface{}{} }
newPath := append(path, endFn)
// execute the path
newPath.Fn(context)
return result
return endFn.Result
}