Revised after review
* Dropped 'script' support * Improved documentation * Made pathFn members private
This commit is contained in:
@@ -70,6 +70,42 @@ your application. Using positions works much like values:
|
|||||||
* `tree.GetPosition("comma.separated.path")` Returns the position of the given
|
* `tree.GetPosition("comma.separated.path")` Returns the position of the given
|
||||||
path in the source.
|
path in the source.
|
||||||
|
|
||||||
|
### Support for queries
|
||||||
|
:
|
||||||
|
Go-toml also supports a JSON-Path style syntax for querying a document for
|
||||||
|
collections of elements, and more.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/pelletier/go-toml"
|
||||||
|
)
|
||||||
|
config, err := toml.Load(`
|
||||||
|
[[book]]
|
||||||
|
title = "The Stand"
|
||||||
|
author = "Stephen King"
|
||||||
|
[[book]]
|
||||||
|
title = "For Whom the Bell Tolls"
|
||||||
|
author = "Earnest Hemmingway"
|
||||||
|
[[book]]
|
||||||
|
title = "Neuromancer"
|
||||||
|
author = "William Gibson"
|
||||||
|
`)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error ", err.Error())
|
||||||
|
} else {
|
||||||
|
// find and print all the authors in the document
|
||||||
|
authors := config.Query("$.book.author")
|
||||||
|
for _, name := authors.Value() {
|
||||||
|
fmt.Println(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
More information about the format of TOML queries can be found on the
|
||||||
|
[godoc page for go-toml](http://godoc.org/github.com/pelletier/go-toml).
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
The documentation is available at
|
The documentation is available at
|
||||||
|
|||||||
@@ -0,0 +1,245 @@
|
|||||||
|
// Package toml is a TOML markup language parser.
|
||||||
|
//
|
||||||
|
// This version supports the specification as described in
|
||||||
|
// https://github.com/toml-lang/toml/blob/master/versions/toml-v0.2.0.md
|
||||||
|
//
|
||||||
|
// TOML Parsing
|
||||||
|
//
|
||||||
|
// TOML data may be parsed in two ways: by file, or by string.
|
||||||
|
//
|
||||||
|
// // load TOML data by filename
|
||||||
|
// tree, err := toml.LoadFile("filename.toml")
|
||||||
|
//
|
||||||
|
// // load TOML data stored in a string
|
||||||
|
// tree, err := toml.Load(stringContainingTomlData)
|
||||||
|
//
|
||||||
|
// Either way, the result is a TomlTree object that can be used to navigate the
|
||||||
|
// structure and data within the original document.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Getting data from the TomlTree
|
||||||
|
//
|
||||||
|
// After parsing TOML data with Load() or LoadFile(), use the Has() and Get()
|
||||||
|
// methods on the returned TomlTree, to find your way through the document data.
|
||||||
|
//
|
||||||
|
// if tree.Has('foo') {
|
||||||
|
// fmt.Prinln("foo is: %v", tree.Get('foo'))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Working with Paths
|
||||||
|
//
|
||||||
|
// Go-toml has support for basic dot-separated key paths on the Has(), Get(), Set()
|
||||||
|
// and GetDefault() methods. These are the same kind of key paths used within the
|
||||||
|
// TOML specification for struct tames.
|
||||||
|
//
|
||||||
|
// // looks for a key named 'baz', within struct 'bar', within struct 'foo'
|
||||||
|
// tree.Has("foo.bar.baz")
|
||||||
|
//
|
||||||
|
// // returns the key at this path, if it is there
|
||||||
|
// tree.Get("foo.bar.baz")
|
||||||
|
//
|
||||||
|
// TOML allows keys to contain '.', which can cause this syntax to be problematic
|
||||||
|
// for some documents. In such cases, use the GetPath(), HasPath(), and SetPath(),
|
||||||
|
// methods to explicitly define the path. This form is also faster, since
|
||||||
|
// it avoids having to parse the passed key for '.' delimiters.
|
||||||
|
//
|
||||||
|
// // looks for a key named 'baz', within struct 'bar', within struct 'foo'
|
||||||
|
// tree.HasPath(string{}{"foo","bar","baz"})
|
||||||
|
//
|
||||||
|
// // returns the key at this path, if it is there
|
||||||
|
// tree.GetPath(string{}{"foo","bar","baz"})
|
||||||
|
//
|
||||||
|
// Note that this is distinct from the heavyweight query syntax supported by
|
||||||
|
// TomlTree.Query() and the Query() struct (see below).
|
||||||
|
//
|
||||||
|
// Position Support
|
||||||
|
//
|
||||||
|
// Each element within the TomlTree is stored with position metadata, which is
|
||||||
|
// invaluable for providing semantic feedback to a user. This helps in
|
||||||
|
// situations where the TOML file parses correctly, but contains data that is
|
||||||
|
// not correct for the application. In such cases, an error message can be
|
||||||
|
// generated that indicates the problem line and column number in the source
|
||||||
|
// TOML document.
|
||||||
|
//
|
||||||
|
// // load TOML data
|
||||||
|
// tree, _ := toml.Load("filename.toml")
|
||||||
|
//
|
||||||
|
// // get an entry and report an error if it's the wrong type
|
||||||
|
// element := tree.Get("foo")
|
||||||
|
// if value, ok := element.(int64); !ok {
|
||||||
|
// return fmt.Errorf("%v: Element 'foo' must be an integer", tree.GetPosition("foo"))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // report an error if an expected element is missing
|
||||||
|
// if !tree.Has("bar") {
|
||||||
|
// return fmt.Errorf("%v: Expected 'bar' element", tree.GetPosition(""))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Query Support
|
||||||
|
//
|
||||||
|
// The TOML 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 := tree.Query("$.foo.bar.baz") // result is 'nil' if the path is not present
|
||||||
|
//
|
||||||
|
// This is equivalent to:
|
||||||
|
//
|
||||||
|
// next := tree.Get("foo")
|
||||||
|
// if next != nil {
|
||||||
|
// next = next.Get("bar")
|
||||||
|
// if next != nil {
|
||||||
|
// next = next.Get("baz")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// result := next
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// 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'
|
||||||
|
// tree.Query("$.foo[-1]")
|
||||||
|
//
|
||||||
|
// Slice expressions are supported, by using ':' to separate a start/end index pair.
|
||||||
|
//
|
||||||
|
// // select up to the first five elements in the array
|
||||||
|
// tree.Query("$.foo[0:5]")
|
||||||
|
//
|
||||||
|
// Slice expressions also allow negative indexes for the start and stop
|
||||||
|
// arguments.
|
||||||
|
//
|
||||||
|
// // select all array elements.
|
||||||
|
// tree.Query("$.foo[0:-1]")
|
||||||
|
//
|
||||||
|
// Slice expressions may have an optional stride/step parameter:
|
||||||
|
//
|
||||||
|
// // select every other element
|
||||||
|
// tree.Query("$.foo[0:-1:2]")
|
||||||
|
//
|
||||||
|
// Slice start and end parameters are also optional:
|
||||||
|
//
|
||||||
|
// // these are all equivalent and select all the values in the array
|
||||||
|
// tree.Query("$.foo[:]")
|
||||||
|
// tree.Query("$.foo[0:]")
|
||||||
|
// tree.Query("$.foo[:-1]")
|
||||||
|
// tree.Query("$.foo[0:-1:]")
|
||||||
|
// tree.Query("$.foo[::1]")
|
||||||
|
// tree.Query("$.foo[0::1]")
|
||||||
|
// tree.Query("$.foo[:-1:1]")
|
||||||
|
// tree.Query("$.foo[0:-1:1]")
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
// tree.Query("$.foo[?(bar)]")
|
||||||
|
//
|
||||||
|
// There are several filters provided with the library:
|
||||||
|
//
|
||||||
|
// tree
|
||||||
|
// Allows nodes of type TomlTree.
|
||||||
|
// 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 QueryResult 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 := tree.Query("$.foo.bar.baz")
|
||||||
|
// for idx, value := results.Values() {
|
||||||
|
// fmt.Println("%v: %v", results.Positions()[idx], value)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Compiled Queries
|
||||||
|
//
|
||||||
|
// Queries may be executed directly on a TomlTree 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 := tree.Query("$.foo.bar.baz")
|
||||||
|
//
|
||||||
|
// // compiled query
|
||||||
|
// query := toml.CompileQuery("$.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, _ := CompileQuery("$[?(bazOnly)]")
|
||||||
|
//
|
||||||
|
// // define the filter, and assign it to the query
|
||||||
|
// query.SetFilter("bazOnly", func(node interface{}) bool{
|
||||||
|
// if tree, ok := node.(*TomlTree); ok {
|
||||||
|
// return tree.Has("baz")
|
||||||
|
// }
|
||||||
|
// return false // reject all other node types
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// // run the query
|
||||||
|
// query.Execute(tree)
|
||||||
|
//
|
||||||
|
package toml
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
// code examples for godoc
|
||||||
|
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func ExampleNodeFilterFn_filterExample() {
|
||||||
|
tree, _ := Load(`
|
||||||
|
[struct_one]
|
||||||
|
foo = "foo"
|
||||||
|
bar = "bar"
|
||||||
|
|
||||||
|
[struct_two]
|
||||||
|
baz = "baz"
|
||||||
|
gorf = "gorf"
|
||||||
|
`)
|
||||||
|
|
||||||
|
// create a query that references a user-defined-filter
|
||||||
|
query, _ := CompileQuery("$[?(bazOnly)]")
|
||||||
|
|
||||||
|
// define the filter, and assign it to the query
|
||||||
|
query.SetFilter("bazOnly", func(node interface{}) bool {
|
||||||
|
if tree, ok := node.(*TomlTree); ok {
|
||||||
|
return tree.Has("baz")
|
||||||
|
}
|
||||||
|
return false // reject all other node types
|
||||||
|
})
|
||||||
|
|
||||||
|
// results contain only the 'struct_two' TomlTree
|
||||||
|
query.Execute(tree)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleQuery_queryExample() {
|
||||||
|
config, _ := 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
|
||||||
|
authors, _ := config.Query("$.book.author")
|
||||||
|
for _, name := range authors.Values() {
|
||||||
|
fmt.Println(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,10 +24,10 @@ func tomlValueCheck(node interface{}, ctx *queryContext) interface{} {
|
|||||||
|
|
||||||
// base match
|
// base match
|
||||||
type matchBase struct {
|
type matchBase struct {
|
||||||
next PathFn
|
next pathFn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *matchBase) SetNext(next PathFn) {
|
func (f *matchBase) setNext(next pathFn) {
|
||||||
f.next = next
|
f.next = next
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,11 +40,11 @@ func newTerminatingFn() *terminatingFn {
|
|||||||
return &terminatingFn{}
|
return &terminatingFn{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *terminatingFn) SetNext(next PathFn) {
|
func (f *terminatingFn) setNext(next pathFn) {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *terminatingFn) Call(node interface{}, ctx *queryContext) {
|
func (f *terminatingFn) call(node interface{}, ctx *queryContext) {
|
||||||
switch castNode := node.(type) {
|
switch castNode := node.(type) {
|
||||||
case *TomlTree:
|
case *TomlTree:
|
||||||
ctx.result.appendResult(node, castNode.position)
|
ctx.result.appendResult(node, castNode.position)
|
||||||
@@ -66,11 +66,11 @@ func newMatchKeyFn(name string) *matchKeyFn {
|
|||||||
return &matchKeyFn{Name: name}
|
return &matchKeyFn{Name: name}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *matchKeyFn) Call(node interface{}, ctx *queryContext) {
|
func (f *matchKeyFn) call(node interface{}, ctx *queryContext) {
|
||||||
if tree, ok := node.(*TomlTree); ok {
|
if tree, ok := node.(*TomlTree); ok {
|
||||||
item := tree.values[f.Name]
|
item := tree.values[f.Name]
|
||||||
if item != nil {
|
if item != nil {
|
||||||
f.next.Call(item, ctx)
|
f.next.call(item, ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,10 +85,10 @@ func newMatchIndexFn(idx int) *matchIndexFn {
|
|||||||
return &matchIndexFn{Idx: idx}
|
return &matchIndexFn{Idx: idx}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *matchIndexFn) Call(node interface{}, ctx *queryContext) {
|
func (f *matchIndexFn) call(node interface{}, ctx *queryContext) {
|
||||||
if arr, ok := tomlValueCheck(node, ctx).([]interface{}); ok {
|
if arr, ok := tomlValueCheck(node, ctx).([]interface{}); ok {
|
||||||
if f.Idx < len(arr) && f.Idx >= 0 {
|
if f.Idx < len(arr) && f.Idx >= 0 {
|
||||||
f.next.Call(arr[f.Idx], ctx)
|
f.next.call(arr[f.Idx], ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@ func newMatchSliceFn(start, end, step int) *matchSliceFn {
|
|||||||
return &matchSliceFn{Start: start, End: end, Step: step}
|
return &matchSliceFn{Start: start, End: end, Step: step}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *matchSliceFn) Call(node interface{}, ctx *queryContext) {
|
func (f *matchSliceFn) call(node interface{}, ctx *queryContext) {
|
||||||
if arr, ok := tomlValueCheck(node, ctx).([]interface{}); ok {
|
if arr, ok := tomlValueCheck(node, ctx).([]interface{}); ok {
|
||||||
// adjust indexes for negative values, reverse ordering
|
// adjust indexes for negative values, reverse ordering
|
||||||
realStart, realEnd := f.Start, f.End
|
realStart, realEnd := f.Start, f.End
|
||||||
@@ -118,7 +118,7 @@ func (f *matchSliceFn) Call(node interface{}, ctx *queryContext) {
|
|||||||
}
|
}
|
||||||
// loop and gather
|
// loop and gather
|
||||||
for idx := realStart; idx < realEnd; idx += f.Step {
|
for idx := realStart; idx < realEnd; idx += f.Step {
|
||||||
f.next.Call(arr[idx], ctx)
|
f.next.call(arr[idx], ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,28 +132,28 @@ func newMatchAnyFn() *matchAnyFn {
|
|||||||
return &matchAnyFn{}
|
return &matchAnyFn{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *matchAnyFn) Call(node interface{}, ctx *queryContext) {
|
func (f *matchAnyFn) call(node interface{}, ctx *queryContext) {
|
||||||
if tree, ok := node.(*TomlTree); ok {
|
if tree, ok := node.(*TomlTree); ok {
|
||||||
for _, v := range tree.values {
|
for _, v := range tree.values {
|
||||||
f.next.Call(v, ctx)
|
f.next.call(v, ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter through union
|
// filter through union
|
||||||
type matchUnionFn struct {
|
type matchUnionFn struct {
|
||||||
Union []PathFn
|
Union []pathFn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *matchUnionFn) SetNext(next PathFn) {
|
func (f *matchUnionFn) setNext(next pathFn) {
|
||||||
for _, fn := range f.Union {
|
for _, fn := range f.Union {
|
||||||
fn.SetNext(next)
|
fn.setNext(next)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *matchUnionFn) Call(node interface{}, ctx *queryContext) {
|
func (f *matchUnionFn) call(node interface{}, ctx *queryContext) {
|
||||||
for _, fn := range f.Union {
|
for _, fn := range f.Union {
|
||||||
fn.Call(node, ctx)
|
fn.call(node, ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,12 +166,12 @@ func newMatchRecursiveFn() *matchRecursiveFn {
|
|||||||
return &matchRecursiveFn{}
|
return &matchRecursiveFn{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *matchRecursiveFn) Call(node interface{}, ctx *queryContext) {
|
func (f *matchRecursiveFn) call(node interface{}, ctx *queryContext) {
|
||||||
if tree, ok := node.(*TomlTree); ok {
|
if tree, ok := node.(*TomlTree); ok {
|
||||||
var visit func(tree *TomlTree)
|
var visit func(tree *TomlTree)
|
||||||
visit = func(tree *TomlTree) {
|
visit = func(tree *TomlTree) {
|
||||||
for _, v := range tree.values {
|
for _, v := range tree.values {
|
||||||
f.next.Call(v, ctx)
|
f.next.call(v, ctx)
|
||||||
switch node := v.(type) {
|
switch node := v.(type) {
|
||||||
case *TomlTree:
|
case *TomlTree:
|
||||||
visit(node)
|
visit(node)
|
||||||
@@ -182,6 +182,7 @@ func (f *matchRecursiveFn) Call(node interface{}, ctx *queryContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
f.next.call(tree, ctx)
|
||||||
visit(tree)
|
visit(tree)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,7 +198,7 @@ func newMatchFilterFn(name string, pos Position) *matchFilterFn {
|
|||||||
return &matchFilterFn{Name: name, Pos: pos}
|
return &matchFilterFn{Name: name, Pos: pos}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *matchFilterFn) Call(node interface{}, ctx *queryContext) {
|
func (f *matchFilterFn) call(node interface{}, ctx *queryContext) {
|
||||||
fn, ok := (*ctx.filters)[f.Name]
|
fn, ok := (*ctx.filters)[f.Name]
|
||||||
if !ok {
|
if !ok {
|
||||||
panic(fmt.Sprintf("%s: query context does not have filter '%s'",
|
panic(fmt.Sprintf("%s: query context does not have filter '%s'",
|
||||||
@@ -206,45 +207,21 @@ func (f *matchFilterFn) Call(node interface{}, ctx *queryContext) {
|
|||||||
switch castNode := tomlValueCheck(node, ctx).(type) {
|
switch castNode := tomlValueCheck(node, ctx).(type) {
|
||||||
case *TomlTree:
|
case *TomlTree:
|
||||||
for _, v := range castNode.values {
|
for _, v := range castNode.values {
|
||||||
if fn(v) {
|
if tv, ok := v.(*tomlValue); ok {
|
||||||
f.next.Call(v, ctx)
|
if fn(tv.value) {
|
||||||
|
f.next.call(v, ctx)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if fn(v) {
|
||||||
|
f.next.call(v, ctx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
for _, v := range castNode {
|
for _, v := range castNode {
|
||||||
if fn(v) {
|
if fn(v) {
|
||||||
f.next.Call(v, ctx)
|
f.next.call(v, ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// match based using result of an externally provided functional filter
|
|
||||||
type matchScriptFn struct {
|
|
||||||
matchBase
|
|
||||||
Pos Position
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMatchScriptFn(name string, pos Position) *matchScriptFn {
|
|
||||||
return &matchScriptFn{Name: name, Pos: pos}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *matchScriptFn) Call(node interface{}, ctx *queryContext) {
|
|
||||||
fn, ok := (*ctx.scripts)[f.Name]
|
|
||||||
if !ok {
|
|
||||||
panic(fmt.Sprintf("%s: query context does not have script '%s'",
|
|
||||||
f.Pos, f.Name))
|
|
||||||
}
|
|
||||||
switch result := fn(tomlValueCheck(node, ctx)).(type) {
|
|
||||||
case string:
|
|
||||||
nextMatch := newMatchKeyFn(result)
|
|
||||||
nextMatch.SetNext(f.next)
|
|
||||||
nextMatch.Call(node, ctx)
|
|
||||||
case int:
|
|
||||||
nextMatch := newMatchIndexFn(result)
|
|
||||||
nextMatch.SetNext(f.next)
|
|
||||||
nextMatch.Call(node, ctx)
|
|
||||||
//TODO: support other return types?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+4
-18
@@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// dump path tree to a string
|
// dump path tree to a string
|
||||||
func pathString(root PathFn) string {
|
func pathString(root pathFn) string {
|
||||||
result := fmt.Sprintf("%T:", root)
|
result := fmt.Sprintf("%T:", root)
|
||||||
switch fn := root.(type) {
|
switch fn := root.(type) {
|
||||||
case *terminatingFn:
|
case *terminatingFn:
|
||||||
@@ -37,9 +37,6 @@ func pathString(root PathFn) string {
|
|||||||
case *matchFilterFn:
|
case *matchFilterFn:
|
||||||
result += fmt.Sprintf("{%s}", fn.Name)
|
result += fmt.Sprintf("{%s}", fn.Name)
|
||||||
result += pathString(fn.next)
|
result += pathString(fn.next)
|
||||||
case *matchScriptFn:
|
|
||||||
result += fmt.Sprintf("{%s}", fn.Name)
|
|
||||||
result += pathString(fn.next)
|
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -61,7 +58,7 @@ func assertPath(t *testing.T, query string, ref *Query) {
|
|||||||
assertPathMatch(t, path, ref)
|
assertPathMatch(t, path, ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildPath(parts ...PathFn) *Query {
|
func buildPath(parts ...pathFn) *Query {
|
||||||
query := newQuery()
|
query := newQuery()
|
||||||
for _, v := range parts {
|
for _, v := range parts {
|
||||||
query.appendPath(v)
|
query.appendPath(v)
|
||||||
@@ -177,7 +174,7 @@ func TestPathUnion(t *testing.T) {
|
|||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$[foo, bar, baz]",
|
"$[foo, bar, baz]",
|
||||||
buildPath(
|
buildPath(
|
||||||
&matchUnionFn{[]PathFn{
|
&matchUnionFn{[]pathFn{
|
||||||
newMatchKeyFn("foo"),
|
newMatchKeyFn("foo"),
|
||||||
newMatchKeyFn("bar"),
|
newMatchKeyFn("bar"),
|
||||||
newMatchKeyFn("baz"),
|
newMatchKeyFn("baz"),
|
||||||
@@ -197,20 +194,9 @@ func TestPathFilterExpr(t *testing.T) {
|
|||||||
assertPath(t,
|
assertPath(t,
|
||||||
"$[?('foo'),?(bar)]",
|
"$[?('foo'),?(bar)]",
|
||||||
buildPath(
|
buildPath(
|
||||||
&matchUnionFn{[]PathFn{
|
&matchUnionFn{[]pathFn{
|
||||||
newMatchFilterFn("foo", Position{}),
|
newMatchFilterFn("foo", Position{}),
|
||||||
newMatchFilterFn("bar", Position{}),
|
newMatchFilterFn("bar", Position{}),
|
||||||
}},
|
}},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathScriptExpr(t *testing.T) {
|
|
||||||
assertPath(t,
|
|
||||||
"$[('foo'),(bar)]",
|
|
||||||
buildPath(
|
|
||||||
&matchUnionFn{[]PathFn{
|
|
||||||
newMatchScriptFn("foo", Position{}),
|
|
||||||
newMatchScriptFn("bar", Position{}),
|
|
||||||
}},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|||||||
+8
-2
@@ -6,7 +6,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Position within a TOML document
|
/*
|
||||||
|
Position of a document element within a TOML document.
|
||||||
|
|
||||||
|
Line and Col are both 1-indexed positions for the element's line number and
|
||||||
|
column number, respectively. Values of zero or less will cause Invalid(),
|
||||||
|
to return true.
|
||||||
|
*/
|
||||||
type Position struct {
|
type Position struct {
|
||||||
Line int // line within the document
|
Line int // line within the document
|
||||||
Col int // column within the line
|
Col int // column within the line
|
||||||
@@ -18,7 +24,7 @@ func (p *Position) String() string {
|
|||||||
return fmt.Sprintf("(%d, %d)", p.Line, p.Col)
|
return fmt.Sprintf("(%d, %d)", p.Line, p.Col)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalid returns wheter or not the position is valid (i.e. with negative or
|
// Returns whether or not the position is valid (i.e. with negative or
|
||||||
// null values)
|
// null values)
|
||||||
func (p *Position) Invalid() bool {
|
func (p *Position) Invalid() bool {
|
||||||
return p.Line <= 0 || p.Col <= 0
|
return p.Line <= 0 || p.Col <= 0
|
||||||
|
|||||||
@@ -1,22 +1,39 @@
|
|||||||
package toml
|
package toml
|
||||||
|
|
||||||
type nodeFilterFn func(node interface{}) bool
|
import (
|
||||||
type nodeFn func(node interface{}) interface{}
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Type of 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
|
||||||
|
|
||||||
|
// The result of Executing a Query
|
||||||
type QueryResult struct {
|
type QueryResult struct {
|
||||||
items []interface{}
|
items []interface{}
|
||||||
positions []Position
|
positions []Position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// appends a value/position pair to the result set
|
||||||
func (r *QueryResult) appendResult(node interface{}, pos Position) {
|
func (r *QueryResult) appendResult(node interface{}, pos Position) {
|
||||||
r.items = append(r.items, node)
|
r.items = append(r.items, node)
|
||||||
r.positions = append(r.positions, pos)
|
r.positions = append(r.positions, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set of values within a QueryResult. The order of values is not guaranteed
|
||||||
|
// to be in document order, and may be different each time a query is executed.
|
||||||
func (r *QueryResult) Values() []interface{} {
|
func (r *QueryResult) Values() []interface{} {
|
||||||
return r.items
|
return r.items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set of positions for values within a QueryResult. Each index in Positions()
|
||||||
|
// corresponds to the entry in Value() of the same index.
|
||||||
func (r *QueryResult) Positions() []Position {
|
func (r *QueryResult) Positions() []Position {
|
||||||
return r.positions
|
return r.positions
|
||||||
}
|
}
|
||||||
@@ -24,23 +41,22 @@ func (r *QueryResult) Positions() []Position {
|
|||||||
// runtime context for executing query paths
|
// runtime context for executing query paths
|
||||||
type queryContext struct {
|
type queryContext struct {
|
||||||
result *QueryResult
|
result *QueryResult
|
||||||
filters *map[string]nodeFilterFn
|
filters *map[string]NodeFilterFn
|
||||||
scripts *map[string]nodeFn
|
|
||||||
lastPosition Position
|
lastPosition Position
|
||||||
}
|
}
|
||||||
|
|
||||||
// generic path functor interface
|
// generic path functor interface
|
||||||
type PathFn interface {
|
type pathFn interface {
|
||||||
SetNext(next PathFn)
|
setNext(next pathFn)
|
||||||
Call(node interface{}, ctx *queryContext)
|
call(node interface{}, ctx *queryContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
// encapsulates a query functor chain and script callbacks
|
// A Query is the representation of a compiled TOML path. A Query is safe
|
||||||
|
// for concurrent use by multiple goroutines.
|
||||||
type Query struct {
|
type Query struct {
|
||||||
root PathFn
|
root pathFn
|
||||||
tail PathFn
|
tail pathFn
|
||||||
filters *map[string]nodeFilterFn
|
filters *map[string]NodeFilterFn
|
||||||
scripts *map[string]nodeFn
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newQuery() *Query {
|
func newQuery() *Query {
|
||||||
@@ -48,25 +64,26 @@ func newQuery() *Query {
|
|||||||
root: nil,
|
root: nil,
|
||||||
tail: nil,
|
tail: nil,
|
||||||
filters: &defaultFilterFunctions,
|
filters: &defaultFilterFunctions,
|
||||||
scripts: &defaultScriptFunctions,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Query) appendPath(next PathFn) {
|
func (q *Query) appendPath(next pathFn) {
|
||||||
if q.root == nil {
|
if q.root == nil {
|
||||||
q.root = next
|
q.root = next
|
||||||
} else {
|
} else {
|
||||||
q.tail.SetNext(next)
|
q.tail.setNext(next)
|
||||||
}
|
}
|
||||||
q.tail = next
|
q.tail = next
|
||||||
next.SetNext(newTerminatingFn()) // init the next functor
|
next.setNext(newTerminatingFn()) // init the next functor
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: return (err,query) instead
|
// Compiles a TOML path expression. The returned Query can be used to match
|
||||||
func Compile(path string) (*Query, error) {
|
// elements within a TomlTree and its descendants.
|
||||||
|
func CompileQuery(path string) (*Query, error) {
|
||||||
return parseQuery(lexQuery(path))
|
return parseQuery(lexQuery(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Executes a query against a TomlTree, and returns the result of the query.
|
||||||
func (q *Query) Execute(tree *TomlTree) *QueryResult {
|
func (q *Query) Execute(tree *TomlTree) *QueryResult {
|
||||||
result := &QueryResult{
|
result := &QueryResult{
|
||||||
items: []interface{}{},
|
items: []interface{}{},
|
||||||
@@ -78,17 +95,18 @@ func (q *Query) Execute(tree *TomlTree) *QueryResult {
|
|||||||
ctx := &queryContext{
|
ctx := &queryContext{
|
||||||
result: result,
|
result: result,
|
||||||
filters: q.filters,
|
filters: q.filters,
|
||||||
scripts: q.scripts,
|
|
||||||
}
|
}
|
||||||
q.root.Call(tree, ctx)
|
q.root.call(tree, ctx)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Query) SetFilter(name string, fn nodeFilterFn) {
|
// 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 {
|
if q.filters == &defaultFilterFunctions {
|
||||||
// clone the static table
|
// clone the static table
|
||||||
q.filters = &map[string]nodeFilterFn{}
|
q.filters = &map[string]NodeFilterFn{}
|
||||||
for k, v := range defaultFilterFunctions {
|
for k, v := range defaultFilterFunctions {
|
||||||
(*q.filters)[k] = v
|
(*q.filters)[k] = v
|
||||||
}
|
}
|
||||||
@@ -96,37 +114,29 @@ func (q *Query) SetFilter(name string, fn nodeFilterFn) {
|
|||||||
(*q.filters)[name] = fn
|
(*q.filters)[name] = fn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Query) SetScript(name string, fn nodeFn) {
|
var defaultFilterFunctions = map[string]NodeFilterFn{
|
||||||
if q.scripts == &defaultScriptFunctions {
|
"tree": func(node interface{}) bool {
|
||||||
// clone the static table
|
_, ok := node.(*TomlTree)
|
||||||
q.scripts = &map[string]nodeFn{}
|
return ok
|
||||||
for k, v := range defaultScriptFunctions {
|
|
||||||
(*q.scripts)[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(*q.scripts)[name] = fn
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultFilterFunctions = map[string]nodeFilterFn{
|
|
||||||
"odd": func(node interface{}) bool {
|
|
||||||
if ii, ok := node.(int64); ok {
|
|
||||||
return (ii & 1) == 1
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
},
|
||||||
"even": func(node interface{}) bool {
|
"int": func(node interface{}) bool {
|
||||||
if ii, ok := node.(int64); ok {
|
_, ok := node.(int64)
|
||||||
return (ii & 1) == 0
|
return ok
|
||||||
}
|
},
|
||||||
return false
|
"float": func(node interface{}) bool {
|
||||||
},
|
_, ok := node.(float64)
|
||||||
}
|
return ok
|
||||||
|
},
|
||||||
var defaultScriptFunctions = map[string]nodeFn{
|
"string": func(node interface{}) bool {
|
||||||
"last": func(node interface{}) interface{} {
|
_, ok := node.(string)
|
||||||
if arr, ok := node.([]interface{}); ok {
|
return ok
|
||||||
return len(arr) - 1
|
},
|
||||||
}
|
"time": func(node interface{}) bool {
|
||||||
return nil
|
_, ok := node.(time.Time)
|
||||||
|
return ok
|
||||||
|
},
|
||||||
|
"bool": func(node interface{}) bool {
|
||||||
|
_, ok := node.(bool)
|
||||||
|
return ok
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-18
@@ -16,7 +16,7 @@ type queryParser struct {
|
|||||||
flow chan token
|
flow chan token
|
||||||
tokensBuffer []token
|
tokensBuffer []token
|
||||||
query *Query
|
query *Query
|
||||||
union []PathFn
|
union []pathFn
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ func (p *queryParser) parseUnionExpr() queryParserStateFn {
|
|||||||
// this state can be traversed after some sub-expressions
|
// this state can be traversed after some sub-expressions
|
||||||
// so be careful when setting up state in the parser
|
// so be careful when setting up state in the parser
|
||||||
if p.union == nil {
|
if p.union == nil {
|
||||||
p.union = []PathFn{}
|
p.union = []pathFn{}
|
||||||
}
|
}
|
||||||
|
|
||||||
loop: // labeled loop for easy breaking
|
loop: // labeled loop for easy breaking
|
||||||
@@ -185,8 +185,6 @@ loop: // labeled loop for easy breaking
|
|||||||
p.union = append(p.union, newMatchKeyFn(tok.val))
|
p.union = append(p.union, newMatchKeyFn(tok.val))
|
||||||
case tokenQuestion:
|
case tokenQuestion:
|
||||||
return p.parseFilterExpr
|
return p.parseFilterExpr
|
||||||
case tokenLeftParen:
|
|
||||||
return p.parseScriptExpr
|
|
||||||
default:
|
default:
|
||||||
return p.parseError(tok, "expected union sub expression, not '%s', %d", tok.val, len(p.union))
|
return p.parseError(tok, "expected union sub expression, not '%s', %d", tok.val, len(p.union))
|
||||||
}
|
}
|
||||||
@@ -266,20 +264,6 @@ func (p *queryParser) parseFilterExpr() queryParserStateFn {
|
|||||||
return p.parseUnionExpr
|
return p.parseUnionExpr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *queryParser) parseScriptExpr() queryParserStateFn {
|
|
||||||
tok := p.getToken()
|
|
||||||
if tok.typ != tokenKey && tok.typ != tokenString {
|
|
||||||
return p.parseError(tok, "expected key or string for script funciton name")
|
|
||||||
}
|
|
||||||
name := tok.val
|
|
||||||
tok = p.getToken()
|
|
||||||
if tok.typ != tokenRightParen {
|
|
||||||
return p.parseError(tok, "expected right-parenthesis for script expression")
|
|
||||||
}
|
|
||||||
p.union = append(p.union, newMatchScriptFn(name, tok.Position))
|
|
||||||
return p.parseUnionExpr
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseQuery(flow chan token) (*Query, error) {
|
func parseQuery(flow chan token) (*Query, error) {
|
||||||
parser := &queryParser{
|
parser := &queryParser{
|
||||||
flow: flow,
|
flow: flow,
|
||||||
|
|||||||
+149
-34
@@ -2,9 +2,11 @@ package toml
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type queryTestNode struct {
|
type queryTestNode struct {
|
||||||
@@ -56,6 +58,12 @@ func valueString(root interface{}) string {
|
|||||||
result += fmt.Sprintf("%d", node)
|
result += fmt.Sprintf("%d", node)
|
||||||
case string:
|
case string:
|
||||||
result += "'" + node + "'"
|
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
|
return result
|
||||||
}
|
}
|
||||||
@@ -76,7 +84,7 @@ func assertQueryPositions(t *testing.T, toml, query string, ref []interface{}) {
|
|||||||
t.Errorf("Non-nil toml parse error: %v", err)
|
t.Errorf("Non-nil toml parse error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
q, err := Compile(query)
|
q, err := CompileQuery(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@@ -221,6 +229,28 @@ func TestQueryRecursionAll(t *testing.T) {
|
|||||||
"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
|
"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
|
||||||
"$..*",
|
"$..*",
|
||||||
[]interface{}{
|
[]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),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, Position{1, 1},
|
||||||
|
},
|
||||||
queryTestNode{
|
queryTestNode{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"bar": map[string]interface{}{
|
"bar": map[string]interface{}{
|
||||||
@@ -291,8 +321,10 @@ func TestQueryRecursionUnionSimple(t *testing.T) {
|
|||||||
[]interface{}{
|
[]interface{}{
|
||||||
queryTestNode{
|
queryTestNode{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"a": int64(1),
|
"bar": map[string]interface{}{
|
||||||
"b": int64(2),
|
"a": int64(1),
|
||||||
|
"b": int64(2),
|
||||||
|
},
|
||||||
}, Position{1, 1},
|
}, Position{1, 1},
|
||||||
},
|
},
|
||||||
queryTestNode{
|
queryTestNode{
|
||||||
@@ -301,6 +333,12 @@ func TestQueryRecursionUnionSimple(t *testing.T) {
|
|||||||
"b": int64(4),
|
"b": int64(4),
|
||||||
}, Position{4, 1},
|
}, Position{4, 1},
|
||||||
},
|
},
|
||||||
|
queryTestNode{
|
||||||
|
map[string]interface{}{
|
||||||
|
"a": int64(1),
|
||||||
|
"b": int64(2),
|
||||||
|
}, Position{1, 1},
|
||||||
|
},
|
||||||
queryTestNode{
|
queryTestNode{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"a": int64(5),
|
"a": int64(5),
|
||||||
@@ -310,59 +348,136 @@ func TestQueryRecursionUnionSimple(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQueryScriptFnLast(t *testing.T) {
|
func TestQueryFilterFn(t *testing.T) {
|
||||||
assertQueryPositions(t,
|
buff, err := ioutil.ReadFile("example.toml")
|
||||||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
if err != nil {
|
||||||
"$.foo.a[(last)]",
|
t.Error(err)
|
||||||
[]interface{}{
|
return
|
||||||
queryTestNode{
|
}
|
||||||
int64(9), Position{2, 1},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestQueryFilterFnOdd(t *testing.T) {
|
assertQueryPositions(t, string(buff),
|
||||||
assertQueryPositions(t,
|
"$..[?(int)]",
|
||||||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
|
||||||
"$.foo.a[?(odd)]",
|
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
queryTestNode{
|
queryTestNode{
|
||||||
int64(1), Position{2, 1},
|
int64(8001), Position{13, 1},
|
||||||
},
|
},
|
||||||
queryTestNode{
|
queryTestNode{
|
||||||
int64(3), Position{2, 1},
|
int64(8001), Position{13, 1},
|
||||||
},
|
},
|
||||||
queryTestNode{
|
queryTestNode{
|
||||||
int64(5), Position{2, 1},
|
int64(8002), Position{13, 1},
|
||||||
},
|
},
|
||||||
queryTestNode{
|
queryTestNode{
|
||||||
int64(7), Position{2, 1},
|
int64(5000), Position{14, 1},
|
||||||
},
|
|
||||||
queryTestNode{
|
|
||||||
int64(9), Position{2, 1},
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
func TestQueryFilterFnEven(t *testing.T) {
|
assertQueryPositions(t, string(buff),
|
||||||
assertQueryPositions(t,
|
"$..[?(string)]",
|
||||||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
|
||||||
"$.foo.a[?(even)]",
|
|
||||||
[]interface{}{
|
[]interface{}{
|
||||||
queryTestNode{
|
queryTestNode{
|
||||||
int64(0), Position{2, 1},
|
"TOML Example", Position{3, 1},
|
||||||
},
|
},
|
||||||
queryTestNode{
|
queryTestNode{
|
||||||
int64(2), Position{2, 1},
|
"Tom Preston-Werner", Position{6, 1},
|
||||||
},
|
},
|
||||||
queryTestNode{
|
queryTestNode{
|
||||||
int64(4), Position{2, 1},
|
"GitHub", Position{7, 1},
|
||||||
},
|
},
|
||||||
queryTestNode{
|
queryTestNode{
|
||||||
int64(6), Position{2, 1},
|
"GitHub Cofounder & CEO\nLikes tater tots and beer.",
|
||||||
|
Position{8, 1},
|
||||||
},
|
},
|
||||||
queryTestNode{
|
queryTestNode{
|
||||||
int64(8), Position{2, 1},
|
"192.168.1.1", Position{12, 1},
|
||||||
|
},
|
||||||
|
queryTestNode{
|
||||||
|
"10.0.0.1", Position{21, 3},
|
||||||
|
},
|
||||||
|
queryTestNode{
|
||||||
|
"eqdc10", Position{22, 3},
|
||||||
|
},
|
||||||
|
queryTestNode{
|
||||||
|
"10.0.0.2", Position{25, 3},
|
||||||
|
},
|
||||||
|
queryTestNode{
|
||||||
|
"eqdc10", 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,
|
||||||
|
}, 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,
|
||||||
|
}, 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",
|
||||||
|
},
|
||||||
|
}, Position{17, 1},
|
||||||
|
},
|
||||||
|
queryTestNode{
|
||||||
|
map[string]interface{}{
|
||||||
|
"ip": "10.0.0.1",
|
||||||
|
"dc": "eqdc10",
|
||||||
|
}, Position{20, 3},
|
||||||
|
},
|
||||||
|
queryTestNode{
|
||||||
|
map[string]interface{}{
|
||||||
|
"ip": "10.0.0.2",
|
||||||
|
"dc": "eqdc10",
|
||||||
|
}, Position{24, 3},
|
||||||
|
},
|
||||||
|
queryTestNode{
|
||||||
|
map[string]interface{}{
|
||||||
|
"data": []interface{}{
|
||||||
|
[]interface{}{"gamma", "delta"},
|
||||||
|
[]interface{}{int64(1), int64(2)},
|
||||||
|
},
|
||||||
|
}, Position{28, 1},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
assertQueryPositions(t, string(buff),
|
||||||
|
"$..[?(time)]",
|
||||||
|
[]interface{}{
|
||||||
|
queryTestNode{
|
||||||
|
tv, Position{9, 1},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
assertQueryPositions(t, string(buff),
|
||||||
|
"$..[?(bool)]",
|
||||||
|
[]interface{}{
|
||||||
|
queryTestNode{
|
||||||
|
true, Position{15, 1},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
// Package toml is a TOML markup language parser.
|
|
||||||
//
|
|
||||||
// This version supports the specification as described in
|
|
||||||
// https://github.com/toml-lang/toml/blob/master/versions/toml-v0.2.0.md
|
|
||||||
package toml
|
package toml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -320,7 +316,7 @@ func (t *TomlTree) toToml(indent, keyspace string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *TomlTree) Query(query string) (*QueryResult, error) {
|
func (t *TomlTree) Query(query string) (*QueryResult, error) {
|
||||||
if q, err := Compile(query); err != nil {
|
if q, err := CompileQuery(query); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return q.Execute(t), nil
|
return q.Execute(t), nil
|
||||||
|
|||||||
Reference in New Issue
Block a user