summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/pelletier/go-toml/query
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/pelletier/go-toml/query')
-rw-r--r--vendor/github.com/pelletier/go-toml/query/doc.go175
-rw-r--r--vendor/github.com/pelletier/go-toml/query/lexer.go357
-rw-r--r--vendor/github.com/pelletier/go-toml/query/lexer_test.go179
-rw-r--r--vendor/github.com/pelletier/go-toml/query/match.go232
-rw-r--r--vendor/github.com/pelletier/go-toml/query/match_test.go202
-rw-r--r--vendor/github.com/pelletier/go-toml/query/parser.go275
-rw-r--r--vendor/github.com/pelletier/go-toml/query/parser_test.go482
-rw-r--r--vendor/github.com/pelletier/go-toml/query/query.go158
-rw-r--r--vendor/github.com/pelletier/go-toml/query/query_test.go157
-rw-r--r--vendor/github.com/pelletier/go-toml/query/tokens.go107
10 files changed, 2324 insertions, 0 deletions
diff --git a/vendor/github.com/pelletier/go-toml/query/doc.go b/vendor/github.com/pelletier/go-toml/query/doc.go
new file mode 100644
index 000000000..f999fc965
--- /dev/null
+++ b/vendor/github.com/pelletier/go-toml/query/doc.go
@@ -0,0 +1,175 @@
+// Package query performs JSONPath-like queries on a TOML document.
+//
+// The query path implementation is based loosely on the JSONPath specification:
+// http://goessner.net/articles/JsonPath/.
+//
+// The idea behind a query path is to allow quick access to any element, or set
+// of elements within TOML document, with a single expression.
+//
+// result, err := query.CompileAndExecute("$.foo.bar.baz", tree)
+//
+// This is roughly equivalent to:
+//
+// next := tree.Get("foo")
+// if next != nil {
+// next = next.Get("bar")
+// if next != nil {
+// next = next.Get("baz")
+// }
+// }
+// result := next
+//
+// err is nil if any parsing exception occurs.
+//
+// If no node in the tree matches the query, result will simply contain an empty list of
+// items.
+//
+// As illustrated above, the query path is much more efficient, especially since
+// the structure of the TOML file can vary. Rather than making assumptions about
+// a document's structure, a query allows the programmer to make structured
+// requests into the document, and get zero or more values as a result.
+//
+// Query syntax
+//
+// The syntax of a query begins with a root token, followed by any number
+// sub-expressions:
+//
+// $
+// Root of the TOML tree. This must always come first.
+// .name
+// Selects child of this node, where 'name' is a TOML key
+// name.
+// ['name']
+// Selects child of this node, where 'name' is a string
+// containing a TOML key name.
+// [index]
+// Selcts child array element at 'index'.
+// ..expr
+// Recursively selects all children, filtered by an a union,
+// index, or slice expression.
+// ..*
+// Recursive selection of all nodes at this point in the
+// tree.
+// .*
+// Selects all children of the current node.
+// [expr,expr]
+// Union operator - a logical 'or' grouping of two or more
+// sub-expressions: index, key name, or filter.
+// [start:end:step]
+// Slice operator - selects array elements from start to
+// end-1, at the given step. All three arguments are
+// optional.
+// [?(filter)]
+// Named filter expression - the function 'filter' is
+// used to filter children at this node.
+//
+// Query Indexes And Slices
+//
+// Index expressions perform no bounds checking, and will contribute no
+// values to the result set if the provided index or index range is invalid.
+// Negative indexes represent values from the end of the array, counting backwards.
+//
+// // select the last index of the array named 'foo'
+// query.CompileAndExecute("$.foo[-1]", tree)
+//
+// Slice expressions are supported, by using ':' to separate a start/end index pair.
+//
+// // select up to the first five elements in the array
+// query.CompileAndExecute("$.foo[0:5]", tree)
+//
+// Slice expressions also allow negative indexes for the start and stop
+// arguments.
+//
+// // select all array elements.
+// query.CompileAndExecute("$.foo[0:-1]", tree)
+//
+// Slice expressions may have an optional stride/step parameter:
+//
+// // select every other element
+// query.CompileAndExecute("$.foo[0:-1:2]", tree)
+//
+// Slice start and end parameters are also optional:
+//
+// // these are all equivalent and select all the values in the array
+// query.CompileAndExecute("$.foo[:]", tree)
+// query.CompileAndExecute("$.foo[0:]", tree)
+// query.CompileAndExecute("$.foo[:-1]", tree)
+// query.CompileAndExecute("$.foo[0:-1:]", tree)
+// query.CompileAndExecute("$.foo[::1]", tree)
+// query.CompileAndExecute("$.foo[0::1]", tree)
+// query.CompileAndExecute("$.foo[:-1:1]", tree)
+// query.CompileAndExecute("$.foo[0:-1:1]", tree)
+//
+// Query Filters
+//
+// Query filters are used within a Union [,] or single Filter [] expression.
+// A filter only allows nodes that qualify through to the next expression,
+// and/or into the result set.
+//
+// // returns children of foo that are permitted by the 'bar' filter.
+// query.CompileAndExecute("$.foo[?(bar)]", tree)
+//
+// There are several filters provided with the library:
+//
+// tree
+// Allows nodes of type Tree.
+// int
+// Allows nodes of type int64.
+// float
+// Allows nodes of type float64.
+// string
+// Allows nodes of type string.
+// time
+// Allows nodes of type time.Time.
+// bool
+// Allows nodes of type bool.
+//
+// Query Results
+//
+// An executed query returns a Result object. This contains the nodes
+// in the TOML tree that qualify the query expression. Position information
+// is also available for each value in the set.
+//
+// // display the results of a query
+// results := query.CompileAndExecute("$.foo.bar.baz", tree)
+// for idx, value := results.Values() {
+// fmt.Println("%v: %v", results.Positions()[idx], value)
+// }
+//
+// Compiled Queries
+//
+// Queries may be executed directly on a Tree object, or compiled ahead
+// of time and executed discretely. The former is more convienent, but has the
+// penalty of having to recompile the query expression each time.
+//
+// // basic query
+// results := query.CompileAndExecute("$.foo.bar.baz", tree)
+//
+// // compiled query
+// query, err := toml.Compile("$.foo.bar.baz")
+// results := query.Execute(tree)
+//
+// // run the compiled query again on a different tree
+// moreResults := query.Execute(anotherTree)
+//
+// User Defined Query Filters
+//
+// Filter expressions may also be user defined by using the SetFilter()
+// function on the Query object. The function must return true/false, which
+// signifies if the passed node is kept or discarded, respectively.
+//
+// // create a query that references a user-defined filter
+// query, _ := query.Compile("$[?(bazOnly)]")
+//
+// // define the filter, and assign it to the query
+// query.SetFilter("bazOnly", func(node interface{}) bool{
+// if tree, ok := node.(*Tree); ok {
+// return tree.Has("baz")
+// }
+// return false // reject all other node types
+// })
+//
+// // run the query
+// query.Execute(tree)
+//
+package query
diff --git a/vendor/github.com/pelletier/go-toml/query/lexer.go b/vendor/github.com/pelletier/go-toml/query/lexer.go
new file mode 100644
index 000000000..6336d52cd
--- /dev/null
+++ b/vendor/github.com/pelletier/go-toml/query/lexer.go
@@ -0,0 +1,357 @@
+// TOML JSONPath lexer.
+//
+// Written using the principles developed by Rob Pike in
+// http://www.youtube.com/watch?v=HxaD_trXwRE
+
+package query
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "unicode/utf8"
+ "github.com/pelletier/go-toml"
+)
+
+// Lexer state function
+type queryLexStateFn func() queryLexStateFn
+
+// Lexer definition
+type queryLexer struct {
+ input string
+ start int
+ pos int
+ width int
+ tokens chan token
+ depth int
+ line int
+ col int
+ stringTerm string
+}
+
+func (l *queryLexer) run() {
+ for state := l.lexVoid; state != nil; {
+ state = state()
+ }
+ close(l.tokens)
+}
+
+func (l *queryLexer) nextStart() {
+ // iterate by runes (utf8 characters)
+ // search for newlines and advance line/col counts
+ for i := l.start; i < l.pos; {
+ r, width := utf8.DecodeRuneInString(l.input[i:])
+ if r == '\n' {
+ l.line++
+ l.col = 1
+ } else {
+ l.col++
+ }
+ i += width
+ }
+ // advance start position to next token
+ l.start = l.pos
+}
+
+func (l *queryLexer) emit(t tokenType) {
+ l.tokens <- token{
+ Position: toml.Position{Line:l.line, Col:l.col},
+ typ: t,
+ val: l.input[l.start:l.pos],
+ }
+ l.nextStart()
+}
+
+func (l *queryLexer) emitWithValue(t tokenType, value string) {
+ l.tokens <- token{
+ Position: toml.Position{Line:l.line, Col:l.col},
+ typ: t,
+ val: value,
+ }
+ l.nextStart()
+}
+
+func (l *queryLexer) next() rune {
+ if l.pos >= len(l.input) {
+ l.width = 0
+ return eof
+ }
+ var r rune
+ r, l.width = utf8.DecodeRuneInString(l.input[l.pos:])
+ l.pos += l.width
+ return r
+}
+
+func (l *queryLexer) ignore() {
+ l.nextStart()
+}
+
+func (l *queryLexer) backup() {
+ l.pos -= l.width
+}
+
+func (l *queryLexer) errorf(format string, args ...interface{}) queryLexStateFn {
+ l.tokens <- token{
+ Position: toml.Position{Line:l.line, Col:l.col},
+ typ: tokenError,
+ val: fmt.Sprintf(format, args...),
+ }
+ return nil
+}
+
+func (l *queryLexer) peek() rune {
+ r := l.next()
+ l.backup()
+ return r
+}
+
+func (l *queryLexer) accept(valid string) bool {
+ if strings.ContainsRune(valid, l.next()) {
+ return true
+ }
+ l.backup()
+ return false
+}
+
+func (l *queryLexer) follow(next string) bool {
+ return strings.HasPrefix(l.input[l.pos:], next)
+}
+
+func (l *queryLexer) lexVoid() queryLexStateFn {
+ for {
+ next := l.peek()
+ switch next {
+ case '$':
+ l.pos++
+ l.emit(tokenDollar)
+ continue
+ case '.':
+ if l.follow("..") {
+ l.pos += 2
+ l.emit(tokenDotDot)
+ } else {
+ l.pos++
+ l.emit(tokenDot)
+ }
+ continue
+ case '[':
+ l.pos++
+ l.emit(tokenLeftBracket)
+ continue
+ case ']':
+ l.pos++
+ l.emit(tokenRightBracket)
+ continue
+ case ',':
+ l.pos++
+ l.emit(tokenComma)
+ continue
+ case '*':
+ l.pos++
+ l.emit(tokenStar)
+ continue
+ case '(':
+ l.pos++
+ l.emit(tokenLeftParen)
+ continue
+ case ')':
+ l.pos++
+ l.emit(tokenRightParen)
+ continue
+ case '?':
+ l.pos++
+ l.emit(tokenQuestion)
+ continue
+ case ':':
+ l.pos++
+ l.emit(tokenColon)
+ continue
+ case '\'':
+ l.ignore()
+ l.stringTerm = string(next)
+ return l.lexString
+ case '"':
+ l.ignore()
+ l.stringTerm = string(next)
+ return l.lexString
+ }
+
+ if isSpace(next) {
+ l.next()
+ l.ignore()
+ continue
+ }
+
+ if isAlphanumeric(next) {
+ return l.lexKey
+ }
+
+ if next == '+' || next == '-' || isDigit(next) {
+ return l.lexNumber
+ }
+
+ if l.next() == eof {
+ break
+ }
+
+ return l.errorf("unexpected char: '%v'", next)
+ }
+ l.emit(tokenEOF)
+ return nil
+}
+
+func (l *queryLexer) lexKey() queryLexStateFn {
+ for {
+ next := l.peek()
+ if !isAlphanumeric(next) {
+ l.emit(tokenKey)
+ return l.lexVoid
+ }
+
+ if l.next() == eof {
+ break
+ }
+ }
+ l.emit(tokenEOF)
+ return nil
+}
+
+func (l *queryLexer) lexString() queryLexStateFn {
+ l.pos++
+ l.ignore()
+ growingString := ""
+
+ for {
+ if l.follow(l.stringTerm) {
+ l.emitWithValue(tokenString, growingString)
+ l.pos++
+ l.ignore()
+ return l.lexVoid
+ }
+
+ if l.follow("\\\"") {
+ l.pos++
+ growingString += "\""
+ } else if l.follow("\\'") {
+ l.pos++
+ growingString += "'"
+ } else if l.follow("\\n") {
+ l.pos++
+ growingString += "\n"
+ } else if l.follow("\\b") {
+ l.pos++
+ growingString += "\b"
+ } else if l.follow("\\f") {
+ l.pos++
+ growingString += "\f"
+ } else if l.follow("\\/") {
+ l.pos++
+ growingString += "/"
+ } else if l.follow("\\t") {
+ l.pos++
+ growingString += "\t"
+ } else if l.follow("\\r") {
+ l.pos++
+ growingString += "\r"
+ } else if l.follow("\\\\") {
+ l.pos++
+ growingString += "\\"
+ } else if l.follow("\\u") {
+ l.pos += 2
+ code := ""
+ for i := 0; i < 4; i++ {
+ c := l.peek()
+ l.pos++
+ if !isHexDigit(c) {
+ return l.errorf("unfinished unicode escape")
+ }
+ code = code + string(c)
+ }
+ l.pos--
+ intcode, err := strconv.ParseInt(code, 16, 32)
+ if err != nil {
+ return l.errorf("invalid unicode escape: \\u" + code)
+ }
+ growingString += string(rune(intcode))
+ } else if l.follow("\\U") {
+ l.pos += 2
+ code := ""
+ for i := 0; i < 8; i++ {
+ c := l.peek()
+ l.pos++
+ if !isHexDigit(c) {
+ return l.errorf("unfinished unicode escape")
+ }
+ code = code + string(c)
+ }
+ l.pos--
+ intcode, err := strconv.ParseInt(code, 16, 32)
+ if err != nil {
+ return l.errorf("invalid unicode escape: \\u" + code)
+ }
+ growingString += string(rune(intcode))
+ } else if l.follow("\\") {
+ l.pos++
+ return l.errorf("invalid escape sequence: \\" + string(l.peek()))
+ } else {
+ growingString += string(l.peek())
+ }
+
+ if l.next() == eof {
+ break
+ }
+ }
+
+ return l.errorf("unclosed string")
+}
+
+func (l *queryLexer) lexNumber() queryLexStateFn {
+ l.ignore()
+ if !l.accept("+") {
+ l.accept("-")
+ }
+ pointSeen := false
+ digitSeen := false
+ for {
+ next := l.next()
+ if next == '.' {
+ if pointSeen {
+ return l.errorf("cannot have two dots in one float")
+ }
+ if !isDigit(l.peek()) {
+ return l.errorf("float cannot end with a dot")
+ }
+ pointSeen = true
+ } else if isDigit(next) {
+ digitSeen = true
+ } else {
+ l.backup()
+ break
+ }
+ if pointSeen && !digitSeen {
+ return l.errorf("cannot start float with a dot")
+ }
+ }
+
+ if !digitSeen {
+ return l.errorf("no digit in that number")
+ }
+ if pointSeen {
+ l.emit(tokenFloat)
+ } else {
+ l.emit(tokenInteger)
+ }
+ return l.lexVoid
+}
+
+// Entry point
+func lexQuery(input string) chan token {
+ l := &queryLexer{
+ input: input,
+ tokens: make(chan token),
+ line: 1,
+ col: 1,
+ }
+ go l.run()
+ return l.tokens
+}
diff --git a/vendor/github.com/pelletier/go-toml/query/lexer_test.go b/vendor/github.com/pelletier/go-toml/query/lexer_test.go
new file mode 100644
index 000000000..e2b733a34
--- /dev/null
+++ b/vendor/github.com/pelletier/go-toml/query/lexer_test.go
@@ -0,0 +1,179 @@
+package query
+
+import (
+ "testing"
+ "github.com/pelletier/go-toml"
+)
+
+func testQLFlow(t *testing.T, input string, expectedFlow []token) {
+ ch := lexQuery(input)
+ for idx, expected := range expectedFlow {
+ token := <-ch
+ if token != expected {
+ t.Log("While testing #", idx, ":", input)
+ t.Log("compared (got)", token, "to (expected)", expected)
+ t.Log("\tvalue:", token.val, "<->", expected.val)
+ t.Log("\tvalue as bytes:", []byte(token.val), "<->", []byte(expected.val))
+ t.Log("\ttype:", token.typ.String(), "<->", expected.typ.String())
+ t.Log("\tline:", token.Line, "<->", expected.Line)
+ t.Log("\tcolumn:", token.Col, "<->", expected.Col)
+ t.Log("compared", token, "to", expected)
+ t.FailNow()
+ }
+ }
+
+ tok, ok := <-ch
+ if ok {
+ t.Log("channel is not closed!")
+ t.Log(len(ch)+1, "tokens remaining:")
+
+ t.Log("token ->", tok)
+ for token := range ch {
+ t.Log("token ->", token)
+ }
+ t.FailNow()
+ }
+}
+
+func TestLexSpecialChars(t *testing.T) {
+ testQLFlow(t, " .$[]..()?*", []token{
+ {toml.Position{1, 2}, tokenDot, "."},
+ {toml.Position{1, 3}, tokenDollar, "$"},
+ {toml.Position{1, 4}, tokenLeftBracket, "["},
+ {toml.Position{1, 5}, tokenRightBracket, "]"},
+ {toml.Position{1, 6}, tokenDotDot, ".."},
+ {toml.Position{1, 8}, tokenLeftParen, "("},
+ {toml.Position{1, 9}, tokenRightParen, ")"},
+ {toml.Position{1, 10}, tokenQuestion, "?"},
+ {toml.Position{1, 11}, tokenStar, "*"},
+ {toml.Position{1, 12}, tokenEOF, ""},
+ })
+}
+
+func TestLexString(t *testing.T) {
+ testQLFlow(t, "'foo\n'", []token{
+ {toml.Position{1, 2}, tokenString, "foo\n"},
+ {toml.Position{2, 2}, tokenEOF, ""},
+ })
+}
+
+func TestLexDoubleString(t *testing.T) {
+ testQLFlow(t, `"bar"`, []token{
+ {toml.Position{1, 2}, tokenString, "bar"},
+ {toml.Position{1, 6}, tokenEOF, ""},
+ })
+}
+
+func TestLexStringEscapes(t *testing.T) {
+ testQLFlow(t, `"foo \" \' \b \f \/ \t \r \\ \u03A9 \U00012345 \n bar"`, []token{
+ {toml.Position{1, 2}, tokenString, "foo \" ' \b \f / \t \r \\ \u03A9 \U00012345 \n bar"},
+ {toml.Position{1, 55}, tokenEOF, ""},
+ })
+}
+
+func TestLexStringUnfinishedUnicode4(t *testing.T) {
+ testQLFlow(t, `"\u000"`, []token{
+ {toml.Position{1, 2}, tokenError, "unfinished unicode escape"},
+ })
+}
+
+func TestLexStringUnfinishedUnicode8(t *testing.T) {
+ testQLFlow(t, `"\U0000"`, []token{
+ {toml.Position{1, 2}, tokenError, "unfinished unicode escape"},
+ })
+}
+
+func TestLexStringInvalidEscape(t *testing.T) {
+ testQLFlow(t, `"\x"`, []token{
+ {toml.Position{1, 2}, tokenError, "invalid escape sequence: \\x"},
+ })
+}
+
+func TestLexStringUnfinished(t *testing.T) {
+ testQLFlow(t, `"bar`, []token{
+ {toml.Position{1, 2}, tokenError, "unclosed string"},
+ })
+}
+
+func TestLexKey(t *testing.T) {
+ testQLFlow(t, "foo", []token{
+ {toml.Position{1, 1}, tokenKey, "foo"},
+ {toml.Position{1, 4}, tokenEOF, ""},
+ })
+}
+
+func TestLexRecurse(t *testing.T) {
+ testQLFlow(t, "$..*", []token{
+ {toml.Position{1, 1}, tokenDollar, "$"},
+ {toml.Position{1, 2}, tokenDotDot, ".."},
+ {toml.Position{1, 4}, tokenStar, "*"},
+ {toml.Position{1, 5}, tokenEOF, ""},
+ })
+}
+
+func TestLexBracketKey(t *testing.T) {
+ testQLFlow(t, "$[foo]", []token{
+ {toml.Position{1, 1}, tokenDollar, "$"},
+ {toml.Position{1, 2}, tokenLeftBracket, "["},
+ {toml.Position{1, 3}, tokenKey, "foo"},
+ {toml.Position{1, 6}, tokenRightBracket, "]"},
+ {toml.Position{1, 7}, tokenEOF, ""},
+ })
+}
+
+func TestLexSpace(t *testing.T) {
+ testQLFlow(t, "foo bar baz", []token{
+ {toml.Position{1, 1}, tokenKey, "foo"},
+ {toml.Position{1, 5}, tokenKey, "bar"},
+ {toml.Position{1, 9}, tokenKey, "baz"},
+ {toml.Position{1, 12}, tokenEOF, ""},
+ })
+}
+
+func TestLexInteger(t *testing.T) {
+ testQLFlow(t, "100 +200 -300", []token{
+ {toml.Position{1, 1}, tokenInteger, "100"},
+ {toml.Position{1, 5}, tokenInteger, "+200"},
+ {toml.Position{1, 10}, tokenInteger, "-300"},
+ {toml.Position{1, 14}, tokenEOF, ""},
+ })
+}
+
+func TestLexFloat(t *testing.T) {
+ testQLFlow(t, "100.0 +200.0 -300.0", []token{
+ {toml.Position{1, 1}, tokenFloat, "100.0"},
+ {toml.Position{1, 7}, tokenFloat, "+200.0"},
+ {toml.Position{1, 14}, tokenFloat, "-300.0"},
+ {toml.Position{1, 20}, tokenEOF, ""},
+ })
+}
+
+func TestLexFloatWithMultipleDots(t *testing.T) {
+ testQLFlow(t, "4.2.", []token{
+ {toml.Position{1, 1}, tokenError, "cannot have two dots in one float"},
+ })
+}
+
+func TestLexFloatLeadingDot(t *testing.T) {
+ testQLFlow(t, "+.1", []token{
+ {toml.Position{1, 1}, tokenError, "cannot start float with a dot"},
+ })
+}
+
+func TestLexFloatWithTrailingDot(t *testing.T) {
+ testQLFlow(t, "42.", []token{
+ {toml.Position{1, 1}, tokenError, "float cannot end with a dot"},
+ })
+}
+
+func TestLexNumberWithoutDigit(t *testing.T) {
+ testQLFlow(t, "+", []token{
+ {toml.Position{1, 1}, tokenError, "no digit in that number"},
+ })
+}
+
+func TestLexUnknown(t *testing.T) {
+ testQLFlow(t, "^", []token{
+ {toml.Position{1, 1}, tokenError, "unexpected char: '94'"},
+ })
+}
diff --git a/vendor/github.com/pelletier/go-toml/query/match.go b/vendor/github.com/pelletier/go-toml/query/match.go
new file mode 100644
index 000000000..d7bb15a45
--- /dev/null
+++ b/vendor/github.com/pelletier/go-toml/query/match.go
@@ -0,0 +1,232 @@
+package query
+
+import (
+ "fmt"
+ "github.com/pelletier/go-toml"
+)
+
+// 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) {
+ 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.([]*toml.Tree); ok {
+ for _, tree := range array {
+ item := tree.Get(f.Name)
+ if item != nil {
+ ctx.lastPosition = tree.GetPosition(f.Name)
+ f.next.call(item, ctx)
+ }
+ }
+ } else if tree, ok := node.(*toml.Tree); ok {
+ item := tree.Get(f.Name)
+ if item != nil {
+ ctx.lastPosition = tree.GetPosition(f.Name)
+ 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 := node.([]interface{}); ok {
+ if f.Idx < len(arr) && f.Idx >= 0 {
+ if treesArray, ok := node.([]*toml.Tree); ok {
+ if len(treesArray) > 0 {
+ ctx.lastPosition = treesArray[0].Position()
+ }
+ }
+ 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 := node.([]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 {
+ if treesArray, ok := node.([]*toml.Tree); ok {
+ if len(treesArray) > 0 {
+ ctx.lastPosition = treesArray[0].Position()
+ }
+ }
+ 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.(*toml.Tree); ok {
+ for _, k := range tree.Keys() {
+ v := tree.Get(k)
+ ctx.lastPosition = tree.GetPosition(k)
+ 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) {
+ originalPosition := ctx.lastPosition
+ if tree, ok := node.(*toml.Tree); ok {
+ var visit func(tree *toml.Tree)
+ visit = func(tree *toml.Tree) {
+ for _, k := range tree.Keys() {
+ v := tree.Get(k)
+ ctx.lastPosition = tree.GetPosition(k)
+ f.next.call(v, ctx)
+ switch node := v.(type) {
+ case *toml.Tree:
+ visit(node)
+ case []*toml.Tree:
+ for _, subtree := range node {
+ visit(subtree)
+ }
+ }
+ }
+ }
+ ctx.lastPosition = originalPosition
+ f.next.call(tree, ctx)
+ visit(tree)
+ }
+}
+
+// match based on an externally provided functional filter
+type matchFilterFn struct {
+ matchBase
+ Pos toml.Position
+ Name string
+}
+
+func newMatchFilterFn(name string, pos toml.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 := node.(type) {
+ case *toml.Tree:
+ for _, k := range castNode.Keys() {
+ v := castNode.Get(k)
+ if fn(v) {
+ ctx.lastPosition = castNode.GetPosition(k)
+ f.next.call(v, ctx)
+ }
+ }
+ case []*toml.Tree:
+ for _, v := range castNode {
+ if fn(v) {
+ if len(castNode) > 0 {
+ ctx.lastPosition = castNode[0].Position()
+ }
+ f.next.call(v, ctx)
+ }
+ }
+ case []interface{}:
+ for _, v := range castNode {
+ if fn(v) {
+ f.next.call(v, ctx)
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/pelletier/go-toml/query/match_test.go b/vendor/github.com/pelletier/go-toml/query/match_test.go
new file mode 100644
index 000000000..567b11cd7
--- /dev/null
+++ b/vendor/github.com/pelletier/go-toml/query/match_test.go
@@ -0,0 +1,202 @@
+package query
+
+import (
+ "fmt"
+ "testing"
+ "github.com/pelletier/go-toml"
+)
+
+// dump path tree to a string
+func pathString(root pathFn) string {
+ result := fmt.Sprintf("%T:", root)
+ switch fn := root.(type) {
+ case *terminatingFn:
+ result += "{}"
+ case *matchKeyFn:
+ result += fmt.Sprintf("{%s}", fn.Name)
+ result += pathString(fn.next)
+ case *matchIndexFn:
+ result += fmt.Sprintf("{%d}", fn.Idx)
+ result += pathString(fn.next)
+ case *matchSliceFn:
+ result += fmt.Sprintf("{%d:%d:%d}",
+ fn.Start, fn.End, fn.Step)
+ result += pathString(fn.next)
+ case *matchAnyFn:
+ result += "{}"
+ result += pathString(fn.next)
+ case *matchUnionFn:
+ result += "{["
+ for _, v := range fn.Union {
+ result += pathString(v) + ", "
+ }
+ result += "]}"
+ case *matchRecursiveFn:
+ result += "{}"
+ result += pathString(fn.next)
+ case *matchFilterFn:
+ result += fmt.Sprintf("{%s}", fn.Name)
+ result += pathString(fn.next)
+ }
+ return result
+}
+
+func assertPathMatch(t *testing.T, path, ref *Query) bool {
+ pathStr := pathString(path.root)
+ refStr := pathString(ref.root)
+ if pathStr != refStr {
+ t.Errorf("paths do not match")
+ t.Log("test:", pathStr)
+ t.Log("ref: ", refStr)
+ return false
+ }
+ return true
+}
+
+func assertPath(t *testing.T, query string, ref *Query) {
+ path, _ := parseQuery(lexQuery(query))
+ assertPathMatch(t, path, ref)
+}
+
+func buildPath(parts ...pathFn) *Query {
+ query := newQuery()
+ for _, v := range parts {
+ query.appendPath(v)
+ }
+ return query
+}
+
+func TestPathRoot(t *testing.T) {
+ assertPath(t,
+ "$",
+ buildPath(
+ // empty
+ ))
+}
+
+func TestPathKey(t *testing.T) {
+ assertPath(t,
+ "$.foo",
+ buildPath(
+ newMatchKeyFn("foo"),
+ ))
+}
+
+func TestPathBracketKey(t *testing.T) {
+ assertPath(t,
+ "$[foo]",
+ buildPath(
+ newMatchKeyFn("foo"),
+ ))
+}
+
+func TestPathBracketStringKey(t *testing.T) {
+ assertPath(t,
+ "$['foo']",
+ buildPath(
+ newMatchKeyFn("foo"),
+ ))
+}
+
+func TestPathIndex(t *testing.T) {
+ assertPath(t,
+ "$[123]",
+ buildPath(
+ newMatchIndexFn(123),
+ ))
+}
+
+func TestPathSliceStart(t *testing.T) {
+ assertPath(t,
+ "$[123:]",
+ buildPath(
+ newMatchSliceFn(123, maxInt, 1),
+ ))
+}
+
+func TestPathSliceStartEnd(t *testing.T) {
+ assertPath(t,
+ "$[123:456]",
+ buildPath(
+ newMatchSliceFn(123, 456, 1),
+ ))
+}
+
+func TestPathSliceStartEndColon(t *testing.T) {
+ assertPath(t,
+ "$[123:456:]",
+ buildPath(
+ newMatchSliceFn(123, 456, 1),
+ ))
+}
+
+func TestPathSliceStartStep(t *testing.T) {
+ assertPath(t,
+ "$[123::7]",
+ buildPath(
+ newMatchSliceFn(123, maxInt, 7),
+ ))
+}
+
+func TestPathSliceEndStep(t *testing.T) {
+ assertPath(t,
+ "$[:456:7]",
+ buildPath(
+ newMatchSliceFn(0, 456, 7),
+ ))
+}
+
+func TestPathSliceStep(t *testing.T) {
+ assertPath(t,
+ "$[::7]",
+ buildPath(
+ newMatchSliceFn(0, maxInt, 7),
+ ))
+}
+
+func TestPathSliceAll(t *testing.T) {
+ assertPath(t,
+ "$[123:456:7]",
+ buildPath(
+ newMatchSliceFn(123, 456, 7),
+ ))
+}
+
+func TestPathAny(t *testing.T) {
+ assertPath(t,
+ "$.*",
+ buildPath(
+ newMatchAnyFn(),
+ ))
+}
+
+func TestPathUnion(t *testing.T) {
+ assertPath(t,
+ "$[foo, bar, baz]",
+ buildPath(
+ &matchUnionFn{[]pathFn{
+ newMatchKeyFn("foo"),
+ newMatchKeyFn("bar"),
+ newMatchKeyFn("baz"),
+ }},
+ ))
+}
+
+func TestPathRecurse(t *testing.T) {
+ assertPath(t,
+ "$..*",
+ buildPath(
+ newMatchRecursiveFn(),
+ ))
+}
+
+func TestPathFilterExpr(t *testing.T) {
+ assertPath(t,
+ "$[?('foo'),?(bar)]",
+ buildPath(
+ &matchUnionFn{[]pathFn{
+ newMatchFilterFn("foo", toml.Position{}),
+ newMatchFilterFn("bar", toml.Position{}),
+ }},
+ ))
+}
diff --git a/vendor/github.com/pelletier/go-toml/query/parser.go b/vendor/github.com/pelletier/go-toml/query/parser.go
new file mode 100644
index 000000000..e4f91b97e
--- /dev/null
+++ b/vendor/github.com/pelletier/go-toml/query/parser.go
@@ -0,0 +1,275 @@
+/*
+ Based on the "jsonpath" spec/concept.
+
+ http://goessner.net/articles/JsonPath/
+ https://code.google.com/p/json-path/
+*/
+
+package query
+
+import (
+ "fmt"
+)
+
+const maxInt = int(^uint(0) >> 1)
+
+type queryParser struct {
+ flow chan token
+ tokensBuffer []token
+ query *Query
+ union []pathFn
+ err error
+}
+
+type queryParserStateFn func() queryParserStateFn
+
+// Formats and panics an error message based on a token
+func (p *queryParser) parseError(tok *token, msg string, args ...interface{}) queryParserStateFn {
+ p.err = fmt.Errorf(tok.Position.String()+": "+msg, args...)
+ return nil // trigger parse to end
+}
+
+func (p *queryParser) run() {
+ for state := p.parseStart; state != nil; {
+ state = state()
+ }
+}
+
+func (p *queryParser) backup(tok *token) {
+ p.tokensBuffer = append(p.tokensBuffer, *tok)
+}
+
+func (p *queryParser) peek() *token {
+ if len(p.tokensBuffer) != 0 {
+ return &(p.tokensBuffer[0])
+ }
+
+ tok, ok := <-p.flow
+ if !ok {
+ return nil
+ }
+ p.backup(&tok)
+ return &tok
+}
+
+func (p *queryParser) lookahead(types ...tokenType) bool {
+ result := true
+ buffer := []token{}
+
+ for _, typ := range types {
+ tok := p.getToken()
+ if tok == nil {
+ result = false
+ break
+ }
+ buffer = append(buffer, *tok)
+ if tok.typ != typ {
+ result = false
+ break
+ }
+ }
+ // add the tokens back to the buffer, and return
+ p.tokensBuffer = append(p.tokensBuffer, buffer...)
+ return result
+}
+
+func (p *queryParser) getToken() *token {
+ if len(p.tokensBuffer) != 0 {
+ tok := p.tokensBuffer[0]
+ p.tokensBuffer = p.tokensBuffer[1:]
+ return &tok
+ }
+ tok, ok := <-p.flow
+ if !ok {
+ return nil
+ }
+ return &tok
+}
+
+func (p *queryParser) parseStart() queryParserStateFn {
+ tok := p.getToken()
+
+ if tok == nil || tok.typ == tokenEOF {
+ return nil
+ }
+
+ if tok.typ != tokenDollar {
+ return p.parseError(tok, "Expected '$' at start of expression")
+ }
+
+ return p.parseMatchExpr
+}
+
+// handle '.' prefix, '[]', and '..'
+func (p *queryParser) parseMatchExpr() queryParserStateFn {
+ tok := p.getToken()
+ switch tok.typ {
+ case tokenDotDot:
+ p.query.appendPath(&matchRecursiveFn{})
+ // nested parse for '..'
+ tok := p.getToken()
+ switch tok.typ {
+ case tokenKey:
+ p.query.appendPath(newMatchKeyFn(tok.val))
+ return p.parseMatchExpr
+ case tokenLeftBracket:
+ return p.parseBracketExpr
+ case tokenStar:
+ // do nothing - the recursive predicate is enough
+ return p.parseMatchExpr
+ }
+
+ case tokenDot:
+ // nested parse for '.'
+ tok := p.getToken()
+ switch tok.typ {
+ case tokenKey:
+ p.query.appendPath(newMatchKeyFn(tok.val))
+ return p.parseMatchExpr
+ case tokenStar:
+ p.query.appendPath(&matchAnyFn{})
+ return p.parseMatchExpr
+ }
+
+ case tokenLeftBracket:
+ return p.parseBracketExpr
+
+ case tokenEOF:
+ return nil // allow EOF at this stage
+ }
+ return p.parseError(tok, "expected match expression")
+}
+
+func (p *queryParser) parseBracketExpr() queryParserStateFn {
+ if p.lookahead(tokenInteger, tokenColon) {
+ return p.parseSliceExpr
+ }
+ if p.peek().typ == tokenColon {
+ return p.parseSliceExpr
+ }
+ return p.parseUnionExpr
+}
+
+func (p *queryParser) parseUnionExpr() queryParserStateFn {
+ var tok *token
+
+ // this state can be traversed after some sub-expressions
+ // so be careful when setting up state in the parser
+ if p.union == nil {
+ p.union = []pathFn{}
+ }
+
+loop: // labeled loop for easy breaking
+ for {
+ if len(p.union) > 0 {
+ // parse delimiter or terminator
+ tok = p.getToken()
+ switch tok.typ {
+ case tokenComma:
+ // do nothing
+ case tokenRightBracket:
+ break loop
+ default:
+ return p.parseError(tok, "expected ',' or ']', not '%s'", tok.val)
+ }
+ }
+
+ // parse sub expression
+ tok = p.getToken()
+ switch tok.typ {
+ case tokenInteger:
+ p.union = append(p.union, newMatchIndexFn(tok.Int()))
+ case tokenKey:
+ p.union = append(p.union, newMatchKeyFn(tok.val))
+ case tokenString:
+ p.union = append(p.union, newMatchKeyFn(tok.val))
+ case tokenQuestion:
+ return p.parseFilterExpr
+ default:
+ return p.parseError(tok, "expected union sub expression, not '%s', %d", tok.val, len(p.union))
+ }
+ }
+
+ // if there is only one sub-expression, use that instead
+ if len(p.union) == 1 {
+ p.query.appendPath(p.union[0])
+ } else {
+ p.query.appendPath(&matchUnionFn{p.union})
+ }
+
+ p.union = nil // clear out state
+ return p.parseMatchExpr
+}
+
+func (p *queryParser) parseSliceExpr() queryParserStateFn {
+ // init slice to grab all elements
+ start, end, step := 0, maxInt, 1
+
+ // parse optional start
+ tok := p.getToken()
+ if tok.typ == tokenInteger {
+ start = tok.Int()
+ tok = p.getToken()
+ }
+ if tok.typ != tokenColon {
+ return p.parseError(tok, "expected ':'")
+ }
+
+ // parse optional end
+ tok = p.getToken()
+ if tok.typ == tokenInteger {
+ end = tok.Int()
+ tok = p.getToken()
+ }
+ if tok.typ == tokenRightBracket {
+ p.query.appendPath(newMatchSliceFn(start, end, step))
+ return p.parseMatchExpr
+ }
+ if tok.typ != tokenColon {
+ return p.parseError(tok, "expected ']' or ':'")
+ }
+
+ // parse optional step
+ tok = p.getToken()
+ if tok.typ == tokenInteger {
+ step = tok.Int()
+ if step < 0 {
+ return p.parseError(tok, "step must be a positive value")
+ }
+ tok = p.getToken()
+ }
+ if tok.typ != tokenRightBracket {
+ return p.parseError(tok, "expected ']'")
+ }
+
+ p.query.appendPath(newMatchSliceFn(start, end, step))
+ return p.parseMatchExpr
+}
+
+func (p *queryParser) parseFilterExpr() queryParserStateFn {
+ tok := p.getToken()
+ if tok.typ != tokenLeftParen {
+ return p.parseError(tok, "expected left-parenthesis for filter expression")
+ }
+ tok = p.getToken()
+ if tok.typ != tokenKey && tok.typ != tokenString {
+ return p.parseError(tok, "expected key or string for filter funciton name")
+ }
+ name := tok.val
+ tok = p.getToken()
+ if tok.typ != tokenRightParen {
+ return p.parseError(tok, "expected right-parenthesis for filter expression")
+ }
+ p.union = append(p.union, newMatchFilterFn(name, tok.Position))
+ return p.parseUnionExpr
+}
+
+func parseQuery(flow chan token) (*Query, error) {
+ parser := &queryParser{
+ flow: flow,
+ tokensBuffer: []token{},
+ query: newQuery(),
+ }
+ parser.run()
+ return parser.query, parser.err
+}
diff --git a/vendor/github.com/pelletier/go-toml/query/parser_test.go b/vendor/github.com/pelletier/go-toml/query/parser_test.go
new file mode 100644
index 000000000..b1d0a3ece
--- /dev/null
+++ b/vendor/github.com/pelletier/go-toml/query/parser_test.go
@@ -0,0 +1,482 @@
+package query
+
+import (
+ "fmt"
+ "io/ioutil"
+ "sort"
+ "strings"
+ "testing"
+ "time"
+ "github.com/pelletier/go-toml"
+)
+
+type queryTestNode struct {
+ value interface{}
+ position toml.Position
+}
+
+func valueString(root interface{}) string {
+ result := "" //fmt.Sprintf("%T:", root)
+ switch node := root.(type) {
+ case *Result:
+ items := []string{}
+ for i, v := range node.Values() {
+ items = append(items, fmt.Sprintf("%s:%s",
+ node.Positions()[i].String(), valueString(v)))
+ }
+ sort.Strings(items)
+ result = "[" + strings.Join(items, ", ") + "]"
+ case queryTestNode:
+ result = fmt.Sprintf("%s:%s",
+ node.position.String(), valueString(node.value))
+ case []interface{}:
+ items := []string{}
+ for _, v := range node {
+ items = append(items, valueString(v))
+ }
+ sort.Strings(items)
+ result = "[" + strings.Join(items, ", ") + "]"
+ case *toml.Tree:
+ // workaround for unreliable map key ordering
+ items := []string{}
+ for _, k := range node.Keys() {
+ v := node.GetPath([]string{k})
+ items = append(items, k+":"+valueString(v))
+ }
+ sort.Strings(items)
+ result = "{" + strings.Join(items, ", ") + "}"
+ case map[string]interface{}:
+ // workaround for unreliable map key ordering
+ items := []string{}
+ for k, v := range node {
+ items = append(items, k+":"+valueString(v))
+ }
+ sort.Strings(items)
+ result = "{" + strings.Join(items, ", ") + "}"
+ case int64:
+ result += fmt.Sprintf("%d", node)
+ case string:
+ result += "'" + node + "'"
+ case float64:
+ result += fmt.Sprintf("%f", node)
+ case bool:
+ result += fmt.Sprintf("%t", node)
+ case time.Time:
+ result += fmt.Sprintf("'%v'", node)
+ }
+ return result
+}
+
+func assertValue(t *testing.T, result, ref interface{}) {
+ pathStr := valueString(result)
+ refStr := valueString(ref)
+ if pathStr != refStr {
+ t.Errorf("values do not match")
+ t.Log("test:", pathStr)
+ t.Log("ref: ", refStr)
+ }
+}
+
+func assertQueryPositions(t *testing.T, tomlDoc string, query string, ref []interface{}) {
+ tree, err := toml.Load(tomlDoc)
+ if err != nil {
+ t.Errorf("Non-nil toml parse error: %v", err)
+ return
+ }
+ q, err := Compile(query)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ results := q.Execute(tree)
+ assertValue(t, results, ref)
+}
+
+func TestQueryRoot(t *testing.T) {
+ assertQueryPositions(t,
+ "a = 42",
+ "$",
+ []interface{}{
+ queryTestNode{
+ map[string]interface{}{
+ "a": int64(42),
+ }, toml.Position{1, 1},
+ },
+ })
+}
+
+func TestQueryKey(t *testing.T) {
+ assertQueryPositions(t,
+ "[foo]\na = 42",
+ "$.foo.a",
+ []interface{}{
+ queryTestNode{
+ int64(42), toml.Position{2, 1},
+ },
+ })
+}
+
+func TestQueryKeyString(t *testing.T) {
+ assertQueryPositions(t,
+ "[foo]\na = 42",
+ "$.foo['a']",
+ []interface{}{
+ queryTestNode{
+ int64(42), toml.Position{2, 1},
+ },
+ })
+}
+
+func TestQueryIndex(t *testing.T) {
+ assertQueryPositions(t,
+ "[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
+ "$.foo.a[5]",
+ []interface{}{
+ queryTestNode{
+ int64(6), toml.Position{2, 1},
+ },
+ })
+}
+
+func TestQuerySliceRange(t *testing.T) {
+ assertQueryPositions(t,
+ "[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
+ "$.foo.a[0:5]",
+ []interface{}{
+ queryTestNode{
+ int64(1), toml.Position{2, 1},
+ },
+ queryTestNode{
+ int64(2), toml.Position{2, 1},
+ },
+ queryTestNode{
+ int64(3), toml.Position{2, 1},
+ },
+ queryTestNode{
+ int64(4), toml.Position{2, 1},
+ },
+ queryTestNode{
+ int64(5), toml.Position{2, 1},
+ },
+ })
+}
+
+func TestQuerySliceStep(t *testing.T) {
+ assertQueryPositions(t,
+ "[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
+ "$.foo.a[0:5:2]",
+ []interface{}{
+ queryTestNode{
+ int64(1), toml.Position{2, 1},
+ },
+ queryTestNode{
+ int64(3), toml.Position{2, 1},
+ },
+ queryTestNode{
+ int64(5), toml.Position{2, 1},
+ },
+ })
+}
+
+func TestQueryAny(t *testing.T) {
+ assertQueryPositions(t,
+ "[foo.bar]\na=1\nb=2\n[foo.baz]\na=3\nb=4",
+ "$.foo.*",
+ []interface{}{
+ queryTestNode{
+ map[string]interface{}{
+ "a": int64(1),
+ "b": int64(2),
+ }, toml.Position{1, 1},
+ },
+ queryTestNode{
+ map[string]interface{}{
+ "a": int64(3),
+ "b": int64(4),
+ }, toml.Position{4, 1},
+ },
+ })
+}
+func TestQueryUnionSimple(t *testing.T) {
+ assertQueryPositions(t,
+ "[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
+ "$.*[bar,foo]",
+ []interface{}{
+ queryTestNode{
+ map[string]interface{}{
+ "a": int64(1),
+ "b": int64(2),
+ }, toml.Position{1, 1},
+ },
+ queryTestNode{
+ map[string]interface{}{
+ "a": int64(3),
+ "b": int64(4),
+ }, toml.Position{4, 1},
+ },
+ queryTestNode{
+ map[string]interface{}{
+ "a": int64(5),
+ "b": int64(6),
+ }, toml.Position{7, 1},
+ },
+ })
+}
+
+func TestQueryRecursionAll(t *testing.T) {
+ assertQueryPositions(t,
+ "[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
+ "$..*",
+ []interface{}{
+ queryTestNode{
+ map[string]interface{}{
+ "foo": map[string]interface{}{
+ "bar": map[string]interface{}{
+ "a": int64(1),
+ "b": int64(2),
+ },
+ },
+ "baz": map[string]interface{}{
+ "foo": map[string]interface{}{
+ "a": int64(3),
+ "b": int64(4),
+ },
+ },
+ "gorf": map[string]interface{}{
+ "foo": map[string]interface{}{
+ "a": int64(5),
+ "b": int64(6),
+ },
+ },
+ }, toml.Position{1, 1},
+ },
+ queryTestNode{
+ map[string]interface{}{
+ "bar": map[string]interface{}{
+ "a": int64(1),
+ "b": int64(2),
+ },
+ }, toml.Position{1, 1},
+ },
+ queryTestNode{
+ map[string]interface{}{
+ "a": int64(1),
+ "b": int64(2),
+ }, toml.Position{1, 1},
+ },
+ queryTestNode{
+ int64(1), toml.Position{2, 1},
+ },
+ queryTestNode{
+ int64(2), toml.Position{3, 1},
+ },
+ queryTestNode{
+ map[string]interface{}{
+ "foo": map[string]interface{}{
+ "a": int64(3),
+ "b": int64(4),
+ },
+ }, toml.Position{4, 1},
+ },
+ queryTestNode{
+ map[string]interface{}{
+ "a": int64(3),
+ "b": int64(4),
+ }, toml.Position{4, 1},
+ },
+ queryTestNode{
+ int64(3), toml.Position{5, 1},
+ },
+ queryTestNode{
+ int64(4), toml.Position{6, 1},
+ },
+ queryTestNode{
+ map[string]interface{}{
+ "foo": map[string]interface{}{
+ "a": int64(5),
+ "b": int64(6),
+ },
+ }, toml.Position{7, 1},
+ },
+ queryTestNode{
+ map[string]interface{}{
+ "a": int64(5),
+ "b": int64(6),
+ }, toml.Position{7, 1},
+ },
+ queryTestNode{
+ int64(5), toml.Position{8, 1},
+ },
+ queryTestNode{
+ int64(6), toml.Position{9, 1},
+ },
+ })
+}
+
+func TestQueryRecursionUnionSimple(t *testing.T) {
+ assertQueryPositions(t,
+ "[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
+ "$..['foo','bar']",
+ []interface{}{
+ queryTestNode{
+ map[string]interface{}{
+ "bar": map[string]interface{}{
+ "a": int64(1),
+ "b": int64(2),
+ },
+ }, toml.Position{1, 1},
+ },
+ queryTestNode{
+ map[string]interface{}{
+ "a": int64(3),
+ "b": int64(4),
+ }, toml.Position{4, 1},
+ },
+ queryTestNode{
+ map[string]interface{}{
+ "a": int64(1),
+ "b": int64(2),
+ }, toml.Position{1, 1},
+ },
+ queryTestNode{
+ map[string]interface{}{
+ "a": int64(5),
+ "b": int64(6),
+ }, toml.Position{7, 1},
+ },
+ })
+}
+
+func TestQueryFilterFn(t *testing.T) {
+ buff, err := ioutil.ReadFile("../example.toml")
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ assertQueryPositions(t, string(buff),
+ "$..[?(int)]",
+ []interface{}{
+ queryTestNode{
+ int64(8001), toml.Position{13, 1},
+ },
+ queryTestNode{
+ int64(8001), toml.Position{13, 1},
+ },
+ queryTestNode{
+ int64(8002), toml.Position{13, 1},
+ },
+ queryTestNode{
+ int64(5000), toml.Position{14, 1},
+ },
+ })
+
+ assertQueryPositions(t, string(buff),
+ "$..[?(string)]",
+ []interface{}{
+ queryTestNode{
+ "TOML Example", toml.Position{3, 1},
+ },
+ queryTestNode{
+ "Tom Preston-Werner", toml.Position{6, 1},
+ },
+ queryTestNode{
+ "GitHub", toml.Position{7, 1},
+ },
+ queryTestNode{
+ "GitHub Cofounder & CEO\nLikes tater tots and beer.",
+ toml.Position{8, 1},
+ },
+ queryTestNode{
+ "192.168.1.1", toml.Position{12, 1},
+ },
+ queryTestNode{
+ "10.0.0.1", toml.Position{21, 3},
+ },
+ queryTestNode{
+ "eqdc10", toml.Position{22, 3},
+ },
+ queryTestNode{
+ "10.0.0.2", toml.Position{25, 3},
+ },
+ queryTestNode{
+ "eqdc10", toml.Position{26, 3},
+ },
+ })
+
+ assertQueryPositions(t, string(buff),
+ "$..[?(float)]",
+ []interface{}{
+ // no float values in document
+ })
+
+ tv, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z")
+ assertQueryPositions(t, string(buff),
+ "$..[?(tree)]",
+ []interface{}{
+ queryTestNode{
+ map[string]interface{}{
+ "name": "Tom Preston-Werner",
+ "organization": "GitHub",
+ "bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.",
+ "dob": tv,
+ }, toml.Position{5, 1},
+ },
+ queryTestNode{
+ map[string]interface{}{
+ "server": "192.168.1.1",
+ "ports": []interface{}{int64(8001), int64(8001), int64(8002)},
+ "connection_max": int64(5000),
+ "enabled": true,
+ }, toml.Position{11, 1},
+ },
+ queryTestNode{
+ map[string]interface{}{
+ "alpha": map[string]interface{}{
+ "ip": "10.0.0.1",
+ "dc": "eqdc10",
+ },
+ "beta": map[string]interface{}{
+ "ip": "10.0.0.2",
+ "dc": "eqdc10",
+ },
+ }, toml.Position{17, 1},
+ },
+ queryTestNode{
+ map[string]interface{}{
+ "ip": "10.0.0.1",
+ "dc": "eqdc10",
+ }, toml.Position{20, 3},
+ },
+ queryTestNode{
+ map[string]interface{}{
+ "ip": "10.0.0.2",
+ "dc": "eqdc10",
+ }, toml.Position{24, 3},
+ },
+ queryTestNode{
+ map[string]interface{}{
+ "data": []interface{}{
+ []interface{}{"gamma", "delta"},
+ []interface{}{int64(1), int64(2)},
+ },
+ }, toml.Position{28, 1},
+ },
+ })
+
+ assertQueryPositions(t, string(buff),
+ "$..[?(time)]",
+ []interface{}{
+ queryTestNode{
+ tv, toml.Position{9, 1},
+ },
+ })
+
+ assertQueryPositions(t, string(buff),
+ "$..[?(bool)]",
+ []interface{}{
+ queryTestNode{
+ true, toml.Position{15, 1},
+ },
+ })
+}
diff --git a/vendor/github.com/pelletier/go-toml/query/query.go b/vendor/github.com/pelletier/go-toml/query/query.go
new file mode 100644
index 000000000..1c6cd8014
--- /dev/null
+++ b/vendor/github.com/pelletier/go-toml/query/query.go
@@ -0,0 +1,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
+ },
+}
diff --git a/vendor/github.com/pelletier/go-toml/query/query_test.go b/vendor/github.com/pelletier/go-toml/query/query_test.go
new file mode 100644
index 000000000..903a8dc73
--- /dev/null
+++ b/vendor/github.com/pelletier/go-toml/query/query_test.go
@@ -0,0 +1,157 @@
+package query
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/pelletier/go-toml"
+)
+
+func assertArrayContainsInAnyOrder(t *testing.T, array []interface{}, objects ...interface{}) {
+ if len(array) != len(objects) {
+ t.Fatalf("array contains %d objects but %d are expected", len(array), len(objects))
+ }
+
+ for _, o := range objects {
+ found := false
+ for _, a := range array {
+ if a == o {
+ found = true
+ break
+ }
+ }
+ if !found {
+ t.Fatal(o, "not found in array", array)
+ }
+ }
+}
+
+func TestQueryExample(t *testing.T) {
+ config, _ := toml.Load(`
+ [[book]]
+ title = "The Stand"
+ author = "Stephen King"
+ [[book]]
+ title = "For Whom the Bell Tolls"
+ author = "Ernest Hemmingway"
+ [[book]]
+ title = "Neuromancer"
+ author = "William Gibson"
+ `)
+ authors, err := CompileAndExecute("$.book.author", config)
+ if err != nil {
+ t.Fatal("unexpected error:", err)
+ }
+ names := authors.Values()
+ if len(names) != 3 {
+ t.Fatalf("query should return 3 names but returned %d", len(names))
+ }
+ assertArrayContainsInAnyOrder(t, names, "Stephen King", "Ernest Hemmingway", "William Gibson")
+}
+
+func TestQueryReadmeExample(t *testing.T) {
+ config, _ := toml.Load(`
+[postgres]
+user = "pelletier"
+password = "mypassword"
+`)
+
+ query, err := Compile("$..[user,password]")
+ if err != nil {
+ t.Fatal("unexpected error:", err)
+ }
+ results := query.Execute(config)
+ values := results.Values()
+ if len(values) != 2 {
+ t.Fatalf("query should return 2 values but returned %d", len(values))
+ }
+ assertArrayContainsInAnyOrder(t, values, "pelletier", "mypassword")
+}
+
+func TestQueryPathNotPresent(t *testing.T) {
+ config, _ := toml.Load(`a = "hello"`)
+ query, err := Compile("$.foo.bar")
+ if err != nil {
+ t.Fatal("unexpected error:", err)
+ }
+ results := query.Execute(config)
+ if err != nil {
+ t.Fatalf("err should be nil. got %s instead", err)
+ }
+ if len(results.items) != 0 {
+ t.Fatalf("no items should be matched. %d matched instead", len(results.items))
+ }
+}
+
+func ExampleNodeFilterFn_filterExample() {
+ tree, _ := toml.Load(`
+ [struct_one]
+ foo = "foo"
+ bar = "bar"
+
+ [struct_two]
+ baz = "baz"
+ gorf = "gorf"
+ `)
+
+ // create a query that references a user-defined-filter
+ query, _ := Compile("$[?(bazOnly)]")
+
+ // define the filter, and assign it to the query
+ query.SetFilter("bazOnly", func(node interface{}) bool {
+ if tree, ok := node.(*toml.Tree); ok {
+ return tree.Has("baz")
+ }
+ return false // reject all other node types
+ })
+
+ // results contain only the 'struct_two' Tree
+ query.Execute(tree)
+}
+
+func ExampleQuery_queryExample() {
+ config, _ := toml.Load(`
+ [[book]]
+ title = "The Stand"
+ author = "Stephen King"
+ [[book]]
+ title = "For Whom the Bell Tolls"
+ author = "Ernest Hemmingway"
+ [[book]]
+ title = "Neuromancer"
+ author = "William Gibson"
+ `)
+
+ // find and print all the authors in the document
+ query, _ := Compile("$.book.author")
+ authors := query.Execute(config)
+ for _, name := range authors.Values() {
+ fmt.Println(name)
+ }
+}
+
+func TestTomlQuery(t *testing.T) {
+ tree, err := toml.Load("[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6")
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ query, err := Compile("$.foo.bar")
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ result := query.Execute(tree)
+ values := result.Values()
+ if len(values) != 1 {
+ t.Errorf("Expected resultset of 1, got %d instead: %v", len(values), values)
+ }
+
+ if tt, ok := values[0].(*toml.Tree); !ok {
+ t.Errorf("Expected type of Tree: %T", values[0])
+ } else if tt.Get("a") != int64(1) {
+ t.Errorf("Expected 'a' with a value 1: %v", tt.Get("a"))
+ } else if tt.Get("b") != int64(2) {
+ t.Errorf("Expected 'b' with a value 2: %v", tt.Get("b"))
+ }
+}
diff --git a/vendor/github.com/pelletier/go-toml/query/tokens.go b/vendor/github.com/pelletier/go-toml/query/tokens.go
new file mode 100644
index 000000000..429e289ab
--- /dev/null
+++ b/vendor/github.com/pelletier/go-toml/query/tokens.go
@@ -0,0 +1,107 @@
+package query
+
+import (
+"fmt"
+"strconv"
+"unicode"
+ "github.com/pelletier/go-toml"
+)
+
+// Define tokens
+type tokenType int
+
+const (
+ eof = -(iota + 1)
+)
+
+const (
+ tokenError tokenType = iota
+ tokenEOF
+ tokenKey
+ tokenString
+ tokenInteger
+ tokenFloat
+ tokenLeftBracket
+ tokenRightBracket
+ tokenLeftParen
+ tokenRightParen
+ tokenComma
+ tokenColon
+ tokenDollar
+ tokenStar
+ tokenQuestion
+ tokenDot
+ tokenDotDot
+)
+
+var tokenTypeNames = []string{
+ "Error",
+ "EOF",
+ "Key",
+ "String",
+ "Integer",
+ "Float",
+ "[",
+ "]",
+ "(",
+ ")",
+ ",",
+ ":",
+ "$",
+ "*",
+ "?",
+ ".",
+ "..",
+}
+
+type token struct {
+ toml.Position
+ typ tokenType
+ val string
+}
+
+func (tt tokenType) String() string {
+ idx := int(tt)
+ if idx < len(tokenTypeNames) {
+ return tokenTypeNames[idx]
+ }
+ return "Unknown"
+}
+
+func (t token) Int() int {
+ if result, err := strconv.Atoi(t.val); err != nil {
+ panic(err)
+ } else {
+ return result
+ }
+}
+
+func (t token) String() string {
+ switch t.typ {
+ case tokenEOF:
+ return "EOF"
+ case tokenError:
+ return t.val
+ }
+
+ return fmt.Sprintf("%q", t.val)
+}
+
+func isSpace(r rune) bool {
+ return r == ' ' || r == '\t'
+}
+
+func isAlphanumeric(r rune) bool {
+ return unicode.IsLetter(r) || r == '_'
+}
+
+func isDigit(r rune) bool {
+ return unicode.IsNumber(r)
+}
+
+func isHexDigit(r rune) bool {
+ return isDigit(r) ||
+ (r >= 'a' && r <= 'f') ||
+ (r >= 'A' && r <= 'F')
+}
+