Added plain assignment

This commit is contained in:
Mike Farah 2020-11-19 17:08:13 +11:00
parent 36084a60a9
commit 75044e480c
9 changed files with 87 additions and 11 deletions

View File

@ -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 ## Examples
### Update parent to be the child value ### Update parent to be the child value
Given a sample.yml file of: Given a sample.yml file of:
@ -17,6 +23,23 @@ a:
g: foof 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 ### Updated multiple paths
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
@ -36,6 +59,24 @@ c: potatoe
``` ```
### Update string value ### 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: Given a sample.yml file of:
```yaml ```yaml
a: a:

View File

@ -33,7 +33,7 @@ e: true
``` ```
then then
```bash ```bash
yq eval '(.. | select(tag == "!!int")) tag = "!!str"' sample.yml yq eval '(.. | select(tag == "!!int")) tag= "!!str"' sample.yml
``` ```
will output will output
```yaml ```yaml

View File

@ -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.

View File

@ -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).

View File

@ -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} 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 AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator}
var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator} var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator}
var AssignTag = &OperationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: AssignTagOperator} var AssignTag = &OperationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: AssignTagOperator}

View File

@ -2,15 +2,28 @@ package yqlib
import "container/list" import "container/list"
type AssignOpPreferences struct {
UpdateAssign bool
}
func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil { if err != nil {
return nil, err 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() { for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) 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 { if err != nil {
return nil, err return nil, err

View File

@ -13,6 +13,14 @@ var assignOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::{a: {g: foof}}\n", "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", description: "Updated multiple paths",
document: `{a: fieldA, b: fieldB, c: fieldC}`, document: `{a: fieldA, b: fieldB, c: fieldC}`,
@ -24,7 +32,16 @@ var assignOperatorScenarios = []expressionScenario{
{ {
description: "Update string value", description: "Update string value",
document: `{a: {b: apple}}`, 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{ expected: []string{
"D0, P[], (doc)::{a: {b: frog}}\n", "D0, P[], (doc)::{a: {b: frog}}\n",
}, },
@ -99,5 +116,5 @@ func TestAssignOperatorScenarios(t *testing.T) {
for _, tt := range assignOperatorScenarios { for _, tt := range assignOperatorScenarios {
testScenario(t, &tt) testScenario(t, &tt)
} }
documentScenarios(t, "Update Assign Operator", assignOperatorScenarios) documentScenarios(t, "Assign Operator", assignOperatorScenarios)
} }

View File

@ -112,6 +112,7 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid
assignmentOp := &Operation{OperationType: AssignAttributes} assignmentOp := &Operation{OperationType: AssignAttributes}
if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode { if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode {
assignmentOp.OperationType = Assign assignmentOp.OperationType = Assign
assignmentOp.Preferences = &AssignOpPreferences{false}
} }
rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs} rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs}

View File

@ -215,11 +215,11 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`collect`), opToken(Collect)) lexer.Add([]byte(`collect`), opToken(Collect))
lexer.Add([]byte(`\s*==\s*`), opToken(Equals)) 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(`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(false))
lexer.Add([]byte(`\.\[-?[0-9]+\]`), arrayIndextoken(true)) 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 && if index != len(tokens)-1 && token.AssignOperation != nil &&
tokens[index+1].TokenType == OperationToken && tokens[index+1].TokenType == OperationToken &&
tokens[index+1].Operation.OperationType == PlainAssign { tokens[index+1].Operation.OperationType == Assign {
token.Operation = token.AssignOperation token.Operation = token.AssignOperation
skipNextToken = true skipNextToken = true
} }