| package toml |
| |
| import ( |
| "fmt" |
| ) |
| |
| // support function to set positions for tomlValues |
| // NOTE: this is done to allow ctx.lastPosition to indicate the start of any |
| // values returned by the query engines |
| func tomlValueCheck(node interface{}, ctx *queryContext) interface{} { |
| switch castNode := node.(type) { |
| case *tomlValue: |
| ctx.lastPosition = castNode.position |
| return castNode.value |
| case []*TomlTree: |
| if len(castNode) > 0 { |
| ctx.lastPosition = castNode[0].position |
| } |
| return node |
| default: |
| return node |
| } |
| } |
| |
| // 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) { |
| switch castNode := node.(type) { |
| case *TomlTree: |
| ctx.result.appendResult(node, castNode.position) |
| case *tomlValue: |
| ctx.result.appendResult(node, castNode.position) |
| default: |
| // use last position for scalars |
| 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.([]*TomlTree); ok { |
| for _, tree := range array { |
| item := tree.values[f.Name] |
| if item != nil { |
| f.next.call(item, ctx) |
| } |
| } |
| } else if tree, ok := node.(*TomlTree); ok { |
| item := tree.values[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 := tomlValueCheck(node, ctx).([]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 := tomlValueCheck(node, ctx).([]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 _, v := range tree.values { |
| 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) { |
| if tree, ok := node.(*TomlTree); ok { |
| var visit func(tree *TomlTree) |
| visit = func(tree *TomlTree) { |
| for _, v := range tree.values { |
| f.next.call(v, ctx) |
| switch node := v.(type) { |
| case *TomlTree: |
| visit(node) |
| case []*TomlTree: |
| for _, subtree := range node { |
| visit(subtree) |
| } |
| } |
| } |
| } |
| f.next.call(tree, ctx) |
| 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.String(), f.Name)) |
| } |
| switch castNode := tomlValueCheck(node, ctx).(type) { |
| case *TomlTree: |
| for _, v := range castNode.values { |
| if tv, ok := v.(*tomlValue); ok { |
| if fn(tv.value) { |
| f.next.call(v, ctx) |
| } |
| } else { |
| if fn(v) { |
| f.next.call(v, ctx) |
| } |
| } |
| } |
| case []interface{}: |
| for _, v := range castNode { |
| if fn(v) { |
| f.next.call(v, ctx) |
| } |
| } |
| } |
| } |