b74544d345
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
143 lines
3.0 KiB
Go
143 lines
3.0 KiB
Go
package jpath
|
|
|
|
import (
|
|
. "github.com/pelletier/go-toml"
|
|
)
|
|
|
|
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})
|
|
}
|
|
|
|
// match single key
|
|
type matchKeyFn struct {
|
|
Name string
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
// match single index
|
|
type matchIndexFn struct {
|
|
Idx int
|
|
}
|
|
|
|
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])
|
|
}
|
|
}
|
|
}
|
|
|
|
// filter by slicing
|
|
type matchSliceFn struct {
|
|
Start, End, Step int
|
|
}
|
|
|
|
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
|
|
endFn := &matchEndFn{ []interface{}{} }
|
|
newPath := append(path, endFn)
|
|
|
|
// execute the path
|
|
newPath.Fn(context)
|
|
return endFn.Result
|
|
}
|