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.Tree); 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.Tree); 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.Tree); 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.Tree); 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.Tree); 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.Tree); ok { var visit func(tree *toml.Tree) visit = func(tree *toml.Tree) { 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.Tree: visit(node) case []*toml.Tree: 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.Tree: for _, k := range castNode.Keys() { v := castNode.Get(k) if fn(v) { ctx.lastPosition = castNode.GetPosition(k) f.next.call(v, ctx) } } case []*toml.Tree: 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) } } } }