diff --git a/jpath/match.go b/jpath/match.go index c517157..bdc3db5 100644 --- a/jpath/match.go +++ b/jpath/match.go @@ -28,7 +28,7 @@ func (f *terminatingFn) SetNext(next PathFn) { } func (f *terminatingFn) Call(node interface{}, ctx *queryContext) { - ctx.appendResult(node) + ctx.result.appendResult(node) } // shim to ease functor writing diff --git a/jpath/parser_test.go b/jpath/parser_test.go index ec05f07..61908de 100644 --- a/jpath/parser_test.go +++ b/jpath/parser_test.go @@ -4,8 +4,55 @@ import ( "fmt" . "github.com/pelletier/go-toml" "testing" + "sort" + "strings" ) +func valueString(root interface{}) string { + result := "" //fmt.Sprintf("%T:", root) + switch node := root.(type) { + case []interface{}: + items := []string{} + for _, v := range node { + items = append(items, valueString(v)) + } + sort.Strings(items) + result = "[" + strings.Join(items, ", ") + "]" + case *TomlTree: + // workaround for unreliable map key ordering + items := []string{} + for _, k := range node.Keys() { + v := node.GetPath([]string{k}) + items = append(items, k + ":" + valueString(v)) + } + sort.Strings(items) + result = "{" + strings.Join(items, ", ") + "}" + case map[string]interface{}: + // workaround for unreliable map key ordering + items := []string{} + for k, v := range node { + items = append(items, k + ":" + valueString(v)) + } + sort.Strings(items) + result = "{" + strings.Join(items, ", ") + "}" + case int64: + result += fmt.Sprintf("%d", node) + case string: + result += "'" + node + "'" + } + return result +} + +func assertValue(t *testing.T, result, ref interface{}) { + pathStr := valueString(result) + refStr := valueString(ref) + if pathStr != refStr { + t.Errorf("values do not match") + t.Log("test:", pathStr) + t.Log("ref: ", refStr) + } +} + func assertQuery(t *testing.T, toml, query string, ref []interface{}) { tree, err := Load(toml) if err != nil { @@ -13,59 +60,9 @@ func assertQuery(t *testing.T, toml, query string, ref []interface{}) { return } results := Compile(query).Execute(tree) - assertValue(t, results, ref, "(("+query+")) -> ") + assertValue(t, results.Values(), ref) } -func assertValue(t *testing.T, result, ref interface{}, location string) { - switch node := ref.(type) { - case []interface{}: - if resultNode, ok := result.([]interface{}); !ok { - t.Errorf("{%s} result value not of type %T: %T", - location, node, resultNode) - } else { - if len(node) != len(resultNode) { - t.Errorf("{%s} lengths do not match: %v vs %v", - location, node, resultNode) - } else { - for i, v := range node { - assertValue(t, resultNode[i], v, fmt.Sprintf("%s[%d]", location, i)) - } - } - } - case map[string]interface{}: - if resultNode, ok := result.(*TomlTree); !ok { - t.Errorf("{%s} result value not of type %T: %T", - location, node, resultNode) - } else { - for k, v := range node { - assertValue(t, resultNode.GetPath([]string{k}), v, location+"."+k) - } - } - case int64: - if resultNode, ok := result.(int64); !ok { - t.Errorf("{%s} result value not of type %T: %T", - location, node, resultNode) - } else { - if node != resultNode { - t.Errorf("{%s} result value does not match", location) - } - } - case string: - if resultNode, ok := result.(string); !ok { - t.Errorf("{%s} result value not of type %T: %T", - location, node, resultNode) - } else { - if node != resultNode { - t.Errorf("{%s} result value does not match", location) - } - } - default: - if fmt.Sprintf("%v", node) != fmt.Sprintf("%v", ref) { - t.Errorf("{%s} result value does not match: %v != %v", - location, node, ref) - } - } -} func TestQueryRoot(t *testing.T) { assertQuery(t, diff --git a/jpath/query.go b/jpath/query.go index 3bf306c..3435515 100644 --- a/jpath/query.go +++ b/jpath/query.go @@ -1,21 +1,44 @@ package jpath import ( - _ "github.com/pelletier/go-toml" + . "github.com/pelletier/go-toml" ) type nodeFilterFn func(node interface{}) bool type nodeFn func(node interface{}) interface{} -// runtime context for executing query paths -type queryContext struct { - filters *map[string]nodeFilterFn - scripts *map[string]nodeFn - results []interface{} +type QueryResult struct { + items []interface{} + positions []Position } -func (c *queryContext) appendResult(value interface{}) { - c.results = append(c.results, value) +// TODO: modify after merging with rest of lib +func (r *QueryResult) appendResult(node interface{}) { + r.items = append(r.items, node) + switch castNode := node.(type) { + case *TomlTree: + r.positions = append(r.positions, castNode.GetPosition("")) + //r.positions = append(r.positions, castNode.position) + //case *tomlValue: + //r.positions = append(r.positions, castNode.position) + default: + r.positions = append(r.positions, Position{}) + } +} + +func (r *QueryResult) Values() []interface{} { + return r.items +} + +func (r *QueryResult) Positions() []Position { + return r.positions +} + +// runtime context for executing query paths +type queryContext struct { + result *QueryResult + filters *map[string]nodeFilterFn + scripts *map[string]nodeFn } // generic path functor interface @@ -56,17 +79,22 @@ func Compile(path string) *Query { return parse(flow) } -func (q *Query) Execute(node interface{}) interface{} { +func (q *Query) Execute(tree *TomlTree) *QueryResult { + result := &QueryResult { + items: []interface{}{}, + positions: []Position{}, + } if q.root == nil { - return []interface{}{node} // identity query for no predicates - } - ctx := &queryContext{ - filters: q.filters, - scripts: q.scripts, - results: []interface{}{}, - } - q.root.Call(node, ctx) - return ctx.results + result.appendResult(tree) + } else { + ctx := &queryContext{ + result: result, + filters: q.filters, + scripts: q.scripts, + } + q.root.Call(tree, ctx) + } + return result } func (q *Query) SetFilter(name string, fn nodeFilterFn) { diff --git a/parser.go b/parser.go index c9f42a3..dcab890 100644 --- a/parser.go +++ b/parser.go @@ -70,9 +70,6 @@ func (p *parser) getToken() *token { func parseStart(p *parser) parserStateFn { tok := p.peek() - // prime position data with root tree instance - p.tree.position = tok.Position - // end of stream, parsing is finished if tok == nil { return nil @@ -279,6 +276,7 @@ func parseArray(p *parser) []interface{} { func parse(flow chan token) *TomlTree { result := newTomlTree() + result.position = Position{1,1} parser := &parser{ flow: flow, tree: result, diff --git a/parser_test.go b/parser_test.go index 761a181..10ac6d7 100644 --- a/parser_test.go +++ b/parser_test.go @@ -396,6 +396,7 @@ func TestDocumentPositions(t *testing.T) { assertPosition(t, "[foo]\nbar=42\nbaz=69", map[string]Position{ + "": Position{1, 1}, "foo": Position{1, 1}, "foo.bar": Position{2, 1}, "foo.baz": Position{3, 1}, @@ -406,6 +407,7 @@ func TestDocumentPositionsWithSpaces(t *testing.T) { assertPosition(t, " [foo]\n bar=42\n baz=69", map[string]Position{ + "": Position{1, 1}, "foo": Position{1, 3}, "foo.bar": Position{2, 3}, "foo.baz": Position{3, 3}, @@ -416,20 +418,9 @@ func TestDocumentPositionsWithGroupArray(t *testing.T) { assertPosition(t, "[[foo]]\nbar=42\nbaz=69", map[string]Position{ + "": Position{1, 1}, "foo": Position{1, 1}, "foo.bar": Position{2, 1}, "foo.baz": Position{3, 1}, }) } - -func TestDocumentPositionsEmptyPath(t *testing.T) { - text := "[foo]\nbar=42\nbaz=69" - tree, err := Load(text) - if err != nil { - t.Errorf("Error loading document text: `%v`", text) - t.Errorf("Error: %v", err) - } - if pos := tree.GetPosition(""); !pos.Invalid() { - t.Errorf("Valid position was returned for empty path") - } -} diff --git a/toml.go b/toml.go index 7cd74b0..678959a 100644 --- a/toml.go +++ b/toml.go @@ -28,7 +28,7 @@ type TomlTree struct { func newTomlTree() *TomlTree { return &TomlTree{ values: make(map[string]interface{}), - position: Position{0, 0}, + position: Position{}, } } @@ -103,7 +103,7 @@ func (t *TomlTree) GetPath(keys []string) interface{} { // GetPosition returns the position of the given key. func (t *TomlTree) GetPosition(key string) Position { if key == "" { - return Position{0, 0} + return t.position } return t.GetPositionPath(strings.Split(key, ".")) }