mirror of
https://github.com/mikefarah/yq.git
synced 2024-11-12 05:38:04 +00:00
merge anchors!
This commit is contained in:
parent
643f2467ee
commit
461c3e719c
@ -1,19 +1,19 @@
|
|||||||
foo: &foo
|
foo: &foo
|
||||||
a: original
|
a: foo_a
|
||||||
thing: coolasdf
|
thing: foo_thing
|
||||||
thirsty: yep
|
c: foo_c
|
||||||
|
|
||||||
bar: &bar
|
bar: &bar
|
||||||
b: 2
|
b: bar_b
|
||||||
thing: coconut
|
thing: bar_thing
|
||||||
c: oldbar
|
c: bar_c
|
||||||
|
|
||||||
foobarList:
|
foobarList:
|
||||||
|
b: foobarList_b
|
||||||
<<: [*foo,*bar]
|
<<: [*foo,*bar]
|
||||||
c: newbar
|
c: foobarList_c
|
||||||
|
|
||||||
foobar:
|
foobar:
|
||||||
|
c: foobar_c
|
||||||
<<: *foo
|
<<: *foo
|
||||||
thirty: well beyond
|
thing: foobar_thing
|
||||||
thing: ice
|
|
||||||
c: 3
|
|
4
go.mod
4
go.mod
@ -1,13 +1,13 @@
|
|||||||
module github.com/mikefarah/yq/v4
|
module github.com/mikefarah/yq/v4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/elliotchance/orderedmap v1.3.0 // indirect
|
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/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
|
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
|
||||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
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 // indirect
|
||||||
github.com/spf13/cobra v1.0.0
|
github.com/spf13/cobra v1.0.0
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/timtadh/data-structures v0.5.3 // indirect
|
github.com/timtadh/data-structures v0.5.3 // indirect
|
||||||
|
@ -17,7 +17,7 @@ type CandidateNode struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *CandidateNode) GetKey() string {
|
func (n *CandidateNode) GetKey() string {
|
||||||
return fmt.Sprintf("%v - %v - %v", n.Document, n.Path, n.Node.Value)
|
return fmt.Sprintf("%v - %v", n.Document, n.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *CandidateNode) Copy() *CandidateNode {
|
func (n *CandidateNode) Copy() *CandidateNode {
|
||||||
|
@ -5,6 +5,8 @@ import (
|
|||||||
|
|
||||||
"container/list"
|
"container/list"
|
||||||
|
|
||||||
|
"github.com/elliotchance/orderedmap"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -65,7 +67,34 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Oper
|
|||||||
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)
|
||||||
return traverseMap(matchingNode, operation)
|
var newMatches = orderedmap.NewOrderedMap()
|
||||||
|
err := traverseMap(newMatches, matchingNode, operation)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if newMatches.Len() == 0 {
|
||||||
|
//no matches, create one automagically
|
||||||
|
valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"}
|
||||||
|
node := matchingNode.Node
|
||||||
|
node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: operation.StringValue}, valueNode)
|
||||||
|
candidateNode := &CandidateNode{
|
||||||
|
Node: valueNode,
|
||||||
|
Path: append(matchingNode.Path, operation.StringValue),
|
||||||
|
Document: matchingNode.Document,
|
||||||
|
}
|
||||||
|
newMatches.Set(candidateNode.GetKey(), candidateNode)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayMatches := make([]*CandidateNode, newMatches.Len())
|
||||||
|
i := 0
|
||||||
|
for el := newMatches.Front(); el != nil; el = el.Next() {
|
||||||
|
arrayMatches[i] = el.Value.(*CandidateNode)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return arrayMatches, nil
|
||||||
|
|
||||||
case yaml.SequenceNode:
|
case yaml.SequenceNode:
|
||||||
log.Debug("its a sequence of %v things!", len(value.Content))
|
log.Debug("its a sequence of %v things!", len(value.Content))
|
||||||
@ -92,15 +121,13 @@ func keyMatches(key *yaml.Node, pathNode *Operation) bool {
|
|||||||
return pathNode.Value == "[]" || Match(key.Value, pathNode.StringValue)
|
return pathNode.Value == "[]" || Match(key.Value, pathNode.StringValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func traverseMap(candidate *CandidateNode, pathNode *Operation) ([]*CandidateNode, error) {
|
func traverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, operation *Operation) error {
|
||||||
// value.Content is a concatenated array of key, value,
|
// value.Content is a concatenated array of key, value,
|
||||||
// so keys are in the even indexes, values in odd.
|
// so keys are in the even indexes, values in odd.
|
||||||
// merge aliases are defined first, but we only want to traverse them
|
// merge aliases are defined first, but we only want to traverse them
|
||||||
// if we don't find a match directly on this node first.
|
// if we don't find a match directly on this node first.
|
||||||
//TODO ALIASES, auto creation?
|
//TODO ALIASES, auto creation?
|
||||||
|
|
||||||
var newMatches = make([]*CandidateNode, 0)
|
|
||||||
|
|
||||||
node := candidate.Node
|
node := candidate.Node
|
||||||
|
|
||||||
var contents = node.Content
|
var contents = node.Content
|
||||||
@ -109,33 +136,44 @@ func traverseMap(candidate *CandidateNode, pathNode *Operation) ([]*CandidateNod
|
|||||||
value := contents[index+1]
|
value := contents[index+1]
|
||||||
|
|
||||||
log.Debug("checking %v (%v)", key.Value, key.Tag)
|
log.Debug("checking %v (%v)", key.Value, key.Tag)
|
||||||
if keyMatches(key, pathNode) {
|
//skip the 'merge' tag, find a direct match first
|
||||||
|
if key.Tag == "!!merge" {
|
||||||
|
log.Debug("Merge anchor")
|
||||||
|
traverseMergeAnchor(newMatches, candidate, value, operation)
|
||||||
|
} else if keyMatches(key, operation) {
|
||||||
log.Debug("MATCHED")
|
log.Debug("MATCHED")
|
||||||
newMatches = append(newMatches, &CandidateNode{
|
candidateNode := &CandidateNode{
|
||||||
Node: value,
|
Node: value,
|
||||||
Path: append(candidate.Path, key.Value),
|
Path: append(candidate.Path, key.Value),
|
||||||
Document: candidate.Document,
|
Document: candidate.Document,
|
||||||
})
|
}
|
||||||
|
newMatches.Set(candidateNode.GetKey(), candidateNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(newMatches) == 0 {
|
|
||||||
//no matches, create one automagically
|
|
||||||
valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"}
|
|
||||||
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 nil
|
||||||
|
|
||||||
return newMatches, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func traverseArray(candidate *CandidateNode, pathNode *Operation) ([]*CandidateNode, error) {
|
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, operation *Operation) {
|
||||||
log.Debug("pathNode Value %v", pathNode.Value)
|
switch value.Kind {
|
||||||
if pathNode.Value == "[]" {
|
case yaml.AliasNode:
|
||||||
|
candidateNode := &CandidateNode{
|
||||||
|
Node: value.Alias,
|
||||||
|
Path: originalCandidate.Path,
|
||||||
|
Document: originalCandidate.Document,
|
||||||
|
}
|
||||||
|
traverseMap(newMatches, candidateNode, operation)
|
||||||
|
case yaml.SequenceNode:
|
||||||
|
for _, childValue := range value.Content {
|
||||||
|
traverseMergeAnchor(newMatches, originalCandidate, childValue, operation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func traverseArray(candidate *CandidateNode, operation *Operation) ([]*CandidateNode, error) {
|
||||||
|
log.Debug("operation Value %v", operation.Value)
|
||||||
|
if operation.Value == "[]" {
|
||||||
|
|
||||||
var contents = candidate.Node.Content
|
var contents = candidate.Node.Content
|
||||||
var newMatches = make([]*CandidateNode, len(contents))
|
var newMatches = make([]*CandidateNode, len(contents))
|
||||||
@ -151,7 +189,7 @@ func traverseArray(candidate *CandidateNode, pathNode *Operation) ([]*CandidateN
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
index := pathNode.Value.(int64)
|
index := operation.Value.(int64)
|
||||||
indexToUse := index
|
indexToUse := index
|
||||||
contentLength := int64(len(candidate.Node.Content))
|
contentLength := int64(len(candidate.Node.Content))
|
||||||
for contentLength <= index {
|
for contentLength <= index {
|
||||||
|
@ -4,6 +4,28 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var mergeDocSample = `
|
||||||
|
foo: &foo
|
||||||
|
a: foo_a
|
||||||
|
thing: foo_thing
|
||||||
|
c: foo_c
|
||||||
|
|
||||||
|
bar: &bar
|
||||||
|
b: bar_b
|
||||||
|
thing: bar_thing
|
||||||
|
c: bar_c
|
||||||
|
|
||||||
|
foobarList:
|
||||||
|
b: foobarList_b
|
||||||
|
<<: [*foo,*bar]
|
||||||
|
c: foobarList_c
|
||||||
|
|
||||||
|
foobar:
|
||||||
|
c: foobar_c
|
||||||
|
<<: *foo
|
||||||
|
thing: foobar_thing
|
||||||
|
`
|
||||||
|
|
||||||
var traversePathOperatorScenarios = []expressionScenario{
|
var traversePathOperatorScenarios = []expressionScenario{
|
||||||
{
|
{
|
||||||
document: `{a: {b: apple}}`,
|
document: `{a: {b: apple}}`,
|
||||||
@ -104,6 +126,88 @@ var traversePathOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[b c], (!!str)::frog\n",
|
"D0, P[b c], (!!str)::frog\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
document: mergeDocSample,
|
||||||
|
expression: `.foobar`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[foobar], (!!map)::c: foobar_c\n!!merge <<: *foo\nthing: foobar_thing\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
document: mergeDocSample,
|
||||||
|
expression: `.foobar.a`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[foobar a], (!!str)::foo_a\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
document: mergeDocSample,
|
||||||
|
expression: `.foobar.c`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[foobar c], (!!str)::foo_c\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
document: mergeDocSample,
|
||||||
|
expression: `.foobar.thing`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[foobar thing], (!!str)::foobar_thing\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
document: mergeDocSample,
|
||||||
|
expression: `.foobar.[]`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[foobar c], (!!str)::foo_c\n",
|
||||||
|
"D0, P[foobar a], (!!str)::foo_a\n",
|
||||||
|
"D0, P[foobar thing], (!!str)::foobar_thing\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
document: mergeDocSample,
|
||||||
|
expression: `.foobarList`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[foobarList], (!!map)::b: foobarList_b\n!!merge <<: [*foo, *bar]\nc: foobarList_c\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
document: mergeDocSample,
|
||||||
|
expression: `.foobarList.a`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[foobarList a], (!!str)::foo_a\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
document: mergeDocSample,
|
||||||
|
expression: `.foobarList.thing`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[foobarList thing], (!!str)::bar_thing\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
document: mergeDocSample,
|
||||||
|
expression: `.foobarList.c`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[foobarList c], (!!str)::foobarList_c\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
document: mergeDocSample,
|
||||||
|
expression: `.foobarList.b`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[foobarList b], (!!str)::bar_b\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
document: mergeDocSample,
|
||||||
|
expression: `.foobarList.[]`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[foobarList b], (!!str)::bar_b\n",
|
||||||
|
"D0, P[foobarList a], (!!str)::foo_a\n",
|
||||||
|
"D0, P[foobarList thing], (!!str)::bar_thing\n",
|
||||||
|
"D0, P[foobarList c], (!!str)::foobarList_c\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTraversePathOperatorScenarios(t *testing.T) {
|
func TestTraversePathOperatorScenarios(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user