From 75044e480c7b1921d0661427b0f6b1daa5c3d51b Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Thu, 19 Nov 2020 17:08:13 +1100 Subject: [PATCH] Added plain assignment --- ... Assign Operator.md => Assign Operator.md} | 43 ++++++++++++++++++- pkg/yqlib/doc/Tag Operator.md | 2 +- pkg/yqlib/doc/headers/Assign Operator.md | 7 +++ .../doc/headers/Update Assign Operator.md | 1 - pkg/yqlib/lib.go | 2 - ...or_assign_update.go => operator_assign.go} | 15 ++++++- ...update_test.go => operator_assign_test.go} | 21 ++++++++- pkg/yqlib/operator_multilpy.go | 1 + pkg/yqlib/path_tokeniser.go | 6 +-- 9 files changed, 87 insertions(+), 11 deletions(-) rename pkg/yqlib/doc/{Update Assign Operator.md => Assign Operator.md} (61%) create mode 100644 pkg/yqlib/doc/headers/Assign Operator.md delete mode 100644 pkg/yqlib/doc/headers/Update Assign Operator.md rename pkg/yqlib/{operator_assign_update.go => operator_assign.go} (77%) rename pkg/yqlib/{operator_assign_update_test.go => operator_assign_test.go} (78%) diff --git a/pkg/yqlib/doc/Update Assign Operator.md b/pkg/yqlib/doc/Assign Operator.md similarity index 61% rename from pkg/yqlib/doc/Update Assign Operator.md rename to pkg/yqlib/doc/Assign Operator.md index 866d2db..0dadc5b 100644 --- a/pkg/yqlib/doc/Update Assign Operator.md +++ b/pkg/yqlib/doc/Assign Operator.md @@ -1,4 +1,10 @@ -Updates the LHS using the expression on the RHS. Note that the RHS runs against the _original_ LHS value, so that you can evaluate a new value based on the old (e.g. increment). +This operator is used to update node values. It can be used in either the: + +### plain form: `=` +Which will assign the LHS node values to the RHS node values. The RHS expression is run against the matching nodes in the pipeline. + +### relative form: `|=` +This will do a similar thing to the plain form, however, the RHS expression is run against _the LHS nodes_. This is useful for updating values based on old values, e.g. increment. ## Examples ### Update parent to be the child value Given a sample.yml file of: @@ -17,6 +23,23 @@ a: g: foof ``` +### Update to be the sibling value +Given a sample.yml file of: +```yaml +a: + b: child +b: sibling +``` +then +```bash +yq eval '.a = .b' sample.yml +``` +will output +```yaml +a: sibling +b: sibling +``` + ### Updated multiple paths Given a sample.yml file of: ```yaml @@ -36,6 +59,24 @@ c: potatoe ``` ### Update string value +Given a sample.yml file of: +```yaml +a: + b: apple +``` +then +```bash +yq eval '.a.b = "frog"' sample.yml +``` +will output +```yaml +a: + b: frog +``` + +### Update string value via |= +Note there is no difference between `=` and `|=` when the RHS is a scalar + Given a sample.yml file of: ```yaml a: diff --git a/pkg/yqlib/doc/Tag Operator.md b/pkg/yqlib/doc/Tag Operator.md index e9e4de2..863a236 100644 --- a/pkg/yqlib/doc/Tag Operator.md +++ b/pkg/yqlib/doc/Tag Operator.md @@ -33,7 +33,7 @@ e: true ``` then ```bash -yq eval '(.. | select(tag == "!!int")) tag = "!!str"' sample.yml +yq eval '(.. | select(tag == "!!int")) tag= "!!str"' sample.yml ``` will output ```yaml diff --git a/pkg/yqlib/doc/headers/Assign Operator.md b/pkg/yqlib/doc/headers/Assign Operator.md new file mode 100644 index 0000000..5cd669c --- /dev/null +++ b/pkg/yqlib/doc/headers/Assign Operator.md @@ -0,0 +1,7 @@ +This operator is used to update node values. It can be used in either the: + +### plain form: `=` +Which will assign the LHS node values to the RHS node values. The RHS expression is run against the matching nodes in the pipeline. + +### relative form: `|=` +This will do a similar thing to the plain form, however, the RHS expression is run against _the LHS nodes_. This is useful for updating values based on old values, e.g. increment. \ No newline at end of file diff --git a/pkg/yqlib/doc/headers/Update Assign Operator.md b/pkg/yqlib/doc/headers/Update Assign Operator.md deleted file mode 100644 index c555f5b..0000000 --- a/pkg/yqlib/doc/headers/Update Assign Operator.md +++ /dev/null @@ -1 +0,0 @@ -Updates the LHS using the expression on the RHS. Note that the RHS runs against the _original_ LHS value, so that you can evaluate a new value based on the old (e.g. increment). \ No newline at end of file diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index d690e2e..d90433a 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -37,8 +37,6 @@ var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: U var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator} -// TODO: implement this -var PlainAssign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator} var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator} var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator} var AssignTag = &OperationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: AssignTagOperator} diff --git a/pkg/yqlib/operator_assign_update.go b/pkg/yqlib/operator_assign.go similarity index 77% rename from pkg/yqlib/operator_assign_update.go rename to pkg/yqlib/operator_assign.go index 8381b47..5a6f3b5 100644 --- a/pkg/yqlib/operator_assign_update.go +++ b/pkg/yqlib/operator_assign.go @@ -2,15 +2,28 @@ package yqlib import "container/list" +type AssignOpPreferences struct { + UpdateAssign bool +} + func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) if err != nil { return nil, err } + preferences := pathNode.Operation.Preferences.(*AssignOpPreferences) + + var rhs *list.List + if !preferences.UpdateAssign { + rhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Rhs) + } + for el := lhs.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) - rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs) + if preferences.UpdateAssign { + rhs, err = d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs) + } if err != nil { return nil, err diff --git a/pkg/yqlib/operator_assign_update_test.go b/pkg/yqlib/operator_assign_test.go similarity index 78% rename from pkg/yqlib/operator_assign_update_test.go rename to pkg/yqlib/operator_assign_test.go index 1fbc5e8..8c5fb38 100644 --- a/pkg/yqlib/operator_assign_update_test.go +++ b/pkg/yqlib/operator_assign_test.go @@ -13,6 +13,14 @@ var assignOperatorScenarios = []expressionScenario{ "D0, P[], (doc)::{a: {g: foof}}\n", }, }, + { + description: "Update to be the sibling value", + document: `{a: {b: child}, b: sibling}`, + expression: `.a = .b`, + expected: []string{ + "D0, P[], (doc)::{a: sibling, b: sibling}\n", + }, + }, { description: "Updated multiple paths", document: `{a: fieldA, b: fieldB, c: fieldC}`, @@ -24,7 +32,16 @@ var assignOperatorScenarios = []expressionScenario{ { description: "Update string value", document: `{a: {b: apple}}`, - expression: `.a.b |= "frog"`, + expression: `.a.b = "frog"`, + expected: []string{ + "D0, P[], (doc)::{a: {b: frog}}\n", + }, + }, + { + description: "Update string value via |=", + subdescription: "Note there is no difference between `=` and `|=` when the RHS is a scalar", + document: `{a: {b: apple}}`, + expression: `.a.b |= "frog"`, expected: []string{ "D0, P[], (doc)::{a: {b: frog}}\n", }, @@ -99,5 +116,5 @@ func TestAssignOperatorScenarios(t *testing.T) { for _, tt := range assignOperatorScenarios { testScenario(t, &tt) } - documentScenarios(t, "Update Assign Operator", assignOperatorScenarios) + documentScenarios(t, "Assign Operator", assignOperatorScenarios) } diff --git a/pkg/yqlib/operator_multilpy.go b/pkg/yqlib/operator_multilpy.go index f18ba44..9f25962 100644 --- a/pkg/yqlib/operator_multilpy.go +++ b/pkg/yqlib/operator_multilpy.go @@ -112,6 +112,7 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid assignmentOp := &Operation{OperationType: AssignAttributes} if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode { assignmentOp.OperationType = Assign + assignmentOp.Preferences = &AssignOpPreferences{false} } rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs} diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index 71614c0..d9c78ef 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -215,11 +215,11 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`collect`), opToken(Collect)) lexer.Add([]byte(`\s*==\s*`), opToken(Equals)) - lexer.Add([]byte(`\s*=\s*`), opToken(PlainAssign)) + lexer.Add([]byte(`\s*=\s*`), opTokenWithPrefs(Assign, nil, &AssignOpPreferences{false})) lexer.Add([]byte(`del`), opToken(DeleteChild)) - lexer.Add([]byte(`\s*\|=\s*`), opToken(Assign)) + lexer.Add([]byte(`\s*\|=\s*`), opTokenWithPrefs(Assign, nil, &AssignOpPreferences{true})) lexer.Add([]byte(`\[-?[0-9]+\]`), arrayIndextoken(false)) lexer.Add([]byte(`\.\[-?[0-9]+\]`), arrayIndextoken(true)) @@ -303,7 +303,7 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) { if index != len(tokens)-1 && token.AssignOperation != nil && tokens[index+1].TokenType == OperationToken && - tokens[index+1].Operation.OperationType == PlainAssign { + tokens[index+1].Operation.OperationType == Assign { token.Operation = token.AssignOperation skipNextToken = true }