Auto cast for add

This commit is contained in:
Mike Farah 2022-01-22 13:17:16 +11:00
parent 6f24e878aa
commit 50df792e49
3 changed files with 105 additions and 9 deletions

View File

@ -265,3 +265,39 @@ will output
cat cat
``` ```
## Custom types: that are really strings
when custom tags are encountered, yq will try to decode the underlying type.
Given a sample.yml file of:
```yaml
a: !horse cat
b: !goat _meow
```
then
```bash
yq eval '.a += .b' sample.yml
```
will output
```yaml
a: !horse cat_meow
b: !goat _meow
```
## Custom types: that are really numbers
when custom tags are encountered, yq will try to decode the underlying type.
Given a sample.yml file of:
```yaml
a: !horse 1.2
b: !goat 2.3
```
then
```bash
yq eval '.a += .b' sample.yml
```
will output
```yaml
a: !horse 3.5
b: !goat 2.3
```

View File

@ -3,6 +3,7 @@ package yqlib
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"strings"
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
@ -55,7 +56,7 @@ func add(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *Candida
case yaml.SequenceNode: case yaml.SequenceNode:
target.Node.Kind = yaml.SequenceNode target.Node.Kind = yaml.SequenceNode
target.Node.Style = lhsNode.Style target.Node.Style = lhsNode.Style
target.Node.Tag = "!!seq" target.Node.Tag = lhsNode.Tag
target.Node.Content = append(lhsNode.Content, toNodes(rhs)...) 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 {
@ -69,12 +70,39 @@ func add(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *Candida
return target, nil return target, nil
} }
func addScalars(target *CandidateNode, lhs *yaml.Node, rhs *yaml.Node) (*CandidateNode, error) { func guessTagFromCustomType(node *yaml.Node) string {
decoder := NewYamlDecoder()
decoder.Init(strings.NewReader(node.Value))
var dataBucket yaml.Node
errorReading := decoder.Decode(&dataBucket)
if errorReading != nil {
log.Warning("could not guess underlying tag type %w", errorReading)
return node.Tag
}
guessedTag := unwrapDoc(&dataBucket).Tag
log.Info("im guessing the tag %v is a %v", node.Tag, guessedTag)
return guessedTag
}
if lhs.Tag == "!!str" { func addScalars(target *CandidateNode, lhs *yaml.Node, rhs *yaml.Node) (*CandidateNode, error) {
target.Node.Tag = "!!str" lhsTag := lhs.Tag
rhsTag := rhs.Tag
lhsIsCustom := false
if !strings.HasPrefix(lhsTag, "!!") {
// custom tag - we have to have a guess
lhsTag = guessTagFromCustomType(lhs)
lhsIsCustom = true
}
if !strings.HasPrefix(rhsTag, "!!") {
// custom tag - we have to have a guess
rhsTag = guessTagFromCustomType(rhs)
}
if lhsTag == "!!str" {
target.Node.Tag = lhs.Tag
target.Node.Value = lhs.Value + rhs.Value target.Node.Value = lhs.Value + rhs.Value
} else if lhs.Tag == "!!int" && rhs.Tag == "!!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 nil, err
@ -84,9 +112,9 @@ func addScalars(target *CandidateNode, lhs *yaml.Node, rhs *yaml.Node) (*Candida
return nil, err return nil, err
} }
sum := lhsNum + rhsNum sum := lhsNum + rhsNum
target.Node.Tag = "!!int" target.Node.Tag = lhs.Tag
target.Node.Value = fmt.Sprintf(format, sum) target.Node.Value = fmt.Sprintf(format, sum)
} else if (lhs.Tag == "!!int" || lhs.Tag == "!!float") && (rhs.Tag == "!!int" || rhs.Tag == "!!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 nil, err
@ -96,10 +124,14 @@ func addScalars(target *CandidateNode, lhs *yaml.Node, rhs *yaml.Node) (*Candida
return nil, err return nil, err
} }
sum := lhsNum + rhsNum sum := lhsNum + rhsNum
target.Node.Tag = "!!float" if lhsIsCustom {
target.Node.Tag = lhs.Tag
} else {
target.Node.Tag = "!!float"
}
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", lhs.Tag, rhs.Tag) return nil, fmt.Errorf("%v cannot be added to %v", lhsTag, rhsTag)
} }
return target, nil return target, nil

View File

@ -144,6 +144,34 @@ var addOperatorScenarios = []expressionScenario{
"D0, P[], (!!str)::cat\n", "D0, P[], (!!str)::cat\n",
}, },
}, },
{
description: "Custom types: that are really strings",
subdescription: "when custom tags are encountered, yq will try to decode the underlying type.",
document: "a: !horse cat\nb: !goat _meow",
expression: `.a += .b`,
expected: []string{
"D0, P[], (doc)::a: !horse cat_meow\nb: !goat _meow\n",
},
},
{
description: "Custom types: that are really numbers",
subdescription: "when custom tags are encountered, yq will try to decode the underlying type.",
document: "a: !horse 1.2\nb: !goat 2.3",
expression: `.a += .b`,
expected: []string{
"D0, P[], (doc)::a: !horse 3.5\nb: !goat 2.3\n",
},
},
{
description: "Custom types: that are really arrays",
skipDoc: true,
subdescription: "when custom tags are encountered, yq will try to decode the underlying type.",
document: "a: !horse [a]\nb: !goat [b]",
expression: `.a += .b`,
expected: []string{
"D0, P[], (doc)::a: !horse [a, b]\nb: !goat [b]\n",
},
},
} }
func TestAddOperatorScenarios(t *testing.T) { func TestAddOperatorScenarios(t *testing.T) {