Added support for adding objects

This commit is contained in:
Mike Farah 2022-01-23 11:35:44 +11:00
parent 9b66e0a094
commit 9b1a7bf451
4 changed files with 96 additions and 20 deletions

View File

@ -4,10 +4,10 @@ Add behaves differently according to the type of the LHS:
* arrays: concatenate * arrays: concatenate
* number scalars: arithmetic addition * number scalars: arithmetic addition
* string scalars: concatenate * string scalars: concatenate
* maps: shallow merge (use the multiply operator (`*`) to deeply merge)
Use `+=` as append assign for things like increment. Note that `.a += .x` is equivalent to running `.a = .a + .x`. Use `+=` as a relative append assign for things like increment. Note that `.a += .x` is equivalent to running `.a = .a + .x`.
Add is not (yet) supported for maps - however you can use merge `*` which will have a similar effect...
## Concatenate and assign arrays ## Concatenate and assign arrays
Given a sample.yml file of: Given a sample.yml file of:
@ -267,6 +267,41 @@ will output
cat cat
``` ```
## Add maps to shallow merge
Adding objects together shallow merges them. Use `*` to deeply merge.
Given a sample.yml file of:
```yaml
a:
thing:
name: Astuff
value: x
a1: cool
b:
thing:
name: Bstuff
legs: 3
b1: neat
```
then
```bash
yq eval '.a += .b' sample.yml
```
will output
```yaml
a:
thing:
name: Bstuff
legs: 3
a1: cool
b1: neat
b:
thing:
name: Bstuff
legs: 3
b1: neat
```
## Custom types: that are really strings ## Custom types: that are really strings
When custom tags are encountered, yq will try to decode the underlying type. When custom tags are encountered, yq will try to decode the underlying type.

View File

@ -4,7 +4,7 @@ Add behaves differently according to the type of the LHS:
* arrays: concatenate * arrays: concatenate
* number scalars: arithmetic addition * number scalars: arithmetic addition
* string scalars: concatenate * string scalars: concatenate
* maps: shallow merge (use the multiply operator (`*`) to deeply merge)
Use `+=` as append assign for things like increment. Note that `.a += .x` is equivalent to running `.a = .a + .x`. Use `+=` as a relative append assign for things like increment. Note that `.a += .x` is equivalent to running `.a = .a + .x`.
Add is not (yet) supported for maps - however you can use merge `*` which will have a similar effect...

View File

@ -52,21 +52,19 @@ func add(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *Candida
switch lhsNode.Kind { switch lhsNode.Kind {
case yaml.MappingNode: case yaml.MappingNode:
return nil, fmt.Errorf("maps not yet supported for addition") addMaps(target, lhs, rhs)
case yaml.SequenceNode: case yaml.SequenceNode:
target.Node.Kind = yaml.SequenceNode addSequences(target, lhs, rhs)
target.Node.Style = lhsNode.Style
target.Node.Tag = lhsNode.Tag
target.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
case yaml.ScalarNode: case yaml.ScalarNode:
if rhs.Node.Kind != yaml.ScalarNode { if rhs.Node.Kind != yaml.ScalarNode {
return nil, fmt.Errorf("%v (%v) cannot be added to a 2%v", rhs.Node.Tag, rhs.Path, lhsNode.Tag) return nil, fmt.Errorf("%v (%v) cannot be added to a %v", rhs.Node.Tag, rhs.Path, lhsNode.Tag)
} }
target.Node.Kind = yaml.ScalarNode target.Node.Kind = yaml.ScalarNode
target.Node.Style = lhsNode.Style target.Node.Style = lhsNode.Style
return addScalars(target, lhsNode, rhs.Node) if err := addScalars(target, lhsNode, rhs.Node); err != nil {
return nil, err
}
} }
return target, nil return target, nil
} }
@ -89,7 +87,7 @@ func guessTagFromCustomType(node *yaml.Node) string {
return guessedTag return guessedTag
} }
func addScalars(target *CandidateNode, lhs *yaml.Node, rhs *yaml.Node) (*CandidateNode, error) { func addScalars(target *CandidateNode, lhs *yaml.Node, rhs *yaml.Node) error {
lhsTag := lhs.Tag lhsTag := lhs.Tag
rhsTag := rhs.Tag rhsTag := rhs.Tag
lhsIsCustom := false lhsIsCustom := false
@ -110,11 +108,11 @@ func addScalars(target *CandidateNode, lhs *yaml.Node, rhs *yaml.Node) (*Candida
} else if lhsTag == "!!int" && rhsTag == "!!int" { } else if lhsTag == "!!int" && rhsTag == "!!int" {
format, lhsNum, err := parseInt(lhs.Value) format, lhsNum, err := parseInt(lhs.Value)
if err != nil { if err != nil {
return nil, err return err
} }
_, rhsNum, err := parseInt(rhs.Value) _, rhsNum, err := parseInt(rhs.Value)
if err != nil { if err != nil {
return nil, err return err
} }
sum := lhsNum + rhsNum sum := lhsNum + rhsNum
target.Node.Tag = lhs.Tag target.Node.Tag = lhs.Tag
@ -122,11 +120,11 @@ func addScalars(target *CandidateNode, lhs *yaml.Node, rhs *yaml.Node) (*Candida
} else if (lhsTag == "!!int" || lhsTag == "!!float") && (rhsTag == "!!int" || rhsTag == "!!float") { } else if (lhsTag == "!!int" || lhsTag == "!!float") && (rhsTag == "!!int" || rhsTag == "!!float") {
lhsNum, err := strconv.ParseFloat(lhs.Value, 64) lhsNum, err := strconv.ParseFloat(lhs.Value, 64)
if err != nil { if err != nil {
return nil, err return err
} }
rhsNum, err := strconv.ParseFloat(rhs.Value, 64) rhsNum, err := strconv.ParseFloat(rhs.Value, 64)
if err != nil { if err != nil {
return nil, err return err
} }
sum := lhsNum + rhsNum sum := lhsNum + rhsNum
if lhsIsCustom { if lhsIsCustom {
@ -136,8 +134,42 @@ func addScalars(target *CandidateNode, lhs *yaml.Node, rhs *yaml.Node) (*Candida
} }
target.Node.Value = fmt.Sprintf("%v", sum) target.Node.Value = fmt.Sprintf("%v", sum)
} else { } else {
return nil, fmt.Errorf("%v cannot be added to %v", lhsTag, rhsTag) return fmt.Errorf("%v cannot be added to %v", lhsTag, rhsTag)
} }
return nil
return target, nil }
func addSequences(target *CandidateNode, lhs *CandidateNode, rhs *CandidateNode) {
target.Node.Kind = yaml.SequenceNode
target.Node.Style = lhs.Node.Style
target.Node.Tag = lhs.Node.Tag
target.Node.Content = make([]*yaml.Node, len(lhs.Node.Content))
copy(target.Node.Content, lhs.Node.Content)
target.Node.Content = append(target.Node.Content, toNodes(rhs)...)
}
func addMaps(target *CandidateNode, lhsC *CandidateNode, rhsC *CandidateNode) {
lhs := lhsC.Node
rhs := rhsC.Node
target.Node.Content = make([]*yaml.Node, len(lhs.Content))
copy(target.Node.Content, lhs.Content)
for index := 0; index < len(rhs.Content); index = index + 2 {
key := rhs.Content[index]
value := rhs.Content[index+1]
log.Debug("finding %v", key.Value)
indexInLhs := findInArray(target.Node, key)
log.Debug("indexInLhs %v", indexInLhs)
if indexInLhs < 0 {
// not in there, append it
target.Node.Content = append(target.Node.Content, key, value)
} else {
// it's there, replace it
target.Node.Content[indexInLhs+1] = value
}
}
target.Node.Kind = yaml.MappingNode
target.Node.Style = lhs.Style
target.Node.Tag = lhs.Tag
} }

View File

@ -144,6 +144,15 @@ var addOperatorScenarios = []expressionScenario{
"D0, P[], (!!str)::cat\n", "D0, P[], (!!str)::cat\n",
}, },
}, },
{
description: "Add maps to shallow merge",
subdescription: "Adding objects together shallow merges them. Use `*` to deeply merge.",
document: "a: {thing: {name: Astuff, value: x}, a1: cool}\nb: {thing: {name: Bstuff, legs: 3}, b1: neat}",
expression: `.a += .b`,
expected: []string{
"D0, P[], (doc)::a: {thing: {name: Bstuff, legs: 3}, a1: cool, b1: neat}\nb: {thing: {name: Bstuff, legs: 3}, b1: neat}\n",
},
},
{ {
description: "Custom types: that are really strings", description: "Custom types: that are really strings",
subdescription: "When custom tags are encountered, yq will try to decode the underlying type.", subdescription: "When custom tags are encountered, yq will try to decode the underlying type.",