Fix index and slice expressions for query (#405)
* Fix index and slice expressions for query Support negative step for slice expressions
This commit is contained in:
@@ -74,7 +74,7 @@ Or use a query:
|
|||||||
q, _ := query.Compile("$..[user,password]")
|
q, _ := query.Compile("$..[user,password]")
|
||||||
results := q.Execute(config)
|
results := q.Execute(config)
|
||||||
for ii, item := range results.Values() {
|
for ii, item := range results.Values() {
|
||||||
fmt.Println("Query result %d: %v", ii, item)
|
fmt.Printf("Query result %d: %v\n", ii, item)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -99,9 +99,9 @@ Go-toml provides two handy command line tools:
|
|||||||
go install github.com/pelletier/go-toml/cmd/tomljson
|
go install github.com/pelletier/go-toml/cmd/tomljson
|
||||||
tomljson --help
|
tomljson --help
|
||||||
```
|
```
|
||||||
|
|
||||||
* `jsontoml`: Reads a JSON file and outputs a TOML representation.
|
* `jsontoml`: Reads a JSON file and outputs a TOML representation.
|
||||||
|
|
||||||
```
|
```
|
||||||
go install github.com/pelletier/go-toml/cmd/jsontoml
|
go install github.com/pelletier/go-toml/cmd/jsontoml
|
||||||
jsontoml --help
|
jsontoml --help
|
||||||
|
|||||||
+201
@@ -0,0 +1,201 @@
|
|||||||
|
# Query package
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```go
|
||||||
|
result, err := query.CompileAndExecute("$.foo.bar.baz", tree)
|
||||||
|
```
|
||||||
|
|
||||||
|
This is roughly equivalent to:
|
||||||
|
|
||||||
|
```go
|
||||||
|
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.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// select all array elements except the last one.
|
||||||
|
query.CompileAndExecute("$.foo[0:-1]", tree)
|
||||||
|
```
|
||||||
|
|
||||||
|
Slice expressions may have an optional stride/step parameter:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// select every other element
|
||||||
|
query.CompileAndExecute("$.foo[0::2]", tree)
|
||||||
|
```
|
||||||
|
|
||||||
|
Slice start and end parameters are also optional:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// these are all equivalent and select all the values in the array
|
||||||
|
query.CompileAndExecute("$.foo[:]", tree)
|
||||||
|
query.CompileAndExecute("$.foo[::]", tree)
|
||||||
|
query.CompileAndExecute("$.foo[::1]", tree)
|
||||||
|
query.CompileAndExecute("$.foo[0:]", tree)
|
||||||
|
query.CompileAndExecute("$.foo[0::]", tree)
|
||||||
|
query.CompileAndExecute("$.foo[0::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.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 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 convenient, but has the
|
||||||
|
penalty of having to recompile the query expression each time.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 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)
|
||||||
|
```
|
||||||
+13
-15
@@ -25,7 +25,7 @@
|
|||||||
// items.
|
// items.
|
||||||
//
|
//
|
||||||
// As illustrated above, the query path is much more efficient, especially since
|
// 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
|
// 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
|
// 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.
|
// requests into the document, and get zero or more values as a result.
|
||||||
//
|
//
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
// sub-expressions:
|
// sub-expressions:
|
||||||
//
|
//
|
||||||
// $
|
// $
|
||||||
// Root of the TOML tree. This must always come first.
|
// Root of the TOML tree. This must always come first.
|
||||||
// .name
|
// .name
|
||||||
// Selects child of this node, where 'name' is a TOML key
|
// Selects child of this node, where 'name' is a TOML key
|
||||||
// name.
|
// name.
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
// sub-expressions: index, key name, or filter.
|
// sub-expressions: index, key name, or filter.
|
||||||
// [start:end:step]
|
// [start:end:step]
|
||||||
// Slice operator - selects array elements from start to
|
// Slice operator - selects array elements from start to
|
||||||
// end-1, at the given step. All three arguments are
|
// end-1, at the given step. All three arguments are
|
||||||
// optional.
|
// optional.
|
||||||
// [?(filter)]
|
// [?(filter)]
|
||||||
// Named filter expression - the function 'filter' is
|
// Named filter expression - the function 'filter' is
|
||||||
@@ -80,25 +80,23 @@
|
|||||||
// Slice expressions also allow negative indexes for the start and stop
|
// Slice expressions also allow negative indexes for the start and stop
|
||||||
// arguments.
|
// arguments.
|
||||||
//
|
//
|
||||||
// // select all array elements.
|
// // select all array elements except the last one.
|
||||||
// query.CompileAndExecute("$.foo[0:-1]", tree)
|
// query.CompileAndExecute("$.foo[0:-1]", tree)
|
||||||
//
|
//
|
||||||
// Slice expressions may have an optional stride/step parameter:
|
// Slice expressions may have an optional stride/step parameter:
|
||||||
//
|
//
|
||||||
// // select every other element
|
// // select every other element
|
||||||
// query.CompileAndExecute("$.foo[0:-1:2]", tree)
|
// query.CompileAndExecute("$.foo[0::2]", tree)
|
||||||
//
|
//
|
||||||
// Slice start and end parameters are also optional:
|
// Slice start and end parameters are also optional:
|
||||||
//
|
//
|
||||||
// // these are all equivalent and select all the values in the array
|
// // these are all equivalent and select all the values in the array
|
||||||
// query.CompileAndExecute("$.foo[:]", tree)
|
// query.CompileAndExecute("$.foo[:]", tree)
|
||||||
// query.CompileAndExecute("$.foo[0:]", tree)
|
// query.CompileAndExecute("$.foo[::]", tree)
|
||||||
// query.CompileAndExecute("$.foo[:-1]", tree)
|
|
||||||
// query.CompileAndExecute("$.foo[0:-1:]", tree)
|
|
||||||
// query.CompileAndExecute("$.foo[::1]", tree)
|
// query.CompileAndExecute("$.foo[::1]", tree)
|
||||||
|
// query.CompileAndExecute("$.foo[0:]", tree)
|
||||||
|
// query.CompileAndExecute("$.foo[0::]", tree)
|
||||||
// query.CompileAndExecute("$.foo[0::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
|
||||||
//
|
//
|
||||||
@@ -126,8 +124,8 @@
|
|||||||
//
|
//
|
||||||
// Query Results
|
// Query Results
|
||||||
//
|
//
|
||||||
// An executed query returns a Result object. This contains the nodes
|
// An executed query returns a Result object. This contains the nodes
|
||||||
// in the TOML tree that qualify the query expression. Position information
|
// in the TOML tree that qualify the query expression. Position information
|
||||||
// is also available for each value in the set.
|
// is also available for each value in the set.
|
||||||
//
|
//
|
||||||
// // display the results of a query
|
// // display the results of a query
|
||||||
@@ -139,7 +137,7 @@
|
|||||||
// Compiled Queries
|
// Compiled Queries
|
||||||
//
|
//
|
||||||
// Queries may be executed directly on a Tree object, or compiled ahead
|
// Queries may be executed directly on a Tree object, or compiled ahead
|
||||||
// of time and executed discretely. The former is more convenient, but has the
|
// of time and executed discretely. The former is more convenient, but has the
|
||||||
// penalty of having to recompile the query expression each time.
|
// penalty of having to recompile the query expression each time.
|
||||||
//
|
//
|
||||||
// // basic query
|
// // basic query
|
||||||
@@ -155,7 +153,7 @@
|
|||||||
// User Defined Query Filters
|
// User Defined Query Filters
|
||||||
//
|
//
|
||||||
// Filter expressions may also be user defined by using the SetFilter()
|
// Filter expressions may also be user defined by using the SetFilter()
|
||||||
// function on the Query object. The function must return true/false, which
|
// function on the Query object. The function must return true/false, which
|
||||||
// signifies if the passed node is kept or discarded, respectively.
|
// signifies if the passed node is kept or discarded, respectively.
|
||||||
//
|
//
|
||||||
// // create a query that references a user-defined filter
|
// // create a query that references a user-defined filter
|
||||||
@@ -166,7 +164,7 @@
|
|||||||
// if tree, ok := node.(*Tree); ok {
|
// if tree, ok := node.(*Tree); ok {
|
||||||
// return tree.Has("baz")
|
// return tree.Has("baz")
|
||||||
// }
|
// }
|
||||||
// return false // reject all other node types
|
// return false // reject all other node types
|
||||||
// })
|
// })
|
||||||
//
|
//
|
||||||
// // run the query
|
// // run the query
|
||||||
|
|||||||
+106
-28
@@ -2,6 +2,7 @@ package query
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
"github.com/pelletier/go-toml"
|
"github.com/pelletier/go-toml"
|
||||||
)
|
)
|
||||||
@@ -71,53 +72,130 @@ func newMatchIndexFn(idx int) *matchIndexFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *matchIndexFn) call(node interface{}, ctx *queryContext) {
|
func (f *matchIndexFn) call(node interface{}, ctx *queryContext) {
|
||||||
if arr, ok := node.([]interface{}); ok {
|
v := reflect.ValueOf(node)
|
||||||
if f.Idx < len(arr) && f.Idx >= 0 {
|
if v.Kind() == reflect.Slice {
|
||||||
if treesArray, ok := node.([]*toml.Tree); ok {
|
if v.Len() == 0 {
|
||||||
if len(treesArray) > 0 {
|
return
|
||||||
ctx.lastPosition = treesArray[0].Position()
|
}
|
||||||
}
|
|
||||||
}
|
// Manage negative values
|
||||||
f.next.call(arr[f.Idx], ctx)
|
idx := f.Idx
|
||||||
|
if idx < 0 {
|
||||||
|
idx += v.Len()
|
||||||
|
}
|
||||||
|
if 0 <= idx && idx < v.Len() {
|
||||||
|
callNextIndexSlice(f.next, node, ctx, v.Index(idx).Interface())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func callNextIndexSlice(next pathFn, node interface{}, ctx *queryContext, value interface{}) {
|
||||||
|
if treesArray, ok := node.([]*toml.Tree); ok {
|
||||||
|
ctx.lastPosition = treesArray[0].Position()
|
||||||
|
}
|
||||||
|
next.call(value, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// filter by slicing
|
// filter by slicing
|
||||||
type matchSliceFn struct {
|
type matchSliceFn struct {
|
||||||
matchBase
|
matchBase
|
||||||
Start, End, Step int
|
Start, End, Step *int
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMatchSliceFn(start, end, step int) *matchSliceFn {
|
func newMatchSliceFn() *matchSliceFn {
|
||||||
return &matchSliceFn{Start: start, End: end, Step: step}
|
return &matchSliceFn{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *matchSliceFn) setStart(start int) *matchSliceFn {
|
||||||
|
f.Start = &start
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *matchSliceFn) setEnd(end int) *matchSliceFn {
|
||||||
|
f.End = &end
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *matchSliceFn) setStep(step int) *matchSliceFn {
|
||||||
|
f.Step = &step
|
||||||
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *matchSliceFn) call(node interface{}, ctx *queryContext) {
|
func (f *matchSliceFn) call(node interface{}, ctx *queryContext) {
|
||||||
if arr, ok := node.([]interface{}); ok {
|
v := reflect.ValueOf(node)
|
||||||
// adjust indexes for negative values, reverse ordering
|
if v.Kind() == reflect.Slice {
|
||||||
realStart, realEnd := f.Start, f.End
|
if v.Len() == 0 {
|
||||||
if realStart < 0 {
|
return
|
||||||
realStart = len(arr) + realStart
|
|
||||||
}
|
}
|
||||||
if realEnd < 0 {
|
|
||||||
realEnd = len(arr) + realEnd
|
var start, end, step int
|
||||||
|
|
||||||
|
// Initialize step
|
||||||
|
if f.Step != nil {
|
||||||
|
step = *f.Step
|
||||||
|
} else {
|
||||||
|
step = 1
|
||||||
}
|
}
|
||||||
if realEnd < realStart {
|
|
||||||
realEnd, realStart = realStart, realEnd // swap
|
// Initialize start
|
||||||
}
|
if f.Start != nil {
|
||||||
// loop and gather
|
start = *f.Start
|
||||||
for idx := realStart; idx < realEnd; idx += f.Step {
|
// Manage negative values
|
||||||
if treesArray, ok := node.([]*toml.Tree); ok {
|
if start < 0 {
|
||||||
if len(treesArray) > 0 {
|
start += v.Len()
|
||||||
ctx.lastPosition = treesArray[0].Position()
|
}
|
||||||
}
|
// Manage out of range values
|
||||||
|
start = max(start, 0)
|
||||||
|
start = min(start, v.Len()-1)
|
||||||
|
} else if step > 0 {
|
||||||
|
start = 0
|
||||||
|
} else {
|
||||||
|
start = v.Len() - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize end
|
||||||
|
if f.End != nil {
|
||||||
|
end = *f.End
|
||||||
|
// Manage negative values
|
||||||
|
if end < 0 {
|
||||||
|
end += v.Len()
|
||||||
|
}
|
||||||
|
// Manage out of range values
|
||||||
|
end = max(end, -1)
|
||||||
|
end = min(end, v.Len())
|
||||||
|
} else if step > 0 {
|
||||||
|
end = v.Len()
|
||||||
|
} else {
|
||||||
|
end = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop on values
|
||||||
|
if step > 0 {
|
||||||
|
for idx := start; idx < end; idx += step {
|
||||||
|
callNextIndexSlice(f.next, node, ctx, v.Index(idx).Interface())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for idx := start; idx > end; idx += step {
|
||||||
|
callNextIndexSlice(f.next, node, ctx, v.Index(idx).Interface())
|
||||||
}
|
}
|
||||||
f.next.call(arr[idx], ctx)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func min(a, b int) int {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func max(a, b int) int {
|
||||||
|
if a > b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
// match anything
|
// match anything
|
||||||
type matchAnyFn struct {
|
type matchAnyFn struct {
|
||||||
matchBase
|
matchBase
|
||||||
|
|||||||
+21
-10
@@ -2,8 +2,10 @@ package query
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pelletier/go-toml"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pelletier/go-toml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// dump path tree to a string
|
// dump path tree to a string
|
||||||
@@ -19,8 +21,17 @@ func pathString(root pathFn) string {
|
|||||||
result += fmt.Sprintf("{%d}", fn.Idx)
|
result += fmt.Sprintf("{%d}", fn.Idx)
|
||||||
result += pathString(fn.next)
|
result += pathString(fn.next)
|
||||||
case *matchSliceFn:
|
case *matchSliceFn:
|
||||||
result += fmt.Sprintf("{%d:%d:%d}",
|
startString, endString, stepString := "nil", "nil", "nil"
|
||||||
fn.Start, fn.End, fn.Step)
|
if fn.Start != nil {
|
||||||
|
startString = strconv.Itoa(*fn.Start)
|
||||||
|
}
|
||||||
|
if fn.End != nil {
|
||||||
|
endString = strconv.Itoa(*fn.End)
|
||||||
|
}
|
||||||
|
if fn.Step != nil {
|
||||||
|
stepString = strconv.Itoa(*fn.Step)
|
||||||
|
}
|
||||||
|
result += fmt.Sprintf("{%s:%s:%s}", startString, endString, stepString)
|
||||||
result += pathString(fn.next)
|
result += pathString(fn.next)
|
||||||
case *matchAnyFn:
|
case *matchAnyFn:
|
||||||
result += "{}"
|
result += "{}"
|
||||||
@@ -110,7 +121,7 @@ func TestPathSliceStart(t *testing.T) {
|
|||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$[123:]",
|
"$[123:]",
|
||||||
buildPath(
|
buildPath(
|
||||||
newMatchSliceFn(123, maxInt, 1),
|
newMatchSliceFn().setStart(123),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +129,7 @@ func TestPathSliceStartEnd(t *testing.T) {
|
|||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$[123:456]",
|
"$[123:456]",
|
||||||
buildPath(
|
buildPath(
|
||||||
newMatchSliceFn(123, 456, 1),
|
newMatchSliceFn().setStart(123).setEnd(456),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +137,7 @@ func TestPathSliceStartEndColon(t *testing.T) {
|
|||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$[123:456:]",
|
"$[123:456:]",
|
||||||
buildPath(
|
buildPath(
|
||||||
newMatchSliceFn(123, 456, 1),
|
newMatchSliceFn().setStart(123).setEnd(456),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +145,7 @@ func TestPathSliceStartStep(t *testing.T) {
|
|||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$[123::7]",
|
"$[123::7]",
|
||||||
buildPath(
|
buildPath(
|
||||||
newMatchSliceFn(123, maxInt, 7),
|
newMatchSliceFn().setStart(123).setStep(7),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +153,7 @@ func TestPathSliceEndStep(t *testing.T) {
|
|||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$[:456:7]",
|
"$[:456:7]",
|
||||||
buildPath(
|
buildPath(
|
||||||
newMatchSliceFn(0, 456, 7),
|
newMatchSliceFn().setEnd(456).setStep(7),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +161,7 @@ func TestPathSliceStep(t *testing.T) {
|
|||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$[::7]",
|
"$[::7]",
|
||||||
buildPath(
|
buildPath(
|
||||||
newMatchSliceFn(0, maxInt, 7),
|
newMatchSliceFn().setStep(7),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +169,7 @@ func TestPathSliceAll(t *testing.T) {
|
|||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$[123:456:7]",
|
"$[123:456:7]",
|
||||||
buildPath(
|
buildPath(
|
||||||
newMatchSliceFn(123, 456, 7),
|
newMatchSliceFn().setStart(123).setEnd(456).setStep(7),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-8
@@ -203,12 +203,13 @@ loop: // labeled loop for easy breaking
|
|||||||
|
|
||||||
func (p *queryParser) parseSliceExpr() queryParserStateFn {
|
func (p *queryParser) parseSliceExpr() queryParserStateFn {
|
||||||
// init slice to grab all elements
|
// init slice to grab all elements
|
||||||
start, end, step := 0, maxInt, 1
|
var start, end, step *int = nil, nil, nil
|
||||||
|
|
||||||
// parse optional start
|
// parse optional start
|
||||||
tok := p.getToken()
|
tok := p.getToken()
|
||||||
if tok.typ == tokenInteger {
|
if tok.typ == tokenInteger {
|
||||||
start = tok.Int()
|
v := tok.Int()
|
||||||
|
start = &v
|
||||||
tok = p.getToken()
|
tok = p.getToken()
|
||||||
}
|
}
|
||||||
if tok.typ != tokenColon {
|
if tok.typ != tokenColon {
|
||||||
@@ -218,11 +219,12 @@ func (p *queryParser) parseSliceExpr() queryParserStateFn {
|
|||||||
// parse optional end
|
// parse optional end
|
||||||
tok = p.getToken()
|
tok = p.getToken()
|
||||||
if tok.typ == tokenInteger {
|
if tok.typ == tokenInteger {
|
||||||
end = tok.Int()
|
v := tok.Int()
|
||||||
|
end = &v
|
||||||
tok = p.getToken()
|
tok = p.getToken()
|
||||||
}
|
}
|
||||||
if tok.typ == tokenRightBracket {
|
if tok.typ == tokenRightBracket {
|
||||||
p.query.appendPath(newMatchSliceFn(start, end, step))
|
p.query.appendPath(&matchSliceFn{Start: start, End: end, Step: step})
|
||||||
return p.parseMatchExpr
|
return p.parseMatchExpr
|
||||||
}
|
}
|
||||||
if tok.typ != tokenColon {
|
if tok.typ != tokenColon {
|
||||||
@@ -232,17 +234,18 @@ func (p *queryParser) parseSliceExpr() queryParserStateFn {
|
|||||||
// parse optional step
|
// parse optional step
|
||||||
tok = p.getToken()
|
tok = p.getToken()
|
||||||
if tok.typ == tokenInteger {
|
if tok.typ == tokenInteger {
|
||||||
step = tok.Int()
|
v := tok.Int()
|
||||||
if step < 0 {
|
if v == 0 {
|
||||||
return p.parseError(tok, "step must be a positive value")
|
return p.parseError(tok, "step cannot be zero")
|
||||||
}
|
}
|
||||||
|
step = &v
|
||||||
tok = p.getToken()
|
tok = p.getToken()
|
||||||
}
|
}
|
||||||
if tok.typ != tokenRightBracket {
|
if tok.typ != tokenRightBracket {
|
||||||
return p.parseError(tok, "expected ']'")
|
return p.parseError(tok, "expected ']'")
|
||||||
}
|
}
|
||||||
|
|
||||||
p.query.appendPath(newMatchSliceFn(start, end, step))
|
p.query.appendPath(&matchSliceFn{Start: start, End: end, Step: step})
|
||||||
return p.parseMatchExpr
|
return p.parseMatchExpr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+225
-98
@@ -78,6 +78,19 @@ func assertValue(t *testing.T, result, ref interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assertParseError(t *testing.T, query string, errString string) {
|
||||||
|
_, err := Compile(query)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("error should be non-nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err.Error() != errString {
|
||||||
|
t.Errorf("error does not match")
|
||||||
|
t.Log("test:", err.Error())
|
||||||
|
t.Log("ref: ", errString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func assertQueryPositions(t *testing.T, tomlDoc string, query string, ref []interface{}) {
|
func assertQueryPositions(t *testing.T, tomlDoc string, query string, ref []interface{}) {
|
||||||
tree, err := toml.Load(tomlDoc)
|
tree, err := toml.Load(tomlDoc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -128,54 +141,213 @@ func TestQueryKeyString(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQueryIndex(t *testing.T) {
|
func TestQueryKeyUnicodeString(t *testing.T) {
|
||||||
assertQueryPositions(t,
|
assertQueryPositions(t,
|
||||||
"[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
|
"['f𝟘.o']\na = 42",
|
||||||
"$.foo.a[5]",
|
"$['f𝟘.o']['a']",
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
queryTestNode{
|
queryTestNode{
|
||||||
int64(6), toml.Position{2, 1},
|
int64(42), toml.Position{2, 1},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestQueryIndexError1(t *testing.T) {
|
||||||
|
assertParseError(t, "$.foo.a[5", "(1, 10): expected ',' or ']', not ''")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryIndexError2(t *testing.T) {
|
||||||
|
assertParseError(t, "$.foo.a[]", "(1, 9): expected union sub expression, not ']', 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryIndex(t *testing.T) {
|
||||||
|
assertQueryPositions(t,
|
||||||
|
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||||||
|
"$.foo.a[5]",
|
||||||
|
[]interface{}{
|
||||||
|
queryTestNode{int64(5), toml.Position{2, 1}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryIndexNegative(t *testing.T) {
|
||||||
|
assertQueryPositions(t,
|
||||||
|
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||||||
|
"$.foo.a[-2]",
|
||||||
|
[]interface{}{
|
||||||
|
queryTestNode{int64(8), toml.Position{2, 1}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryIndexWrong(t *testing.T) {
|
||||||
|
assertQueryPositions(t,
|
||||||
|
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||||||
|
"$.foo.a[99]",
|
||||||
|
[]interface{}{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryIndexEmpty(t *testing.T) {
|
||||||
|
assertQueryPositions(t,
|
||||||
|
"[foo]\na = []",
|
||||||
|
"$.foo.a[5]",
|
||||||
|
[]interface{}{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryIndexTree(t *testing.T) {
|
||||||
|
assertQueryPositions(t,
|
||||||
|
"[[foo]]\na = [0,1,2,3,4,5,6,7,8,9]\n[[foo]]\nb = 3",
|
||||||
|
"$.foo[1].b",
|
||||||
|
[]interface{}{
|
||||||
|
queryTestNode{int64(3), toml.Position{4, 1}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuerySliceError1(t *testing.T) {
|
||||||
|
assertParseError(t, "$.foo.a[3:?]", "(1, 11): expected ']' or ':'")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuerySliceError2(t *testing.T) {
|
||||||
|
assertParseError(t, "$.foo.a[:::]", "(1, 11): expected ']'")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuerySliceError3(t *testing.T) {
|
||||||
|
assertParseError(t, "$.foo.a[::0]", "(1, 11): step cannot be zero")
|
||||||
|
}
|
||||||
|
|
||||||
func TestQuerySliceRange(t *testing.T) {
|
func TestQuerySliceRange(t *testing.T) {
|
||||||
assertQueryPositions(t,
|
assertQueryPositions(t,
|
||||||
"[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
|
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||||||
"$.foo.a[0:5]",
|
"$.foo.a[:5]",
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
queryTestNode{
|
queryTestNode{int64(0), toml.Position{2, 1}},
|
||||||
int64(1), toml.Position{2, 1},
|
queryTestNode{int64(1), toml.Position{2, 1}},
|
||||||
},
|
queryTestNode{int64(2), toml.Position{2, 1}},
|
||||||
queryTestNode{
|
queryTestNode{int64(3), toml.Position{2, 1}},
|
||||||
int64(2), toml.Position{2, 1},
|
queryTestNode{int64(4), 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) {
|
func TestQuerySliceStep(t *testing.T) {
|
||||||
assertQueryPositions(t,
|
assertQueryPositions(t,
|
||||||
"[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
|
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||||||
"$.foo.a[0:5:2]",
|
"$.foo.a[0:5:2]",
|
||||||
|
[]interface{}{
|
||||||
|
queryTestNode{int64(0), toml.Position{2, 1}},
|
||||||
|
queryTestNode{int64(2), toml.Position{2, 1}},
|
||||||
|
queryTestNode{int64(4), toml.Position{2, 1}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuerySliceStartNegative(t *testing.T) {
|
||||||
|
assertQueryPositions(t,
|
||||||
|
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||||||
|
"$.foo.a[-3:]",
|
||||||
|
[]interface{}{
|
||||||
|
queryTestNode{int64(7), toml.Position{2, 1}},
|
||||||
|
queryTestNode{int64(8), toml.Position{2, 1}},
|
||||||
|
queryTestNode{int64(9), toml.Position{2, 1}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuerySliceEndNegative(t *testing.T) {
|
||||||
|
assertQueryPositions(t,
|
||||||
|
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||||||
|
"$.foo.a[:-6]",
|
||||||
|
[]interface{}{
|
||||||
|
queryTestNode{int64(0), toml.Position{2, 1}},
|
||||||
|
queryTestNode{int64(1), toml.Position{2, 1}},
|
||||||
|
queryTestNode{int64(2), toml.Position{2, 1}},
|
||||||
|
queryTestNode{int64(3), toml.Position{2, 1}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuerySliceStepNegative(t *testing.T) {
|
||||||
|
assertQueryPositions(t,
|
||||||
|
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||||||
|
"$.foo.a[::-2]",
|
||||||
|
[]interface{}{
|
||||||
|
queryTestNode{int64(9), toml.Position{2, 1}},
|
||||||
|
queryTestNode{int64(7), toml.Position{2, 1}},
|
||||||
|
queryTestNode{int64(5), toml.Position{2, 1}},
|
||||||
|
queryTestNode{int64(3), toml.Position{2, 1}},
|
||||||
|
queryTestNode{int64(1), toml.Position{2, 1}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuerySliceStartOverRange(t *testing.T) {
|
||||||
|
assertQueryPositions(t,
|
||||||
|
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||||||
|
"$.foo.a[-99:3]",
|
||||||
|
[]interface{}{
|
||||||
|
queryTestNode{int64(0), toml.Position{2, 1}},
|
||||||
|
queryTestNode{int64(1), toml.Position{2, 1}},
|
||||||
|
queryTestNode{int64(2), toml.Position{2, 1}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuerySliceStartOverRangeNegative(t *testing.T) {
|
||||||
|
assertQueryPositions(t,
|
||||||
|
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||||||
|
"$.foo.a[99:7:-1]",
|
||||||
|
[]interface{}{
|
||||||
|
queryTestNode{int64(9), toml.Position{2, 1}},
|
||||||
|
queryTestNode{int64(8), toml.Position{2, 1}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuerySliceEndOverRange(t *testing.T) {
|
||||||
|
assertQueryPositions(t,
|
||||||
|
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||||||
|
"$.foo.a[7:99]",
|
||||||
|
[]interface{}{
|
||||||
|
queryTestNode{int64(7), toml.Position{2, 1}},
|
||||||
|
queryTestNode{int64(8), toml.Position{2, 1}},
|
||||||
|
queryTestNode{int64(9), toml.Position{2, 1}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuerySliceEndOverRangeNegative(t *testing.T) {
|
||||||
|
assertQueryPositions(t,
|
||||||
|
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||||||
|
"$.foo.a[2:-99:-1]",
|
||||||
|
[]interface{}{
|
||||||
|
queryTestNode{int64(2), toml.Position{2, 1}},
|
||||||
|
queryTestNode{int64(1), toml.Position{2, 1}},
|
||||||
|
queryTestNode{int64(0), toml.Position{2, 1}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuerySliceWrongRange(t *testing.T) {
|
||||||
|
assertQueryPositions(t,
|
||||||
|
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||||||
|
"$.foo.a[5:3]",
|
||||||
|
[]interface{}{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuerySliceWrongRangeNegative(t *testing.T) {
|
||||||
|
assertQueryPositions(t,
|
||||||
|
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||||||
|
"$.foo.a[3:5:-1]",
|
||||||
|
[]interface{}{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuerySliceEmpty(t *testing.T) {
|
||||||
|
assertQueryPositions(t,
|
||||||
|
"[foo]\na = []",
|
||||||
|
"$.foo.a[5:]",
|
||||||
|
[]interface{}{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuerySliceTree(t *testing.T) {
|
||||||
|
assertQueryPositions(t,
|
||||||
|
"[[foo]]\na='nok'\n[[foo]]\na = [0,1,2,3,4,5,6,7,8,9]\n[[foo]]\na='ok'\nb = 3",
|
||||||
|
"$.foo[1:].a",
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
queryTestNode{
|
queryTestNode{
|
||||||
int64(1), toml.Position{2, 1},
|
[]interface{}{
|
||||||
},
|
int64(0), int64(1), int64(2), int64(3), int64(4),
|
||||||
queryTestNode{
|
int64(5), int64(6), int64(7), int64(8), int64(9)},
|
||||||
int64(3), toml.Position{2, 1},
|
toml.Position{4, 1}},
|
||||||
},
|
queryTestNode{"ok", toml.Position{6, 1}},
|
||||||
queryTestNode{
|
|
||||||
int64(5), toml.Position{2, 1},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,12 +437,8 @@ func TestQueryRecursionAll(t *testing.T) {
|
|||||||
"b": int64(2),
|
"b": int64(2),
|
||||||
}, toml.Position{1, 1},
|
}, toml.Position{1, 1},
|
||||||
},
|
},
|
||||||
queryTestNode{
|
queryTestNode{int64(1), toml.Position{2, 1}},
|
||||||
int64(1), toml.Position{2, 1},
|
queryTestNode{int64(2), toml.Position{3, 1}},
|
||||||
},
|
|
||||||
queryTestNode{
|
|
||||||
int64(2), toml.Position{3, 1},
|
|
||||||
},
|
|
||||||
queryTestNode{
|
queryTestNode{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"foo": map[string]interface{}{
|
"foo": map[string]interface{}{
|
||||||
@@ -285,12 +453,8 @@ func TestQueryRecursionAll(t *testing.T) {
|
|||||||
"b": int64(4),
|
"b": int64(4),
|
||||||
}, toml.Position{4, 1},
|
}, toml.Position{4, 1},
|
||||||
},
|
},
|
||||||
queryTestNode{
|
queryTestNode{int64(3), toml.Position{5, 1}},
|
||||||
int64(3), toml.Position{5, 1},
|
queryTestNode{int64(4), toml.Position{6, 1}},
|
||||||
},
|
|
||||||
queryTestNode{
|
|
||||||
int64(4), toml.Position{6, 1},
|
|
||||||
},
|
|
||||||
queryTestNode{
|
queryTestNode{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"foo": map[string]interface{}{
|
"foo": map[string]interface{}{
|
||||||
@@ -305,12 +469,8 @@ func TestQueryRecursionAll(t *testing.T) {
|
|||||||
"b": int64(6),
|
"b": int64(6),
|
||||||
}, toml.Position{7, 1},
|
}, toml.Position{7, 1},
|
||||||
},
|
},
|
||||||
queryTestNode{
|
queryTestNode{int64(5), toml.Position{8, 1}},
|
||||||
int64(5), toml.Position{8, 1},
|
queryTestNode{int64(6), toml.Position{9, 1}},
|
||||||
},
|
|
||||||
queryTestNode{
|
|
||||||
int64(6), toml.Position{9, 1},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,59 +518,30 @@ func TestQueryFilterFn(t *testing.T) {
|
|||||||
assertQueryPositions(t, string(buff),
|
assertQueryPositions(t, string(buff),
|
||||||
"$..[?(int)]",
|
"$..[?(int)]",
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
queryTestNode{
|
queryTestNode{int64(8001), toml.Position{13, 1}},
|
||||||
int64(8001), toml.Position{13, 1},
|
queryTestNode{int64(8001), toml.Position{13, 1}},
|
||||||
},
|
queryTestNode{int64(8002), toml.Position{13, 1}},
|
||||||
queryTestNode{
|
queryTestNode{int64(5000), toml.Position{14, 1}},
|
||||||
int64(8001), toml.Position{13, 1},
|
|
||||||
},
|
|
||||||
queryTestNode{
|
|
||||||
int64(8002), toml.Position{13, 1},
|
|
||||||
},
|
|
||||||
queryTestNode{
|
|
||||||
int64(5000), toml.Position{14, 1},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
assertQueryPositions(t, string(buff),
|
assertQueryPositions(t, string(buff),
|
||||||
"$..[?(string)]",
|
"$..[?(string)]",
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
queryTestNode{
|
queryTestNode{"TOML Example", toml.Position{3, 1}},
|
||||||
"TOML Example", toml.Position{3, 1},
|
queryTestNode{"Tom Preston-Werner", toml.Position{6, 1}},
|
||||||
},
|
queryTestNode{"GitHub", toml.Position{7, 1}},
|
||||||
queryTestNode{
|
queryTestNode{"GitHub Cofounder & CEO\nLikes tater tots and beer.", toml.Position{8, 1}},
|
||||||
"Tom Preston-Werner", toml.Position{6, 1},
|
queryTestNode{"192.168.1.1", toml.Position{12, 1}},
|
||||||
},
|
queryTestNode{"10.0.0.1", toml.Position{21, 3}},
|
||||||
queryTestNode{
|
queryTestNode{"eqdc10", toml.Position{22, 3}},
|
||||||
"GitHub", toml.Position{7, 1},
|
queryTestNode{"10.0.0.2", toml.Position{25, 3}},
|
||||||
},
|
queryTestNode{"eqdc10", toml.Position{26, 3}},
|
||||||
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),
|
assertQueryPositions(t, string(buff),
|
||||||
"$..[?(float)]",
|
"$..[?(float)]",
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
queryTestNode{
|
queryTestNode{4e-08, toml.Position{30, 1}},
|
||||||
4e-08, toml.Position{30, 1},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
tv, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z")
|
tv, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z")
|
||||||
@@ -471,16 +602,12 @@ func TestQueryFilterFn(t *testing.T) {
|
|||||||
assertQueryPositions(t, string(buff),
|
assertQueryPositions(t, string(buff),
|
||||||
"$..[?(time)]",
|
"$..[?(time)]",
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
queryTestNode{
|
queryTestNode{tv, toml.Position{9, 1}},
|
||||||
tv, toml.Position{9, 1},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
assertQueryPositions(t, string(buff),
|
assertQueryPositions(t, string(buff),
|
||||||
"$..[?(bool)]",
|
"$..[?(bool)]",
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
queryTestNode{
|
queryTestNode{true, toml.Position{15, 1}},
|
||||||
true, toml.Position{15, 1},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-20
@@ -26,6 +26,14 @@ func assertArrayContainsInAnyOrder(t *testing.T, array []interface{}, objects ..
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkQuery(t *testing.T, tree *toml.Tree, query string, objects ...interface{}) {
|
||||||
|
results, err := CompileAndExecute(query, tree)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unexpected error:", err)
|
||||||
|
}
|
||||||
|
assertArrayContainsInAnyOrder(t, results.Values(), objects...)
|
||||||
|
}
|
||||||
|
|
||||||
func TestQueryExample(t *testing.T) {
|
func TestQueryExample(t *testing.T) {
|
||||||
config, _ := toml.Load(`
|
config, _ := toml.Load(`
|
||||||
[[book]]
|
[[book]]
|
||||||
@@ -37,16 +45,18 @@ func TestQueryExample(t *testing.T) {
|
|||||||
[[book]]
|
[[book]]
|
||||||
title = "Neuromancer"
|
title = "Neuromancer"
|
||||||
author = "William Gibson"
|
author = "William Gibson"
|
||||||
`)
|
`)
|
||||||
authors, err := CompileAndExecute("$.book.author", config)
|
|
||||||
if err != nil {
|
checkQuery(t, config, "$.book.author", "Stephen King", "Ernest Hemmingway", "William Gibson")
|
||||||
t.Fatal("unexpected error:", err)
|
|
||||||
}
|
checkQuery(t, config, "$.book[0].author", "Stephen King")
|
||||||
names := authors.Values()
|
checkQuery(t, config, "$.book[-1].author", "William Gibson")
|
||||||
if len(names) != 3 {
|
checkQuery(t, config, "$.book[1:].author", "Ernest Hemmingway", "William Gibson")
|
||||||
t.Fatalf("query should return 3 names but returned %d", len(names))
|
checkQuery(t, config, "$.book[-1:].author", "William Gibson")
|
||||||
}
|
checkQuery(t, config, "$.book[::2].author", "William Gibson", "Stephen King")
|
||||||
assertArrayContainsInAnyOrder(t, names, "Stephen King", "Ernest Hemmingway", "William Gibson")
|
checkQuery(t, config, "$.book[::-1].author", "William Gibson", "Ernest Hemmingway", "Stephen King")
|
||||||
|
checkQuery(t, config, "$.book[:].author", "Stephen King", "Ernest Hemmingway", "William Gibson")
|
||||||
|
checkQuery(t, config, "$.book[::].author", "Stephen King", "Ernest Hemmingway", "William Gibson")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQueryReadmeExample(t *testing.T) {
|
func TestQueryReadmeExample(t *testing.T) {
|
||||||
@@ -56,16 +66,7 @@ user = "pelletier"
|
|||||||
password = "mypassword"
|
password = "mypassword"
|
||||||
`)
|
`)
|
||||||
|
|
||||||
query, err := Compile("$..[user,password]")
|
checkQuery(t, config, "$..[user,password]", "pelletier", "mypassword")
|
||||||
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) {
|
func TestQueryPathNotPresent(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user