2811a1a3c9
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
234 lines
4.7 KiB
Go
234 lines
4.7 KiB
Go
package jpath
|
|
|
|
import (
|
|
"fmt"
|
|
. "github.com/pelletier/go-toml"
|
|
)
|
|
|
|
// base match
|
|
type matchBase struct {
|
|
next PathFn
|
|
}
|
|
|
|
func (f *matchBase) SetNext(next PathFn) {
|
|
f.next = next
|
|
}
|
|
|
|
// terminating functor - gathers results
|
|
type terminatingFn struct {
|
|
// empty
|
|
}
|
|
|
|
func newTerminatingFn() *terminatingFn {
|
|
return &terminatingFn{}
|
|
}
|
|
|
|
func (f *terminatingFn) SetNext(next PathFn) {
|
|
// do nothing
|
|
}
|
|
|
|
func (f *terminatingFn) Call(node interface{}, ctx *queryContext) {
|
|
ctx.result.appendResult(node)
|
|
}
|
|
|
|
// shim to ease functor writing
|
|
func treeValue(tree *TomlTree, key string) interface{} {
|
|
return tree.GetPath([]string{key})
|
|
}
|
|
|
|
// match single key
|
|
type matchKeyFn struct {
|
|
matchBase
|
|
Name string
|
|
}
|
|
|
|
func newMatchKeyFn(name string) *matchKeyFn {
|
|
return &matchKeyFn{Name: name}
|
|
}
|
|
|
|
func (f *matchKeyFn) Call(node interface{}, ctx *queryContext) {
|
|
if tree, ok := node.(*TomlTree); ok {
|
|
item := treeValue(tree, f.Name)
|
|
if item != nil {
|
|
f.next.Call(item, ctx)
|
|
}
|
|
}
|
|
}
|
|
|
|
// match single index
|
|
type matchIndexFn struct {
|
|
matchBase
|
|
Idx int
|
|
}
|
|
|
|
func newMatchIndexFn(idx int) *matchIndexFn {
|
|
return &matchIndexFn{Idx: idx}
|
|
}
|
|
|
|
func (f *matchIndexFn) Call(node interface{}, ctx *queryContext) {
|
|
if arr, ok := node.([]interface{}); ok {
|
|
if f.Idx < len(arr) && f.Idx >= 0 {
|
|
f.next.Call(arr[f.Idx], ctx)
|
|
}
|
|
}
|
|
}
|
|
|
|
// filter by slicing
|
|
type matchSliceFn struct {
|
|
matchBase
|
|
Start, End, Step int
|
|
}
|
|
|
|
func newMatchSliceFn(start, end, step int) *matchSliceFn {
|
|
return &matchSliceFn{Start: start, End: end, Step: step}
|
|
}
|
|
|
|
func (f *matchSliceFn) Call(node interface{}, ctx *queryContext) {
|
|
if arr, ok := node.([]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 {
|
|
f.next.Call(arr[idx], ctx)
|
|
}
|
|
}
|
|
}
|
|
|
|
// match anything
|
|
type matchAnyFn struct {
|
|
matchBase
|
|
}
|
|
|
|
func newMatchAnyFn() *matchAnyFn {
|
|
return &matchAnyFn{}
|
|
}
|
|
|
|
func (f *matchAnyFn) Call(node interface{}, ctx *queryContext) {
|
|
if tree, ok := node.(*TomlTree); ok {
|
|
for _, key := range tree.Keys() {
|
|
item := treeValue(tree, key)
|
|
f.next.Call(item, ctx)
|
|
}
|
|
}
|
|
}
|
|
|
|
// filter through union
|
|
type matchUnionFn struct {
|
|
Union []PathFn
|
|
}
|
|
|
|
func (f *matchUnionFn) SetNext(next PathFn) {
|
|
for _, fn := range f.Union {
|
|
fn.SetNext(next)
|
|
}
|
|
}
|
|
|
|
func (f *matchUnionFn) Call(node interface{}, ctx *queryContext) {
|
|
for _, fn := range f.Union {
|
|
fn.Call(node, ctx)
|
|
}
|
|
}
|
|
|
|
// match every single last node in the tree
|
|
type matchRecursiveFn struct {
|
|
matchBase
|
|
}
|
|
|
|
func newMatchRecursiveFn() *matchRecursiveFn {
|
|
return &matchRecursiveFn{}
|
|
}
|
|
|
|
func (f *matchRecursiveFn) Call(node interface{}, ctx *queryContext) {
|
|
if tree, ok := node.(*TomlTree); ok {
|
|
var visit func(tree *TomlTree)
|
|
visit = func(tree *TomlTree) {
|
|
for _, key := range tree.Keys() {
|
|
item := treeValue(tree, key)
|
|
f.next.Call(item, ctx)
|
|
switch node := item.(type) {
|
|
case *TomlTree:
|
|
visit(node)
|
|
case []*TomlTree:
|
|
for _, subtree := range node {
|
|
visit(subtree)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
visit(tree)
|
|
}
|
|
}
|
|
|
|
// match based on an externally provided functional filter
|
|
type matchFilterFn struct {
|
|
matchBase
|
|
Pos Position
|
|
Name string
|
|
}
|
|
|
|
func newMatchFilterFn(name string, pos Position) *matchFilterFn {
|
|
return &matchFilterFn{Name: name, Pos: pos}
|
|
}
|
|
|
|
func (f *matchFilterFn) Call(node interface{}, ctx *queryContext) {
|
|
fn, ok := (*ctx.filters)[f.Name]
|
|
if !ok {
|
|
panic(fmt.Sprintf("%s: query context does not have filter '%s'",
|
|
f.Pos, f.Name))
|
|
}
|
|
switch castNode := node.(type) {
|
|
case *TomlTree:
|
|
for _, k := range castNode.Keys() {
|
|
v := castNode.GetPath([]string{k})
|
|
if fn(v) {
|
|
f.next.Call(v, ctx)
|
|
}
|
|
}
|
|
case []interface{}:
|
|
for _, v := range castNode {
|
|
if fn(v) {
|
|
f.next.Call(v, ctx)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// match based using result of an externally provided functional filter
|
|
type matchScriptFn struct {
|
|
matchBase
|
|
Pos Position
|
|
Name string
|
|
}
|
|
|
|
func newMatchScriptFn(name string, pos Position) *matchScriptFn {
|
|
return &matchScriptFn{Name: name, Pos: pos}
|
|
}
|
|
|
|
func (f *matchScriptFn) Call(node interface{}, ctx *queryContext) {
|
|
fn, ok := (*ctx.scripts)[f.Name]
|
|
if !ok {
|
|
panic(fmt.Sprintf("%s: query context does not have script '%s'",
|
|
f.Pos, f.Name))
|
|
}
|
|
switch result := fn(node).(type) {
|
|
case string:
|
|
nextMatch := newMatchKeyFn(result)
|
|
nextMatch.SetNext(f.next)
|
|
nextMatch.Call(node, ctx)
|
|
case int:
|
|
nextMatch := newMatchIndexFn(result)
|
|
nextMatch.SetNext(f.next)
|
|
nextMatch.Call(node, ctx)
|
|
//TODO: support other return types?
|
|
}
|
|
}
|