package jpath import ( . "github.com/pelletier/go-toml" ) // result set for storage of results type pathResult struct { Values []interface{} } func newPathResult() *pathResult { return &pathResult { Values: []interface{}{}, } } func (r *pathResult) Append(value interface{}) { r.Values = append(r.Values, value) } // generic path functor interface type PathFn interface{ SetNext(next PathFn) Call(context interface{}, results *pathResult) } // contains a functor chain type QueryPath struct { root PathFn tail PathFn } func newQueryPath() *QueryPath { return &QueryPath { root: nil, tail: nil, } } func (path *QueryPath) Append(next PathFn) { if path.root == nil { path.root = next } else { path.tail.SetNext(next) } path.tail = next next.SetNext(newTerminatingFn()) // init the next functor } func (path *QueryPath) Call(context interface{}) []interface{} { results := newPathResult() if path.root == nil { results.Append(context) // identity query for no predicates } else { path.root.Call(context, results) } return results.Values } // base match type matchBase struct { next PathFn } func (f *matchBase) SetNext(next PathFn) { f.next = next } // terminating functor - gathers results type terminatingFn struct { // empty } func newTerminatingFn() *terminatingFn { return &terminatingFn{} } func (f *terminatingFn) SetNext(next PathFn) { // do nothing } func (f *terminatingFn) Call(context interface{}, results *pathResult) { results.Append(context) } // shim to ease functor writing func treeValue(tree *TomlTree, key string) interface{} { return tree.GetPath([]string{key}) } // match single key type matchKeyFn struct { matchBase Name string } func newMatchKeyFn(name string) *matchKeyFn { return &matchKeyFn{ Name: name } } func (f *matchKeyFn) Call(context interface{}, results *pathResult) { if tree, ok := context.(*TomlTree); ok { item := treeValue(tree, f.Name) if item != nil { f.next.Call(item, results) } } } // match single index type matchIndexFn struct { matchBase Idx int } func newMatchIndexFn(idx int) *matchIndexFn { return &matchIndexFn{ Idx: idx } } func (f *matchIndexFn) Call(context interface{}, results *pathResult) { if arr, ok := context.([]interface{}); ok { if f.Idx < len(arr) && f.Idx >= 0 { f.next.Call(arr[f.Idx], results) } } } // filter by slicing type matchSliceFn struct { matchBase Start, End, Step int } func newMatchSliceFn(start, end, step int) *matchSliceFn { return &matchSliceFn{ Start: start, End: end, Step: step } } func (f *matchSliceFn) Call(context interface{}, results *pathResult) { if arr, ok := context.([]interface{}); ok { // adjust indexes for negative values, reverse ordering realStart, realEnd := f.Start, f.End if realStart < 0 { realStart = len(arr) + realStart } if realEnd < 0 { realEnd = len(arr) + realEnd } if realEnd < realStart { realEnd, realStart = realStart, realEnd // swap } // loop and gather for idx := realStart; idx < realEnd; idx += f.Step { f.next.Call(arr[idx], results) } } } // match anything type matchAnyFn struct { matchBase // empty } func newMatchAnyFn() *matchAnyFn { return &matchAnyFn{} } func (f *matchAnyFn) Call(context interface{}, results *pathResult) { if tree, ok := context.(*TomlTree); ok { for _, key := range tree.Keys() { item := treeValue(tree, key) f.next.Call(item, results) } } } // filter through union type matchUnionFn struct { Union []PathFn } func (f *matchUnionFn) SetNext(next PathFn) { for _, fn := range f.Union { fn.SetNext(next) } } func (f *matchUnionFn) Call(context interface{}, results *pathResult) { for _, fn := range f.Union { fn.Call(context, results) } } // match every single last node in the tree type matchRecursiveFn struct { matchBase } func newMatchRecursiveFn() *matchRecursiveFn{ return &matchRecursiveFn{} } func (f *matchRecursiveFn) Call(context interface{}, results *pathResult) { if tree, ok := context.(*TomlTree); ok { var visit func(tree *TomlTree) visit = func(tree *TomlTree) { for _, key := range tree.Keys() { item := treeValue(tree, key) f.next.Call(item, results) switch node := item.(type) { case *TomlTree: visit(node) case []*TomlTree: for _, subtree := range node { visit(subtree) } } } } visit(tree) } }