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) } } } }