blob: bdc3db59b097ccfbb08367d6caa703a3a57afa3d [file]
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?
}
}