Merge branch 'subtract'

This commit is contained in:
Mike Farah 2021-09-12 21:59:07 +10:00
commit 084a0f6f1e
4 changed files with 166 additions and 7 deletions

View File

@ -1,4 +1,43 @@
## Array subtraction
Running
```bash
yq eval --null-input '[1,2] - [2,3]'
```
will output
```yaml
- 1
```
## Array subtraction with nested array
Running
```bash
yq eval --null-input '[[1], 1, 2] - [[1], 3]'
```
will output
```yaml
- 1
- 2
```
## Array subtraction with nested object
Note that order of the keys does not matter
Given a sample.yml file of:
```yaml
- a: b
c: d
- a: b
```
then
```bash
yq eval '. - [{"c": "d", "a": "b"}]' sample.yml
```
will output
```yaml
- a: b
```
## Number subtraction - float ## Number subtraction - float
If the lhs or rhs are floats then the expression will be calculated with floats. If the lhs or rhs are floats then the expression will be calculated with floats.

View File

@ -121,6 +121,64 @@ type Operation struct {
UpdateAssign bool // used for assign ops, when true it means we evaluate the rhs given the lhs UpdateAssign bool // used for assign ops, when true it means we evaluate the rhs given the lhs
} }
func recurseNodeArrayEqual(lhs *yaml.Node, rhs *yaml.Node) bool {
if len(lhs.Content) != len(rhs.Content) {
return false
}
for index := 0; index < len(lhs.Content); index = index + 1 {
if !recursiveNodeEqual(lhs.Content[index], rhs.Content[index]) {
return false
}
}
return true
}
func findInArray(array *yaml.Node, item *yaml.Node) int {
for index := 0; index < len(array.Content); index = index + 1 {
if recursiveNodeEqual(array.Content[index], item) {
return index
}
}
return -1
}
func recurseNodeObjectEqual(lhs *yaml.Node, rhs *yaml.Node) bool {
if len(lhs.Content) != len(rhs.Content) {
return false
}
for index := 0; index < len(lhs.Content); index = index + 2 {
key := lhs.Content[index]
value := lhs.Content[index+1]
indexInRhs := findInArray(rhs, key)
if indexInRhs == -1 || !recursiveNodeEqual(value, rhs.Content[indexInRhs+1]) {
return false
}
}
return true
}
func recursiveNodeEqual(lhs *yaml.Node, rhs *yaml.Node) bool {
if lhs.Kind != rhs.Kind || lhs.Tag != rhs.Tag {
return false
} else if lhs.Tag == "!!null" {
return true
} else if lhs.Kind == yaml.ScalarNode {
return lhs.Value == rhs.Value
} else if lhs.Kind == yaml.SequenceNode {
return recurseNodeArrayEqual(lhs, rhs)
} else if lhs.Kind == yaml.MappingNode {
return recurseNodeObjectEqual(lhs, rhs)
}
return false
}
// yaml numbers can be hex encoded... // yaml numbers can be hex encoded...
func parseInt(numberString string) (string, int64, error) { func parseInt(numberString string) (string, int64, error) {
if strings.HasPrefix(numberString, "0x") || if strings.HasPrefix(numberString, "0x") ||

View File

@ -5,7 +5,7 @@ import (
"strconv" "strconv"
yaml "gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
func createSubtractOp(lhs *ExpressionNode, rhs *ExpressionNode) *ExpressionNode { func createSubtractOp(lhs *ExpressionNode, rhs *ExpressionNode) *ExpressionNode {
@ -24,6 +24,24 @@ func subtractOperator(d *dataTreeNavigator, context Context, expressionNode *Exp
return crossFunction(d, context.ReadOnlyClone(), expressionNode, subtract, false) return crossFunction(d, context.ReadOnlyClone(), expressionNode, subtract, false)
} }
func subtractArray(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
newLhsArray := make([]*yaml.Node, 0)
for lindex := 0; lindex < len(lhs.Node.Content); lindex = lindex + 1 {
shouldInclude := true
for rindex := 0; rindex < len(rhs.Node.Content) && shouldInclude; rindex = rindex + 1 {
if recursiveNodeEqual(lhs.Node.Content[lindex], rhs.Node.Content[rindex]) {
shouldInclude = false
}
}
if shouldInclude {
newLhsArray = append(newLhsArray, lhs.Node.Content[lindex])
}
}
lhs.Node.Content = newLhsArray
return lhs, nil
}
func subtract(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func subtract(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = unwrapDoc(lhs.Node) lhs.Node = unwrapDoc(lhs.Node)
rhs.Node = unwrapDoc(rhs.Node) rhs.Node = unwrapDoc(rhs.Node)
@ -40,14 +58,13 @@ func subtract(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *Ca
case yaml.MappingNode: case yaml.MappingNode:
return nil, fmt.Errorf("Maps not yet supported for subtraction") return nil, fmt.Errorf("Maps not yet supported for subtraction")
case yaml.SequenceNode: case yaml.SequenceNode:
return nil, fmt.Errorf("Sequences not yet supported for subtraction") if rhs.Node.Kind != yaml.SequenceNode {
// target.Node.Kind = yaml.SequenceNode return nil, fmt.Errorf("%v (%v) cannot be subtracted from %v", rhs.Node.Tag, rhs.Path, lhsNode.Tag)
// target.Node.Style = lhsNode.Style }
// target.Node.Tag = "!!seq" return subtractArray(d, context, lhs, 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 {
return nil, fmt.Errorf("%v (%v) cannot be added to a %v", rhs.Node.Tag, rhs.Path, lhsNode.Tag) return nil, fmt.Errorf("%v (%v) cannot be subtracted from %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

View File

@ -13,6 +13,51 @@ var subtractOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::{}\n", "D0, P[], (doc)::{}\n",
}, },
}, },
{
description: "Array subtraction",
expression: `[1,2] - [2,3]`,
expected: []string{
"D0, P[], (!!seq)::- 1\n",
},
},
{
skipDoc: true,
expression: `[2,1,2,2] - [2,3]`,
expected: []string{
"D0, P[], (!!seq)::- 1\n",
},
},
{
description: "Array subtraction with nested array",
expression: `[[1], 1, 2] - [[1], 3]`,
expected: []string{
"D0, P[], (!!seq)::- 1\n- 2\n",
},
},
{
skipDoc: true,
expression: `[[1], 1, [[[2]]]] - [[1], [[[3]]]]`,
expected: []string{
"D0, P[], (!!seq)::- 1\n- - - - 2\n",
},
},
{
description: "Array subtraction with nested object",
subdescription: `Note that order of the keys does not matter`,
document: `[{a: b, c: d}, {a: b}]`,
expression: `. - [{"c": "d", "a": "b"}]`,
expected: []string{
"D0, P[], (!!seq)::[{a: b}]\n",
},
},
{
skipDoc: true,
document: `[{a: [1], c: d}, {a: [2], c: d}, {a: b}]`,
expression: `. - [{"c": "d", "a": [1]}]`,
expected: []string{
"D0, P[], (!!seq)::[{a: [2], c: d}, {a: b}]\n",
},
},
{ {
description: "Number subtraction - float", description: "Number subtraction - float",
subdescription: "If the lhs or rhs are floats then the expression will be calculated with floats.", subdescription: "If the lhs or rhs are floats then the expression will be calculated with floats.",