diff --git a/go.mod b/go.mod index a77d199..64cafbf 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/mikefarah/yq/v3 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/goccy/go-yaml v1.8.1 github.com/kylelemons/godebug v1.1.0 // indirect diff --git a/pkg/yqlib/treeops/data_tree_navigator.go b/pkg/yqlib/treeops/data_tree_navigator.go index d502047..1b7dbc6 100644 --- a/pkg/yqlib/treeops/data_tree_navigator.go +++ b/pkg/yqlib/treeops/data_tree_navigator.go @@ -3,7 +3,8 @@ package treeops import ( "fmt" - "github.com/elliotchance/orderedmap" + "container/list" + "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) { - var matchingNodeMap = orderedmap.NewOrderedMap() + var matchingNodeMap = list.New() for _, n := range matchingNodes { - matchingNodeMap.Set(n.GetKey(), n) + matchingNodeMap.PushBack(n) } matchedNodes, err := d.getMatchingNodes(matchingNodeMap, pathNode) @@ -43,7 +44,7 @@ func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pat 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 { log.Debugf("getMatchingNodes - nothing to do") return matchingNodes, nil diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index 02a955c..50deca2 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -2,9 +2,9 @@ package treeops import ( "bytes" + "container/list" "fmt" - "github.com/elliotchance/orderedmap" "gopkg.in/op/go-logging.v1" "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 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 AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator} var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 40, Handler: MultiplyOperator} 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 Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator} 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 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 -func NodesToString(collection *orderedmap.OrderedMap) string { +func NodesToString(collection *list.List) string { if !log.IsEnabledFor(logging.DEBUG) { return "" } diff --git a/pkg/yqlib/treeops/operation_collection_object_test.go b/pkg/yqlib/treeops/operation_collection_object_test.go new file mode 100644 index 0000000..4a522df --- /dev/null +++ b/pkg/yqlib/treeops/operation_collection_object_test.go @@ -0,0 +1,2 @@ +package treeops + diff --git a/pkg/yqlib/treeops/operator_assign.go b/pkg/yqlib/treeops/operator_assign.go index 60ee08e..4c8222b 100644 --- a/pkg/yqlib/treeops/operator_assign.go +++ b/pkg/yqlib/treeops/operator_assign.go @@ -1,8 +1,8 @@ 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) if err != nil { return nil, err @@ -27,7 +27,7 @@ func AssignOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, } // 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) if err != nil { return nil, err diff --git a/pkg/yqlib/treeops/operator_booleans.go b/pkg/yqlib/treeops/operator_booleans.go index 4e6135d..ceee713 100644 --- a/pkg/yqlib/treeops/operator_booleans.go +++ b/pkg/yqlib/treeops/operator_booleans.go @@ -1,7 +1,8 @@ package treeops import ( - "github.com/elliotchance/orderedmap" + "container/list" + "gopkg.in/yaml.v3" ) @@ -23,8 +24,8 @@ func isTruthy(c *CandidateNode) (bool, error) { type boolOp func(bool, bool) bool -func booleanOp(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode, op boolOp) (*orderedmap.OrderedMap, error) { - var results = orderedmap.NewOrderedMap() +func booleanOp(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode, op boolOp) (*list.List, error) { + var results = list.New() for el := matchingNodes.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) @@ -52,7 +53,7 @@ func booleanOp(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathN } 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 } -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") return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool { 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") return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool { return b1 && b2 diff --git a/pkg/yqlib/treeops/operator_booleans_test.go b/pkg/yqlib/treeops/operator_booleans_test.go index 59f9895..f3013fb 100644 --- a/pkg/yqlib/treeops/operator_booleans_test.go +++ b/pkg/yqlib/treeops/operator_booleans_test.go @@ -21,6 +21,7 @@ var booleanOperatorScenarios = []expressionScenario{ document: `{a: true, b: false}`, expression: `.[] or (false, true)`, expected: []string{ + "D0, P[a], (!!bool)::true\n", "D0, P[a], (!!bool)::true\n", "D0, P[b], (!!bool)::false\n", "D0, P[b], (!!bool)::true\n", diff --git a/pkg/yqlib/treeops/operator_collect.go b/pkg/yqlib/treeops/operator_collect.go index a1197fa..c4e1f7c 100644 --- a/pkg/yqlib/treeops/operator_collect.go +++ b/pkg/yqlib/treeops/operator_collect.go @@ -1,14 +1,15 @@ package treeops import ( - "github.com/elliotchance/orderedmap" + "container/list" + "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") - var results = orderedmap.NewOrderedMap() + var results = list.New() 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} - results.Set(collectC.GetKey(), collectC) + results.PushBack(collectC) return results, nil } diff --git a/pkg/yqlib/treeops/operator_collect_object.go b/pkg/yqlib/treeops/operator_collect_object.go new file mode 100644 index 0000000..74ca176 --- /dev/null +++ b/pkg/yqlib/treeops/operator_collect_object.go @@ -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 +} diff --git a/pkg/yqlib/treeops/operator_create_map.go b/pkg/yqlib/treeops/operator_create_map.go new file mode 100644 index 0000000..eaea672 --- /dev/null +++ b/pkg/yqlib/treeops/operator_create_map.go @@ -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 +} diff --git a/pkg/yqlib/treeops/operator_create_map_test.go b/pkg/yqlib/treeops/operator_create_map_test.go new file mode 100644 index 0000000..39a2e6e --- /dev/null +++ b/pkg/yqlib/treeops/operator_create_map_test.go @@ -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) + } +} diff --git a/pkg/yqlib/treeops/operator_delete.go b/pkg/yqlib/treeops/operator_delete.go index c80479b..23adac2 100644 --- a/pkg/yqlib/treeops/operator_delete.go +++ b/pkg/yqlib/treeops/operator_delete.go @@ -1,11 +1,12 @@ package treeops import ( - "github.com/elliotchance/orderedmap" + "container/list" + "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) if err != nil { return nil, err @@ -16,8 +17,8 @@ func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *orderedmap.Ordered for el := lhs.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) - elMap := orderedmap.NewOrderedMap() - elMap.Set(candidate.GetKey(), candidate) + elMap := list.New() + elMap.PushBack(candidate) nodesToDelete, err := d.getMatchingNodes(elMap, pathNode.Rhs) log.Debug("nodesToDelete:\n%v", NodesToString(nodesToDelete)) if err != nil { @@ -35,7 +36,7 @@ func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *orderedmap.Ordered return lhs, nil } -func deleteFromMap(candidate *CandidateNode, nodesToDelete *orderedmap.OrderedMap) { +func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) { log.Debug("deleteFromMap") node := candidate.Node contents := node.Content @@ -50,7 +51,8 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *orderedmap.OrderedMa Document: candidate.Document, 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) @@ -61,7 +63,7 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *orderedmap.OrderedMa node.Content = newContents } -func deleteFromArray(candidate *CandidateNode, nodesToDelete *orderedmap.OrderedMap) { +func deleteFromArray(candidate *CandidateNode, nodesToDelete *list.List) { log.Debug("deleteFromArray") node := candidate.Node contents := node.Content @@ -70,13 +72,14 @@ func deleteFromArray(candidate *CandidateNode, nodesToDelete *orderedmap.Ordered for index := 0; index < len(contents); index = index + 1 { value := contents[index] - childCandidate := &CandidateNode{ - Node: value, - Document: candidate.Document, - Path: append(candidate.Path, index), - } + // childCandidate := &CandidateNode{ + // Node: value, + // Document: candidate.Document, + // Path: append(candidate.Path, index), + // } - _, shouldDelete := nodesToDelete.Get(childCandidate.GetKey()) + // _, shouldDelete := nodesToDelete.Get(childCandidate.GetKey()) + shouldDelete := true if !shouldDelete { newContents = append(newContents, value) } diff --git a/pkg/yqlib/treeops/operator_equals.go b/pkg/yqlib/treeops/operator_equals.go index cee24ff..63bf6d7 100644 --- a/pkg/yqlib/treeops/operator_equals.go +++ b/pkg/yqlib/treeops/operator_equals.go @@ -1,10 +1,10 @@ package treeops 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") return crossFunction(d, matchingNodes, pathNode, isEquals) } diff --git a/pkg/yqlib/treeops/operator_equals_test.go b/pkg/yqlib/treeops/operator_equals_test.go index dca372f..02e4479 100644 --- a/pkg/yqlib/treeops/operator_equals_test.go +++ b/pkg/yqlib/treeops/operator_equals_test.go @@ -5,35 +5,30 @@ import ( ) var equalsOperatorScenarios = []expressionScenario{ - // { - // document: `[cat,goat,dog]`, - // expression: `(.[] == "*at")`, - // expected: []string{ - // "D0, P[], (!!bool)::true\n", - // }, - // }, { - // document: `[cat,goat,dog]`, - // expression: `.[] | (. == "*at")`, - // expected: []string{ - // "D0, P[0], (!!bool)::true\n", - // "D0, P[1], (!!bool)::true\n", - // "D0, P[2], (!!bool)::false\n", - // }, - // }, { - // document: `[3, 4, 5]`, - // expression: `.[] | (. == 4)`, - // expected: []string{ - // "D0, P[0], (!!bool)::false\n", - // "D0, P[1], (!!bool)::true\n", - // "D0, P[2], (!!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: `[cat,goat,dog]`, + expression: `.[] | (. == "*at")`, + expected: []string{ + "D0, P[0], (!!bool)::true\n", + "D0, P[1], (!!bool)::true\n", + "D0, P[2], (!!bool)::false\n", + }, + }, { + document: `[3, 4, 5]`, + expression: `.[] | (. == 4)`, + expected: []string{ + "D0, P[0], (!!bool)::false\n", + "D0, P[1], (!!bool)::true\n", + "D0, P[2], (!!bool)::false\n", + }, + }, { + document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`, + expression: `.a | (.[].b == "apple")`, + expected: []string{ + "D0, P[a cat b], (!!bool)::true\n", + "D0, P[a pat b], (!!bool)::false\n", + }, + }, { document: ``, expression: `null == null`, @@ -41,13 +36,13 @@ var equalsOperatorScenarios = []expressionScenario{ "D0, P[], (!!bool)::true\n", }, }, - // { - // document: ``, - // expression: `null == ~`, - // expected: []string{ - // "D0, P[], (!!bool)::true\n", - // }, - // }, + { + document: ``, + expression: `null == ~`, + expected: []string{ + "D0, P[], (!!bool)::true\n", + }, + }, } func TestEqualOperatorScenarios(t *testing.T) { diff --git a/pkg/yqlib/treeops/operator_multilpy.go b/pkg/yqlib/treeops/operator_multilpy.go index e253960..a83ed05 100644 --- a/pkg/yqlib/treeops/operator_multilpy.go +++ b/pkg/yqlib/treeops/operator_multilpy.go @@ -3,13 +3,14 @@ package treeops import ( "fmt" - "github.com/elliotchance/orderedmap" + "container/list" + "gopkg.in/yaml.v3" ) 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) if err != nil { return nil, err @@ -21,7 +22,7 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, p return nil, err } - var results = orderedmap.NewOrderedMap() + var results = list.New() for el := lhs.Front(); el != nil; el = el.Next() { lhsCandidate := el.Value.(*CandidateNode) @@ -32,14 +33,14 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, p if err != nil { return nil, err } - results.Set(resultCandidate.GetKey(), resultCandidate) + results.PushBack(resultCandidate) } } 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") 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) { if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode || (lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) { - var results = orderedmap.NewOrderedMap() + var results = list.New() recursiveDecent(d, results, nodeToMap(rhs)) var pathIndexToStartFrom int = 0 diff --git a/pkg/yqlib/treeops/operator_not.go b/pkg/yqlib/treeops/operator_not.go index 4610b0e..0497bac 100644 --- a/pkg/yqlib/treeops/operator_not.go +++ b/pkg/yqlib/treeops/operator_not.go @@ -1,10 +1,10 @@ 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") - var results = orderedmap.NewOrderedMap() + var results = list.New() for el := matchMap.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) @@ -14,7 +14,7 @@ func NotOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode return nil, errDecoding } result := createBooleanCandidate(candidate, !truthy) - results.Set(result.GetKey(), result) + results.PushBack(result) } return results, nil } diff --git a/pkg/yqlib/treeops/operator_recursive_descent.go b/pkg/yqlib/treeops/operator_recursive_descent.go index 38513b7..3609846 100644 --- a/pkg/yqlib/treeops/operator_recursive_descent.go +++ b/pkg/yqlib/treeops/operator_recursive_descent.go @@ -1,11 +1,11 @@ package treeops import ( - "github.com/elliotchance/orderedmap" + "container/list" ) -func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { - var results = orderedmap.NewOrderedMap() +func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { + var results = list.New() err := recursiveDecent(d, results, matchMap) if err != nil { @@ -15,13 +15,13 @@ func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *orderedmap.Ordered 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: "[]"} splatTreeNode := &PathTreeNode{Operation: splatOperation} for el := matchMap.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) - results.Set(candidate.GetKey(), candidate) + results.PushBack(candidate) children, err := TraversePathOperator(d, nodeToMap(candidate), splatTreeNode) diff --git a/pkg/yqlib/treeops/operator_select.go b/pkg/yqlib/treeops/operator_select.go index fb92a90..704987f 100644 --- a/pkg/yqlib/treeops/operator_select.go +++ b/pkg/yqlib/treeops/operator_select.go @@ -1,13 +1,13 @@ package treeops 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") - var results = orderedmap.NewOrderedMap() + var results = list.New() for el := matchingNodes.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) @@ -29,7 +29,7 @@ func SelectOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, } if includeResult { - results.Set(candidate.GetKey(), candidate) + results.PushBack(candidate) } } } diff --git a/pkg/yqlib/treeops/operator_self.go b/pkg/yqlib/treeops/operator_self.go index 170ef2c..bd98d32 100644 --- a/pkg/yqlib/treeops/operator_self.go +++ b/pkg/yqlib/treeops/operator_self.go @@ -1,7 +1,7 @@ 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 } diff --git a/pkg/yqlib/treeops/operator_traverse_path.go b/pkg/yqlib/treeops/operator_traverse_path.go index b279719..dc05c34 100644 --- a/pkg/yqlib/treeops/operator_traverse_path.go +++ b/pkg/yqlib/treeops/operator_traverse_path.go @@ -3,13 +3,14 @@ package treeops import ( "fmt" - "github.com/elliotchance/orderedmap" + "container/list" + "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") - var matchingNodeMap = orderedmap.NewOrderedMap() + var matchingNodeMap = list.New() var newNodes []*CandidateNode var err error @@ -19,7 +20,7 @@ func TraversePathOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, return nil, err } 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)) value := matchingNode.Node - if value.Kind == 0 { + if value.Tag == "!!null" { log.Debugf("Guessing kind") // we must ahve added this automatically, lets guess what it should be now switch pathNode.Value.(type) { @@ -41,6 +42,7 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *Opera log.Debugf("probabel a map") value.Kind = yaml.MappingNode } + value.Tag = "" } switch value.Kind { @@ -110,7 +112,7 @@ func traverseMap(candidate *CandidateNode, pathNode *Operation) ([]*CandidateNod } if len(newMatches) == 0 { //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) newMatches = append(newMatches, &CandidateNode{ Node: valueNode, @@ -145,7 +147,7 @@ func traverseArray(candidate *CandidateNode, pathNode *Operation) ([]*CandidateN indexToUse := index contentLength := int64(len(candidate.Node.Content)) 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)) } diff --git a/pkg/yqlib/treeops/operator_traverse_path_test.go b/pkg/yqlib/treeops/operator_traverse_path_test.go index 8f7fadf..d5ecbc2 100644 --- a/pkg/yqlib/treeops/operator_traverse_path_test.go +++ b/pkg/yqlib/treeops/operator_traverse_path_test.go @@ -24,21 +24,21 @@ var traversePathOperatorScenarios = []expressionScenario{ document: `{}`, expression: `.a.b`, expected: []string{ - "D0, P[a b], ()::null\n", + "D0, P[a b], (!!null)::null\n", }, }, { document: `{}`, expression: `.[1].a`, expected: []string{ - "D0, P[1 a], ()::null\n", + "D0, P[1 a], (!!null)::null\n", }, }, { document: `{}`, expression: `.a.[1]`, expected: []string{ - "D0, P[a 1], ()::null\n", + "D0, P[a 1], (!!null)::null\n", }, }, { @@ -55,7 +55,7 @@ var traversePathOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[a cat b], (!!int)::3\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{ "D0, P[a cat], (!!str)::apple\n", "D0, P[a mad], (!!str)::things\n", - "D0, P[a fad], ()::null\n", + "D0, P[a fad], (!!null)::null\n", }, }, { diff --git a/pkg/yqlib/treeops/operator_union.go b/pkg/yqlib/treeops/operator_union.go index df44e9f..8c100ba 100644 --- a/pkg/yqlib/treeops/operator_union.go +++ b/pkg/yqlib/treeops/operator_union.go @@ -1,8 +1,8 @@ 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) if err != nil { return nil, err @@ -13,7 +13,7 @@ func UnionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, p } for el := rhs.Front(); el != nil; el = el.Next() { node := el.Value.(*CandidateNode) - lhs.Set(node.GetKey(), node) + lhs.PushBack(node) } return lhs, nil } diff --git a/pkg/yqlib/treeops/operator_value.go b/pkg/yqlib/treeops/operator_value.go index a1bdb5d..0769a21 100644 --- a/pkg/yqlib/treeops/operator_value.go +++ b/pkg/yqlib/treeops/operator_value.go @@ -1,8 +1,8 @@ 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) return nodeToMap(pathNode.Operation.CandidateNode), nil } diff --git a/pkg/yqlib/treeops/operators.go b/pkg/yqlib/treeops/operators.go index f09f1c5..c584f9d 100644 --- a/pkg/yqlib/treeops/operators.go +++ b/pkg/yqlib/treeops/operators.go @@ -1,15 +1,15 @@ package treeops import ( + "container/list" "fmt" - "github.com/elliotchance/orderedmap" "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) if err != nil { return nil, err @@ -26,34 +26,15 @@ func createBooleanCandidate(owner *CandidateNode, value bool) *CandidateNode { return &CandidateNode{Node: node, Document: owner.Document, Path: owner.Path} } -func nodeToMap(candidate *CandidateNode) *orderedmap.OrderedMap { - elMap := orderedmap.NewOrderedMap() - elMap.Set(candidate.GetKey(), candidate) +func nodeToMap(candidate *CandidateNode) *list.List { + elMap := list.New() + elMap.PushBack(candidate) return elMap } -func IntersectionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, 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) { +func LengthOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { log.Debugf("-- lengthOperation") - var results = orderedmap.NewOrderedMap() + var results = list.New() for el := matchMap.Front(); el != nil; el = el.Next() { 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"} lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} - results.Set(candidate.GetKey(), lengthCand) + results.PushBack(lengthCand) } return results, nil diff --git a/pkg/yqlib/treeops/path_parse_test.go b/pkg/yqlib/treeops/path_parse_test.go index 16bcf24..825b42a 100644 --- a/pkg/yqlib/treeops/path_parse_test.go +++ b/pkg/yqlib/treeops/path_parse_test.go @@ -59,6 +59,16 @@ var pathTests = []struct { append(make([]interface{}, 0), "[", "true (bool)", "]"), 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", ")")}, diff --git a/pkg/yqlib/treeops/path_tokeniser.go b/pkg/yqlib/treeops/path_tokeniser.go index c207d40..59953f8 100644 --- a/pkg/yqlib/treeops/path_tokeniser.go +++ b/pkg/yqlib/treeops/path_tokeniser.go @@ -20,6 +20,8 @@ const ( CloseBracket OpenCollect CloseCollect + OpenCollectObject + CloseCollectObject ) type Token struct { @@ -40,6 +42,10 @@ func (t *Token) toString() string { return "[" } else if t.TokenType == CloseCollect { return "]" + } else if t.TokenType == OpenCollectObject { + return "{" + } else if t.TokenType == CloseCollectObject { + return "}" } else { return fmt.Sprintf("NFI") } @@ -174,6 +180,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`\.\.`), opToken(RecursiveDescent)) lexer.Add([]byte(`,`), opToken(Union)) + lexer.Add([]byte(`:\s*`), opToken(CreateMap)) lexer.Add([]byte(`length`), opToken(Length)) lexer.Add([]byte(`select`), opToken(Select)) 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(`\."[^ "]+"`), pathToken(true)) - lexer.Add([]byte(`\.[^ \[\],\|\.\[\(\)=]+`), pathToken(false)) + lexer.Add([]byte(`\.[^ \}\{\:\[\],\|\.\[\(\)=]+`), pathToken(false)) lexer.Add([]byte(`\.`), selfToken()) lexer.Add([]byte(`\|`), opToken(Pipe)) @@ -214,6 +221,8 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`\[`), literalToken(OpenCollect, false)) 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(`[^ \,\|\.\[\(\)=]+`), stringValue(false)) diff --git a/pkg/yqlib/treeops/sample.yaml b/pkg/yqlib/treeops/sample.yaml index fcd6274..f151d6f 100644 --- a/pkg/yqlib/treeops/sample.yaml +++ b/pkg/yqlib/treeops/sample.yaml @@ -1 +1 @@ -{a: {also: [1]}, b: {also: me}} \ No newline at end of file +{name: Mike, pets: [cat, dog]} \ No newline at end of file diff --git a/pkg/yqlib/treeops/temp.json b/pkg/yqlib/treeops/temp.json index 9ca3567..f5cf07d 100644 --- a/pkg/yqlib/treeops/temp.json +++ b/pkg/yqlib/treeops/temp.json @@ -1,10 +1,7 @@ { - "a": { - "also": [ - 1 - ] - }, - "b": { - "also": "me" - } + "name": "Mike", + "pets": [ + "cat", + "dog" + ] }