Move query to its own subpackage (#152)

Move all the query system to its own package. The reason is to
avoid it to rely on unexported methods and structures, and move
it out of the main package since this is really not a core
feature. It is still tied to the toml.TomlTree and toml.Position
structures for now.

* Move query mechanism to its own subpackage
* Rename QueryResult to Result to avoid stutter
* Add query.CompileAndExecute

Fixes #116
This commit is contained in:
Thomas Pelletier
2017-05-07 17:14:13 -07:00
committed by GitHub
parent 64bc956d5e
commit 23f644976a
17 changed files with 665 additions and 532 deletions
+232
View File
@@ -0,0 +1,232 @@
package query
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, ctx.lastPosition)
}
// 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 array, ok := node.([]*toml.TomlTree); ok {
for _, tree := range array {
item := tree.Get(f.Name)
if item != nil {
ctx.lastPosition = tree.GetPosition(f.Name)
f.next.call(item, ctx)
}
}
} else if tree, ok := node.(*toml.TomlTree); ok {
item := tree.Get(f.Name)
if item != nil {
ctx.lastPosition = tree.GetPosition(f.Name)
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 {
if treesArray, ok := node.([]*toml.TomlTree); ok {
if len(treesArray) > 0 {
ctx.lastPosition = treesArray[0].Position()
}
}
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 {
if treesArray, ok := node.([]*toml.TomlTree); ok {
if len(treesArray) > 0 {
ctx.lastPosition = treesArray[0].Position()
}
}
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.(*toml.TomlTree); ok {
for _, k := range tree.Keys() {
v := tree.Get(k)
ctx.lastPosition = tree.GetPosition(k)
f.next.call(v, 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) {
originalPosition := ctx.lastPosition
if tree, ok := node.(*toml.TomlTree); ok {
var visit func(tree *toml.TomlTree)
visit = func(tree *toml.TomlTree) {
for _, k := range tree.Keys() {
v := tree.Get(k)
ctx.lastPosition = tree.GetPosition(k)
f.next.call(v, ctx)
switch node := v.(type) {
case *toml.TomlTree:
visit(node)
case []*toml.TomlTree:
for _, subtree := range node {
visit(subtree)
}
}
}
}
ctx.lastPosition = originalPosition
f.next.call(tree, ctx)
visit(tree)
}
}
// match based on an externally provided functional filter
type matchFilterFn struct {
matchBase
Pos toml.Position
Name string
}
func newMatchFilterFn(name string, pos toml.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.String(), f.Name))
}
switch castNode := node.(type) {
case *toml.TomlTree:
for _, k := range castNode.Keys() {
v := castNode.Get(k)
if fn(v) {
ctx.lastPosition = castNode.GetPosition(k)
f.next.call(v, ctx)
}
}
case []*toml.TomlTree:
for _, v := range castNode {
if fn(v) {
if len(castNode) > 0 {
ctx.lastPosition = castNode[0].Position()
}
f.next.call(v, ctx)
}
}
case []interface{}:
for _, v := range castNode {
if fn(v) {
f.next.call(v, ctx)
}
}
}
}