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