mirror of
https://github.com/taigrr/yq
synced 2025-01-18 04:53:17 -08:00
Compare commits
7 Commits
v4.6.2
...
substitute
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
daf0bfe1b9 | ||
|
|
750a00ec35 | ||
|
|
25e0a824c5 | ||
|
|
095f921f62 | ||
|
|
12d3425b4a | ||
|
|
0249f00bd5 | ||
|
|
42b1bf9678 |
@@ -119,6 +119,8 @@ See [webi](https://webinstall.dev/)
|
||||
Supported by @adithyasunil26 (https://github.com/webinstall/webi-installers/tree/master/yq)
|
||||
|
||||
### Windows:
|
||||
[](https://chocolatey.org/packages/yq)
|
||||
[](https://chocolatey.org/packages/yq)
|
||||
```
|
||||
choco install yq
|
||||
```
|
||||
|
||||
@@ -11,7 +11,7 @@ var (
|
||||
GitDescribe string
|
||||
|
||||
// Version is main version number that is being run at the moment.
|
||||
Version = "4.6.2"
|
||||
Version = "4.6.3"
|
||||
|
||||
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
|
||||
// then it means that it is a final release. Otherwise, this is a pre-release
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM mikefarah/yq:4.6.2
|
||||
FROM mikefarah/yq:4.6.3
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference aliases and remove anchor names).
|
||||
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference (or expands) aliases and remove anchor names).
|
||||
|
||||
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.
|
||||
|
||||
|
||||
@@ -18,6 +18,40 @@ will output
|
||||
cat; meow; 1; ; true
|
||||
```
|
||||
|
||||
## Substitute / Replace string
|
||||
This uses golang regex, described [here](https://github.com/google/re2/wiki/Syntax)
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: dogs are great
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a |= sub("dogs", "cats")' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: cats are great
|
||||
```
|
||||
|
||||
## Substitute / Replace string with regex
|
||||
This uses golang regex, described [here](https://github.com/google/re2/wiki/Syntax)
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
b: heat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.[] |= sub("([a])", "${1}r")' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: cart
|
||||
b: heart
|
||||
```
|
||||
|
||||
## Split strings
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
71
pkg/yqlib/doc/Subtract.md
Normal file
71
pkg/yqlib/doc/Subtract.md
Normal file
@@ -0,0 +1,71 @@
|
||||
|
||||
## Number subtraction - float
|
||||
If the lhs or rhs are floats then the expression will be calculated with floats.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: 3
|
||||
b: 4.5
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a = .a - .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: -1.5
|
||||
b: 4.5
|
||||
```
|
||||
|
||||
## Number subtraction - float
|
||||
If the lhs or rhs are floats then the expression will be calculated with floats.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: 3
|
||||
b: 4.5
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a = .a - .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: -1.5
|
||||
b: 4.5
|
||||
```
|
||||
|
||||
## Number subtraction - int
|
||||
If both the lhs and rhs are ints then the expression will be calculated with ints.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: 3
|
||||
b: 4
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a = .a - .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: -1
|
||||
b: 4
|
||||
```
|
||||
|
||||
## Decrement numbers
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: 3
|
||||
b: 5
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.[] -= 1' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: 2
|
||||
b: 4
|
||||
```
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference aliases and remove anchor names).
|
||||
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference (or expands) aliases and remove anchor names).
|
||||
|
||||
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.
|
||||
|
||||
|
||||
@@ -264,6 +264,8 @@ func initLexer() (*lex.Lexer, error) {
|
||||
lexer.Add([]byte(`splitDoc`), opToken(splitDocumentOpType))
|
||||
|
||||
lexer.Add([]byte(`join`), opToken(joinStringOpType))
|
||||
lexer.Add([]byte(`sub`), opToken(subStringOpType))
|
||||
|
||||
lexer.Add([]byte(`split`), opToken(splitStringOpType))
|
||||
lexer.Add([]byte(`keys`), opToken(keysOpType))
|
||||
|
||||
@@ -325,6 +327,8 @@ func initLexer() (*lex.Lexer, error) {
|
||||
lexer.Add([]byte(`\*[\+|\?d]*`), multiplyWithPrefs())
|
||||
lexer.Add([]byte(`\+`), opToken(addOpType))
|
||||
lexer.Add([]byte(`\+=`), opToken(addAssignOpType))
|
||||
lexer.Add([]byte(`\-`), opToken(subtractOpType))
|
||||
lexer.Add([]byte(`\-=`), opToken(subtractAssignOpType))
|
||||
lexer.Add([]byte(`\$[a-zA-Z_-0-9]+`), getVariableOpToken())
|
||||
lexer.Add([]byte(`as`), opToken(assignVariableOpType))
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ var pipeOpType = &operationType{Type: "PIPE", NumArgs: 2, Precedence: 30, Handle
|
||||
|
||||
var assignOpType = &operationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: assignUpdateOperator}
|
||||
var addAssignOpType = &operationType{Type: "ADD_ASSIGN", NumArgs: 2, Precedence: 40, Handler: addAssignOperator}
|
||||
var subtractAssignOpType = &operationType{Type: "SUBTRACT_ASSIGN", NumArgs: 2, Precedence: 40, Handler: subtractAssignOperator}
|
||||
|
||||
var assignAttributesOpType = &operationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: assignAttributesOperator}
|
||||
var assignStyleOpType = &operationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: assignStyleOperator}
|
||||
@@ -46,6 +47,7 @@ var assignAliasOpType = &operationType{Type: "ASSIGN_ALIAS", NumArgs: 2, Precede
|
||||
|
||||
var multiplyOpType = &operationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 42, Handler: multiplyOperator}
|
||||
var addOpType = &operationType{Type: "ADD", NumArgs: 2, Precedence: 42, Handler: addOperator}
|
||||
var subtractOpType = &operationType{Type: "SUBTRACT", NumArgs: 2, Precedence: 42, Handler: subtractOperator}
|
||||
var alternativeOpType = &operationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 42, Handler: alternativeOperator}
|
||||
|
||||
var equalsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: equalsOperator}
|
||||
@@ -73,6 +75,7 @@ var getPathOpType = &operationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50,
|
||||
var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: explodeOperator}
|
||||
var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator}
|
||||
var joinStringOpType = &operationType{Type: "JOIN", NumArgs: 1, Precedence: 50, Handler: joinStringOperator}
|
||||
var subStringOpType = &operationType{Type: "SUBSTR", NumArgs: 1, Precedence: 50, Handler: substituteStringOperator}
|
||||
var splitStringOpType = &operationType{Type: "SPLIT", NumArgs: 1, Precedence: 50, Handler: splitStringOperator}
|
||||
|
||||
var keysOpType = &operationType{Type: "KEYS", NumArgs: 0, Precedence: 50, Handler: keysOperator}
|
||||
|
||||
@@ -39,7 +39,7 @@ func toNodes(candidate *CandidateNode) []*yaml.Node {
|
||||
func addOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("Add operator")
|
||||
|
||||
return crossFunction(d, context, expressionNode, add)
|
||||
return crossFunction(d, context, expressionNode, add, false)
|
||||
}
|
||||
|
||||
func add(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package yqlib
|
||||
|
||||
// corssFunction no matches
|
||||
// can boolean use crossfunction
|
||||
|
||||
func alternativeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("-- alternative")
|
||||
return crossFunction(d, context, expressionNode, alternativeFunc)
|
||||
return crossFunction(d, context, expressionNode, alternativeFunc, true)
|
||||
}
|
||||
|
||||
func alternativeFunc(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
if lhs == nil {
|
||||
return rhs, nil
|
||||
}
|
||||
lhs.Node = unwrapDoc(lhs.Node)
|
||||
rhs.Node = unwrapDoc(rhs.Node)
|
||||
log.Debugf("Alternative LHS: %v", lhs.Node.Tag)
|
||||
|
||||
@@ -13,6 +13,14 @@ var alternativeOperatorScenarios = []expressionScenario{
|
||||
"D0, P[a], (!!str)::bridge\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
expression: `select(tag == "seq") // "cat"`,
|
||||
skipDoc: true,
|
||||
document: `a: frog`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "LHS is not defined",
|
||||
expression: `.a // "hello"`,
|
||||
|
||||
@@ -49,7 +49,7 @@ func orOperator(d *dataTreeNavigator, context Context, expressionNode *Expressio
|
||||
return crossFunction(d, context, expressionNode, performBoolOp(
|
||||
func(b1 bool, b2 bool) bool {
|
||||
return b1 || b2
|
||||
}))
|
||||
}), false)
|
||||
}
|
||||
|
||||
func andOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
@@ -57,7 +57,7 @@ func andOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
|
||||
return crossFunction(d, context, expressionNode, performBoolOp(
|
||||
func(b1 bool, b2 bool) bool {
|
||||
return b1 && b2
|
||||
}))
|
||||
}), false)
|
||||
}
|
||||
|
||||
func notOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
|
||||
@@ -62,7 +62,7 @@ func sequenceFor(d *dataTreeNavigator, context Context, matchingNode *CandidateN
|
||||
}
|
||||
|
||||
return &CandidateNode{Node: &node, Document: document, Path: path}, nil
|
||||
})
|
||||
}, false)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -4,7 +4,7 @@ import "gopkg.in/yaml.v3"
|
||||
|
||||
func equalsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("-- equalsOperation")
|
||||
return crossFunction(d, context, expressionNode, isEquals(false))
|
||||
return crossFunction(d, context, expressionNode, isEquals(false), false)
|
||||
}
|
||||
|
||||
func isEquals(flip bool) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
@@ -29,5 +29,5 @@ func isEquals(flip bool) func(d *dataTreeNavigator, context Context, lhs *Candid
|
||||
|
||||
func notEqualsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("-- equalsOperation")
|
||||
return crossFunction(d, context, expressionNode, isEquals(true))
|
||||
return crossFunction(d, context, expressionNode, isEquals(true), false)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ type multiplyPreferences struct {
|
||||
|
||||
func multiplyOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("-- MultiplyOperator")
|
||||
return crossFunction(d, context, expressionNode, multiply(expressionNode.Operation.Preferences.(multiplyPreferences)))
|
||||
return crossFunction(d, context, expressionNode, multiply(expressionNode.Operation.Preferences.(multiplyPreferences)), false)
|
||||
}
|
||||
|
||||
func multiply(preferences multiplyPreferences) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
|
||||
@@ -283,5 +283,5 @@ func TestMultiplyOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range multiplyOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Multiply", multiplyOperatorScenarios)
|
||||
documentScenarios(t, "Multiply (Merge)", multiplyOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -3,11 +3,77 @@ package yqlib
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func getSubstituteParameters(d *dataTreeNavigator, block *ExpressionNode, context Context) (string, string, error) {
|
||||
regEx := ""
|
||||
replacementText := ""
|
||||
|
||||
regExNodes, err := d.GetMatchingNodes(context, block.Lhs)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if regExNodes.MatchingNodes.Front() != nil {
|
||||
regEx = regExNodes.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
|
||||
log.Debug("regEx %v", regEx)
|
||||
|
||||
replacementNodes, err := d.GetMatchingNodes(context, block.Rhs)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if replacementNodes.MatchingNodes.Front() != nil {
|
||||
replacementText = replacementNodes.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
|
||||
return regEx, replacementText, nil
|
||||
}
|
||||
|
||||
func substitute(original string, regex *regexp.Regexp, replacement string) *yaml.Node {
|
||||
replacedString := regex.ReplaceAllString(original, replacement)
|
||||
return &yaml.Node{Kind: yaml.ScalarNode, Value: replacedString, Tag: "!!str"}
|
||||
}
|
||||
|
||||
func substituteStringOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
//rhs block operator
|
||||
//lhs of block = regex
|
||||
//rhs of block = replacement expression
|
||||
block := expressionNode.Rhs
|
||||
|
||||
regExStr, replacementText, err := getSubstituteParameters(d, block, context)
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
regEx, err := regexp.Compile(regExStr)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := unwrapDoc(candidate.Node)
|
||||
if node.Tag != "!!str" {
|
||||
return Context{}, fmt.Errorf("cannot sustitute with %v, can only substitute strings", node.Tag)
|
||||
}
|
||||
|
||||
targetNode := substitute(node.Value, regEx, replacementText)
|
||||
result := candidate.CreateChild(nil, targetNode)
|
||||
results.PushBack(result)
|
||||
}
|
||||
|
||||
return context.ChildContext(results), nil
|
||||
|
||||
}
|
||||
|
||||
func joinStringOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("-- joinStringOperator")
|
||||
joinStr := ""
|
||||
@@ -26,7 +92,7 @@ func joinStringOperator(d *dataTreeNavigator, context Context, expressionNode *E
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := unwrapDoc(candidate.Node)
|
||||
if node.Kind != yaml.SequenceNode {
|
||||
return Context{}, fmt.Errorf("Cannot join with %v, can only join arrays of scalars", node.Tag)
|
||||
return Context{}, fmt.Errorf("cannot join with %v, can only join arrays of scalars", node.Tag)
|
||||
}
|
||||
targetNode := join(node.Content, joinStr)
|
||||
result := candidate.CreateChild(nil, targetNode)
|
||||
|
||||
@@ -13,6 +13,24 @@ var stringsOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (!!str)::cat; meow; 1; ; true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Substitute / Replace string",
|
||||
subdescription: "This uses golang regex, described [here](https://github.com/google/re2/wiki/Syntax)",
|
||||
document: `a: dogs are great`,
|
||||
expression: `.a |= sub("dogs", "cats")`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: cats are great\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Substitute / Replace string with regex",
|
||||
subdescription: "This uses golang regex, described [here](https://github.com/google/re2/wiki/Syntax)",
|
||||
document: "a: cat\nb: heat",
|
||||
expression: `.[] |= sub("([a])", "${1}r")`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: cart\nb: heart\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Split strings",
|
||||
document: `"cat; meow; 1; ; true"`,
|
||||
|
||||
97
pkg/yqlib/operator_subtract.go
Normal file
97
pkg/yqlib/operator_subtract.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"strconv"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func createSubtractOp(lhs *ExpressionNode, rhs *ExpressionNode) *ExpressionNode {
|
||||
return &ExpressionNode{Operation: &Operation{OperationType: subtractOpType},
|
||||
Lhs: lhs,
|
||||
Rhs: rhs}
|
||||
}
|
||||
|
||||
func subtractAssignOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
assignmentOp := &Operation{OperationType: assignOpType}
|
||||
assignmentOp.UpdateAssign = true
|
||||
selfExpression := &ExpressionNode{Operation: &Operation{OperationType: selfReferenceOpType}}
|
||||
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: expressionNode.Lhs, Rhs: createSubtractOp(selfExpression, expressionNode.Rhs)}
|
||||
return d.GetMatchingNodes(context, assignmentOpNode)
|
||||
}
|
||||
|
||||
func subtractOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("Subtract operator")
|
||||
|
||||
return crossFunction(d, context, expressionNode, subtract, false)
|
||||
}
|
||||
|
||||
func subtract(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
lhs.Node = unwrapDoc(lhs.Node)
|
||||
rhs.Node = unwrapDoc(rhs.Node)
|
||||
|
||||
lhsNode := lhs.Node
|
||||
|
||||
if lhsNode.Tag == "!!null" {
|
||||
return lhs.CreateChild(nil, rhs.Node), nil
|
||||
}
|
||||
|
||||
target := lhs.CreateChild(nil, &yaml.Node{})
|
||||
|
||||
switch lhsNode.Kind {
|
||||
case yaml.MappingNode:
|
||||
return nil, fmt.Errorf("Maps not yet supported for subtraction")
|
||||
case yaml.SequenceNode:
|
||||
return nil, fmt.Errorf("Sequences not yet supported for subtraction")
|
||||
// target.Node.Kind = yaml.SequenceNode
|
||||
// target.Node.Style = lhsNode.Style
|
||||
// target.Node.Tag = "!!seq"
|
||||
// target.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
|
||||
case yaml.ScalarNode:
|
||||
if rhs.Node.Kind != yaml.ScalarNode {
|
||||
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.Style = lhsNode.Style
|
||||
return subtractScalars(target, lhsNode, rhs.Node)
|
||||
}
|
||||
|
||||
return target, nil
|
||||
}
|
||||
|
||||
func subtractScalars(target *CandidateNode, lhs *yaml.Node, rhs *yaml.Node) (*CandidateNode, error) {
|
||||
|
||||
if lhs.Tag == "!!str" {
|
||||
return nil, fmt.Errorf("strings cannot be subtracted")
|
||||
} else if lhs.Tag == "!!int" && rhs.Tag == "!!int" {
|
||||
lhsNum, err := strconv.Atoi(lhs.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rhsNum, err := strconv.Atoi(rhs.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := lhsNum - rhsNum
|
||||
target.Node.Tag = "!!int"
|
||||
target.Node.Value = fmt.Sprintf("%v", result)
|
||||
} else if (lhs.Tag == "!!int" || lhs.Tag == "!!float") && (rhs.Tag == "!!int" || rhs.Tag == "!!float") {
|
||||
lhsNum, err := strconv.ParseFloat(lhs.Value, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rhsNum, err := strconv.ParseFloat(rhs.Value, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := lhsNum - rhsNum
|
||||
target.Node.Tag = "!!float"
|
||||
target.Node.Value = fmt.Sprintf("%v", result)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%v cannot be added to %v", lhs.Tag, rhs.Tag)
|
||||
}
|
||||
|
||||
return target, nil
|
||||
}
|
||||
50
pkg/yqlib/operator_subtract_test.go
Normal file
50
pkg/yqlib/operator_subtract_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var subtractOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Number subtraction - float",
|
||||
subdescription: "If the lhs or rhs are floats then the expression will be calculated with floats.",
|
||||
document: `{a: 3, b: 4.5}`,
|
||||
expression: `.a = .a - .b`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: -1.5, b: 4.5}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Number subtraction - float",
|
||||
subdescription: "If the lhs or rhs are floats then the expression will be calculated with floats.",
|
||||
document: `{a: 3, b: 4.5}`,
|
||||
expression: `.a = .a - .b`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: -1.5, b: 4.5}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Number subtraction - int",
|
||||
subdescription: "If both the lhs and rhs are ints then the expression will be calculated with ints.",
|
||||
document: `{a: 3, b: 4}`,
|
||||
expression: `.a = .a - .b`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: -1, b: 4}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Decrement numbers",
|
||||
document: `{a: 3, b: 5}`,
|
||||
expression: `.[] -= 1`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: 2, b: 4}\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestSubtractOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range subtractOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Subtract", subtractOperatorScenarios)
|
||||
}
|
||||
@@ -24,7 +24,20 @@ func emptyOperator(d *dataTreeNavigator, context Context, expressionNode *Expres
|
||||
|
||||
type crossFunctionCalculation func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error)
|
||||
|
||||
func doCrossFunc(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode, calculation crossFunctionCalculation) (Context, error) {
|
||||
func resultsForRhs(d *dataTreeNavigator, context Context, lhsCandidate *CandidateNode, rhs Context, calculation crossFunctionCalculation, results *list.List) error {
|
||||
for rightEl := rhs.MatchingNodes.Front(); rightEl != nil; rightEl = rightEl.Next() {
|
||||
log.Debugf("Applying calc")
|
||||
rhsCandidate := rightEl.Value.(*CandidateNode)
|
||||
resultCandidate, err := calculation(d, context, lhsCandidate, rhsCandidate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
results.PushBack(resultCandidate)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func doCrossFunc(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode, calculation crossFunctionCalculation, calcWhenEmpty bool) (Context, error) {
|
||||
var results = list.New()
|
||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
||||
if err != nil {
|
||||
@@ -38,24 +51,33 @@ func doCrossFunc(d *dataTreeNavigator, context Context, expressionNode *Expressi
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
lhsCandidate := el.Value.(*CandidateNode)
|
||||
|
||||
for rightEl := rhs.MatchingNodes.Front(); rightEl != nil; rightEl = rightEl.Next() {
|
||||
log.Debugf("Applying calc")
|
||||
rhsCandidate := rightEl.Value.(*CandidateNode)
|
||||
resultCandidate, err := calculation(d, context, lhsCandidate, rhsCandidate)
|
||||
if calcWhenEmpty && lhs.MatchingNodes.Len() == 0 {
|
||||
if rhs.MatchingNodes.Len() == 0 {
|
||||
resultCandidate, err := calculation(d, context, nil, nil)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
results.PushBack(resultCandidate)
|
||||
}
|
||||
err := resultsForRhs(d, context, nil, rhs, calculation, results)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
}
|
||||
|
||||
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
lhsCandidate := el.Value.(*CandidateNode)
|
||||
|
||||
err := resultsForRhs(d, context, lhsCandidate, rhs, calculation, results)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
}
|
||||
return context.ChildContext(results), nil
|
||||
}
|
||||
|
||||
func crossFunction(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode, calculation crossFunctionCalculation) (Context, error) {
|
||||
func crossFunction(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode, calculation crossFunctionCalculation, calcWhenEmpty bool) (Context, error) {
|
||||
var results = list.New()
|
||||
|
||||
var evaluateAllTogether = true
|
||||
@@ -66,11 +88,11 @@ func crossFunction(d *dataTreeNavigator, context Context, expressionNode *Expres
|
||||
}
|
||||
}
|
||||
if evaluateAllTogether {
|
||||
return doCrossFunc(d, context, expressionNode, calculation)
|
||||
return doCrossFunc(d, context, expressionNode, calculation, calcWhenEmpty)
|
||||
}
|
||||
|
||||
for matchEl := context.MatchingNodes.Front(); matchEl != nil; matchEl = matchEl.Next() {
|
||||
innerResults, err := doCrossFunc(d, context.SingleChildContext(matchEl.Value.(*CandidateNode)), expressionNode, calculation)
|
||||
innerResults, err := doCrossFunc(d, context.SingleChildContext(matchEl.Value.(*CandidateNode)), expressionNode, calculation, calcWhenEmpty)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
- increment version in snapcraft.yaml
|
||||
- increment version in github-action/Dockerfile
|
||||
- make sure local build passes
|
||||
- run gosec (manual because docker platforms)
|
||||
- commit version update changes
|
||||
- tag git with same version number
|
||||
- commit vX tag - this will trigger github actions
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: yq
|
||||
version: '4.6.2'
|
||||
version: '4.6.3'
|
||||
summary: A lightweight and portable command-line YAML processor
|
||||
description: |
|
||||
The aim of the project is to be the jq or sed of yaml files.
|
||||
|
||||
Reference in New Issue
Block a user