Final Toml-Path Solution
* Refactored type names and file names to mesh with existing TOML library more closely * Added QueryResult structure that provides values and position data * Added Query() method to TomlTree type * Tests, tests, and more tests * Fixed bug where positions returned from some tables were invalid * Added test case for bug patch The bugfix was an interesting case. Position information wasn't being set in cases where createPath was called. So table names like [foo.bar] would result in table 'foo' having no position.
This commit is contained in:
@@ -0,0 +1,368 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type queryTestNode struct {
|
||||
value interface{}
|
||||
position Position
|
||||
}
|
||||
|
||||
func valueString(root interface{}) string {
|
||||
result := "" //fmt.Sprintf("%T:", root)
|
||||
switch node := root.(type) {
|
||||
case *tomlValue:
|
||||
return valueString(node.value)
|
||||
case *QueryResult:
|
||||
items := []string{}
|
||||
for i, v := range node.Values() {
|
||||
items = append(items, fmt.Sprintf("%s:%s",
|
||||
node.Positions()[i].String(), valueString(v)))
|
||||
}
|
||||
sort.Strings(items)
|
||||
result = "[" + strings.Join(items, ", ") + "]"
|
||||
case queryTestNode:
|
||||
result = fmt.Sprintf("%s:%s",
|
||||
node.position.String(), valueString(node.value))
|
||||
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 assertQueryPositions(t *testing.T, toml, query string, ref []interface{}) {
|
||||
tree, err := Load(toml)
|
||||
if err != nil {
|
||||
t.Errorf("Non-nil toml parse error: %v", err)
|
||||
return
|
||||
}
|
||||
q, err := Compile(query)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
results := q.Execute(tree)
|
||||
assertValue(t, results, ref)
|
||||
}
|
||||
|
||||
func TestQueryRoot(t *testing.T) {
|
||||
assertQueryPositions(t,
|
||||
"a = 42",
|
||||
"$",
|
||||
[]interface{}{
|
||||
queryTestNode{
|
||||
map[string]interface{}{
|
||||
"a": int64(42),
|
||||
}, Position{1, 1},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestQueryKey(t *testing.T) {
|
||||
assertQueryPositions(t,
|
||||
"[foo]\na = 42",
|
||||
"$.foo.a",
|
||||
[]interface{}{
|
||||
queryTestNode{
|
||||
int64(42), Position{2,1},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestQueryKeyString(t *testing.T) {
|
||||
assertQueryPositions(t,
|
||||
"[foo]\na = 42",
|
||||
"$.foo['a']",
|
||||
[]interface{}{
|
||||
queryTestNode{
|
||||
int64(42), Position{2,1},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestQueryIndex(t *testing.T) {
|
||||
assertQueryPositions(t,
|
||||
"[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
|
||||
"$.foo.a[5]",
|
||||
[]interface{}{
|
||||
queryTestNode{
|
||||
int64(6), Position{2,1},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestQuerySliceRange(t *testing.T) {
|
||||
assertQueryPositions(t,
|
||||
"[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
|
||||
"$.foo.a[0:5]",
|
||||
[]interface{}{
|
||||
queryTestNode{
|
||||
int64(1), Position{2,1},
|
||||
},
|
||||
queryTestNode{
|
||||
int64(2), Position{2,1},
|
||||
},
|
||||
queryTestNode{
|
||||
int64(3), Position{2,1},
|
||||
},
|
||||
queryTestNode{
|
||||
int64(4), Position{2,1},
|
||||
},
|
||||
queryTestNode{
|
||||
int64(5), Position{2,1},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestQuerySliceStep(t *testing.T) {
|
||||
assertQueryPositions(t,
|
||||
"[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
|
||||
"$.foo.a[0:5:2]",
|
||||
[]interface{}{
|
||||
queryTestNode{
|
||||
int64(1), Position{2,1},
|
||||
},
|
||||
queryTestNode{
|
||||
int64(3), Position{2,1},
|
||||
},
|
||||
queryTestNode{
|
||||
int64(5), Position{2,1},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestQueryAny(t *testing.T) {
|
||||
assertQueryPositions(t,
|
||||
"[foo.bar]\na=1\nb=2\n[foo.baz]\na=3\nb=4",
|
||||
"$.foo.*",
|
||||
[]interface{}{
|
||||
queryTestNode{
|
||||
map[string]interface{}{
|
||||
"a": int64(1),
|
||||
"b": int64(2),
|
||||
}, Position{1,1},
|
||||
},
|
||||
queryTestNode{
|
||||
map[string]interface{}{
|
||||
"a": int64(3),
|
||||
"b": int64(4),
|
||||
}, Position{4,1},
|
||||
},
|
||||
})
|
||||
}
|
||||
func TestQueryUnionSimple(t *testing.T) {
|
||||
assertQueryPositions(t,
|
||||
"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
|
||||
"$.*[bar,foo]",
|
||||
[]interface{}{
|
||||
queryTestNode{
|
||||
map[string]interface{}{
|
||||
"a": int64(1),
|
||||
"b": int64(2),
|
||||
}, Position{1,1},
|
||||
},
|
||||
queryTestNode{
|
||||
map[string]interface{}{
|
||||
"a": int64(3),
|
||||
"b": int64(4),
|
||||
}, Position{4,1},
|
||||
},
|
||||
queryTestNode{
|
||||
map[string]interface{}{
|
||||
"a": int64(5),
|
||||
"b": int64(6),
|
||||
}, Position{7,1},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestQueryRecursionAll(t *testing.T) {
|
||||
assertQueryPositions(t,
|
||||
"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
|
||||
"$..*",
|
||||
[]interface{}{
|
||||
queryTestNode{
|
||||
map[string]interface{}{
|
||||
"bar": map[string]interface{}{
|
||||
"a": int64(1),
|
||||
"b": int64(2),
|
||||
},
|
||||
}, Position{1,1},
|
||||
},
|
||||
queryTestNode{
|
||||
map[string]interface{}{
|
||||
"a": int64(1),
|
||||
"b": int64(2),
|
||||
}, Position{1,1},
|
||||
},
|
||||
queryTestNode{
|
||||
int64(1), Position{2,1},
|
||||
},
|
||||
queryTestNode{
|
||||
int64(2), Position{3,1},
|
||||
},
|
||||
queryTestNode{
|
||||
map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"a": int64(3),
|
||||
"b": int64(4),
|
||||
},
|
||||
}, Position{4,1},
|
||||
},
|
||||
queryTestNode{
|
||||
map[string]interface{}{
|
||||
"a": int64(3),
|
||||
"b": int64(4),
|
||||
}, Position{4,1},
|
||||
},
|
||||
queryTestNode{
|
||||
int64(3), Position{5,1},
|
||||
},
|
||||
queryTestNode{
|
||||
int64(4), Position{6,1},
|
||||
},
|
||||
queryTestNode{
|
||||
map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"a": int64(5),
|
||||
"b": int64(6),
|
||||
},
|
||||
}, Position{7,1},
|
||||
},
|
||||
queryTestNode{
|
||||
map[string]interface{}{
|
||||
"a": int64(5),
|
||||
"b": int64(6),
|
||||
}, Position{7,1},
|
||||
},
|
||||
queryTestNode{
|
||||
int64(5), Position{8,1},
|
||||
},
|
||||
queryTestNode{
|
||||
int64(6), Position{9,1},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestQueryRecursionUnionSimple(t *testing.T) {
|
||||
assertQueryPositions(t,
|
||||
"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
|
||||
"$..['foo','bar']",
|
||||
[]interface{}{
|
||||
queryTestNode{
|
||||
map[string]interface{}{
|
||||
"a": int64(1),
|
||||
"b": int64(2),
|
||||
}, Position{1,1},
|
||||
},
|
||||
queryTestNode{
|
||||
map[string]interface{}{
|
||||
"a": int64(3),
|
||||
"b": int64(4),
|
||||
}, Position{4,1},
|
||||
},
|
||||
queryTestNode{
|
||||
map[string]interface{}{
|
||||
"a": int64(5),
|
||||
"b": int64(6),
|
||||
}, Position{7,1},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestQueryScriptFnLast(t *testing.T) {
|
||||
assertQueryPositions(t,
|
||||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||||
"$.foo.a[(last)]",
|
||||
[]interface{}{
|
||||
queryTestNode{
|
||||
int64(9), Position{2,1},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestQueryFilterFnOdd(t *testing.T) {
|
||||
assertQueryPositions(t,
|
||||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||||
"$.foo.a[?(odd)]",
|
||||
[]interface{}{
|
||||
queryTestNode{
|
||||
int64(1), Position{2,1},
|
||||
},
|
||||
queryTestNode{
|
||||
int64(3), Position{2,1},
|
||||
},
|
||||
queryTestNode{
|
||||
int64(5), Position{2,1},
|
||||
},
|
||||
queryTestNode{
|
||||
int64(7), Position{2,1},
|
||||
},
|
||||
queryTestNode{
|
||||
int64(9), Position{2,1},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestQueryFilterFnEven(t *testing.T) {
|
||||
assertQueryPositions(t,
|
||||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||||
"$.foo.a[?(even)]",
|
||||
[]interface{}{
|
||||
queryTestNode{
|
||||
int64(0), Position{2,1},
|
||||
},
|
||||
queryTestNode{
|
||||
int64(2), Position{2,1},
|
||||
},
|
||||
queryTestNode{
|
||||
int64(4), Position{2,1},
|
||||
},
|
||||
queryTestNode{
|
||||
int64(6), Position{2,1},
|
||||
},
|
||||
queryTestNode{
|
||||
int64(8), Position{2,1},
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user