gofmt pass
This commit is contained in:
+8
-8
@@ -80,11 +80,11 @@ func (tt tokenType) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t token) Int() int {
|
func (t token) Int() int {
|
||||||
if result, err := strconv.Atoi(t.val); err != nil {
|
if result, err := strconv.Atoi(t.val); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
} else {
|
} else {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t token) String() string {
|
func (t token) String() string {
|
||||||
@@ -291,10 +291,10 @@ func lexVoid(l *lexer) stateFn {
|
|||||||
return lexString
|
return lexString
|
||||||
}
|
}
|
||||||
|
|
||||||
if isSpace(next) {
|
if isSpace(next) {
|
||||||
l.next()
|
l.next()
|
||||||
l.ignore()
|
l.ignore()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if isAlphanumeric(next) {
|
if isAlphanumeric(next) {
|
||||||
|
|||||||
@@ -97,4 +97,3 @@ func TestLexSpace(t *testing.T) {
|
|||||||
token{Position{1, 12}, tokenEOF, ""},
|
token{Position{1, 12}, tokenEOF, ""},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+124
-125
@@ -1,35 +1,34 @@
|
|||||||
package jpath
|
package jpath
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
. "github.com/pelletier/go-toml"
|
. "github.com/pelletier/go-toml"
|
||||||
"fmt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
// base match
|
// base match
|
||||||
type matchBase struct {
|
type matchBase struct {
|
||||||
next PathFn
|
next PathFn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *matchBase) SetNext(next PathFn) {
|
func (f *matchBase) SetNext(next PathFn) {
|
||||||
f.next = next
|
f.next = next
|
||||||
}
|
}
|
||||||
|
|
||||||
// terminating functor - gathers results
|
// terminating functor - gathers results
|
||||||
type terminatingFn struct {
|
type terminatingFn struct {
|
||||||
// empty
|
// empty
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTerminatingFn() *terminatingFn {
|
func newTerminatingFn() *terminatingFn {
|
||||||
return &terminatingFn{}
|
return &terminatingFn{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *terminatingFn) SetNext(next PathFn) {
|
func (f *terminatingFn) SetNext(next PathFn) {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *terminatingFn) Call(node interface{}, ctx *queryContext) {
|
func (f *terminatingFn) Call(node interface{}, ctx *queryContext) {
|
||||||
ctx.appendResult(node)
|
ctx.appendResult(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
// shim to ease functor writing
|
// shim to ease functor writing
|
||||||
@@ -39,196 +38,196 @@ func treeValue(tree *TomlTree, key string) interface{} {
|
|||||||
|
|
||||||
// match single key
|
// match single key
|
||||||
type matchKeyFn struct {
|
type matchKeyFn struct {
|
||||||
matchBase
|
matchBase
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMatchKeyFn(name string) *matchKeyFn {
|
func newMatchKeyFn(name string) *matchKeyFn {
|
||||||
return &matchKeyFn{ Name: name }
|
return &matchKeyFn{Name: name}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *matchKeyFn) Call(node interface{}, ctx *queryContext) {
|
func (f *matchKeyFn) Call(node interface{}, ctx *queryContext) {
|
||||||
if tree, ok := node.(*TomlTree); ok {
|
if tree, ok := node.(*TomlTree); ok {
|
||||||
item := treeValue(tree, f.Name)
|
item := treeValue(tree, f.Name)
|
||||||
if item != nil {
|
if item != nil {
|
||||||
f.next.Call(item, ctx)
|
f.next.Call(item, ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// match single index
|
// match single index
|
||||||
type matchIndexFn struct {
|
type matchIndexFn struct {
|
||||||
matchBase
|
matchBase
|
||||||
Idx int
|
Idx int
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMatchIndexFn(idx int) *matchIndexFn {
|
func newMatchIndexFn(idx int) *matchIndexFn {
|
||||||
return &matchIndexFn{ Idx: idx }
|
return &matchIndexFn{Idx: idx}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *matchIndexFn) Call(node interface{}, ctx *queryContext) {
|
func (f *matchIndexFn) Call(node interface{}, ctx *queryContext) {
|
||||||
if arr, ok := node.([]interface{}); ok {
|
if arr, ok := node.([]interface{}); ok {
|
||||||
if f.Idx < len(arr) && f.Idx >= 0 {
|
if f.Idx < len(arr) && f.Idx >= 0 {
|
||||||
f.next.Call(arr[f.Idx], ctx)
|
f.next.Call(arr[f.Idx], ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter by slicing
|
// filter by slicing
|
||||||
type matchSliceFn struct {
|
type matchSliceFn struct {
|
||||||
matchBase
|
matchBase
|
||||||
Start, End, Step int
|
Start, End, Step int
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMatchSliceFn(start, end, step int) *matchSliceFn {
|
func newMatchSliceFn(start, end, step int) *matchSliceFn {
|
||||||
return &matchSliceFn{ Start: start, End: end, Step: step }
|
return &matchSliceFn{Start: start, End: end, Step: step}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *matchSliceFn) Call(node interface{}, ctx *queryContext) {
|
func (f *matchSliceFn) Call(node interface{}, ctx *queryContext) {
|
||||||
if arr, ok := node.([]interface{}); ok {
|
if arr, ok := node.([]interface{}); ok {
|
||||||
// adjust indexes for negative values, reverse ordering
|
// adjust indexes for negative values, reverse ordering
|
||||||
realStart, realEnd := f.Start, f.End
|
realStart, realEnd := f.Start, f.End
|
||||||
if realStart < 0 {
|
if realStart < 0 {
|
||||||
realStart = len(arr) + realStart
|
realStart = len(arr) + realStart
|
||||||
}
|
}
|
||||||
if realEnd < 0 {
|
if realEnd < 0 {
|
||||||
realEnd = len(arr) + realEnd
|
realEnd = len(arr) + realEnd
|
||||||
}
|
}
|
||||||
if realEnd < realStart {
|
if realEnd < realStart {
|
||||||
realEnd, realStart = realStart, realEnd // swap
|
realEnd, realStart = realStart, realEnd // swap
|
||||||
}
|
}
|
||||||
// loop and gather
|
// loop and gather
|
||||||
for idx := realStart; idx < realEnd; idx += f.Step {
|
for idx := realStart; idx < realEnd; idx += f.Step {
|
||||||
f.next.Call(arr[idx], ctx)
|
f.next.Call(arr[idx], ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// match anything
|
// match anything
|
||||||
type matchAnyFn struct {
|
type matchAnyFn struct {
|
||||||
matchBase
|
matchBase
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMatchAnyFn() *matchAnyFn {
|
func newMatchAnyFn() *matchAnyFn {
|
||||||
return &matchAnyFn{}
|
return &matchAnyFn{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *matchAnyFn) Call(node interface{}, ctx *queryContext) {
|
func (f *matchAnyFn) Call(node interface{}, ctx *queryContext) {
|
||||||
if tree, ok := node.(*TomlTree); ok {
|
if tree, ok := node.(*TomlTree); ok {
|
||||||
for _, key := range tree.Keys() {
|
for _, key := range tree.Keys() {
|
||||||
item := treeValue(tree, key)
|
item := treeValue(tree, key)
|
||||||
f.next.Call(item, ctx)
|
f.next.Call(item, ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter through union
|
// filter through union
|
||||||
type matchUnionFn struct {
|
type matchUnionFn struct {
|
||||||
Union []PathFn
|
Union []PathFn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *matchUnionFn) SetNext(next PathFn) {
|
func (f *matchUnionFn) SetNext(next PathFn) {
|
||||||
for _, fn := range f.Union {
|
for _, fn := range f.Union {
|
||||||
fn.SetNext(next)
|
fn.SetNext(next)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *matchUnionFn) Call(node interface{}, ctx *queryContext) {
|
func (f *matchUnionFn) Call(node interface{}, ctx *queryContext) {
|
||||||
for _, fn := range f.Union {
|
for _, fn := range f.Union {
|
||||||
fn.Call(node, ctx)
|
fn.Call(node, ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// match every single last node in the tree
|
// match every single last node in the tree
|
||||||
type matchRecursiveFn struct {
|
type matchRecursiveFn struct {
|
||||||
matchBase
|
matchBase
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMatchRecursiveFn() *matchRecursiveFn{
|
func newMatchRecursiveFn() *matchRecursiveFn {
|
||||||
return &matchRecursiveFn{}
|
return &matchRecursiveFn{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *matchRecursiveFn) Call(node interface{}, ctx *queryContext) {
|
func (f *matchRecursiveFn) Call(node interface{}, ctx *queryContext) {
|
||||||
if tree, ok := node.(*TomlTree); ok {
|
if tree, ok := node.(*TomlTree); ok {
|
||||||
var visit func(tree *TomlTree)
|
var visit func(tree *TomlTree)
|
||||||
visit = func(tree *TomlTree) {
|
visit = func(tree *TomlTree) {
|
||||||
for _, key := range tree.Keys() {
|
for _, key := range tree.Keys() {
|
||||||
item := treeValue(tree, key)
|
item := treeValue(tree, key)
|
||||||
f.next.Call(item, ctx)
|
f.next.Call(item, ctx)
|
||||||
switch node := item.(type) {
|
switch node := item.(type) {
|
||||||
case *TomlTree:
|
case *TomlTree:
|
||||||
visit(node)
|
visit(node)
|
||||||
case []*TomlTree:
|
case []*TomlTree:
|
||||||
for _, subtree := range node {
|
for _, subtree := range node {
|
||||||
visit(subtree)
|
visit(subtree)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
visit(tree)
|
visit(tree)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// match based on an externally provided functional filter
|
// match based on an externally provided functional filter
|
||||||
type matchFilterFn struct {
|
type matchFilterFn struct {
|
||||||
matchBase
|
matchBase
|
||||||
Pos Position
|
Pos Position
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMatchFilterFn(name string, pos Position) *matchFilterFn {
|
func newMatchFilterFn(name string, pos Position) *matchFilterFn {
|
||||||
return &matchFilterFn{ Name: name, Pos: pos }
|
return &matchFilterFn{Name: name, Pos: pos}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *matchFilterFn) Call(node interface{}, ctx *queryContext) {
|
func (f *matchFilterFn) Call(node interface{}, ctx *queryContext) {
|
||||||
fn, ok := (*ctx.filters)[f.Name]
|
fn, ok := (*ctx.filters)[f.Name]
|
||||||
if !ok {
|
if !ok {
|
||||||
panic(fmt.Sprintf("%s: query context does not have filter '%s'",
|
panic(fmt.Sprintf("%s: query context does not have filter '%s'",
|
||||||
f.Pos, f.Name))
|
f.Pos, f.Name))
|
||||||
}
|
}
|
||||||
switch castNode := node.(type) {
|
switch castNode := node.(type) {
|
||||||
case *TomlTree:
|
case *TomlTree:
|
||||||
for _, k := range castNode.Keys() {
|
for _, k := range castNode.Keys() {
|
||||||
v := castNode.GetPath([]string{k})
|
v := castNode.GetPath([]string{k})
|
||||||
if fn(v) {
|
if fn(v) {
|
||||||
f.next.Call(v, ctx)
|
f.next.Call(v, ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
for _, v := range castNode {
|
for _, v := range castNode {
|
||||||
if fn(v) {
|
if fn(v) {
|
||||||
f.next.Call(v, ctx)
|
f.next.Call(v, ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// match based using result of an externally provided functional filter
|
// match based using result of an externally provided functional filter
|
||||||
type matchScriptFn struct {
|
type matchScriptFn struct {
|
||||||
matchBase
|
matchBase
|
||||||
Pos Position
|
Pos Position
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMatchScriptFn(name string, pos Position) *matchScriptFn {
|
func newMatchScriptFn(name string, pos Position) *matchScriptFn {
|
||||||
return &matchScriptFn{ Name: name, Pos: pos }
|
return &matchScriptFn{Name: name, Pos: pos}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *matchScriptFn) Call(node interface{}, ctx *queryContext) {
|
func (f *matchScriptFn) Call(node interface{}, ctx *queryContext) {
|
||||||
fn, ok := (*ctx.scripts)[f.Name]
|
fn, ok := (*ctx.scripts)[f.Name]
|
||||||
if !ok {
|
if !ok {
|
||||||
panic(fmt.Sprintf("%s: query context does not have script '%s'",
|
panic(fmt.Sprintf("%s: query context does not have script '%s'",
|
||||||
f.Pos, f.Name))
|
f.Pos, f.Name))
|
||||||
}
|
}
|
||||||
switch result := fn(node).(type) {
|
switch result := fn(node).(type) {
|
||||||
case string:
|
case string:
|
||||||
nextMatch := newMatchKeyFn(result)
|
nextMatch := newMatchKeyFn(result)
|
||||||
nextMatch.SetNext(f.next)
|
nextMatch.SetNext(f.next)
|
||||||
nextMatch.Call(node, ctx)
|
nextMatch.Call(node, ctx)
|
||||||
case int:
|
case int:
|
||||||
nextMatch := newMatchIndexFn(result)
|
nextMatch := newMatchIndexFn(result)
|
||||||
nextMatch.SetNext(f.next)
|
nextMatch.SetNext(f.next)
|
||||||
nextMatch.Call(node, ctx)
|
nextMatch.Call(node, ctx)
|
||||||
//TODO: support other return types?
|
//TODO: support other return types?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+96
-96
@@ -1,218 +1,218 @@
|
|||||||
package jpath
|
package jpath
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
. "github.com/pelletier/go-toml"
|
. "github.com/pelletier/go-toml"
|
||||||
"fmt"
|
"math"
|
||||||
"math"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// dump path tree to a string
|
// dump path tree to a string
|
||||||
func pathString(root PathFn) string {
|
func pathString(root PathFn) string {
|
||||||
result := fmt.Sprintf("%T:", root)
|
result := fmt.Sprintf("%T:", root)
|
||||||
switch fn := root.(type) {
|
switch fn := root.(type) {
|
||||||
case *terminatingFn:
|
case *terminatingFn:
|
||||||
result += "{}"
|
result += "{}"
|
||||||
case *matchKeyFn:
|
case *matchKeyFn:
|
||||||
result += fmt.Sprintf("{%s}", fn.Name)
|
result += fmt.Sprintf("{%s}", fn.Name)
|
||||||
result += pathString(fn.next)
|
result += pathString(fn.next)
|
||||||
case *matchIndexFn:
|
case *matchIndexFn:
|
||||||
result += fmt.Sprintf("{%d}", fn.Idx)
|
result += fmt.Sprintf("{%d}", fn.Idx)
|
||||||
result += pathString(fn.next)
|
result += pathString(fn.next)
|
||||||
case *matchSliceFn:
|
case *matchSliceFn:
|
||||||
result += fmt.Sprintf("{%d:%d:%d}",
|
result += fmt.Sprintf("{%d:%d:%d}",
|
||||||
fn.Start, fn.End, fn.Step)
|
fn.Start, fn.End, fn.Step)
|
||||||
result += pathString(fn.next)
|
result += pathString(fn.next)
|
||||||
case *matchAnyFn:
|
case *matchAnyFn:
|
||||||
result += "{}"
|
result += "{}"
|
||||||
result += pathString(fn.next)
|
result += pathString(fn.next)
|
||||||
case *matchUnionFn:
|
case *matchUnionFn:
|
||||||
result += "{["
|
result += "{["
|
||||||
for _, v := range fn.Union {
|
for _, v := range fn.Union {
|
||||||
result += pathString(v) + ", "
|
result += pathString(v) + ", "
|
||||||
}
|
}
|
||||||
result += "]}"
|
result += "]}"
|
||||||
case *matchRecursiveFn:
|
case *matchRecursiveFn:
|
||||||
result += "{}"
|
result += "{}"
|
||||||
result += pathString(fn.next)
|
result += pathString(fn.next)
|
||||||
case *matchFilterFn:
|
case *matchFilterFn:
|
||||||
result += fmt.Sprintf("{%s}", fn.Name)
|
result += fmt.Sprintf("{%s}", fn.Name)
|
||||||
result += pathString(fn.next)
|
result += pathString(fn.next)
|
||||||
case *matchScriptFn:
|
case *matchScriptFn:
|
||||||
result += fmt.Sprintf("{%s}", fn.Name)
|
result += fmt.Sprintf("{%s}", fn.Name)
|
||||||
result += pathString(fn.next)
|
result += pathString(fn.next)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertPathMatch(t *testing.T, path, ref *Query) bool {
|
func assertPathMatch(t *testing.T, path, ref *Query) bool {
|
||||||
pathStr := pathString(path.root)
|
pathStr := pathString(path.root)
|
||||||
refStr := pathString(ref.root)
|
refStr := pathString(ref.root)
|
||||||
if pathStr != refStr {
|
if pathStr != refStr {
|
||||||
t.Errorf("paths do not match")
|
t.Errorf("paths do not match")
|
||||||
t.Log("test:", pathStr)
|
t.Log("test:", pathStr)
|
||||||
t.Log("ref: ", refStr)
|
t.Log("ref: ", refStr)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertPath(t *testing.T, query string, ref *Query) {
|
func assertPath(t *testing.T, query string, ref *Query) {
|
||||||
_, flow := lex(query)
|
_, flow := lex(query)
|
||||||
path := parse(flow)
|
path := parse(flow)
|
||||||
assertPathMatch(t, path, ref)
|
assertPathMatch(t, path, ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildPath(parts... PathFn) *Query {
|
func buildPath(parts ...PathFn) *Query {
|
||||||
query := newQuery()
|
query := newQuery()
|
||||||
for _, v := range parts {
|
for _, v := range parts {
|
||||||
query.appendPath(v)
|
query.appendPath(v)
|
||||||
}
|
}
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathRoot(t *testing.T) {
|
func TestPathRoot(t *testing.T) {
|
||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$",
|
"$",
|
||||||
buildPath(
|
buildPath(
|
||||||
// empty
|
// empty
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathKey(t *testing.T) {
|
func TestPathKey(t *testing.T) {
|
||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$.foo",
|
"$.foo",
|
||||||
buildPath(
|
buildPath(
|
||||||
newMatchKeyFn("foo"),
|
newMatchKeyFn("foo"),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathBracketKey(t *testing.T) {
|
func TestPathBracketKey(t *testing.T) {
|
||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$[foo]",
|
"$[foo]",
|
||||||
buildPath(
|
buildPath(
|
||||||
newMatchKeyFn("foo"),
|
newMatchKeyFn("foo"),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathBracketStringKey(t *testing.T) {
|
func TestPathBracketStringKey(t *testing.T) {
|
||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$['foo']",
|
"$['foo']",
|
||||||
buildPath(
|
buildPath(
|
||||||
newMatchKeyFn("foo"),
|
newMatchKeyFn("foo"),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathIndex(t *testing.T) {
|
func TestPathIndex(t *testing.T) {
|
||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$[123]",
|
"$[123]",
|
||||||
buildPath(
|
buildPath(
|
||||||
newMatchIndexFn(123),
|
newMatchIndexFn(123),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathSliceStart(t *testing.T) {
|
func TestPathSliceStart(t *testing.T) {
|
||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$[123:]",
|
"$[123:]",
|
||||||
buildPath(
|
buildPath(
|
||||||
newMatchSliceFn(123, math.MaxInt64, 1),
|
newMatchSliceFn(123, math.MaxInt64, 1),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathSliceStartEnd(t *testing.T) {
|
func TestPathSliceStartEnd(t *testing.T) {
|
||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$[123:456]",
|
"$[123:456]",
|
||||||
buildPath(
|
buildPath(
|
||||||
newMatchSliceFn(123, 456, 1),
|
newMatchSliceFn(123, 456, 1),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathSliceStartEndColon(t *testing.T) {
|
func TestPathSliceStartEndColon(t *testing.T) {
|
||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$[123:456:]",
|
"$[123:456:]",
|
||||||
buildPath(
|
buildPath(
|
||||||
newMatchSliceFn(123, 456, 1),
|
newMatchSliceFn(123, 456, 1),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathSliceStartStep(t *testing.T) {
|
func TestPathSliceStartStep(t *testing.T) {
|
||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$[123::7]",
|
"$[123::7]",
|
||||||
buildPath(
|
buildPath(
|
||||||
newMatchSliceFn(123, math.MaxInt64, 7),
|
newMatchSliceFn(123, math.MaxInt64, 7),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathSliceEndStep(t *testing.T) {
|
func TestPathSliceEndStep(t *testing.T) {
|
||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$[:456:7]",
|
"$[:456:7]",
|
||||||
buildPath(
|
buildPath(
|
||||||
newMatchSliceFn(0, 456, 7),
|
newMatchSliceFn(0, 456, 7),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathSliceStep(t *testing.T) {
|
func TestPathSliceStep(t *testing.T) {
|
||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$[::7]",
|
"$[::7]",
|
||||||
buildPath(
|
buildPath(
|
||||||
newMatchSliceFn(0, math.MaxInt64, 7),
|
newMatchSliceFn(0, math.MaxInt64, 7),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathSliceAll(t *testing.T) {
|
func TestPathSliceAll(t *testing.T) {
|
||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$[123:456:7]",
|
"$[123:456:7]",
|
||||||
buildPath(
|
buildPath(
|
||||||
newMatchSliceFn(123, 456, 7),
|
newMatchSliceFn(123, 456, 7),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathAny(t *testing.T) {
|
func TestPathAny(t *testing.T) {
|
||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$.*",
|
"$.*",
|
||||||
buildPath(
|
buildPath(
|
||||||
newMatchAnyFn(),
|
newMatchAnyFn(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathUnion(t *testing.T) {
|
func TestPathUnion(t *testing.T) {
|
||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$[foo, bar, baz]",
|
"$[foo, bar, baz]",
|
||||||
buildPath(
|
buildPath(
|
||||||
&matchUnionFn{ []PathFn {
|
&matchUnionFn{[]PathFn{
|
||||||
newMatchKeyFn("foo"),
|
newMatchKeyFn("foo"),
|
||||||
newMatchKeyFn("bar"),
|
newMatchKeyFn("bar"),
|
||||||
newMatchKeyFn("baz"),
|
newMatchKeyFn("baz"),
|
||||||
}},
|
}},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathRecurse(t *testing.T) {
|
func TestPathRecurse(t *testing.T) {
|
||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$..*",
|
"$..*",
|
||||||
buildPath(
|
buildPath(
|
||||||
newMatchRecursiveFn(),
|
newMatchRecursiveFn(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathFilterExpr(t *testing.T) {
|
func TestPathFilterExpr(t *testing.T) {
|
||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$[?('foo'),?(bar)]",
|
"$[?('foo'),?(bar)]",
|
||||||
buildPath(
|
buildPath(
|
||||||
&matchUnionFn{ []PathFn {
|
&matchUnionFn{[]PathFn{
|
||||||
newMatchFilterFn("foo", Position{}),
|
newMatchFilterFn("foo", Position{}),
|
||||||
newMatchFilterFn("bar", Position{}),
|
newMatchFilterFn("bar", Position{}),
|
||||||
}},
|
}},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathScriptExpr(t *testing.T) {
|
func TestPathScriptExpr(t *testing.T) {
|
||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$[('foo'),(bar)]",
|
"$[('foo'),(bar)]",
|
||||||
buildPath(
|
buildPath(
|
||||||
&matchUnionFn{ []PathFn {
|
&matchUnionFn{[]PathFn{
|
||||||
newMatchScriptFn("foo", Position{}),
|
newMatchScriptFn("foo", Position{}),
|
||||||
newMatchScriptFn("bar", Position{}),
|
newMatchScriptFn("bar", Position{}),
|
||||||
}},
|
}},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
+100
-100
@@ -16,7 +16,7 @@ type parser struct {
|
|||||||
flow chan token
|
flow chan token
|
||||||
tokensBuffer []token
|
tokensBuffer []token
|
||||||
path *Query
|
path *Query
|
||||||
union []PathFn
|
union []PathFn
|
||||||
}
|
}
|
||||||
|
|
||||||
type parserStateFn func(*parser) parserStateFn
|
type parserStateFn func(*parser) parserStateFn
|
||||||
@@ -49,25 +49,25 @@ func (p *parser) peek() *token {
|
|||||||
return &tok
|
return &tok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) lookahead(types... tokenType) bool {
|
func (p *parser) lookahead(types ...tokenType) bool {
|
||||||
result := true
|
result := true
|
||||||
buffer := []token{}
|
buffer := []token{}
|
||||||
|
|
||||||
for _, typ := range types {
|
for _, typ := range types {
|
||||||
tok := p.getToken()
|
tok := p.getToken()
|
||||||
if tok == nil {
|
if tok == nil {
|
||||||
result = false
|
result = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
buffer = append(buffer, *tok)
|
buffer = append(buffer, *tok)
|
||||||
if tok.typ != typ {
|
if tok.typ != typ {
|
||||||
result = false
|
result = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// add the tokens back to the buffer, and return
|
// add the tokens back to the buffer, and return
|
||||||
p.tokensBuffer = append(p.tokensBuffer, buffer...)
|
p.tokensBuffer = append(p.tokensBuffer, buffer...)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) getToken() *token {
|
func (p *parser) getToken() *token {
|
||||||
@@ -102,31 +102,31 @@ func parseMatchExpr(p *parser) parserStateFn {
|
|||||||
tok := p.getToken()
|
tok := p.getToken()
|
||||||
switch tok.typ {
|
switch tok.typ {
|
||||||
case tokenDotDot:
|
case tokenDotDot:
|
||||||
p.path.appendPath(&matchRecursiveFn{})
|
p.path.appendPath(&matchRecursiveFn{})
|
||||||
// nested parse for '..'
|
// nested parse for '..'
|
||||||
tok := p.getToken()
|
tok := p.getToken()
|
||||||
switch tok.typ {
|
switch tok.typ {
|
||||||
case tokenKey:
|
case tokenKey:
|
||||||
p.path.appendPath(newMatchKeyFn(tok.val))
|
p.path.appendPath(newMatchKeyFn(tok.val))
|
||||||
return parseMatchExpr
|
return parseMatchExpr
|
||||||
case tokenLBracket:
|
case tokenLBracket:
|
||||||
return parseBracketExpr
|
return parseBracketExpr
|
||||||
case tokenStar:
|
case tokenStar:
|
||||||
// do nothing - the recursive predicate is enough
|
// do nothing - the recursive predicate is enough
|
||||||
return parseMatchExpr
|
return parseMatchExpr
|
||||||
}
|
}
|
||||||
|
|
||||||
case tokenDot:
|
case tokenDot:
|
||||||
// nested parse for '.'
|
// nested parse for '.'
|
||||||
tok := p.getToken()
|
tok := p.getToken()
|
||||||
switch tok.typ {
|
switch tok.typ {
|
||||||
case tokenKey:
|
case tokenKey:
|
||||||
p.path.appendPath(newMatchKeyFn(tok.val))
|
p.path.appendPath(newMatchKeyFn(tok.val))
|
||||||
return parseMatchExpr
|
return parseMatchExpr
|
||||||
case tokenStar:
|
case tokenStar:
|
||||||
p.path.appendPath(&matchAnyFn{})
|
p.path.appendPath(&matchAnyFn{})
|
||||||
return parseMatchExpr
|
return parseMatchExpr
|
||||||
}
|
}
|
||||||
|
|
||||||
case tokenLBracket:
|
case tokenLBracket:
|
||||||
return parseBracketExpr
|
return parseBracketExpr
|
||||||
@@ -139,38 +139,38 @@ func parseMatchExpr(p *parser) parserStateFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseBracketExpr(p *parser) parserStateFn {
|
func parseBracketExpr(p *parser) parserStateFn {
|
||||||
if p.lookahead(tokenInteger, tokenColon) {
|
if p.lookahead(tokenInteger, tokenColon) {
|
||||||
return parseSliceExpr
|
return parseSliceExpr
|
||||||
}
|
}
|
||||||
if p.peek().typ == tokenColon {
|
if p.peek().typ == tokenColon {
|
||||||
return parseSliceExpr
|
return parseSliceExpr
|
||||||
}
|
}
|
||||||
return parseUnionExpr
|
return parseUnionExpr
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseUnionExpr(p *parser) parserStateFn {
|
func parseUnionExpr(p *parser) parserStateFn {
|
||||||
var tok *token
|
var tok *token
|
||||||
|
|
||||||
// this state can be traversed after some sub-expressions
|
// this state can be traversed after some sub-expressions
|
||||||
// so be careful when setting up state in the parser
|
// so be careful when setting up state in the parser
|
||||||
if p.union == nil {
|
if p.union == nil {
|
||||||
p.union = []PathFn{}
|
p.union = []PathFn{}
|
||||||
}
|
}
|
||||||
|
|
||||||
loop: // labeled loop for easy breaking
|
loop: // labeled loop for easy breaking
|
||||||
for {
|
for {
|
||||||
if len(p.union) > 0 {
|
if len(p.union) > 0 {
|
||||||
// parse delimiter or terminator
|
// parse delimiter or terminator
|
||||||
tok = p.getToken()
|
tok = p.getToken()
|
||||||
switch tok.typ {
|
switch tok.typ {
|
||||||
case tokenComma:
|
case tokenComma:
|
||||||
// do nothing
|
// do nothing
|
||||||
case tokenRBracket:
|
case tokenRBracket:
|
||||||
break loop
|
break loop
|
||||||
default:
|
default:
|
||||||
p.raiseError(tok, "expected ',' or ']', not '%s'", tok.val)
|
p.raiseError(tok, "expected ',' or ']', not '%s'", tok.val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse sub expression
|
// parse sub expression
|
||||||
tok = p.getToken()
|
tok = p.getToken()
|
||||||
@@ -190,14 +190,14 @@ loop: // labeled loop for easy breaking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there is only one sub-expression, use that instead
|
// if there is only one sub-expression, use that instead
|
||||||
if len(p.union) == 1 {
|
if len(p.union) == 1 {
|
||||||
p.path.appendPath(p.union[0])
|
p.path.appendPath(p.union[0])
|
||||||
}else {
|
} else {
|
||||||
p.path.appendPath(&matchUnionFn{p.union})
|
p.path.appendPath(&matchUnionFn{p.union})
|
||||||
}
|
}
|
||||||
|
|
||||||
p.union = nil // clear out state
|
p.union = nil // clear out state
|
||||||
return parseMatchExpr
|
return parseMatchExpr
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,11 +221,11 @@ func parseSliceExpr(p *parser) parserStateFn {
|
|||||||
end = tok.Int()
|
end = tok.Int()
|
||||||
tok = p.getToken()
|
tok = p.getToken()
|
||||||
}
|
}
|
||||||
if tok.typ == tokenRBracket {
|
if tok.typ == tokenRBracket {
|
||||||
p.path.appendPath(newMatchSliceFn(start, end, step))
|
p.path.appendPath(newMatchSliceFn(start, end, step))
|
||||||
return parseMatchExpr
|
return parseMatchExpr
|
||||||
}
|
}
|
||||||
if tok.typ != tokenColon {
|
if tok.typ != tokenColon {
|
||||||
p.raiseError(tok, "expected ']' or ':'")
|
p.raiseError(tok, "expected ']' or ':'")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,33 +247,33 @@ func parseSliceExpr(p *parser) parserStateFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseFilterExpr(p *parser) parserStateFn {
|
func parseFilterExpr(p *parser) parserStateFn {
|
||||||
tok := p.getToken()
|
tok := p.getToken()
|
||||||
if tok.typ != tokenLParen {
|
if tok.typ != tokenLParen {
|
||||||
p.raiseError(tok, "expected left-parenthesis for filter expression")
|
p.raiseError(tok, "expected left-parenthesis for filter expression")
|
||||||
}
|
}
|
||||||
tok = p.getToken()
|
tok = p.getToken()
|
||||||
if tok.typ != tokenKey && tok.typ != tokenString {
|
if tok.typ != tokenKey && tok.typ != tokenString {
|
||||||
p.raiseError(tok, "expected key or string for filter funciton name")
|
p.raiseError(tok, "expected key or string for filter funciton name")
|
||||||
}
|
}
|
||||||
name := tok.val
|
name := tok.val
|
||||||
tok = p.getToken()
|
tok = p.getToken()
|
||||||
if tok.typ != tokenRParen {
|
if tok.typ != tokenRParen {
|
||||||
p.raiseError(tok, "expected right-parenthesis for filter expression")
|
p.raiseError(tok, "expected right-parenthesis for filter expression")
|
||||||
}
|
}
|
||||||
p.union = append(p.union, newMatchFilterFn(name, tok.Position))
|
p.union = append(p.union, newMatchFilterFn(name, tok.Position))
|
||||||
return parseUnionExpr
|
return parseUnionExpr
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseScriptExpr(p *parser) parserStateFn {
|
func parseScriptExpr(p *parser) parserStateFn {
|
||||||
tok := p.getToken()
|
tok := p.getToken()
|
||||||
if tok.typ != tokenKey && tok.typ != tokenString {
|
if tok.typ != tokenKey && tok.typ != tokenString {
|
||||||
p.raiseError(tok, "expected key or string for script funciton name")
|
p.raiseError(tok, "expected key or string for script funciton name")
|
||||||
}
|
}
|
||||||
name := tok.val
|
name := tok.val
|
||||||
tok = p.getToken()
|
tok = p.getToken()
|
||||||
if tok.typ != tokenRParen {
|
if tok.typ != tokenRParen {
|
||||||
p.raiseError(tok, "expected right-parenthesis for script expression")
|
p.raiseError(tok, "expected right-parenthesis for script expression")
|
||||||
}
|
}
|
||||||
p.union = append(p.union, newMatchScriptFn(name, tok.Position))
|
p.union = append(p.union, newMatchScriptFn(name, tok.Position))
|
||||||
return parseUnionExpr
|
return parseUnionExpr
|
||||||
}
|
}
|
||||||
|
|||||||
+148
-148
@@ -12,8 +12,8 @@ func assertQuery(t *testing.T, toml, query string, ref []interface{}) {
|
|||||||
t.Errorf("Non-nil toml parse error: %v", err)
|
t.Errorf("Non-nil toml parse error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
results := Compile(query).Execute(tree)
|
results := Compile(query).Execute(tree)
|
||||||
assertValue(t, results, ref, "((" + query + ")) -> ")
|
assertValue(t, results, ref, "(("+query+")) -> ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertValue(t *testing.T, result, ref interface{}, location string) {
|
func assertValue(t *testing.T, result, ref interface{}, location string) {
|
||||||
@@ -23,14 +23,14 @@ func assertValue(t *testing.T, result, ref interface{}, location string) {
|
|||||||
t.Errorf("{%s} result value not of type %T: %T",
|
t.Errorf("{%s} result value not of type %T: %T",
|
||||||
location, node, resultNode)
|
location, node, resultNode)
|
||||||
} else {
|
} else {
|
||||||
if len(node) != len(resultNode) {
|
if len(node) != len(resultNode) {
|
||||||
t.Errorf("{%s} lengths do not match: %v vs %v",
|
t.Errorf("{%s} lengths do not match: %v vs %v",
|
||||||
location, node, resultNode)
|
location, node, resultNode)
|
||||||
} else {
|
} else {
|
||||||
for i, v := range node {
|
for i, v := range node {
|
||||||
assertValue(t, resultNode[i], v, fmt.Sprintf("%s[%d]", location, i))
|
assertValue(t, resultNode[i], v, fmt.Sprintf("%s[%d]", location, i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
if resultNode, ok := result.(*TomlTree); !ok {
|
if resultNode, ok := result.(*TomlTree); !ok {
|
||||||
@@ -79,186 +79,186 @@ func TestQueryRoot(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestQueryKey(t *testing.T) {
|
func TestQueryKey(t *testing.T) {
|
||||||
assertQuery(t,
|
assertQuery(t,
|
||||||
"[foo]\na = 42",
|
"[foo]\na = 42",
|
||||||
"$.foo.a",
|
"$.foo.a",
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
int64(42),
|
int64(42),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQueryKeyString(t *testing.T) {
|
func TestQueryKeyString(t *testing.T) {
|
||||||
assertQuery(t,
|
assertQuery(t,
|
||||||
"[foo]\na = 42",
|
"[foo]\na = 42",
|
||||||
"$.foo['a']",
|
"$.foo['a']",
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
int64(42),
|
int64(42),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQueryIndex(t *testing.T) {
|
func TestQueryIndex(t *testing.T) {
|
||||||
assertQuery(t,
|
assertQuery(t,
|
||||||
"[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
|
"[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
|
||||||
"$.foo.a[0]",
|
"$.foo.a[0]",
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
int64(1),
|
int64(1),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQuerySliceRange(t *testing.T) {
|
func TestQuerySliceRange(t *testing.T) {
|
||||||
assertQuery(t,
|
assertQuery(t,
|
||||||
"[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
|
"[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
|
||||||
"$.foo.a[0:5]",
|
"$.foo.a[0:5]",
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
int64(1),
|
int64(1),
|
||||||
int64(2),
|
int64(2),
|
||||||
int64(3),
|
int64(3),
|
||||||
int64(4),
|
int64(4),
|
||||||
int64(5),
|
int64(5),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQuerySliceStep(t *testing.T) {
|
func TestQuerySliceStep(t *testing.T) {
|
||||||
assertQuery(t,
|
assertQuery(t,
|
||||||
"[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
|
"[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
|
||||||
"$.foo.a[0:5:2]",
|
"$.foo.a[0:5:2]",
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
int64(1),
|
int64(1),
|
||||||
int64(3),
|
int64(3),
|
||||||
int64(5),
|
int64(5),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQueryAny(t *testing.T) {
|
func TestQueryAny(t *testing.T) {
|
||||||
assertQuery(t,
|
assertQuery(t,
|
||||||
"[foo.bar]\na=1\nb=2\n[foo.baz]\na=3\nb=4",
|
"[foo.bar]\na=1\nb=2\n[foo.baz]\na=3\nb=4",
|
||||||
"$.foo.*",
|
"$.foo.*",
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"a": int64(1),
|
"a": int64(1),
|
||||||
"b": int64(2),
|
"b": int64(2),
|
||||||
},
|
},
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"a": int64(3),
|
"a": int64(3),
|
||||||
"b": int64(4),
|
"b": int64(4),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
func TestQueryUnionSimple(t *testing.T) {
|
func TestQueryUnionSimple(t *testing.T) {
|
||||||
assertQuery(t,
|
assertQuery(t,
|
||||||
"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
|
"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
|
||||||
"$.*[bar,foo]",
|
"$.*[bar,foo]",
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"a": int64(1),
|
"a": int64(1),
|
||||||
"b": int64(2),
|
"b": int64(2),
|
||||||
},
|
},
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"a": int64(3),
|
"a": int64(3),
|
||||||
"b": int64(4),
|
"b": int64(4),
|
||||||
},
|
},
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"a": int64(5),
|
"a": int64(5),
|
||||||
"b": int64(6),
|
"b": int64(6),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQueryRecursionAll(t *testing.T) {
|
func TestQueryRecursionAll(t *testing.T) {
|
||||||
assertQuery(t,
|
assertQuery(t,
|
||||||
"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
|
"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
|
||||||
"$..*",
|
"$..*",
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"bar": map[string]interface{}{
|
"bar": map[string]interface{}{
|
||||||
"a": int64(1),
|
"a": int64(1),
|
||||||
"b": int64(2),
|
"b": int64(2),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"a": int64(1),
|
"a": int64(1),
|
||||||
"b": int64(2),
|
"b": int64(2),
|
||||||
},
|
},
|
||||||
int64(1),
|
int64(1),
|
||||||
int64(2),
|
int64(2),
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"foo": map[string]interface{}{
|
"foo": map[string]interface{}{
|
||||||
"a": int64(3),
|
"a": int64(3),
|
||||||
"b": int64(4),
|
"b": int64(4),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"a": int64(3),
|
"a": int64(3),
|
||||||
"b": int64(4),
|
"b": int64(4),
|
||||||
},
|
},
|
||||||
int64(3),
|
int64(3),
|
||||||
int64(4),
|
int64(4),
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"foo": map[string]interface{}{
|
"foo": map[string]interface{}{
|
||||||
"a": int64(5),
|
"a": int64(5),
|
||||||
"b": int64(6),
|
"b": int64(6),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"a": int64(5),
|
"a": int64(5),
|
||||||
"b": int64(6),
|
"b": int64(6),
|
||||||
},
|
},
|
||||||
int64(5),
|
int64(5),
|
||||||
int64(6),
|
int64(6),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQueryRecursionUnionSimple(t *testing.T) {
|
func TestQueryRecursionUnionSimple(t *testing.T) {
|
||||||
assertQuery(t,
|
assertQuery(t,
|
||||||
"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
|
"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
|
||||||
"$..['foo','bar']",
|
"$..['foo','bar']",
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"a": int64(1),
|
"a": int64(1),
|
||||||
"b": int64(2),
|
"b": int64(2),
|
||||||
},
|
},
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"a": int64(3),
|
"a": int64(3),
|
||||||
"b": int64(4),
|
"b": int64(4),
|
||||||
},
|
},
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"a": int64(5),
|
"a": int64(5),
|
||||||
"b": int64(6),
|
"b": int64(6),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQueryScriptFnLast(t *testing.T) {
|
func TestQueryScriptFnLast(t *testing.T) {
|
||||||
assertQuery(t,
|
assertQuery(t,
|
||||||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||||||
"$.foo.a[(last)]",
|
"$.foo.a[(last)]",
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
int64(9),
|
int64(9),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQueryFilterFnOdd(t *testing.T) {
|
func TestQueryFilterFnOdd(t *testing.T) {
|
||||||
assertQuery(t,
|
assertQuery(t,
|
||||||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||||||
"$.foo.a[?(odd)]",
|
"$.foo.a[?(odd)]",
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
int64(1),
|
int64(1),
|
||||||
int64(3),
|
int64(3),
|
||||||
int64(5),
|
int64(5),
|
||||||
int64(7),
|
int64(7),
|
||||||
int64(9),
|
int64(9),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQueryFilterFnEven(t *testing.T) {
|
func TestQueryFilterFnEven(t *testing.T) {
|
||||||
assertQuery(t,
|
assertQuery(t,
|
||||||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||||||
"$.foo.a[?(even)]",
|
"$.foo.a[?(even)]",
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
int64(0),
|
int64(0),
|
||||||
int64(2),
|
int64(2),
|
||||||
int64(4),
|
int64(4),
|
||||||
int64(6),
|
int64(6),
|
||||||
int64(8),
|
int64(8),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+72
-73
@@ -1,7 +1,7 @@
|
|||||||
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
|
||||||
@@ -9,109 +9,108 @@ type nodeFn func(node interface{}) interface{}
|
|||||||
|
|
||||||
// runtime context for executing query paths
|
// runtime context for executing query paths
|
||||||
type queryContext struct {
|
type queryContext struct {
|
||||||
filters *map[string]nodeFilterFn
|
filters *map[string]nodeFilterFn
|
||||||
scripts *map[string]nodeFn
|
scripts *map[string]nodeFn
|
||||||
results []interface{}
|
results []interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *queryContext) appendResult(value interface{}) {
|
func (c *queryContext) appendResult(value interface{}) {
|
||||||
c.results = append(c.results, value)
|
c.results = append(c.results, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// generic path functor interface
|
// generic path functor interface
|
||||||
type PathFn interface {
|
type PathFn interface {
|
||||||
SetNext(next PathFn)
|
SetNext(next PathFn)
|
||||||
Call(node interface{}, ctx *queryContext)
|
Call(node interface{}, ctx *queryContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
// encapsulates a query functor chain and script callbacks
|
// encapsulates a query functor chain and script callbacks
|
||||||
type Query struct {
|
type Query struct {
|
||||||
root PathFn
|
root PathFn
|
||||||
tail PathFn
|
tail PathFn
|
||||||
filters *map[string]nodeFilterFn
|
filters *map[string]nodeFilterFn
|
||||||
scripts *map[string]nodeFn
|
scripts *map[string]nodeFn
|
||||||
}
|
}
|
||||||
|
|
||||||
func newQuery() *Query {
|
func newQuery() *Query {
|
||||||
return &Query {
|
return &Query{
|
||||||
root: nil,
|
root: nil,
|
||||||
tail: nil,
|
tail: nil,
|
||||||
filters: &defaultFilterFunctions,
|
filters: &defaultFilterFunctions,
|
||||||
scripts: &defaultScriptFunctions,
|
scripts: &defaultScriptFunctions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Query) appendPath(next PathFn) {
|
func (q *Query) appendPath(next PathFn) {
|
||||||
if q.root == nil {
|
if q.root == nil {
|
||||||
q.root = next
|
q.root = next
|
||||||
} else {
|
} else {
|
||||||
q.tail.SetNext(next)
|
q.tail.SetNext(next)
|
||||||
}
|
}
|
||||||
q.tail = next
|
q.tail = next
|
||||||
next.SetNext(newTerminatingFn()) // init the next functor
|
next.SetNext(newTerminatingFn()) // init the next functor
|
||||||
}
|
}
|
||||||
|
|
||||||
func Compile(path string) *Query {
|
func Compile(path string) *Query {
|
||||||
_, flow := lex(path)
|
_, flow := lex(path)
|
||||||
return parse(flow)
|
return parse(flow)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Query) Execute(node interface{}) interface{} {
|
func (q *Query) Execute(node interface{}) interface{} {
|
||||||
if q.root == nil {
|
if q.root == nil {
|
||||||
return []interface{}{node} // identity query for no predicates
|
return []interface{}{node} // identity query for no predicates
|
||||||
}
|
}
|
||||||
ctx := &queryContext {
|
ctx := &queryContext{
|
||||||
filters: q.filters,
|
filters: q.filters,
|
||||||
scripts: q.scripts,
|
scripts: q.scripts,
|
||||||
results: []interface{}{},
|
results: []interface{}{},
|
||||||
}
|
}
|
||||||
q.root.Call(node, ctx)
|
q.root.Call(node, ctx)
|
||||||
return ctx.results
|
return ctx.results
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Query) SetFilter(name string, fn nodeFilterFn) {
|
func (q *Query) SetFilter(name string, fn nodeFilterFn) {
|
||||||
if q.filters == &defaultFilterFunctions {
|
if q.filters == &defaultFilterFunctions {
|
||||||
// clone the static table
|
// clone the static table
|
||||||
q.filters = &map[string]nodeFilterFn{}
|
q.filters = &map[string]nodeFilterFn{}
|
||||||
for k, v := range defaultFilterFunctions {
|
for k, v := range defaultFilterFunctions {
|
||||||
(*q.filters)[k] = v
|
(*q.filters)[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(*q.filters)[name] = fn
|
(*q.filters)[name] = fn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Query) SetScript(name string, fn nodeFn) {
|
func (q *Query) SetScript(name string, fn nodeFn) {
|
||||||
if q.scripts == &defaultScriptFunctions {
|
if q.scripts == &defaultScriptFunctions {
|
||||||
// clone the static table
|
// clone the static table
|
||||||
q.scripts = &map[string]nodeFn{}
|
q.scripts = &map[string]nodeFn{}
|
||||||
for k, v := range defaultScriptFunctions {
|
for k, v := range defaultScriptFunctions {
|
||||||
(*q.scripts)[k] = v
|
(*q.scripts)[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(*q.scripts)[name] = fn
|
(*q.scripts)[name] = fn
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultFilterFunctions = map[string]nodeFilterFn {
|
var defaultFilterFunctions = map[string]nodeFilterFn{
|
||||||
"odd": func(node interface{}) bool {
|
"odd": func(node interface{}) bool {
|
||||||
if ii, ok := node.(int64); ok {
|
if ii, ok := node.(int64); ok {
|
||||||
return (ii & 1) == 1
|
return (ii & 1) == 1
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
"even": func(node interface{}) bool {
|
"even": func(node interface{}) bool {
|
||||||
if ii, ok := node.(int64); ok {
|
if ii, ok := node.(int64); ok {
|
||||||
return (ii & 1) == 0
|
return (ii & 1) == 0
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultScriptFunctions = map[string]nodeFn {
|
var defaultScriptFunctions = map[string]nodeFn{
|
||||||
"last": func(node interface{}) interface{} {
|
"last": func(node interface{}) interface{} {
|
||||||
if arr, ok := node.([]interface{}); ok {
|
if arr, ok := node.([]interface{}); ok {
|
||||||
return len(arr)-1
|
return len(arr) - 1
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user