package jpath import ( . "github.com/pelletier/go-toml" "fmt" ) // 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.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? } }