| package toml |
| |
| import ( |
| "time" |
| ) |
| |
| // 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 |
| |
| // QueryResult is the result of Executing a Query. |
| type QueryResult struct { |
| items []interface{} |
| positions []Position |
| } |
| |
| // appends a value/position pair to the result set. |
| func (r *QueryResult) appendResult(node interface{}, pos Position) { |
| r.items = append(r.items, node) |
| r.positions = append(r.positions, pos) |
| } |
| |
| // Values is a 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{} { |
| values := make([]interface{}, len(r.items)) |
| for i, v := range r.items { |
| o, ok := v.(*tomlValue) |
| if ok { |
| values[i] = o.value |
| } else { |
| values[i] = v |
| } |
| } |
| return values |
| } |
| |
| // Positions is a 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 { |
| return r.positions |
| } |
| |
| // runtime context for executing query paths |
| type queryContext struct { |
| result *QueryResult |
| filters *map[string]NodeFilterFn |
| lastPosition Position |
| } |
| |
| // generic path functor interface |
| type pathFn interface { |
| setNext(next pathFn) |
| 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 |
| } |
| |
| // CompileQuery compiles a TOML path expression. The returned Query can be used |
| // to match elements within a TomlTree and its descendants. |
| func CompileQuery(path string) (*Query, error) { |
| return parseQuery(lexQuery(path)) |
| } |
| |
| // Execute executes a query against a TomlTree, and returns the result of the query. |
| func (q *Query) Execute(tree *TomlTree) *QueryResult { |
| result := &QueryResult{ |
| items: []interface{}{}, |
| positions: []Position{}, |
| } |
| if q.root == nil { |
| result.appendResult(tree, tree.GetPosition("")) |
| } else { |
| ctx := &queryContext{ |
| result: result, |
| filters: q.filters, |
| } |
| q.root.call(tree, ctx) |
| } |
| return result |
| } |
| |
| // 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.(*TomlTree) |
| 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 |
| }, |
| } |