This commit is contained in:
Mike Farah 2020-10-21 12:54:58 +11:00
parent 6a698332dd
commit 65e6e492cd
28 changed files with 232 additions and 148 deletions

2
go.mod
View File

@ -1,7 +1,7 @@
module github.com/mikefarah/yq/v3 module github.com/mikefarah/yq/v3
require ( require (
github.com/elliotchance/orderedmap v1.3.0 github.com/elliotchance/orderedmap v1.3.0 // indirect
github.com/fatih/color v1.9.0 github.com/fatih/color v1.9.0
github.com/goccy/go-yaml v1.8.1 github.com/goccy/go-yaml v1.8.1
github.com/kylelemons/godebug v1.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect

View File

@ -3,7 +3,8 @@ package treeops
import ( import (
"fmt" "fmt"
"github.com/elliotchance/orderedmap" "container/list"
"gopkg.in/op/go-logging.v1" "gopkg.in/op/go-logging.v1"
) )
@ -24,10 +25,10 @@ func NewDataTreeNavigator(navigationPrefs NavigationPrefs) DataTreeNavigator {
} }
func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pathNode *PathTreeNode) ([]*CandidateNode, error) { func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pathNode *PathTreeNode) ([]*CandidateNode, error) {
var matchingNodeMap = orderedmap.NewOrderedMap() var matchingNodeMap = list.New()
for _, n := range matchingNodes { for _, n := range matchingNodes {
matchingNodeMap.Set(n.GetKey(), n) matchingNodeMap.PushBack(n)
} }
matchedNodes, err := d.getMatchingNodes(matchingNodeMap, pathNode) matchedNodes, err := d.getMatchingNodes(matchingNodeMap, pathNode)
@ -43,7 +44,7 @@ func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pat
return values, nil return values, nil
} }
func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
if pathNode == nil { if pathNode == nil {
log.Debugf("getMatchingNodes - nothing to do") log.Debugf("getMatchingNodes - nothing to do")
return matchingNodes, nil return matchingNodes, nil

View File

@ -2,9 +2,9 @@ package treeops
import ( import (
"bytes" "bytes"
"container/list"
"fmt" "fmt"
"github.com/elliotchance/orderedmap"
"gopkg.in/op/go-logging.v1" "gopkg.in/op/go-logging.v1"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -22,17 +22,18 @@ var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOpera
var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOperator} var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOperator}
var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator} var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator}
var Intersection = &OperationType{Type: "INTERSECTION", NumArgs: 2, Precedence: 20, Handler: IntersectionOperator}
var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignOperator} var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignOperator}
var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator} var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator}
var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 40, Handler: MultiplyOperator} var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 40, Handler: MultiplyOperator}
var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator} var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator}
var CreateMap = &OperationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: CreateMapOperator}
var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 45, Handler: PipeOperator} var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 45, Handler: PipeOperator}
var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator} var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator}
var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator} var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator}
var CollectObject = &OperationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: CollectObjectOperator}
var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator} var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
var DocumentFilter = &OperationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator} var DocumentFilter = &OperationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
@ -129,7 +130,7 @@ func (l *lib) Get(document int, documentNode *yaml.Node, path string) ([]*Candid
} }
//use for debugging only //use for debugging only
func NodesToString(collection *orderedmap.OrderedMap) string { func NodesToString(collection *list.List) string {
if !log.IsEnabledFor(logging.DEBUG) { if !log.IsEnabledFor(logging.DEBUG) {
return "" return ""
} }

View File

@ -0,0 +1,2 @@
package treeops

View File

@ -1,8 +1,8 @@
package treeops package treeops
import "github.com/elliotchance/orderedmap" import "container/list"
func AssignOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { func AssignOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil { if err != nil {
return nil, err return nil, err
@ -27,7 +27,7 @@ func AssignOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap,
} }
// does not update content or values // does not update content or values
func AssignAttributesOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { func AssignAttributesOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -1,7 +1,8 @@
package treeops package treeops
import ( import (
"github.com/elliotchance/orderedmap" "container/list"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -23,8 +24,8 @@ func isTruthy(c *CandidateNode) (bool, error) {
type boolOp func(bool, bool) bool type boolOp func(bool, bool) bool
func booleanOp(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode, op boolOp) (*orderedmap.OrderedMap, error) { func booleanOp(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode, op boolOp) (*list.List, error) {
var results = orderedmap.NewOrderedMap() var results = list.New()
for el := matchingNodes.Front(); el != nil; el = el.Next() { for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
@ -52,7 +53,7 @@ func booleanOp(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathN
} }
boolResult := createBooleanCandidate(lhsCandidate, op(lhsTrue, rhsTrue)) boolResult := createBooleanCandidate(lhsCandidate, op(lhsTrue, rhsTrue))
results.Set(boolResult.GetKey(), boolResult) results.PushBack(boolResult)
} }
} }
@ -60,14 +61,14 @@ func booleanOp(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathN
return results, nil return results, nil
} }
func OrOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { func OrOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- orOp") log.Debugf("-- orOp")
return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool { return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool {
return b1 || b2 return b1 || b2
}) })
} }
func AndOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { func AndOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- AndOp") log.Debugf("-- AndOp")
return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool { return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool {
return b1 && b2 return b1 && b2

View File

@ -21,6 +21,7 @@ var booleanOperatorScenarios = []expressionScenario{
document: `{a: true, b: false}`, document: `{a: true, b: false}`,
expression: `.[] or (false, true)`, expression: `.[] or (false, true)`,
expected: []string{ expected: []string{
"D0, P[a], (!!bool)::true\n",
"D0, P[a], (!!bool)::true\n", "D0, P[a], (!!bool)::true\n",
"D0, P[b], (!!bool)::false\n", "D0, P[b], (!!bool)::false\n",
"D0, P[b], (!!bool)::true\n", "D0, P[b], (!!bool)::true\n",

View File

@ -1,14 +1,15 @@
package treeops package treeops
import ( import (
"github.com/elliotchance/orderedmap" "container/list"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
func CollectOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { func CollectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- collectOperation") log.Debugf("-- collectOperation")
var results = orderedmap.NewOrderedMap() var results = list.New()
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"} node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
@ -26,7 +27,7 @@ func CollectOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, path
} }
collectC := &CandidateNode{Node: node, Document: document, Path: path} collectC := &CandidateNode{Node: node, Document: document, Path: path}
results.Set(collectC.GetKey(), collectC) results.PushBack(collectC)
return results, nil return results, nil
} }

View File

@ -0,0 +1,8 @@
package treeops
import "container/list"
func CollectObjectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- collectObjectOperation")
return nil, nil
}

View File

@ -0,0 +1,43 @@
package treeops
import (
"container/list"
"gopkg.in/yaml.v3"
)
func CreateMapOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- createMapOperation")
var path []interface{} = nil
var document uint = 0
if matchingNodes.Front() != nil {
sample := matchingNodes.Front().Value.(*CandidateNode)
path = sample.Path
document = sample.Document
}
mapPairs, err := crossFunction(d, matchingNodes, pathNode,
func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
node := yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
log.Debugf("LHS:", lhs.Node.Value)
log.Debugf("RHS:", rhs.Node.Value)
node.Content = []*yaml.Node{
lhs.Node,
rhs.Node,
}
return &CandidateNode{Node: &node, Document: document, Path: path}, nil
})
if err != nil {
return nil, err
}
//wrap up all the pairs into an array
node := yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
for mapPair := mapPairs.Front(); mapPair != nil; mapPair = mapPair.Next() {
mapPairCandidate := mapPair.Value.(*CandidateNode)
log.Debugf("Collecting %v into sequence", NodeToString(mapPairCandidate))
node.Content = append(node.Content, mapPairCandidate.Node)
}
return nodeToMap(&CandidateNode{Node: &node, Document: document, Path: path}), nil
}

View File

@ -0,0 +1,28 @@
package treeops
import (
"testing"
)
var createMapOperatorScenarios = []expressionScenario{
{
document: `{name: Mike, age: 32}`,
expression: `.name: .age`,
expected: []string{
"D0, P[], (!!seq)::- Mike: 32\n",
},
},
{
document: `{name: Mike, pets: [cat, dog]}`,
expression: `.name: .pets[]`,
expected: []string{
"D0, P[], (!!seq)::- Mike: cat\n- Mike: dog\n",
},
},
}
func TestCreateMapOperatorScenarios(t *testing.T) {
for _, tt := range createMapOperatorScenarios {
testScenario(t, &tt)
}
}

View File

@ -1,11 +1,12 @@
package treeops package treeops
import ( import (
"github.com/elliotchance/orderedmap" "container/list"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil { if err != nil {
return nil, err return nil, err
@ -16,8 +17,8 @@ func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *orderedmap.Ordered
for el := lhs.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
elMap := orderedmap.NewOrderedMap() elMap := list.New()
elMap.Set(candidate.GetKey(), candidate) elMap.PushBack(candidate)
nodesToDelete, err := d.getMatchingNodes(elMap, pathNode.Rhs) nodesToDelete, err := d.getMatchingNodes(elMap, pathNode.Rhs)
log.Debug("nodesToDelete:\n%v", NodesToString(nodesToDelete)) log.Debug("nodesToDelete:\n%v", NodesToString(nodesToDelete))
if err != nil { if err != nil {
@ -35,7 +36,7 @@ func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *orderedmap.Ordered
return lhs, nil return lhs, nil
} }
func deleteFromMap(candidate *CandidateNode, nodesToDelete *orderedmap.OrderedMap) { func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) {
log.Debug("deleteFromMap") log.Debug("deleteFromMap")
node := candidate.Node node := candidate.Node
contents := node.Content contents := node.Content
@ -50,7 +51,8 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *orderedmap.OrderedMa
Document: candidate.Document, Document: candidate.Document,
Path: append(candidate.Path, key.Value), Path: append(candidate.Path, key.Value),
} }
_, shouldDelete := nodesToDelete.Get(childCandidate.GetKey()) // _, shouldDelete := nodesToDelete.Get(childCandidate.GetKey())
shouldDelete := true
log.Debugf("shouldDelete %v ? %v", childCandidate.GetKey(), shouldDelete) log.Debugf("shouldDelete %v ? %v", childCandidate.GetKey(), shouldDelete)
@ -61,7 +63,7 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *orderedmap.OrderedMa
node.Content = newContents node.Content = newContents
} }
func deleteFromArray(candidate *CandidateNode, nodesToDelete *orderedmap.OrderedMap) { func deleteFromArray(candidate *CandidateNode, nodesToDelete *list.List) {
log.Debug("deleteFromArray") log.Debug("deleteFromArray")
node := candidate.Node node := candidate.Node
contents := node.Content contents := node.Content
@ -70,13 +72,14 @@ func deleteFromArray(candidate *CandidateNode, nodesToDelete *orderedmap.Ordered
for index := 0; index < len(contents); index = index + 1 { for index := 0; index < len(contents); index = index + 1 {
value := contents[index] value := contents[index]
childCandidate := &CandidateNode{ // childCandidate := &CandidateNode{
Node: value, // Node: value,
Document: candidate.Document, // Document: candidate.Document,
Path: append(candidate.Path, index), // Path: append(candidate.Path, index),
} // }
_, shouldDelete := nodesToDelete.Get(childCandidate.GetKey()) // _, shouldDelete := nodesToDelete.Get(childCandidate.GetKey())
shouldDelete := true
if !shouldDelete { if !shouldDelete {
newContents = append(newContents, value) newContents = append(newContents, value)
} }

View File

@ -1,10 +1,10 @@
package treeops package treeops
import ( import (
"github.com/elliotchance/orderedmap" "container/list"
) )
func EqualsOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { func EqualsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- equalsOperation") log.Debugf("-- equalsOperation")
return crossFunction(d, matchingNodes, pathNode, isEquals) return crossFunction(d, matchingNodes, pathNode, isEquals)
} }

View File

@ -5,35 +5,30 @@ import (
) )
var equalsOperatorScenarios = []expressionScenario{ var equalsOperatorScenarios = []expressionScenario{
// { {
// document: `[cat,goat,dog]`, document: `[cat,goat,dog]`,
// expression: `(.[] == "*at")`, expression: `.[] | (. == "*at")`,
// expected: []string{ expected: []string{
// "D0, P[], (!!bool)::true\n", "D0, P[0], (!!bool)::true\n",
// }, "D0, P[1], (!!bool)::true\n",
// }, { "D0, P[2], (!!bool)::false\n",
// document: `[cat,goat,dog]`, },
// expression: `.[] | (. == "*at")`, }, {
// expected: []string{ document: `[3, 4, 5]`,
// "D0, P[0], (!!bool)::true\n", expression: `.[] | (. == 4)`,
// "D0, P[1], (!!bool)::true\n", expected: []string{
// "D0, P[2], (!!bool)::false\n", "D0, P[0], (!!bool)::false\n",
// }, "D0, P[1], (!!bool)::true\n",
// }, { "D0, P[2], (!!bool)::false\n",
// document: `[3, 4, 5]`, },
// expression: `.[] | (. == 4)`, }, {
// expected: []string{ document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`,
// "D0, P[0], (!!bool)::false\n", expression: `.a | (.[].b == "apple")`,
// "D0, P[1], (!!bool)::true\n", expected: []string{
// "D0, P[2], (!!bool)::false\n", "D0, P[a cat b], (!!bool)::true\n",
// }, "D0, P[a pat b], (!!bool)::false\n",
// }, { },
// document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`, },
// expression: `.a | (.[].b == "apple")`,
// expected: []string{
// "D0, P[a], (!!bool)::true\n",
// },
// },
{ {
document: ``, document: ``,
expression: `null == null`, expression: `null == null`,
@ -41,13 +36,13 @@ var equalsOperatorScenarios = []expressionScenario{
"D0, P[], (!!bool)::true\n", "D0, P[], (!!bool)::true\n",
}, },
}, },
// { {
// document: ``, document: ``,
// expression: `null == ~`, expression: `null == ~`,
// expected: []string{ expected: []string{
// "D0, P[], (!!bool)::true\n", "D0, P[], (!!bool)::true\n",
// }, },
// }, },
} }
func TestEqualOperatorScenarios(t *testing.T) { func TestEqualOperatorScenarios(t *testing.T) {

View File

@ -3,13 +3,14 @@ package treeops
import ( import (
"fmt" "fmt"
"github.com/elliotchance/orderedmap" "container/list"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
type CrossFunctionCalculation func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) type CrossFunctionCalculation func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error)
func crossFunction(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode, calculation CrossFunctionCalculation) (*orderedmap.OrderedMap, error) { func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode, calculation CrossFunctionCalculation) (*list.List, error) {
lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil { if err != nil {
return nil, err return nil, err
@ -21,7 +22,7 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, p
return nil, err return nil, err
} }
var results = orderedmap.NewOrderedMap() var results = list.New()
for el := lhs.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
lhsCandidate := el.Value.(*CandidateNode) lhsCandidate := el.Value.(*CandidateNode)
@ -32,14 +33,14 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, p
if err != nil { if err != nil {
return nil, err return nil, err
} }
results.Set(resultCandidate.GetKey(), resultCandidate) results.PushBack(resultCandidate)
} }
} }
return results, nil return results, nil
} }
func MultiplyOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- MultiplyOperator") log.Debugf("-- MultiplyOperator")
return crossFunction(d, matchingNodes, pathNode, multiply) return crossFunction(d, matchingNodes, pathNode, multiply)
} }
@ -47,7 +48,7 @@ func MultiplyOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap
func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode || if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode ||
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) { (lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
var results = orderedmap.NewOrderedMap() var results = list.New()
recursiveDecent(d, results, nodeToMap(rhs)) recursiveDecent(d, results, nodeToMap(rhs))
var pathIndexToStartFrom int = 0 var pathIndexToStartFrom int = 0

View File

@ -1,10 +1,10 @@
package treeops package treeops
import "github.com/elliotchance/orderedmap" import "container/list"
func NotOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { func NotOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- notOperation") log.Debugf("-- notOperation")
var results = orderedmap.NewOrderedMap() var results = list.New()
for el := matchMap.Front(); el != nil; el = el.Next() { for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
@ -14,7 +14,7 @@ func NotOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode
return nil, errDecoding return nil, errDecoding
} }
result := createBooleanCandidate(candidate, !truthy) result := createBooleanCandidate(candidate, !truthy)
results.Set(result.GetKey(), result) results.PushBack(result)
} }
return results, nil return results, nil
} }

View File

@ -1,11 +1,11 @@
package treeops package treeops
import ( import (
"github.com/elliotchance/orderedmap" "container/list"
) )
func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
var results = orderedmap.NewOrderedMap() var results = list.New()
err := recursiveDecent(d, results, matchMap) err := recursiveDecent(d, results, matchMap)
if err != nil { if err != nil {
@ -15,13 +15,13 @@ func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *orderedmap.Ordered
return results, nil return results, nil
} }
func recursiveDecent(d *dataTreeNavigator, results *orderedmap.OrderedMap, matchMap *orderedmap.OrderedMap) error { func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List) error {
splatOperation := &Operation{OperationType: TraversePath, Value: "[]"} splatOperation := &Operation{OperationType: TraversePath, Value: "[]"}
splatTreeNode := &PathTreeNode{Operation: splatOperation} splatTreeNode := &PathTreeNode{Operation: splatOperation}
for el := matchMap.Front(); el != nil; el = el.Next() { for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
results.Set(candidate.GetKey(), candidate) results.PushBack(candidate)
children, err := TraversePathOperator(d, nodeToMap(candidate), splatTreeNode) children, err := TraversePathOperator(d, nodeToMap(candidate), splatTreeNode)

View File

@ -1,13 +1,13 @@
package treeops package treeops
import ( import (
"github.com/elliotchance/orderedmap" "container/list"
) )
func SelectOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { func SelectOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- selectOperation") log.Debugf("-- selectOperation")
var results = orderedmap.NewOrderedMap() var results = list.New()
for el := matchingNodes.Front(); el != nil; el = el.Next() { for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
@ -29,7 +29,7 @@ func SelectOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap,
} }
if includeResult { if includeResult {
results.Set(candidate.GetKey(), candidate) results.PushBack(candidate)
} }
} }
} }

View File

@ -1,7 +1,7 @@
package treeops package treeops
import "github.com/elliotchance/orderedmap" import "container/list"
func SelfOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { func SelfOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
return matchMap, nil return matchMap, nil
} }

View File

@ -3,13 +3,14 @@ package treeops
import ( import (
"fmt" "fmt"
"github.com/elliotchance/orderedmap" "container/list"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
func TraversePathOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { func TraversePathOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- Traversing") log.Debugf("-- Traversing")
var matchingNodeMap = orderedmap.NewOrderedMap() var matchingNodeMap = list.New()
var newNodes []*CandidateNode var newNodes []*CandidateNode
var err error var err error
@ -19,7 +20,7 @@ func TraversePathOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap,
return nil, err return nil, err
} }
for _, n := range newNodes { for _, n := range newNodes {
matchingNodeMap.Set(n.GetKey(), n) matchingNodeMap.PushBack(n)
} }
} }
@ -30,7 +31,7 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *Opera
log.Debug("Traversing %v", NodeToString(matchingNode)) log.Debug("Traversing %v", NodeToString(matchingNode))
value := matchingNode.Node value := matchingNode.Node
if value.Kind == 0 { if value.Tag == "!!null" {
log.Debugf("Guessing kind") log.Debugf("Guessing kind")
// we must ahve added this automatically, lets guess what it should be now // we must ahve added this automatically, lets guess what it should be now
switch pathNode.Value.(type) { switch pathNode.Value.(type) {
@ -41,6 +42,7 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *Opera
log.Debugf("probabel a map") log.Debugf("probabel a map")
value.Kind = yaml.MappingNode value.Kind = yaml.MappingNode
} }
value.Tag = ""
} }
switch value.Kind { switch value.Kind {
@ -110,7 +112,7 @@ func traverseMap(candidate *CandidateNode, pathNode *Operation) ([]*CandidateNod
} }
if len(newMatches) == 0 { if len(newMatches) == 0 {
//no matches, create one automagically //no matches, create one automagically
valueNode := &yaml.Node{Tag: "!!null"} valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"}
node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: pathNode.StringValue}, valueNode) node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: pathNode.StringValue}, valueNode)
newMatches = append(newMatches, &CandidateNode{ newMatches = append(newMatches, &CandidateNode{
Node: valueNode, Node: valueNode,
@ -145,7 +147,7 @@ func traverseArray(candidate *CandidateNode, pathNode *Operation) ([]*CandidateN
indexToUse := index indexToUse := index
contentLength := int64(len(candidate.Node.Content)) contentLength := int64(len(candidate.Node.Content))
for contentLength <= index { for contentLength <= index {
candidate.Node.Content = append(candidate.Node.Content, &yaml.Node{Tag: "!!null"}) candidate.Node.Content = append(candidate.Node.Content, &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"})
contentLength = int64(len(candidate.Node.Content)) contentLength = int64(len(candidate.Node.Content))
} }

View File

@ -24,21 +24,21 @@ var traversePathOperatorScenarios = []expressionScenario{
document: `{}`, document: `{}`,
expression: `.a.b`, expression: `.a.b`,
expected: []string{ expected: []string{
"D0, P[a b], ()::null\n", "D0, P[a b], (!!null)::null\n",
}, },
}, },
{ {
document: `{}`, document: `{}`,
expression: `.[1].a`, expression: `.[1].a`,
expected: []string{ expected: []string{
"D0, P[1 a], ()::null\n", "D0, P[1 a], (!!null)::null\n",
}, },
}, },
{ {
document: `{}`, document: `{}`,
expression: `.a.[1]`, expression: `.a.[1]`,
expected: []string{ expected: []string{
"D0, P[a 1], ()::null\n", "D0, P[a 1], (!!null)::null\n",
}, },
}, },
{ {
@ -55,7 +55,7 @@ var traversePathOperatorScenarios = []expressionScenario{
expected: []string{ expected: []string{
"D0, P[a cat b], (!!int)::3\n", "D0, P[a cat b], (!!int)::3\n",
"D0, P[a mad b], (!!int)::4\n", "D0, P[a mad b], (!!int)::4\n",
"D0, P[a fad b], ()::null\n", "D0, P[a fad b], (!!null)::null\n",
}, },
}, },
{ {
@ -72,7 +72,7 @@ var traversePathOperatorScenarios = []expressionScenario{
expected: []string{ expected: []string{
"D0, P[a cat], (!!str)::apple\n", "D0, P[a cat], (!!str)::apple\n",
"D0, P[a mad], (!!str)::things\n", "D0, P[a mad], (!!str)::things\n",
"D0, P[a fad], ()::null\n", "D0, P[a fad], (!!null)::null\n",
}, },
}, },
{ {

View File

@ -1,8 +1,8 @@
package treeops package treeops
import "github.com/elliotchance/orderedmap" import "container/list"
func UnionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { func UnionOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil { if err != nil {
return nil, err return nil, err
@ -13,7 +13,7 @@ func UnionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, p
} }
for el := rhs.Front(); el != nil; el = el.Next() { for el := rhs.Front(); el != nil; el = el.Next() {
node := el.Value.(*CandidateNode) node := el.Value.(*CandidateNode)
lhs.Set(node.GetKey(), node) lhs.PushBack(node)
} }
return lhs, nil return lhs, nil
} }

View File

@ -1,8 +1,8 @@
package treeops package treeops
import "github.com/elliotchance/orderedmap" import "container/list"
func ValueOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { func ValueOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debug("value = %v", pathNode.Operation.CandidateNode.Node.Value) log.Debug("value = %v", pathNode.Operation.CandidateNode.Node.Value)
return nodeToMap(pathNode.Operation.CandidateNode), nil return nodeToMap(pathNode.Operation.CandidateNode), nil
} }

View File

@ -1,15 +1,15 @@
package treeops package treeops
import ( import (
"container/list"
"fmt" "fmt"
"github.com/elliotchance/orderedmap"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
type OperatorHandler func(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) type OperatorHandler func(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error)
func PipeOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { func PipeOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil { if err != nil {
return nil, err return nil, err
@ -26,34 +26,15 @@ func createBooleanCandidate(owner *CandidateNode, value bool) *CandidateNode {
return &CandidateNode{Node: node, Document: owner.Document, Path: owner.Path} return &CandidateNode{Node: node, Document: owner.Document, Path: owner.Path}
} }
func nodeToMap(candidate *CandidateNode) *orderedmap.OrderedMap { func nodeToMap(candidate *CandidateNode) *list.List {
elMap := orderedmap.NewOrderedMap() elMap := list.New()
elMap.Set(candidate.GetKey(), candidate) elMap.PushBack(candidate)
return elMap return elMap
} }
func IntersectionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { func LengthOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
return nil, err
}
rhs, err := d.getMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
var matchingNodeMap = orderedmap.NewOrderedMap()
for el := lhs.Front(); el != nil; el = el.Next() {
_, exists := rhs.Get(el.Key)
if exists {
matchingNodeMap.Set(el.Key, el.Value)
}
}
return matchingNodeMap, nil
}
func LengthOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) {
log.Debugf("-- lengthOperation") log.Debugf("-- lengthOperation")
var results = orderedmap.NewOrderedMap() var results = list.New()
for el := matchMap.Front(); el != nil; el = el.Next() { for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
@ -71,7 +52,7 @@ func LengthOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathN
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"} node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"}
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.Set(candidate.GetKey(), lengthCand) results.PushBack(lengthCand)
} }
return results, nil return results, nil

View File

@ -59,6 +59,16 @@ var pathTests = []struct {
append(make([]interface{}, 0), "[", "true (bool)", "]"), append(make([]interface{}, 0), "[", "true (bool)", "]"),
append(make([]interface{}, 0), "true (bool)", "COLLECT", "PIPE"), append(make([]interface{}, 0), "true (bool)", "COLLECT", "PIPE"),
}, },
{
`"mike": .a`,
append(make([]interface{}, 0), "mike (string)", "CREATE_MAP", "a"),
append(make([]interface{}, 0), "mike (string)", "a", "CREATE_MAP"),
},
{
`.a: "mike"`,
append(make([]interface{}, 0), "a", "CREATE_MAP", "mike (string)"),
append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP"),
},
// {".animals | .==cat", append(make([]interface{}, 0), "animals", "TRAVERSE", "SELF", "EQUALS", "cat")}, // {".animals | .==cat", append(make([]interface{}, 0), "animals", "TRAVERSE", "SELF", "EQUALS", "cat")},
// {".animals | (. == cat)", append(make([]interface{}, 0), "animals", "TRAVERSE", "(", "SELF", "EQUALS", "cat", ")")}, // {".animals | (. == cat)", append(make([]interface{}, 0), "animals", "TRAVERSE", "(", "SELF", "EQUALS", "cat", ")")},

View File

@ -20,6 +20,8 @@ const (
CloseBracket CloseBracket
OpenCollect OpenCollect
CloseCollect CloseCollect
OpenCollectObject
CloseCollectObject
) )
type Token struct { type Token struct {
@ -40,6 +42,10 @@ func (t *Token) toString() string {
return "[" return "["
} else if t.TokenType == CloseCollect { } else if t.TokenType == CloseCollect {
return "]" return "]"
} else if t.TokenType == OpenCollectObject {
return "{"
} else if t.TokenType == CloseCollectObject {
return "}"
} else { } else {
return fmt.Sprintf("NFI") return fmt.Sprintf("NFI")
} }
@ -174,6 +180,7 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`\.\.`), opToken(RecursiveDescent)) lexer.Add([]byte(`\.\.`), opToken(RecursiveDescent))
lexer.Add([]byte(`,`), opToken(Union)) lexer.Add([]byte(`,`), opToken(Union))
lexer.Add([]byte(`:\s*`), opToken(CreateMap))
lexer.Add([]byte(`length`), opToken(Length)) lexer.Add([]byte(`length`), opToken(Length))
lexer.Add([]byte(`select`), opToken(Select)) lexer.Add([]byte(`select`), opToken(Select))
lexer.Add([]byte(`or`), opToken(Or)) lexer.Add([]byte(`or`), opToken(Or))
@ -195,7 +202,7 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`d[0-9]+`), documentToken()) // $0 lexer.Add([]byte(`d[0-9]+`), documentToken()) // $0
lexer.Add([]byte(`\."[^ "]+"`), pathToken(true)) lexer.Add([]byte(`\."[^ "]+"`), pathToken(true))
lexer.Add([]byte(`\.[^ \[\],\|\.\[\(\)=]+`), pathToken(false)) lexer.Add([]byte(`\.[^ \}\{\:\[\],\|\.\[\(\)=]+`), pathToken(false))
lexer.Add([]byte(`\.`), selfToken()) lexer.Add([]byte(`\.`), selfToken())
lexer.Add([]byte(`\|`), opToken(Pipe)) lexer.Add([]byte(`\|`), opToken(Pipe))
@ -214,6 +221,8 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`\[`), literalToken(OpenCollect, false)) lexer.Add([]byte(`\[`), literalToken(OpenCollect, false))
lexer.Add([]byte(`\]`), literalToken(CloseCollect, true)) lexer.Add([]byte(`\]`), literalToken(CloseCollect, true))
lexer.Add([]byte(`\{`), literalToken(OpenCollectObject, false))
lexer.Add([]byte(`\}`), literalToken(CloseCollectObject, true))
lexer.Add([]byte(`\*`), opToken(Multiply)) lexer.Add([]byte(`\*`), opToken(Multiply))
// lexer.Add([]byte(`[^ \,\|\.\[\(\)=]+`), stringValue(false)) // lexer.Add([]byte(`[^ \,\|\.\[\(\)=]+`), stringValue(false))

View File

@ -1 +1 @@
{a: {also: [1]}, b: {also: me}} {name: Mike, pets: [cat, dog]}

View File

@ -1,10 +1,7 @@
{ {
"a": { "name": "Mike",
"also": [ "pets": [
1 "cat",
"dog"
] ]
},
"b": {
"also": "me"
}
} }