diff --git a/pkg/yqlib/doc/Delete.md b/pkg/yqlib/doc/Delete.md index 601bf30a..4f214f79 100644 --- a/pkg/yqlib/doc/Delete.md +++ b/pkg/yqlib/doc/Delete.md @@ -14,6 +14,23 @@ will output a: cat ``` +## Delete nested entry in map +Given a sample.yml file of: +```yaml +a: + a1: fred + a2: frood +``` +then +```bash +yq eval 'del(.a.a1)' sample.yml +``` +will output +```yaml +a: + a2: frood +``` + ## Delete entry in array Given a sample.yml file of: ```yaml @@ -31,6 +48,21 @@ will output - 3 ``` +## Delete nested entry in array +Given a sample.yml file of: +```yaml +- a: cat + b: dog +``` +then +```bash +yq eval 'del(.[0].a)' sample.yml +``` +will output +```yaml +- b: dog +``` + ## Delete no matches Given a sample.yml file of: ```yaml diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index c1d7dd37..8b900a74 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -73,6 +73,7 @@ var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Pre var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: SelectOperator} var Has = &OperationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: HasOperator} var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: DeleteChildOperator} +var DeleteImmediateChild = &OperationType{Type: "DELETE_IMMEDIATE_CHILD", NumArgs: 1, Precedence: 40, Handler: DeleteImmediateChildOperator} type Operation struct { OperationType *OperationType diff --git a/pkg/yqlib/operator_delete.go b/pkg/yqlib/operator_delete.go index 0e01af50..34c39634 100644 --- a/pkg/yqlib/operator_delete.go +++ b/pkg/yqlib/operator_delete.go @@ -2,39 +2,67 @@ package yqlib import ( "container/list" + "fmt" yaml "gopkg.in/yaml.v3" ) func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { - // for each lhs, splat the node, - // the intersect it against the rhs expression - // recreate the contents using only the intersection result. - for el := matchingNodes.Front(); el != nil; el = el.Next() { + nodesToDelete, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs) + + if err != nil { + return nil, err + } + + for el := nodesToDelete.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) - elMap := list.New() - elMap.PushBack(candidate) - nodesToDelete, err := d.GetMatchingNodes(elMap, pathNode.Rhs) - log.Debug("nodesToDelete:\n%v", NodesToString(nodesToDelete)) - if err != nil { - return nil, err + + deleteImmediateChildOp := &Operation{ + OperationType: DeleteImmediateChild, + Value: candidate.Path[len(candidate.Path)-1], } - realNode := UnwrapDoc(candidate.Node) + deleteImmediateChildOpNode := &PathTreeNode{ + Operation: deleteImmediateChildOp, + Rhs: createTraversalTree(candidate.Path[0 : len(candidate.Path)-1]), + } - if realNode.Kind == yaml.SequenceNode { - deleteFromArray(candidate, nodesToDelete) - } 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)) + _, err := d.GetMatchingNodes(matchingNodes, deleteImmediateChildOpNode) + if err != nil { + return nil, err } } return matchingNodes, nil } -func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) { +func DeleteImmediateChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + parents, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs) + + if err != nil { + return nil, err + } + + childPath := pathNode.Operation.Value + + log.Debug("childPath to remove %v", childPath) + + for el := parents.Front(); el != nil; el = el.Next() { + parent := el.Value.(*CandidateNode) + parentNode := UnwrapDoc(parent.Node) + if parentNode.Kind == yaml.MappingNode { + deleteFromMap(parent, childPath) + } else if parentNode.Kind == yaml.SequenceNode { + deleteFromArray(parent, childPath) + } else { + return nil, fmt.Errorf("Cannot delete nodes from parent of tag %v", parentNode.Tag) + } + + } + return matchingNodes, nil +} + +func deleteFromMap(candidate *CandidateNode, childPath interface{}) { log.Debug("deleteFromMap") node := UnwrapDoc(candidate.Node) contents := node.Content @@ -50,12 +78,7 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) { Path: append(candidate.Path, key.Value), } - shouldDelete := false - for el := nodesToDelete.Front(); el != nil && !shouldDelete; el = el.Next() { - if el.Value.(*CandidateNode).GetKey() == childCandidate.GetKey() { - shouldDelete = true - } - } + shouldDelete := key.Value == childPath log.Debugf("shouldDelete %v ? %v", childCandidate.GetKey(), shouldDelete) @@ -66,7 +89,7 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) { node.Content = newContents } -func deleteFromArray(candidate *CandidateNode, nodesToDelete *list.List) { +func deleteFromArray(candidate *CandidateNode, childPath interface{}) { log.Debug("deleteFromArray") node := UnwrapDoc(candidate.Node) contents := node.Content @@ -75,18 +98,7 @@ func deleteFromArray(candidate *CandidateNode, nodesToDelete *list.List) { for index := 0; index < len(contents); index = index + 1 { value := contents[index] - childCandidate := &CandidateNode{ - Node: value, - Document: candidate.Document, - Path: append(candidate.Path, index), - } - - shouldDelete := false - for el := nodesToDelete.Front(); el != nil && !shouldDelete; el = el.Next() { - if el.Value.(*CandidateNode).GetKey() == childCandidate.GetKey() { - shouldDelete = true - } - } + shouldDelete := fmt.Sprintf("%v", index) == fmt.Sprintf("%v", childPath) if !shouldDelete { newContents = append(newContents, value) diff --git a/pkg/yqlib/operator_delete_test.go b/pkg/yqlib/operator_delete_test.go index 43e02d15..54b4c020 100644 --- a/pkg/yqlib/operator_delete_test.go +++ b/pkg/yqlib/operator_delete_test.go @@ -13,6 +13,22 @@ var deleteOperatorScenarios = []expressionScenario{ "D0, P[], (doc)::{a: cat}\n", }, }, + { + description: "Delete nested entry in map", + document: `{a: {a1: fred, a2: frood}}`, + expression: `del(.a.a1)`, + expected: []string{ + "D0, P[], (doc)::{a: {a2: frood}}\n", + }, + }, + { + skipDoc: true, + document: `{a: {a1: fred, a2: frood}}`, + expression: `del(.. | select(.=="frood"))`, + expected: []string{ + "D0, P[], (!!map)::{a: {a1: fred}}\n", + }, + }, { description: "Delete entry in array", document: `[1,2,3]`, @@ -21,6 +37,14 @@ var deleteOperatorScenarios = []expressionScenario{ "D0, P[], (doc)::[1, 3]\n", }, }, + { + description: "Delete nested entry in array", + document: `[{a: cat, b: dog}]`, + expression: `del(.[0].a)`, + expected: []string{ + "D0, P[], (doc)::[{b: dog}]\n", + }, + }, { description: "Delete no matches", document: `{a: cat, b: dog}`, diff --git a/pkg/yqlib/operator_multiply.go b/pkg/yqlib/operator_multiply.go index dc1af6a3..cdf0c868 100644 --- a/pkg/yqlib/operator_multiply.go +++ b/pkg/yqlib/operator_multiply.go @@ -104,19 +104,6 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode, return lhs, nil } -func createTraversalTree(path []interface{}) *PathTreeNode { - if len(path) == 0 { - return &PathTreeNode{Operation: &Operation{OperationType: SelfReference}} - } else if len(path) == 1 { - return &PathTreeNode{Operation: &Operation{OperationType: TraversePath, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}} - } - return &PathTreeNode{ - Operation: &Operation{OperationType: ShortPipe}, - Lhs: createTraversalTree(path[0:1]), - Rhs: createTraversalTree(path[1:])} - -} - func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) error { log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs)) diff --git a/pkg/yqlib/operators.go b/pkg/yqlib/operators.go index f9f16122..2013d544 100644 --- a/pkg/yqlib/operators.go +++ b/pkg/yqlib/operators.go @@ -2,6 +2,7 @@ package yqlib import ( "container/list" + "fmt" "gopkg.in/yaml.v3" ) @@ -33,3 +34,15 @@ func nodeToMap(candidate *CandidateNode) *list.List { elMap.PushBack(candidate) return elMap } + +func createTraversalTree(path []interface{}) *PathTreeNode { + if len(path) == 0 { + return &PathTreeNode{Operation: &Operation{OperationType: SelfReference}} + } else if len(path) == 1 { + return &PathTreeNode{Operation: &Operation{OperationType: TraversePath, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}} + } + return &PathTreeNode{ + Operation: &Operation{OperationType: ShortPipe}, + Lhs: createTraversalTree(path[0:1]), + Rhs: createTraversalTree(path[1:])} +}