This commit is contained in:
Mike Farah 2020-11-28 10:41:09 +11:00
parent 13679e51e2
commit 3cecb4e383
8 changed files with 135 additions and 53 deletions

View File

@ -66,9 +66,9 @@ b:
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: {things: great} a: {things: great}
b: b:
also: "me" also: "me"
``` ```
then then
```bash ```bash
@ -76,9 +76,6 @@ yq eval '. * {"a":.b}' sample.yml
``` ```
will output will output
```yaml ```yaml
a: {things: great, also: "me"}
b:
also: "me"
``` ```
## Merge arrays ## Merge arrays
@ -109,6 +106,38 @@ b:
- 5 - 5
``` ```
## Merge, appending arrays
Given a sample.yml file of:
```yaml
a:
array:
- 1
- 2
- animal: dog
value: coconut
b:
array:
- 3
- 4
- animal: cat
value: banana
```
then
```bash
yq eval '.a *+ .b' sample.yml
```
will output
```yaml
array:
- 1
- 2
- animal: dog
- 3
- 4
- animal: cat
value: banana
```
## Merge to prefix an element ## Merge to prefix an element
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml

View File

@ -8,6 +8,10 @@ import (
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
type AddPreferences struct {
InPlace bool
}
func toNodes(candidates *list.List) []*yaml.Node { func toNodes(candidates *list.List) []*yaml.Node {
if candidates.Len() == 0 { if candidates.Len() == 0 {
@ -41,26 +45,35 @@ func AddOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathT
return nil, err return nil, err
} }
preferences := pathNode.Operation.Preferences
inPlace := preferences != nil && preferences.(*AddPreferences).InPlace
for el := lhs.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
lhsCandidate := el.Value.(*CandidateNode) lhsCandidate := el.Value.(*CandidateNode)
lhsNode := UnwrapDoc(lhsCandidate.Node) lhsNode := UnwrapDoc(lhsCandidate.Node)
var newBlank = &CandidateNode{ var target *CandidateNode
Path: lhsCandidate.Path,
Document: lhsCandidate.Document, if inPlace {
Filename: lhsCandidate.Filename, target = lhsCandidate
Node: &yaml.Node{}, } else {
target = &CandidateNode{
Path: lhsCandidate.Path,
Document: lhsCandidate.Document,
Filename: lhsCandidate.Filename,
Node: &yaml.Node{},
}
} }
switch lhsNode.Kind { switch lhsNode.Kind {
case yaml.MappingNode: case yaml.MappingNode:
return nil, fmt.Errorf("Maps not yet supported for addition") return nil, fmt.Errorf("Maps not yet supported for addition")
case yaml.SequenceNode: case yaml.SequenceNode:
newBlank.Node.Kind = yaml.SequenceNode target.Node.Kind = yaml.SequenceNode
newBlank.Node.Style = lhsNode.Style target.Node.Style = lhsNode.Style
newBlank.Node.Tag = "!!seq" target.Node.Tag = "!!seq"
newBlank.Node.Content = append(lhsNode.Content, toNodes(rhs)...) target.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
results.PushBack(newBlank) results.PushBack(target)
case yaml.ScalarNode: case yaml.ScalarNode:
return nil, fmt.Errorf("Scalars not yet supported for addition") return nil, fmt.Errorf("Scalars not yet supported for addition")
} }

View File

@ -5,6 +5,9 @@ import (
) )
var addOperatorScenarios = []expressionScenario{ var addOperatorScenarios = []expressionScenario{
{
description: "+= test and doc",
},
{ {
description: "Concatenate arrays", description: "Concatenate arrays",
document: `{a: [1,2], b: [3,4]}`, document: `{a: [1,2], b: [3,4]}`,

View File

@ -94,7 +94,7 @@ func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.
newCandidate.Path = nil newCandidate.Path = nil
newCandidate, err = multiply(d, newCandidate, splatCandidate) newCandidate, err = multiply(&MultiplyPreferences{AppendArrays: false})(d, newCandidate, splatCandidate)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -43,39 +43,49 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pat
return results, nil return results, nil
} }
type MultiplyPreferences struct {
AppendArrays bool
}
func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- MultiplyOperator") log.Debugf("-- MultiplyOperator")
return crossFunction(d, matchingNodes, pathNode, multiply) return crossFunction(d, matchingNodes, pathNode, multiply(pathNode.Operation.Preferences.(*MultiplyPreferences)))
} }
func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func multiply(preferences *MultiplyPreferences) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = UnwrapDoc(lhs.Node) return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
rhs.Node = UnwrapDoc(rhs.Node) lhs.Node = UnwrapDoc(lhs.Node)
log.Debugf("Multipling LHS: %v", lhs.Node.Tag) rhs.Node = UnwrapDoc(rhs.Node)
log.Debugf("- RHS: %v", rhs.Node.Tag) log.Debugf("Multipling LHS: %v", lhs.Node.Tag)
log.Debugf("- RHS: %v", rhs.Node.Tag)
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode || shouldAppendArrays := preferences.AppendArrays
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode ||
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
var newBlank = &CandidateNode{
Path: lhs.Path,
Document: lhs.Document,
Filename: lhs.Filename,
Node: &yaml.Node{},
}
var newThing, err = mergeObjects(d, newBlank, lhs, false)
if err != nil {
return nil, err
}
return mergeObjects(d, newThing, rhs, shouldAppendArrays)
var newBlank = &CandidateNode{
Path: lhs.Path,
Document: lhs.Document,
Filename: lhs.Filename,
Node: &yaml.Node{},
} }
var newThing, err = mergeObjects(d, newBlank, lhs) return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag)
if err != nil {
return nil, err
}
return mergeObjects(d, newThing, rhs)
} }
return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag)
} }
func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) (*CandidateNode, error) {
var results = list.New() var results = list.New()
err := recursiveDecent(d, results, nodeToMap(rhs))
// shouldn't recurse arrays if appending
err := recursiveDecent(d, results, nodeToMap(rhs), !shouldAppendArrays)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -86,7 +96,7 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode)
} }
for el := results.Front(); el != nil; el = el.Next() { for el := results.Front(); el != nil; el = el.Next() {
err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode)) err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode), shouldAppendArrays)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -107,7 +117,8 @@ func createTraversalTree(path []interface{}) *PathTreeNode {
} }
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode) error { func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) error {
log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs)) log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs))
lhsPath := rhs.Path[pathIndexToStartFrom:] lhsPath := rhs.Path[pathIndexToStartFrom:]
@ -116,6 +127,10 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid
if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode { if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode {
assignmentOp.OperationType = Assign assignmentOp.OperationType = Assign
assignmentOp.Preferences = &AssignOpPreferences{false} assignmentOp.Preferences = &AssignOpPreferences{false}
} else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode {
log.Debugf("append! lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs))
assignmentOp.OperationType = Add
assignmentOp.Preferences = &AddPreferences{InPlace: true}
} }
rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs} rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs}

View File

@ -5,6 +5,9 @@ import (
) )
var multiplyOperatorScenarios = []expressionScenario{ var multiplyOperatorScenarios = []expressionScenario{
{
description: "*+ doc",
},
{ {
skipDoc: true, skipDoc: true,
document: `{a: {also: [1]}, b: {also: me}}`, document: `{a: {also: [1]}, b: {also: me}}`,
@ -80,15 +83,15 @@ var multiplyOperatorScenarios = []expressionScenario{
description: "Merge keeps style of LHS", description: "Merge keeps style of LHS",
dontFormatInputForDoc: true, dontFormatInputForDoc: true,
document: `a: {things: great} document: `a: {things: great}
b: b:
also: "me" also: "me"
`, `,
expression: `. * {"a":.b}`, expression: `. * {"a":.b}`,
expected: []string{ expected: []string{
`D0, P[], (!!map)::a: {things: great, also: "me"} `D0, P[], (!!map)::a: {things: great, also: "me"}
b: b:
also: "me" also: "me"
`, `,
}, },
}, },
{ {
@ -99,6 +102,22 @@ b:
"D0, P[], (!!map)::{a: [3, 4, 5], b: [3, 4, 5]}\n", "D0, P[], (!!map)::{a: [3, 4, 5], b: [3, 4, 5]}\n",
}, },
}, },
{
skipDoc: true,
document: `{a: [1], b: [2]}`,
expression: `.a *+ .b`,
expected: []string{
"D0, P[a], (!!seq)::[1, 2]\n",
},
},
{
description: "Merge, appending arrays",
document: `{a: {array: [1, 2, animal: dog], value: coconut}, b: {array: [3, 4, animal: cat], value: banana}}`,
expression: `.a *+ .b`,
expected: []string{
"D0, P[a], (!!map)::{array: [1, 2, {animal: dog}, 3, 4, {animal: cat}], value: banana}\n",
},
},
{ {
description: "Merge to prefix an element", description: "Merge to prefix an element",
document: `{a: cat, b: dog}`, document: `{a: cat, b: dog}`,

View File

@ -3,13 +3,13 @@ package yqlib
import ( import (
"container/list" "container/list"
"gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
var results = list.New() var results = list.New()
err := recursiveDecent(d, results, matchMap) err := recursiveDecent(d, results, matchMap, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -17,7 +17,7 @@ func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNod
return results, nil return results, nil
} }
func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List) error { func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List, recurseArray bool) error {
for el := matchMap.Front(); el != nil; el = el.Next() { for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
@ -26,14 +26,15 @@ func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.Li
log.Debugf("Recursive Decent, added %v", NodeToString(candidate)) log.Debugf("Recursive Decent, added %v", NodeToString(candidate))
results.PushBack(candidate) results.PushBack(candidate)
if candidate.Node.Kind != yaml.AliasNode && len(candidate.Node.Content) > 0 { if candidate.Node.Kind != yaml.AliasNode && len(candidate.Node.Content) > 0 &&
(recurseArray || candidate.Node.Kind != yaml.SequenceNode) {
children, err := Splat(d, nodeToMap(candidate)) children, err := Splat(d, nodeToMap(candidate))
if err != nil { if err != nil {
return err return err
} }
err = recursiveDecent(d, results, children) err = recursiveDecent(d, results, children, recurseArray)
if err != nil { if err != nil {
return err return err
} }

View File

@ -251,8 +251,10 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`\]`), literalToken(CloseCollect, true)) lexer.Add([]byte(`\]`), literalToken(CloseCollect, true))
lexer.Add([]byte(`\{`), literalToken(OpenCollectObject, false)) lexer.Add([]byte(`\{`), literalToken(OpenCollectObject, false))
lexer.Add([]byte(`\}`), literalToken(CloseCollectObject, true)) lexer.Add([]byte(`\}`), literalToken(CloseCollectObject, true))
lexer.Add([]byte(`\*`), opToken(Multiply)) lexer.Add([]byte(`\*`), opTokenWithPrefs(Multiply, nil, &MultiplyPreferences{AppendArrays: false}))
lexer.Add([]byte(`\*\+`), opTokenWithPrefs(Multiply, nil, &MultiplyPreferences{AppendArrays: true}))
lexer.Add([]byte(`\+`), opToken(Add)) lexer.Add([]byte(`\+`), opToken(Add))
lexer.Add([]byte(`\+=`), opTokenWithPrefs(Add, nil, &AddPreferences{InPlace: true}))
err := lexer.Compile() err := lexer.Compile()
if err != nil { if err != nil {