From dba41ffed7748ad28e7c4a9c178e4ec114de8527 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 3 Dec 2021 09:23:16 +1100 Subject: [PATCH] Assignment op no longer clobbers anchor (#1029) --- pkg/yqlib/candidate_node.go | 11 +++++++---- pkg/yqlib/doc/assign-update.md | 16 ++++++++++++++++ pkg/yqlib/expression_tokeniser.go | 3 ++- pkg/yqlib/operator_assign.go | 30 +++++++++++++++++++++++------- pkg/yqlib/operator_assign_test.go | 10 ++++++++++ 5 files changed, 58 insertions(+), 12 deletions(-) diff --git a/pkg/yqlib/candidate_node.go b/pkg/yqlib/candidate_node.go index b2c0e47d..547e955f 100644 --- a/pkg/yqlib/candidate_node.go +++ b/pkg/yqlib/candidate_node.go @@ -104,14 +104,14 @@ func (n *CandidateNode) Copy() (*CandidateNode, error) { } // updates this candidate from the given candidate node -func (n *CandidateNode) UpdateFrom(other *CandidateNode) { +func (n *CandidateNode) UpdateFrom(other *CandidateNode, prefs assignPreferences) { - n.UpdateAttributesFrom(other) + n.UpdateAttributesFrom(other, prefs) n.Node.Content = other.Node.Content n.Node.Value = other.Node.Value } -func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) { +func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode, prefs assignPreferences) { log.Debug("UpdateAttributesFrom: n: %v other: %v", n.GetKey(), other.GetKey()) if n.Node.Kind != other.Node.Kind { // clear out the contents when switching to a different type @@ -122,7 +122,10 @@ func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) { n.Node.Kind = other.Node.Kind n.Node.Tag = other.Node.Tag n.Node.Alias = other.Node.Alias - n.Node.Anchor = other.Node.Anchor + + if !prefs.DontOverWriteAnchor { + n.Node.Anchor = other.Node.Anchor + } // merge will pickup the style of the new thing // when autocreating nodes diff --git a/pkg/yqlib/doc/assign-update.md b/pkg/yqlib/doc/assign-update.md index ad71c81b..ad175b53 100644 --- a/pkg/yqlib/doc/assign-update.md +++ b/pkg/yqlib/doc/assign-update.md @@ -196,6 +196,22 @@ will output {a: {b: bogs}} ``` +## Update node value that has an anchor +Anchor will remaple + +Given a sample.yml file of: +```yaml +a: &cool cat +``` +then +```bash +yq eval '.a = "dog"' sample.yml +``` +will output +```yaml +a: &cool dog +``` + ## Update empty object and array Given a sample.yml file of: ```yaml diff --git a/pkg/yqlib/expression_tokeniser.go b/pkg/yqlib/expression_tokeniser.go index 562ddccd..6b34ab68 100644 --- a/pkg/yqlib/expression_tokeniser.go +++ b/pkg/yqlib/expression_tokeniser.go @@ -94,7 +94,8 @@ func assignOpToken(updateAssign bool) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { log.Debug("assignOpToken %v", string(m.Bytes)) value := string(m.Bytes) - op := &Operation{OperationType: assignOpType, Value: assignOpType.Type, StringValue: value, UpdateAssign: updateAssign} + prefs := assignPreferences{DontOverWriteAnchor: true} + op := &Operation{OperationType: assignOpType, Value: assignOpType.Type, StringValue: value, UpdateAssign: updateAssign, Preferences: prefs} return &token{TokenType: operationToken, Operation: op}, nil } } diff --git a/pkg/yqlib/operator_assign.go b/pkg/yqlib/operator_assign.go index cbc53837..61417df7 100644 --- a/pkg/yqlib/operator_assign.go +++ b/pkg/yqlib/operator_assign.go @@ -1,9 +1,16 @@ package yqlib -func assignUpdateFunc(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { - rhs.Node = unwrapDoc(rhs.Node) - lhs.UpdateFrom(rhs) - return lhs, nil +type assignPreferences struct { + DontOverWriteAnchor bool +} + +func assignUpdateFunc(prefs assignPreferences) crossFunctionCalculation { + return func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { + rhs.Node = unwrapDoc(rhs.Node) + + lhs.UpdateFrom(rhs, prefs) + return lhs, nil + } } func assignUpdateOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { @@ -12,9 +19,14 @@ func assignUpdateOperator(d *dataTreeNavigator, context Context, expressionNode return Context{}, err } + prefs := assignPreferences{} + if expressionNode.Operation.Preferences != nil { + prefs = expressionNode.Operation.Preferences.(assignPreferences) + } + if !expressionNode.Operation.UpdateAssign { // this works because we already ran against LHS with an editable context. - _, err := crossFunction(d, context.ReadOnlyClone(), expressionNode, assignUpdateFunc, false) + _, err := crossFunction(d, context.ReadOnlyClone(), expressionNode, assignUpdateFunc(prefs), false) return context, err } @@ -33,7 +45,7 @@ func assignUpdateOperator(d *dataTreeNavigator, context Context, expressionNode if first != nil { rhsCandidate := first.Value.(*CandidateNode) rhsCandidate.Node = unwrapDoc(rhsCandidate.Node) - candidate.UpdateFrom(rhsCandidate) + candidate.UpdateFrom(rhsCandidate, prefs) } } @@ -60,7 +72,11 @@ func assignAttributesOperator(d *dataTreeNavigator, context Context, expressionN first := rhs.MatchingNodes.Front() if first != nil { - candidate.UpdateAttributesFrom(first.Value.(*CandidateNode)) + prefs := assignPreferences{} + if expressionNode.Operation.Preferences != nil { + prefs = expressionNode.Operation.Preferences.(assignPreferences) + } + candidate.UpdateAttributesFrom(first.Value.(*CandidateNode), prefs) } } return context, nil diff --git a/pkg/yqlib/operator_assign_test.go b/pkg/yqlib/operator_assign_test.go index 98b2d572..9ecca5d4 100644 --- a/pkg/yqlib/operator_assign_test.go +++ b/pkg/yqlib/operator_assign_test.go @@ -145,6 +145,16 @@ var assignOperatorScenarios = []expressionScenario{ "D0, P[], (doc)::{a: {b: bogs}}\n", }, }, + { + description: "Update node value that has an anchor", + subdescription: "Anchor will remaple", + dontFormatInputForDoc: true, + document: `a: &cool cat`, + expression: `.a = "dog"`, + expected: []string{ + "D0, P[], (doc)::a: &cool dog\n", + }, + }, { description: "Update empty object and array", dontFormatInputForDoc: true,