diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 56030da..c891404 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -5,8 +5,8 @@ import ( "container/list" "fmt" - "gopkg.in/op/go-logging.v1" - "gopkg.in/yaml.v3" + logging "gopkg.in/op/go-logging.v1" + yaml "gopkg.in/yaml.v3" ) type OperationType struct { @@ -70,7 +70,7 @@ var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Pre var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: SelectOperator} -var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 2, Precedence: 40, Handler: DeleteChildOperator} +var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: DeleteChildOperator} // var Exists = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35} // filters matches if they have the existing path diff --git a/pkg/yqlib/operator_assign_update_test.go b/pkg/yqlib/operator_assign_update_test.go index e1b5870..eb2018c 100644 --- a/pkg/yqlib/operator_assign_update_test.go +++ b/pkg/yqlib/operator_assign_update_test.go @@ -6,13 +6,23 @@ import ( var assignOperatorScenarios = []expressionScenario{ { - document: `{a: {b: apple}}`, - expression: `.a.b |= "frog"`, + description: "Update parent to be the child value", + document: `{a: {b: {g: foof}}}`, + expression: `.a |= .b`, + expected: []string{ + "D0, P[], (doc)::{a: {g: foof}}\n", + }, + }, + { + description: "Update string value", + document: `{a: {b: apple}}`, + expression: `.a.b |= "frog"`, expected: []string{ "D0, P[], (doc)::{a: {b: frog}}\n", }, }, { + skipDoc: true, document: `{a: {b: apple}}`, expression: `.a.b | (. |= "frog")`, expected: []string{ @@ -20,6 +30,7 @@ var assignOperatorScenarios = []expressionScenario{ }, }, { + skipDoc: true, document: `{a: {b: apple}}`, expression: `.a.b |= 5`, expected: []string{ @@ -27,6 +38,7 @@ var assignOperatorScenarios = []expressionScenario{ }, }, { + skipDoc: true, document: `{a: {b: apple}}`, expression: `.a.b |= 3.142`, expected: []string{ @@ -34,41 +46,39 @@ var assignOperatorScenarios = []expressionScenario{ }, }, { - document: `{a: {b: {g: foof}}}`, - expression: `.a |= .b`, - expected: []string{ - "D0, P[], (doc)::{a: {g: foof}}\n", - }, - }, - { - document: `{a: {b: apple, c: cactus}}`, - expression: `.a[] | select(. == "apple") |= "frog"`, + description: "Update selected results", + document: `{a: {b: apple, c: cactus}}`, + expression: `.a[] | select(. == "apple") |= "frog"`, expected: []string{ "D0, P[], (doc)::{a: {b: frog, c: cactus}}\n", }, }, { - document: `[candy, apple, sandy]`, - expression: `.[] | select(. == "*andy") |= "bogs"`, + description: "Update array values", + document: `[candy, apple, sandy]`, + expression: `.[] | select(. == "*andy") |= "bogs"`, expected: []string{ "D0, P[], (doc)::[bogs, apple, bogs]\n", }, }, { - document: `{}`, - expression: `.a.b |= "bogs"`, + description: "Update empty object", + document: `{}`, + expression: `.a.b |= "bogs"`, expected: []string{ "D0, P[], (doc)::{a: {b: bogs}}\n", }, }, { - document: `{}`, - expression: `.a.b[0] |= "bogs"`, + description: "Update empty object and array", + document: `{}`, + expression: `.a.b[0] |= "bogs"`, expected: []string{ "D0, P[], (doc)::{a: {b: [bogs]}}\n", }, }, { + skipDoc: true, document: `{}`, expression: `.a.b[1].c |= "bogs"`, expected: []string{ @@ -81,4 +91,5 @@ func TestAssignOperatorScenarios(t *testing.T) { for _, tt := range assignOperatorScenarios { testScenario(t, &tt) } + documentScenarios(t, "Update Assign Operator", assignOperatorScenarios) } diff --git a/pkg/yqlib/operator_delete.go b/pkg/yqlib/operator_delete.go index 362207d..e19d343 100644 --- a/pkg/yqlib/operator_delete.go +++ b/pkg/yqlib/operator_delete.go @@ -3,19 +3,15 @@ package yqlib import ( "container/list" - "gopkg.in/yaml.v3" + yaml "gopkg.in/yaml.v3" ) 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 - } // for each lhs, splat the node, // the intersect it against the rhs expression // recreate the contents using only the intersection result. - for el := lhs.Front(); el != nil; el = el.Next() { + for el := matchingNodes.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) elMap := list.New() elMap.PushBack(candidate) @@ -25,20 +21,22 @@ func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNod return nil, err } - if candidate.Node.Kind == yaml.SequenceNode { + realNode := UnwrapDoc(candidate.Node) + + if realNode.Kind == yaml.SequenceNode { deleteFromArray(candidate, nodesToDelete) - } else if candidate.Node.Kind == yaml.MappingNode { + } else if realNode.Kind == yaml.MappingNode { deleteFromMap(candidate, nodesToDelete) } else { log.Debug("Cannot delete from node that's not a map or array %v", NodeToString(candidate)) } } - return lhs, nil + return matchingNodes, nil } func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) { log.Debug("deleteFromMap") - node := candidate.Node + node := UnwrapDoc(candidate.Node) contents := node.Content newContents := make([]*yaml.Node, 0) @@ -51,8 +49,13 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) { Document: candidate.Document, Path: append(candidate.Path, key.Value), } - // _, shouldDelete := nodesToDelete.Get(childCandidate.GetKey()) - shouldDelete := true + + shouldDelete := false + for el := nodesToDelete.Front(); el != nil && shouldDelete == false; el = el.Next() { + if el.Value.(*CandidateNode).GetKey() == childCandidate.GetKey() { + shouldDelete = true + } + } log.Debugf("shouldDelete %v ? %v", childCandidate.GetKey(), shouldDelete) @@ -65,21 +68,26 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) { func deleteFromArray(candidate *CandidateNode, nodesToDelete *list.List) { log.Debug("deleteFromArray") - node := candidate.Node + node := UnwrapDoc(candidate.Node) contents := node.Content newContents := make([]*yaml.Node, 0) 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 := false + for el := nodesToDelete.Front(); el != nil && shouldDelete == false; el = el.Next() { + if el.Value.(*CandidateNode).GetKey() == childCandidate.GetKey() { + shouldDelete = true + } + } - // _, shouldDelete := nodesToDelete.Get(childCandidate.GetKey()) - shouldDelete := true if !shouldDelete { newContents = append(newContents, value) } diff --git a/pkg/yqlib/operator_delete_test.go b/pkg/yqlib/operator_delete_test.go new file mode 100644 index 0000000..68476e5 --- /dev/null +++ b/pkg/yqlib/operator_delete_test.go @@ -0,0 +1,39 @@ +package yqlib + +import ( + "testing" +) + +var deleteOperatorScenarios = []expressionScenario{ + { + description: "Delete entry in map", + document: `{a: cat, b: dog}`, + expression: `del(.b)`, + expected: []string{ + "D0, P[], (doc)::{a: cat}\n", + }, + }, + { + description: "Delete entry in array", + document: `[1,2,3]`, + expression: `del(.[1])`, + expected: []string{ + "D0, P[], (doc)::[1, 3]\n", + }, + }, + { + description: "Delete no matches", + document: `{a: cat, b: dog}`, + expression: `del(.c)`, + expected: []string{ + "D0, P[], (doc)::{a: cat, b: dog}\n", + }, + }, +} + +func TestDeleteOperatorScenarios(t *testing.T) { + for _, tt := range deleteOperatorScenarios { + testScenario(t, &tt) + } + documentScenarios(t, "Delete Operator", deleteOperatorScenarios) +} diff --git a/pkg/yqlib/operator_explode_test.go b/pkg/yqlib/operator_explode_test.go index 7318b9a..e7c609c 100644 --- a/pkg/yqlib/operator_explode_test.go +++ b/pkg/yqlib/operator_explode_test.go @@ -6,27 +6,31 @@ import ( var explodeTest = []expressionScenario{ { - document: `{a: mike}`, - expression: `explode(.a)`, - expected: []string{ - "D0, P[], (doc)::{a: mike}\n", - }, - }, - { - document: `{f : {a: &a cat, b: *a}}`, - expression: `explode(.f)`, + description: "Explode alias and anchor", + document: `{f : {a: &a cat, b: *a}}`, + expression: `explode(.f)`, expected: []string{ "D0, P[], (doc)::{f: {a: cat, b: cat}}\n", }, }, { - document: `{f : {a: &a cat, *a: b}}`, - expression: `explode(.f)`, + description: "Explode with no aliases or anchors", + document: `{a: mike}`, + expression: `explode(.a)`, + expected: []string{ + "D0, P[], (doc)::{a: mike}\n", + }, + }, + { + description: "Explode with alias keys", + document: `{f : {a: &a cat, *a: b}}`, + expression: `explode(.f)`, expected: []string{ "D0, P[], (doc)::{f: {a: cat, cat: b}}\n", }, }, { + skipDoc: true, document: mergeDocSample, expression: `.foo* | explode(.) | (. style="flow")`, expected: []string{ @@ -36,6 +40,7 @@ var explodeTest = []expressionScenario{ }, }, { + skipDoc: true, document: mergeDocSample, expression: `.foo* | explode(explode(.)) | (. style="flow")`, expected: []string{ @@ -45,6 +50,7 @@ var explodeTest = []expressionScenario{ }, }, { + skipDoc: true, document: `{f : {a: &a cat, b: &b {f: *a}, *a: *b}}`, expression: `explode(.f)`, expected: []string{ @@ -57,4 +63,5 @@ func TestExplodeOperatorScenarios(t *testing.T) { for _, tt := range explodeTest { testScenario(t, &tt) } + documentScenarios(t, "Explode Operator", explodeTest) } diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index aa79484..aba1119 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -209,7 +209,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`\s*==\s*`), opToken(Equals)) - lexer.Add([]byte(`\s*.-\s*`), opToken(DeleteChild)) + lexer.Add([]byte(`del`), opToken(DeleteChild)) lexer.Add([]byte(`\s*\|=\s*`), opToken(Assign))