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
// 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
}

View File

@ -7,6 +7,11 @@ Which will assign the LHS node values to the RHS node values. The RHS expression
### 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.
### Flags
- `c` clobber custom tags
{% hint style="warning" %}
Note that versions prior to 4.18 require the 'eval/e' command to be specified. 
@ -235,3 +240,37 @@ a:
- 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

@ -7,3 +7,7 @@ Which will assign the LHS node values to the RHS node values. The RHS expression
### 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.
### 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
- `?` only merge _existing_ fields
- `n` only merge _new_ fields
- `c` clobber custom tags
### 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
- `?` only merge _existing_ fields
- `n` only merge _new_ fields
- `c` clobber custom tags
### Merge two files together
@ -481,3 +482,26 @@ b: !goat
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},
{"LessThan", `\s*<\s*`, opTokenWithPrefs(compareOpType, nil, compareTypePref{OrEqual: false, Greater: false}), 0},
{"AssignRelative", `\|=`, assignOpToken(true), 0},
{"Assign", `=`, assignOpToken(false), 0},
{"AssignRelative", `\|=[c]*`, assignOpToken(true), 0},
{"Assign", `=[c]*`, assignOpToken(false), 0},
{`whitespace`, `[ \t\n]+`, nil, 0},
@ -194,8 +194,8 @@ var participleYqRules = []*participleYqRule{
{"Union", `,`, opToken(unionOpType), 0},
{"MultiplyAssign", `\*=[\+|\?dn]*`, multiplyWithPrefs(multiplyAssignOpType), 0},
{"Multiply", `\*[\+|\?dn]*`, multiplyWithPrefs(multiplyOpType), 0},
{"MultiplyAssign", `\*=[\+|\?cdn]*`, multiplyWithPrefs(multiplyAssignOpType), 0},
{"Multiply", `\*[\+|\?cdn]*`, multiplyWithPrefs(multiplyOpType), 0},
{"AddAssign", `\+=`, opToken(addAssignOpType), 0},
{"Add", `\+`, opToken(addOpType), 0},
@ -317,6 +317,9 @@ func assignOpToken(updateAssign bool) yqAction {
log.Debug("assignOpToken %v", rawToken.Value)
value := rawToken.Value
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}
return &token{TokenType: operationToken, Operation: op}, nil
}
@ -387,6 +390,7 @@ func envSubstWithOptions() yqAction {
func multiplyWithPrefs(op *operationType) yqAction {
return func(rawToken lexer.Token) (*token, error) {
prefs := multiplyPreferences{}
prefs.AssignPrefs = assignPreferences{}
options := rawToken.Value
if strings.Contains(options, "+") {
prefs.AppendArrays = true
@ -395,11 +399,14 @@ func multiplyWithPrefs(op *operationType) yqAction {
prefs.TraversePrefs = traversePreferences{DontAutoCreate: true}
}
if strings.Contains(options, "n") {
prefs.AssignPrefs = assignPreferences{OnlyWriteNull: true}
prefs.AssignPrefs.OnlyWriteNull = true
}
if strings.Contains(options, "d") {
prefs.DeepMergeArrays = true
}
if strings.Contains(options, "c") {
prefs.AssignPrefs.ClobberCustomTags = true
}
prefs.TraversePrefs.DontFollowAlias = true
op := &Operation{OperationType: op, Value: multiplyOpType.Type, StringValue: options, Preferences: prefs}
return &token{TokenType: operationToken, Operation: op}, nil

View File

@ -3,6 +3,7 @@ package yqlib
type assignPreferences struct {
DontOverWriteAnchor bool
OnlyWriteNull bool
ClobberCustomTags bool
}
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) {
lhs, err := d.GetMatchingNodes(context, expressionNode.LHS)
if err != nil {
return Context{}, err
}
prefs := assignPreferences{}
// they way *= (multipleAssign) is handled, we set the multiplePrefs
// on the node, not assignPrefs. Long story.
if p, ok := expressionNode.Operation.Preferences.(assignPreferences); ok {
prefs = p
}
prefs := getAssignPreferences(expressionNode.Operation.Preferences)
log.Debug("assignUpdateOperator prefs: %v", prefs)
if !expressionNode.Operation.UpdateAssign {
// 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",
},
},
{
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) {

View File

@ -523,6 +523,35 @@ var multiplyOperatorScenarios = []expressionScenario{
"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,
description: "Custom types: that are really maps",

View File

@ -5,6 +5,7 @@ import (
"fmt"
"github.com/jinzhu/copier"
logging "gopkg.in/op/go-logging.v1"
"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() {
log.Debugf("Applying calc")
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)
if err != nil {
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:
- Fixed JSON decoder to maintain object key order.