Added new "c" flag to clobber custom tags when needed

This commit is contained in:
Mike Farah 2022-08-29 15:37:25 +10:00
parent b1a40a9fb7
commit b4ca184108
11 changed files with 155 additions and 14 deletions

View File

@ -157,7 +157,7 @@ func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode, prefs assignP
n.Node.Kind = other.Node.Kind n.Node.Kind = other.Node.Kind
// don't clobber custom tags... // don't clobber custom tags...
if strings.HasPrefix(n.Node.Tag, "!!") || n.Node.Tag == "" { if prefs.ClobberCustomTags || strings.HasPrefix(n.Node.Tag, "!!") || n.Node.Tag == "" {
n.Node.Tag = other.Node.Tag n.Node.Tag = other.Node.Tag
} }

View File

@ -7,6 +7,11 @@ Which will assign the LHS node values to the RHS node values. The RHS expression
### relative form: `|=` ### 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. 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.
### Flags
- `c` clobber custom tags
{% hint style="warning" %} {% hint style="warning" %}
Note that versions prior to 4.18 require the 'eval/e' command to be specified.  Note that versions prior to 4.18 require the 'eval/e' command to be specified. 
@ -235,3 +240,37 @@ a:
- bogs - bogs
``` ```
## Custom types are maintained by default
Given a sample.yml file of:
```yaml
a: !cat meow
b: !dog woof
```
then
```bash
yq '.a = .b' sample.yml
```
will output
```yaml
a: !cat woof
b: !dog woof
```
## Custom types: clovver
Use the `c` option to clobber custom tags
Given a sample.yml file of:
```yaml
a: !cat meow
b: !dog woof
```
then
```bash
yq '.a =c .b' sample.yml
```
will output
```yaml
a: !dog woof
b: !dog woof
```

View File

@ -6,4 +6,8 @@ This operator is used to update node values. It can be used in either the:
Which will assign the LHS node values to the RHS node values. The RHS expression is run against the matching nodes in the pipeline. 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: `|=` ### 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. 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.
### Flags
- `c` clobber custom tags

View File

@ -14,6 +14,7 @@ You can control how objects are merged by using one or more of the following fla
- `d` deeply merge arrays - `d` deeply merge arrays
- `?` only merge _existing_ fields - `?` only merge _existing_ fields
- `n` only merge _new_ fields - `n` only merge _new_ fields
- `c` clobber custom tags
### Merge two files together ### Merge two files together

View File

@ -14,6 +14,7 @@ You can control how objects are merged by using one or more of the following fla
- `d` deeply merge arrays - `d` deeply merge arrays
- `?` only merge _existing_ fields - `?` only merge _existing_ fields
- `n` only merge _new_ fields - `n` only merge _new_ fields
- `c` clobber custom tags
### Merge two files together ### Merge two files together
@ -481,3 +482,26 @@ b: !goat
dog: woof dog: woof
``` ```
## Custom types: clobber tags
Use the `c` option to clobber custom tags. Note that the second tag is now used
Given a sample.yml file of:
```yaml
a: !horse
cat: meow
b: !goat
dog: woof
```
then
```bash
yq '.a *=c .b' sample.yml
```
will output
```yaml
a: !goat
cat: meow
dog: woof
b: !goat
dog: woof
```

View File

@ -182,8 +182,8 @@ var participleYqRules = []*participleYqRule{
{"GreaterThan", `\s*>\s*`, opTokenWithPrefs(compareOpType, nil, compareTypePref{OrEqual: false, Greater: true}), 0}, {"GreaterThan", `\s*>\s*`, opTokenWithPrefs(compareOpType, nil, compareTypePref{OrEqual: false, Greater: true}), 0},
{"LessThan", `\s*<\s*`, opTokenWithPrefs(compareOpType, nil, compareTypePref{OrEqual: false, Greater: false}), 0}, {"LessThan", `\s*<\s*`, opTokenWithPrefs(compareOpType, nil, compareTypePref{OrEqual: false, Greater: false}), 0},
{"AssignRelative", `\|=`, assignOpToken(true), 0}, {"AssignRelative", `\|=[c]*`, assignOpToken(true), 0},
{"Assign", `=`, assignOpToken(false), 0}, {"Assign", `=[c]*`, assignOpToken(false), 0},
{`whitespace`, `[ \t\n]+`, nil, 0}, {`whitespace`, `[ \t\n]+`, nil, 0},
@ -194,8 +194,8 @@ var participleYqRules = []*participleYqRule{
{"Union", `,`, opToken(unionOpType), 0}, {"Union", `,`, opToken(unionOpType), 0},
{"MultiplyAssign", `\*=[\+|\?dn]*`, multiplyWithPrefs(multiplyAssignOpType), 0}, {"MultiplyAssign", `\*=[\+|\?cdn]*`, multiplyWithPrefs(multiplyAssignOpType), 0},
{"Multiply", `\*[\+|\?dn]*`, multiplyWithPrefs(multiplyOpType), 0}, {"Multiply", `\*[\+|\?cdn]*`, multiplyWithPrefs(multiplyOpType), 0},
{"AddAssign", `\+=`, opToken(addAssignOpType), 0}, {"AddAssign", `\+=`, opToken(addAssignOpType), 0},
{"Add", `\+`, opToken(addOpType), 0}, {"Add", `\+`, opToken(addOpType), 0},
@ -317,6 +317,9 @@ func assignOpToken(updateAssign bool) yqAction {
log.Debug("assignOpToken %v", rawToken.Value) log.Debug("assignOpToken %v", rawToken.Value)
value := rawToken.Value value := rawToken.Value
prefs := assignPreferences{DontOverWriteAnchor: true} prefs := assignPreferences{DontOverWriteAnchor: true}
if strings.Contains(value, "c") {
prefs.ClobberCustomTags = true
}
op := &Operation{OperationType: assignOpType, Value: assignOpType.Type, StringValue: value, UpdateAssign: updateAssign, Preferences: prefs} op := &Operation{OperationType: assignOpType, Value: assignOpType.Type, StringValue: value, UpdateAssign: updateAssign, Preferences: prefs}
return &token{TokenType: operationToken, Operation: op}, nil return &token{TokenType: operationToken, Operation: op}, nil
} }
@ -387,6 +390,7 @@ func envSubstWithOptions() yqAction {
func multiplyWithPrefs(op *operationType) yqAction { func multiplyWithPrefs(op *operationType) yqAction {
return func(rawToken lexer.Token) (*token, error) { return func(rawToken lexer.Token) (*token, error) {
prefs := multiplyPreferences{} prefs := multiplyPreferences{}
prefs.AssignPrefs = assignPreferences{}
options := rawToken.Value options := rawToken.Value
if strings.Contains(options, "+") { if strings.Contains(options, "+") {
prefs.AppendArrays = true prefs.AppendArrays = true
@ -395,11 +399,14 @@ func multiplyWithPrefs(op *operationType) yqAction {
prefs.TraversePrefs = traversePreferences{DontAutoCreate: true} prefs.TraversePrefs = traversePreferences{DontAutoCreate: true}
} }
if strings.Contains(options, "n") { if strings.Contains(options, "n") {
prefs.AssignPrefs = assignPreferences{OnlyWriteNull: true} prefs.AssignPrefs.OnlyWriteNull = true
} }
if strings.Contains(options, "d") { if strings.Contains(options, "d") {
prefs.DeepMergeArrays = true prefs.DeepMergeArrays = true
} }
if strings.Contains(options, "c") {
prefs.AssignPrefs.ClobberCustomTags = true
}
prefs.TraversePrefs.DontFollowAlias = true prefs.TraversePrefs.DontFollowAlias = true
op := &Operation{OperationType: op, Value: multiplyOpType.Type, StringValue: options, Preferences: prefs} op := &Operation{OperationType: op, Value: multiplyOpType.Type, StringValue: options, Preferences: prefs}
return &token{TokenType: operationToken, Operation: op}, nil return &token{TokenType: operationToken, Operation: op}, nil

View File

@ -3,6 +3,7 @@ package yqlib
type assignPreferences struct { type assignPreferences struct {
DontOverWriteAnchor bool DontOverWriteAnchor bool
OnlyWriteNull bool OnlyWriteNull bool
ClobberCustomTags bool
} }
func assignUpdateFunc(prefs assignPreferences) crossFunctionCalculation { func assignUpdateFunc(prefs assignPreferences) crossFunctionCalculation {
@ -15,18 +16,29 @@ func assignUpdateFunc(prefs assignPreferences) crossFunctionCalculation {
} }
} }
// they way *= (multipleAssign) is handled, we set the multiplePrefs
// on the node, not assignPrefs. Long story.
func getAssignPreferences(preferences interface{}) assignPreferences {
prefs := assignPreferences{}
switch typedPref := preferences.(type) {
case assignPreferences:
prefs = typedPref
case multiplyPreferences:
prefs = typedPref.AssignPrefs
}
return prefs
}
func assignUpdateOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func assignUpdateOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
lhs, err := d.GetMatchingNodes(context, expressionNode.LHS) lhs, err := d.GetMatchingNodes(context, expressionNode.LHS)
if err != nil { if err != nil {
return Context{}, err return Context{}, err
} }
prefs := assignPreferences{} prefs := getAssignPreferences(expressionNode.Operation.Preferences)
// they way *= (multipleAssign) is handled, we set the multiplePrefs
// on the node, not assignPrefs. Long story. log.Debug("assignUpdateOperator prefs: %v", prefs)
if p, ok := expressionNode.Operation.Preferences.(assignPreferences); ok {
prefs = p
}
if !expressionNode.Operation.UpdateAssign { if !expressionNode.Operation.UpdateAssign {
// this works because we already ran against LHS with an editable context. // this works because we already ran against LHS with an editable context.

View File

@ -208,6 +208,23 @@ var assignOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::a:\n b:\n - null\n - c: bogs\n", "D0, P[], (doc)::a:\n b:\n - null\n - c: bogs\n",
}, },
}, },
{
description: "Custom types are maintained by default",
document: "a: !cat meow\nb: !dog woof",
expression: `.a = .b`,
expected: []string{
"D0, P[], (doc)::a: !cat woof\nb: !dog woof\n",
},
},
{
description: "Custom types: clovver",
subdescription: "Use the `c` option to clobber custom tags",
document: "a: !cat meow\nb: !dog woof",
expression: `.a =c .b`,
expected: []string{
"D0, P[], (doc)::a: !dog woof\nb: !dog woof\n",
},
},
} }
func TestAssignOperatorScenarios(t *testing.T) { func TestAssignOperatorScenarios(t *testing.T) {

View File

@ -523,6 +523,35 @@ var multiplyOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::a: !horse {cat: meow, dog: woof}\nb: !goat {dog: woof}\n", "D0, P[], (doc)::a: !horse {cat: meow, dog: woof}\nb: !goat {dog: woof}\n",
}, },
}, },
{
description: "Custom types: clobber tags",
subdescription: "Use the `c` option to clobber custom tags. Note that the second tag is now used",
document: "a: !horse {cat: meow}\nb: !goat {dog: woof}",
expression: ".a *=c .b",
expected: []string{
"D0, P[], (doc)::a: !goat {cat: meow, dog: woof}\nb: !goat {dog: woof}\n",
},
},
{
skipDoc: true,
description: "Custom types: clobber tags - *=",
subdescription: "Use the `c` option to clobber custom tags - on both the `=` and `*` operator. Note that the second tag is now used",
document: "a: !horse {cat: meow}\nb: !goat {dog: woof}",
expression: ".a =c .a *c .b",
expected: []string{
"D0, P[], (doc)::a: !goat {cat: meow, dog: woof}\nb: !goat {dog: woof}\n",
},
},
{
skipDoc: true,
description: "Custom types: dont clobber tags - *=",
subdescription: "Use the `c` option to clobber custom tags - on both the `=` and `*` operator. Note that the second tag is now used",
document: "a: !horse {cat: meow}\nb: !goat {dog: woof}",
expression: ".a *= .b",
expected: []string{
"D0, P[], (doc)::a: !horse {cat: meow, dog: woof}\nb: !goat {dog: woof}\n",
},
},
{ {
skipDoc: true, skipDoc: true,
description: "Custom types: that are really maps", description: "Custom types: that are really maps",

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
logging "gopkg.in/op/go-logging.v1"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -83,8 +84,10 @@ func resultsForRHS(d *dataTreeNavigator, context Context, lhsCandidate *Candidat
} }
for rightEl := rhs.MatchingNodes.Front(); rightEl != nil; rightEl = rightEl.Next() { for rightEl := rhs.MatchingNodes.Front(); rightEl != nil; rightEl = rightEl.Next() {
log.Debugf("Applying calc")
rhsCandidate := rightEl.Value.(*CandidateNode) rhsCandidate := rightEl.Value.(*CandidateNode)
if !log.IsEnabledFor(logging.DEBUG) {
log.Debugf("Applying lhs: %v, rhsCandidate, %v", NodeToString(lhsCandidate), NodeToString(rhsCandidate))
}
resultCandidate, err := prefs.Calculation(d, context, lhsCandidate, rhsCandidate) resultCandidate, err := prefs.Calculation(d, context, lhsCandidate, rhsCandidate)
if err != nil { if err != nil {
return err return err

View File

@ -1,3 +1,8 @@
4.27.3:
- Added new 'c' merge and assign flag that clobbers custom tags
- Bumped go dependency to fix CVE (#1316)
- Updated dependencies
4.27.2: 4.27.2:
- Fixed JSON decoder to maintain object key order. - Fixed JSON decoder to maintain object key order.