mirror of
https://github.com/mikefarah/yq.git
synced 2024-12-19 20:19:04 +00:00
autovivification, merge!
This commit is contained in:
parent
49b810cedd
commit
2ddf8dd4ed
2
go.mod
2
go.mod
@ -4,7 +4,7 @@ require (
|
|||||||
github.com/elliotchance/orderedmap v1.3.0
|
github.com/elliotchance/orderedmap v1.3.0
|
||||||
github.com/fatih/color v1.9.0
|
github.com/fatih/color v1.9.0
|
||||||
github.com/goccy/go-yaml v1.8.1
|
github.com/goccy/go-yaml v1.8.1
|
||||||
github.com/kylelemons/godebug v1.1.0
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.7 // indirect
|
github.com/mattn/go-colorable v0.1.7 // indirect
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/spf13/cobra v1.0.0
|
github.com/spf13/cobra v1.0.0
|
||||||
|
@ -20,11 +20,19 @@ func (n *CandidateNode) GetKey() string {
|
|||||||
|
|
||||||
// updates this candidate from the given candidate node
|
// updates this candidate from the given candidate node
|
||||||
func (n *CandidateNode) UpdateFrom(other *CandidateNode) {
|
func (n *CandidateNode) UpdateFrom(other *CandidateNode) {
|
||||||
|
|
||||||
n.Node.Content = other.Node.Content
|
n.Node.Content = other.Node.Content
|
||||||
n.Node.Value = other.Node.Value
|
n.Node.Value = other.Node.Value
|
||||||
|
n.UpdateAttributesFrom(other)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) {
|
||||||
n.Node.Kind = other.Node.Kind
|
n.Node.Kind = other.Node.Kind
|
||||||
n.Node.Tag = other.Node.Tag
|
n.Node.Tag = other.Node.Tag
|
||||||
n.Node.Style = other.Node.Style
|
n.Node.Style = other.Node.Style
|
||||||
|
n.Node.FootComment = other.Node.FootComment
|
||||||
|
n.Node.HeadComment = other.Node.HeadComment
|
||||||
|
n.Node.LineComment = other.Node.LineComment
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *CandidateNode) PathStackToString() string {
|
func (n *CandidateNode) PathStackToString() string {
|
||||||
|
@ -81,7 +81,7 @@ func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *orderedmap.OrderedMa
|
|||||||
} else if pathNode.PathElement.PathElementType == PathKey {
|
} else if pathNode.PathElement.PathElementType == PathKey {
|
||||||
return d.traverse(matchingNodes, pathNode.PathElement)
|
return d.traverse(matchingNodes, pathNode.PathElement)
|
||||||
} else if pathNode.PathElement.PathElementType == Value {
|
} else if pathNode.PathElement.PathElementType == Value {
|
||||||
return nodeToMap(BuildCandidateNodeFrom(pathNode.PathElement)), nil
|
return nodeToMap(pathNode.PathElement.CandidateNode), nil
|
||||||
} else {
|
} else {
|
||||||
handler := pathNode.PathElement.OperationType.Handler
|
handler := pathNode.PathElement.OperationType.Handler
|
||||||
if handler != nil {
|
if handler != nil {
|
||||||
|
@ -48,6 +48,18 @@ func (t *traverser) traverseMap(candidate *CandidateNode, pathNode *PathElement)
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(newMatches) == 0 {
|
||||||
|
//no matches, create one automagically
|
||||||
|
valueNode := &yaml.Node{}
|
||||||
|
node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: pathNode.StringValue}, valueNode)
|
||||||
|
newMatches = append(newMatches, &CandidateNode{
|
||||||
|
Node: valueNode,
|
||||||
|
Path: append(candidate.Path, pathNode.StringValue),
|
||||||
|
Document: candidate.Document,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return newMatches, nil
|
return newMatches, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,9 +84,9 @@ func (t *traverser) traverseArray(candidate *CandidateNode, pathNode *PathElemen
|
|||||||
index := pathNode.Value.(int64)
|
index := pathNode.Value.(int64)
|
||||||
indexToUse := index
|
indexToUse := index
|
||||||
contentLength := int64(len(candidate.Node.Content))
|
contentLength := int64(len(candidate.Node.Content))
|
||||||
if contentLength <= index {
|
for contentLength <= index {
|
||||||
// handle auto append here
|
candidate.Node.Content = append(candidate.Node.Content, &yaml.Node{})
|
||||||
return make([]*CandidateNode, 0), nil
|
contentLength = int64(len(candidate.Node.Content))
|
||||||
}
|
}
|
||||||
|
|
||||||
if indexToUse < 0 {
|
if indexToUse < 0 {
|
||||||
@ -94,8 +106,22 @@ func (t *traverser) traverseArray(candidate *CandidateNode, pathNode *PathElemen
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *traverser) Traverse(matchingNode *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) {
|
func (t *traverser) Traverse(matchingNode *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) {
|
||||||
log.Debug(NodeToString(matchingNode))
|
log.Debug("Traversing %v", NodeToString(matchingNode))
|
||||||
value := matchingNode.Node
|
value := matchingNode.Node
|
||||||
|
|
||||||
|
if value.Kind == 0 {
|
||||||
|
log.Debugf("Guessing kind")
|
||||||
|
// we must ahve added this automatically, lets guess what it should be now
|
||||||
|
switch pathNode.Value.(type) {
|
||||||
|
case int, int64:
|
||||||
|
log.Debugf("probably an array")
|
||||||
|
value.Kind = yaml.SequenceNode
|
||||||
|
default:
|
||||||
|
log.Debugf("probabel a map")
|
||||||
|
value.Kind = yaml.MappingNode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch value.Kind {
|
switch value.Kind {
|
||||||
case yaml.MappingNode:
|
case yaml.MappingNode:
|
||||||
log.Debug("its a map with %v entries", len(value.Content)/2)
|
log.Debug("its a map with %v entries", len(value.Content)/2)
|
||||||
|
@ -40,6 +40,7 @@ var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: U
|
|||||||
var Intersection = &OperationType{Type: "INTERSECTION", NumArgs: 2, Precedence: 20, Handler: IntersectionOperator}
|
var Intersection = &OperationType{Type: "INTERSECTION", NumArgs: 2, Precedence: 20, Handler: IntersectionOperator}
|
||||||
|
|
||||||
var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignOperator}
|
var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignOperator}
|
||||||
|
var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator}
|
||||||
var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 40, Handler: MultiplyOperator}
|
var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 40, Handler: MultiplyOperator}
|
||||||
|
|
||||||
var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator}
|
var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator}
|
||||||
@ -65,6 +66,7 @@ type PathElement struct {
|
|||||||
OperationType *OperationType
|
OperationType *OperationType
|
||||||
Value interface{}
|
Value interface{}
|
||||||
StringValue string
|
StringValue string
|
||||||
|
CandidateNode *CandidateNode // used for Value Path elements
|
||||||
}
|
}
|
||||||
|
|
||||||
// debugging purposes only
|
// debugging purposes only
|
||||||
|
@ -25,3 +25,28 @@ func AssignOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap,
|
|||||||
}
|
}
|
||||||
return matchingNodes, nil
|
return matchingNodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// does not update content or values
|
||||||
|
func AssignAttributesOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) {
|
||||||
|
lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||||
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
|
||||||
|
rhs, err := d.getMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// grab the first value
|
||||||
|
first := rhs.Front()
|
||||||
|
|
||||||
|
if first != nil {
|
||||||
|
candidate.UpdateAttributesFrom(first.Value.(*CandidateNode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matchingNodes, nil
|
||||||
|
}
|
||||||
|
@ -47,6 +47,24 @@ var assignOperatorScenarios = []expressionScenario{
|
|||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!seq)::[bogs, apple, bogs]\n",
|
"D0, P[], (!!seq)::[bogs, apple, bogs]\n",
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
document: `{}`,
|
||||||
|
expression: `.a.b |= "bogs"`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!map)::{a: {b: bogs}}\n",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
document: `{}`,
|
||||||
|
expression: `.a.b[0] |= "bogs"`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!map)::{a: {b: [bogs]}}\n",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
document: `{}`,
|
||||||
|
expression: `.a.b[1].c |= "bogs"`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!map)::{a: {b: [null, {c: bogs}]}}\n",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
package treeops
|
|
||||||
|
|
||||||
import "github.com/elliotchance/orderedmap"
|
|
||||||
|
|
||||||
func MultiplyOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) {
|
|
||||||
lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
|
||||||
candidate := el.Value.(*CandidateNode)
|
|
||||||
|
|
||||||
// TODO handle scalar mulitplication
|
|
||||||
switch candidate.Node.Kind {
|
|
||||||
case
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return matchingNodes, nil
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
package treeops
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var mergeOperatorScenarios = []expressionScenario{
|
|
||||||
{
|
|
||||||
document: `{a: frog, b: cat}`,
|
|
||||||
expression: `.a * .b`,
|
|
||||||
expected: []string{
|
|
||||||
"D0, P[], (!!map)::{a: cat, b: cat}\n",
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
document: `{a: {things: great}, b: {also: me}}`,
|
|
||||||
expression: `.a * .b`,
|
|
||||||
expected: []string{
|
|
||||||
"D0, P[], (!!map)::{a: {also: me, things: great}, b: {also: me}}\n",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMergeOperatorScenarios(t *testing.T) {
|
|
||||||
for _, tt := range mergeOperatorScenarios {
|
|
||||||
testScenario(t, &tt)
|
|
||||||
}
|
|
||||||
}
|
|
90
pkg/yqlib/treeops/operator_multilpy.go
Normal file
90
pkg/yqlib/treeops/operator_multilpy.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package treeops
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/elliotchance/orderedmap"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MultiplyOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) {
|
||||||
|
lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rhs, err := d.getMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = orderedmap.NewOrderedMap()
|
||||||
|
|
||||||
|
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||||
|
lhsCandidate := el.Value.(*CandidateNode)
|
||||||
|
|
||||||
|
for rightEl := rhs.Front(); rightEl != nil; rightEl = rightEl.Next() {
|
||||||
|
rhsCandidate := rightEl.Value.(*CandidateNode)
|
||||||
|
resultCandidate, err := multiply(d, lhsCandidate, rhsCandidate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
results.Set(resultCandidate.GetKey(), resultCandidate)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return matchingNodes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||||
|
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode {
|
||||||
|
var results = orderedmap.NewOrderedMap()
|
||||||
|
recursiveDecent(d, results, nodeToMap(rhs))
|
||||||
|
|
||||||
|
var pathIndexToStartFrom int = 0
|
||||||
|
if results.Front() != nil {
|
||||||
|
pathIndexToStartFrom = len(results.Front().Value.(*CandidateNode).Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
for el := results.Front(); el != nil; el = el.Next() {
|
||||||
|
err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lhs, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("Cannot multiply %v with %v", NodeToString(lhs), NodeToString(rhs))
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTraversalTree(path []interface{}) *PathTreeNode {
|
||||||
|
if len(path) == 0 {
|
||||||
|
return &PathTreeNode{PathElement: &PathElement{PathElementType: SelfReference}}
|
||||||
|
} else if len(path) == 1 {
|
||||||
|
return &PathTreeNode{PathElement: &PathElement{PathElementType: PathKey, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}}
|
||||||
|
}
|
||||||
|
return &PathTreeNode{
|
||||||
|
PathElement: &PathElement{PathElementType: Operation, OperationType: Pipe},
|
||||||
|
Lhs: createTraversalTree(path[0:1]),
|
||||||
|
Rhs: createTraversalTree(path[1:])}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode) error {
|
||||||
|
log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs))
|
||||||
|
|
||||||
|
lhsPath := rhs.Path[pathIndexToStartFrom:]
|
||||||
|
|
||||||
|
assignmentOp := &PathElement{PathElementType: Operation, OperationType: AssignAttributes}
|
||||||
|
if rhs.Node.Kind == yaml.ScalarNode {
|
||||||
|
assignmentOp.OperationType = Assign
|
||||||
|
}
|
||||||
|
rhsOp := &PathElement{PathElementType: Value, CandidateNode: rhs}
|
||||||
|
|
||||||
|
assignmentOpNode := &PathTreeNode{PathElement: assignmentOp, Lhs: createTraversalTree(lhsPath), Rhs: &PathTreeNode{PathElement: rhsOp}}
|
||||||
|
|
||||||
|
_, err := d.getMatchingNodes(nodeToMap(lhs), assignmentOpNode)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
27
pkg/yqlib/treeops/operator_multiply_test.go
Normal file
27
pkg/yqlib/treeops/operator_multiply_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package treeops
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var multiplyOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
// document: `{a: frog, b: cat}`,
|
||||||
|
// expression: `.a * .b`,
|
||||||
|
// expected: []string{
|
||||||
|
// "D0, P[], (!!map)::{a: cat, b: cat}\n",
|
||||||
|
// },
|
||||||
|
// }, {
|
||||||
|
document: `{a: {things: great}, b: {also: me}}`,
|
||||||
|
expression: `.a * .b`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!map)::{a: {things: great, also: me}, b: {also: me}}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiplyOperatorScenarios(t *testing.T) {
|
||||||
|
for _, tt := range multiplyOperatorScenarios {
|
||||||
|
testScenario(t, &tt)
|
||||||
|
}
|
||||||
|
}
|
@ -33,7 +33,11 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*PathElement,
|
|||||||
for _, token := range tokens {
|
for _, token := range tokens {
|
||||||
log.Debugf("postfix processing token %v", token.Value)
|
log.Debugf("postfix processing token %v", token.Value)
|
||||||
switch token.PathElementType {
|
switch token.PathElementType {
|
||||||
case PathKey, SelfReference, Value:
|
case Value:
|
||||||
|
var candidateNode = BuildCandidateNodeFrom(token)
|
||||||
|
var pathElement = PathElement{PathElementType: token.PathElementType, Value: token.Value, StringValue: token.StringValue, CandidateNode: candidateNode}
|
||||||
|
result = append(result, &pathElement)
|
||||||
|
case PathKey, SelfReference:
|
||||||
var pathElement = PathElement{PathElementType: token.PathElementType, Value: token.Value, StringValue: token.StringValue}
|
var pathElement = PathElement{PathElementType: token.PathElementType, Value: token.Value, StringValue: token.StringValue}
|
||||||
result = append(result, &pathElement)
|
result = append(result, &pathElement)
|
||||||
case OpenBracket, OpenCollect:
|
case OpenBracket, OpenCollect:
|
||||||
|
@ -2,11 +2,11 @@ package treeops
|
|||||||
|
|
||||||
import "gopkg.in/yaml.v3"
|
import "gopkg.in/yaml.v3"
|
||||||
|
|
||||||
func BuildCandidateNodeFrom(p *PathElement) *CandidateNode {
|
func BuildCandidateNodeFrom(token *Token) *CandidateNode {
|
||||||
var node yaml.Node = yaml.Node{Kind: yaml.ScalarNode}
|
var node yaml.Node = yaml.Node{Kind: yaml.ScalarNode}
|
||||||
node.Value = p.StringValue
|
node.Value = token.StringValue
|
||||||
|
|
||||||
switch p.Value.(type) {
|
switch token.Value.(type) {
|
||||||
case float32, float64:
|
case float32, float64:
|
||||||
node.Tag = "!!float"
|
node.Tag = "!!float"
|
||||||
case int, int64, int32:
|
case int, int64, int32:
|
||||||
|
Loading…
Reference in New Issue
Block a user