summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/pelletier/go-toml/query/query.go
blob: 1c6cd801434e26ff7b26d8b203b4b85183d6ce31 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package query

import (
	"time"

	"github.com/pelletier/go-toml"
)

// NodeFilterFn represents a user-defined filter function, for use with
// Query.SetFilter().
//
// The return value of the function must indicate if 'node' is to be included
// at this stage of the TOML path.  Returning true will include the node, and
// returning false will exclude it.
//
// NOTE: Care should be taken to write script callbacks such that they are safe
// to use from multiple goroutines.
type NodeFilterFn func(node interface{}) bool

// Result is the result of Executing a Query.
type Result struct {
	items     []interface{}
	positions []toml.Position
}

// appends a value/position pair to the result set.
func (r *Result) appendResult(node interface{}, pos toml.Position) {
	r.items = append(r.items, node)
	r.positions = append(r.positions, pos)
}

// Values is a set of values within a Result.  The order of values is not
// guaranteed to be in document order, and may be different each time a query is
// executed.
func (r Result) Values() []interface{} {
	return r.items
}

// Positions is a set of positions for values within a Result.  Each index
// in Positions() corresponds to the entry in Value() of the same index.
func (r Result) Positions() []toml.Position {
	return r.positions
}

// runtime context for executing query paths
type queryContext struct {
	result       *Result
	filters      *map[string]NodeFilterFn
	lastPosition toml.Position
}

// generic path functor interface
type pathFn interface {
	setNext(next pathFn)
	// it is the caller's responsibility to set the ctx.lastPosition before invoking call()
	// node can be one of: *toml.Tree, []*toml.Tree, or a scalar
	call(node interface{}, ctx *queryContext)
}

// A Query is the representation of a compiled TOML path.  A Query is safe
// for concurrent use by multiple goroutines.
type Query struct {
	root    pathFn
	tail    pathFn
	filters *map[string]NodeFilterFn
}

func newQuery() *Query {
	return &Query{
		root:    nil,
		tail:    nil,
		filters: &defaultFilterFunctions,
	}
}

func (q *Query) appendPath(next pathFn) {
	if q.root == nil {
		q.root = next
	} else {
		q.tail.setNext(next)
	}
	q.tail = next
	next.setNext(newTerminatingFn()) // init the next functor
}

// Compile compiles a TOML path expression. The returned Query can be used
// to match elements within a Tree and its descendants. See Execute.
func Compile(path string) (*Query, error) {
	return parseQuery(lexQuery(path))
}

// Execute executes a query against a Tree, and returns the result of the query.
func (q *Query) Execute(tree *toml.Tree) *Result {
	result := &Result{
		items:     []interface{}{},
		positions: []toml.Position{},
	}
	if q.root == nil {
		result.appendResult(tree, tree.GetPosition(""))
	} else {
		ctx := &queryContext{
			result:  result,
			filters: q.filters,
		}
		ctx.lastPosition = tree.Position()
		q.root.call(tree, ctx)
	}
	return result
}

// CompileAndExecute is a shorthand for Compile(path) followed by Execute(tree).
func CompileAndExecute(path string, tree *toml.Tree) (*Result, error) {
	query, err := Compile(path)
	if err != nil {
		return nil, err
	}
	return query.Execute(tree), nil
}

// SetFilter sets a user-defined filter function.  These may be used inside
// "?(..)" query expressions to filter TOML document elements within a query.
func (q *Query) SetFilter(name string, fn NodeFilterFn) {
	if q.filters == &defaultFilterFunctions {
		// clone the static table
		q.filters = &map[string]NodeFilterFn{}
		for k, v := range defaultFilterFunctions {
			(*q.filters)[k] = v
		}
	}
	(*q.filters)[name] = fn
}

var defaultFilterFunctions = map[string]NodeFilterFn{
	"tree": func(node interface{}) bool {
		_, ok := node.(*toml.Tree)
		return ok
	},
	"int": func(node interface{}) bool {
		_, ok := node.(int64)
		return ok
	},
	"float": func(node interface{}) bool {
		_, ok := node.(float64)
		return ok
	},
	"string": func(node interface{}) bool {
		_, ok := node.(string)
		return ok
	},
	"time": func(node interface{}) bool {
		_, ok := node.(time.Time)
		return ok
	},
	"bool": func(node interface{}) bool {
		_, ok := node.(bool)
		return ok
	},
}