1package query 2 3import ( 4 "fmt" 5 "github.com/pelletier/go-toml" 6) 7 8// base match 9type matchBase struct { 10 next pathFn 11} 12 13func (f *matchBase) setNext(next pathFn) { 14 f.next = next 15} 16 17// terminating functor - gathers results 18type terminatingFn struct { 19 // empty 20} 21 22func newTerminatingFn() *terminatingFn { 23 return &terminatingFn{} 24} 25 26func (f *terminatingFn) setNext(next pathFn) { 27 // do nothing 28} 29 30func (f *terminatingFn) call(node interface{}, ctx *queryContext) { 31 ctx.result.appendResult(node, ctx.lastPosition) 32} 33 34// match single key 35type matchKeyFn struct { 36 matchBase 37 Name string 38} 39 40func newMatchKeyFn(name string) *matchKeyFn { 41 return &matchKeyFn{Name: name} 42} 43 44func (f *matchKeyFn) call(node interface{}, ctx *queryContext) { 45 if array, ok := node.([]*toml.Tree); ok { 46 for _, tree := range array { 47 item := tree.Get(f.Name) 48 if item != nil { 49 ctx.lastPosition = tree.GetPosition(f.Name) 50 f.next.call(item, ctx) 51 } 52 } 53 } else if tree, ok := node.(*toml.Tree); ok { 54 item := tree.Get(f.Name) 55 if item != nil { 56 ctx.lastPosition = tree.GetPosition(f.Name) 57 f.next.call(item, ctx) 58 } 59 } 60} 61 62// match single index 63type matchIndexFn struct { 64 matchBase 65 Idx int 66} 67 68func newMatchIndexFn(idx int) *matchIndexFn { 69 return &matchIndexFn{Idx: idx} 70} 71 72func (f *matchIndexFn) call(node interface{}, ctx *queryContext) { 73 if arr, ok := node.([]interface{}); ok { 74 if f.Idx < len(arr) && f.Idx >= 0 { 75 if treesArray, ok := node.([]*toml.Tree); ok { 76 if len(treesArray) > 0 { 77 ctx.lastPosition = treesArray[0].Position() 78 } 79 } 80 f.next.call(arr[f.Idx], ctx) 81 } 82 } 83} 84 85// filter by slicing 86type matchSliceFn struct { 87 matchBase 88 Start, End, Step int 89} 90 91func newMatchSliceFn(start, end, step int) *matchSliceFn { 92 return &matchSliceFn{Start: start, End: end, Step: step} 93} 94 95func (f *matchSliceFn) call(node interface{}, ctx *queryContext) { 96 if arr, ok := node.([]interface{}); ok { 97 // adjust indexes for negative values, reverse ordering 98 realStart, realEnd := f.Start, f.End 99 if realStart < 0 { 100 realStart = len(arr) + realStart 101 } 102 if realEnd < 0 { 103 realEnd = len(arr) + realEnd 104 } 105 if realEnd < realStart { 106 realEnd, realStart = realStart, realEnd // swap 107 } 108 // loop and gather 109 for idx := realStart; idx < realEnd; idx += f.Step { 110 if treesArray, ok := node.([]*toml.Tree); ok { 111 if len(treesArray) > 0 { 112 ctx.lastPosition = treesArray[0].Position() 113 } 114 } 115 f.next.call(arr[idx], ctx) 116 } 117 } 118} 119 120// match anything 121type matchAnyFn struct { 122 matchBase 123} 124 125func newMatchAnyFn() *matchAnyFn { 126 return &matchAnyFn{} 127} 128 129func (f *matchAnyFn) call(node interface{}, ctx *queryContext) { 130 if tree, ok := node.(*toml.Tree); ok { 131 for _, k := range tree.Keys() { 132 v := tree.Get(k) 133 ctx.lastPosition = tree.GetPosition(k) 134 f.next.call(v, ctx) 135 } 136 } 137} 138 139// filter through union 140type matchUnionFn struct { 141 Union []pathFn 142} 143 144func (f *matchUnionFn) setNext(next pathFn) { 145 for _, fn := range f.Union { 146 fn.setNext(next) 147 } 148} 149 150func (f *matchUnionFn) call(node interface{}, ctx *queryContext) { 151 for _, fn := range f.Union { 152 fn.call(node, ctx) 153 } 154} 155 156// match every single last node in the tree 157type matchRecursiveFn struct { 158 matchBase 159} 160 161func newMatchRecursiveFn() *matchRecursiveFn { 162 return &matchRecursiveFn{} 163} 164 165func (f *matchRecursiveFn) call(node interface{}, ctx *queryContext) { 166 originalPosition := ctx.lastPosition 167 if tree, ok := node.(*toml.Tree); ok { 168 var visit func(tree *toml.Tree) 169 visit = func(tree *toml.Tree) { 170 for _, k := range tree.Keys() { 171 v := tree.Get(k) 172 ctx.lastPosition = tree.GetPosition(k) 173 f.next.call(v, ctx) 174 switch node := v.(type) { 175 case *toml.Tree: 176 visit(node) 177 case []*toml.Tree: 178 for _, subtree := range node { 179 visit(subtree) 180 } 181 } 182 } 183 } 184 ctx.lastPosition = originalPosition 185 f.next.call(tree, ctx) 186 visit(tree) 187 } 188} 189 190// match based on an externally provided functional filter 191type matchFilterFn struct { 192 matchBase 193 Pos toml.Position 194 Name string 195} 196 197func newMatchFilterFn(name string, pos toml.Position) *matchFilterFn { 198 return &matchFilterFn{Name: name, Pos: pos} 199} 200 201func (f *matchFilterFn) call(node interface{}, ctx *queryContext) { 202 fn, ok := (*ctx.filters)[f.Name] 203 if !ok { 204 panic(fmt.Sprintf("%s: query context does not have filter '%s'", 205 f.Pos.String(), f.Name)) 206 } 207 switch castNode := node.(type) { 208 case *toml.Tree: 209 for _, k := range castNode.Keys() { 210 v := castNode.Get(k) 211 if fn(v) { 212 ctx.lastPosition = castNode.GetPosition(k) 213 f.next.call(v, ctx) 214 } 215 } 216 case []*toml.Tree: 217 for _, v := range castNode { 218 if fn(v) { 219 if len(castNode) > 0 { 220 ctx.lastPosition = castNode[0].Position() 221 } 222 f.next.call(v, ctx) 223 } 224 } 225 case []interface{}: 226 for _, v := range castNode { 227 if fn(v) { 228 f.next.call(v, ctx) 229 } 230 } 231 } 232} 233