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
145 lines
3.0 KiB
Go
145 lines
3.0 KiB
Go
package jpath
|
|
|
|
import (
|
|
. "github.com/pelletier/go-toml"
|
|
)
|
|
|
|
type nodeFilterFn func(node interface{}) bool
|
|
type nodeFn func(node interface{}) interface{}
|
|
|
|
type QueryResult struct {
|
|
items []interface{}
|
|
positions []Position
|
|
}
|
|
|
|
// TODO: modify after merging with rest of lib
|
|
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
|
|
type PathFn interface {
|
|
SetNext(next PathFn)
|
|
Call(node interface{}, ctx *queryContext)
|
|
}
|
|
|
|
// encapsulates a query functor chain and script callbacks
|
|
type Query struct {
|
|
root PathFn
|
|
tail PathFn
|
|
filters *map[string]nodeFilterFn
|
|
scripts *map[string]nodeFn
|
|
}
|
|
|
|
func newQuery() *Query {
|
|
return &Query{
|
|
root: nil,
|
|
tail: nil,
|
|
filters: &defaultFilterFunctions,
|
|
scripts: &defaultScriptFunctions,
|
|
}
|
|
}
|
|
|
|
func (q *Query) appendPath(next PathFn) {
|
|
if q.root == nil {
|
|
q.root = next
|
|
} else {
|
|
q.tail.SetNext(next)
|
|
}
|
|
q.tail = next
|
|
next.SetNext(newTerminatingFn()) // init the next functor
|
|
}
|
|
|
|
func Compile(path string) *Query {
|
|
_, flow := lex(path)
|
|
return parse(flow)
|
|
}
|
|
|
|
func (q *Query) Execute(tree *TomlTree) *QueryResult {
|
|
result := &QueryResult {
|
|
items: []interface{}{},
|
|
positions: []Position{},
|
|
}
|
|
if q.root == nil {
|
|
result.appendResult(tree)
|
|
} else {
|
|
ctx := &queryContext{
|
|
result: result,
|
|
filters: q.filters,
|
|
scripts: q.scripts,
|
|
}
|
|
q.root.Call(tree, ctx)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (q *Query) SetFilter(name string, fn nodeFilterFn) {
|
|
if q.filters == &defaultFilterFunctions {
|
|
// clone the static table
|
|
q.filters = &map[string]nodeFilterFn{}
|
|
for k, v := range defaultFilterFunctions {
|
|
(*q.filters)[k] = v
|
|
}
|
|
}
|
|
(*q.filters)[name] = fn
|
|
}
|
|
|
|
func (q *Query) SetScript(name string, fn nodeFn) {
|
|
if q.scripts == &defaultScriptFunctions {
|
|
// clone the static table
|
|
q.scripts = &map[string]nodeFn{}
|
|
for k, v := range defaultScriptFunctions {
|
|
(*q.scripts)[k] = v
|
|
}
|
|
}
|
|
(*q.scripts)[name] = fn
|
|
}
|
|
|
|
var defaultFilterFunctions = map[string]nodeFilterFn{
|
|
"odd": func(node interface{}) bool {
|
|
if ii, ok := node.(int64); ok {
|
|
return (ii & 1) == 1
|
|
}
|
|
return false
|
|
},
|
|
"even": func(node interface{}) bool {
|
|
if ii, ok := node.(int64); ok {
|
|
return (ii & 1) == 0
|
|
}
|
|
return false
|
|
},
|
|
}
|
|
|
|
var defaultScriptFunctions = map[string]nodeFn{
|
|
"last": func(node interface{}) interface{} {
|
|
if arr, ok := node.([]interface{}); ok {
|
|
return len(arr) - 1
|
|
}
|
|
return nil
|
|
},
|
|
}
|