mirror of
https://github.com/mikefarah/yq.git
synced 2026-07-05 20:15:36 +00:00
Fix merge anchor exploding
- Allow inline maps instead of just aliases - Allow aliased sequences - Disallow other types - Use tag `!!merge` instead of key `<<` - Fix insertion index for sequence merge Closes #2386
This commit is contained in:
parent
c3782799c5
commit
bfcb3fc6b7
@ -92,8 +92,8 @@ yq '.[4] | explode(.)' sample.yml
|
|||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
r: 10
|
r: 10
|
||||||
x: 1
|
|
||||||
y: 2
|
y: 2
|
||||||
|
x: 1
|
||||||
```
|
```
|
||||||
|
|
||||||
## Get anchor
|
## Get anchor
|
||||||
@ -293,8 +293,8 @@ bar:
|
|||||||
foobarList:
|
foobarList:
|
||||||
b: bar_b
|
b: bar_b
|
||||||
thing: foo_thing
|
thing: foo_thing
|
||||||
c: foobarList_c
|
|
||||||
a: foo_a
|
a: foo_a
|
||||||
|
c: foobarList_c
|
||||||
foobar:
|
foobar:
|
||||||
c: foo_c
|
c: foo_c
|
||||||
a: foo_a
|
a: foo_a
|
||||||
|
|||||||
@ -147,27 +147,15 @@ func reconstructAliasedMap(node *CandidateNode, context Context) error {
|
|||||||
keyNode := node.Content[index]
|
keyNode := node.Content[index]
|
||||||
valueNode := node.Content[index+1]
|
valueNode := node.Content[index+1]
|
||||||
log.Debugf("traversing %v", keyNode.Value)
|
log.Debugf("traversing %v", keyNode.Value)
|
||||||
if keyNode.Value != "<<" {
|
if keyNode.Tag != "!!merge" {
|
||||||
err := overrideEntry(node, keyNode, valueNode, index, context.ChildContext(newContent))
|
err := overrideEntry(node, keyNode, valueNode, index, context.ChildContext(newContent))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if valueNode.Kind == SequenceNode {
|
err := applyMergeAnchor(node, valueNode, index, context, newContent)
|
||||||
log.Debugf("an alias merge list!")
|
if err != nil {
|
||||||
for index := len(valueNode.Content) - 1; index >= 0; index = index - 1 {
|
return err
|
||||||
aliasNode := valueNode.Content[index]
|
|
||||||
err := applyAlias(node, aliasNode.Alias, index, context.ChildContext(newContent))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Debugf("an alias merge!")
|
|
||||||
err := applyAlias(node, valueNode.Alias, index, context.ChildContext(newContent))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,7 +196,7 @@ func explodeNode(node *CandidateNode, context Context) error {
|
|||||||
hasAlias := false
|
hasAlias := false
|
||||||
for index := 0; index < len(node.Content); index = index + 2 {
|
for index := 0; index < len(node.Content); index = index + 2 {
|
||||||
keyNode := node.Content[index]
|
keyNode := node.Content[index]
|
||||||
if keyNode.Value == "<<" {
|
if keyNode.Tag == "!!merge" {
|
||||||
hasAlias = true
|
hasAlias = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -237,19 +225,51 @@ func explodeNode(node *CandidateNode, context Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyAlias(node *CandidateNode, alias *CandidateNode, aliasIndex int, newContent Context) error {
|
func applyMergeAnchor(node *CandidateNode, merge *CandidateNode, mergeIndex int, context Context, newContent *list.List) error {
|
||||||
log.Debug("alias is nil ?")
|
if merge.Kind == AliasNode {
|
||||||
if alias == nil {
|
merge = merge.Alias
|
||||||
|
}
|
||||||
|
switch merge.Kind {
|
||||||
|
case MappingNode:
|
||||||
|
log.Debugf("a merge map!")
|
||||||
|
return applyMergeAnchorMap(node, merge, mergeIndex, context.ChildContext(newContent))
|
||||||
|
case SequenceNode:
|
||||||
|
log.Debugf("a merge list!")
|
||||||
|
// Earlier keys take precedence
|
||||||
|
for index := len(merge.Content) - 1; index >= 0; index = index - 1 {
|
||||||
|
childValue := merge.Content[index]
|
||||||
|
if childValue.Kind == AliasNode {
|
||||||
|
childValue = childValue.Alias
|
||||||
|
}
|
||||||
|
if childValue.Kind != MappingNode {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"can only use merge anchors with maps (!!map) or sequences (!!seq) of maps, but got sequence containing %v",
|
||||||
|
childValue.Tag)
|
||||||
|
}
|
||||||
|
err := applyMergeAnchorMap(node, childValue, mergeIndex, context.ChildContext(newContent))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("can only use merge anchors with maps (!!map) or sequences (!!seq) of maps, but got %v", merge.Tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyMergeAnchorMap(node *CandidateNode, mergeMap *CandidateNode, aliasIndex int, newContent Context) error {
|
||||||
|
log.Debug("merge map is nil ?")
|
||||||
|
if mergeMap == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Debug("alias: %v", NodeToString(alias))
|
log.Debug("merge map: %v", NodeToString(mergeMap))
|
||||||
if alias.Kind != MappingNode {
|
if mergeMap.Kind != MappingNode {
|
||||||
return fmt.Errorf("merge anchor only supports maps, got %v instead", alias.Tag)
|
return fmt.Errorf("applyMergeAnchorMap expects !!map, got %v instead", mergeMap.Tag)
|
||||||
}
|
}
|
||||||
for index := 0; index < len(alias.Content); index = index + 2 {
|
for index := 0; index < len(mergeMap.Content); index = index + 2 {
|
||||||
keyNode := alias.Content[index]
|
keyNode := mergeMap.Content[index]
|
||||||
log.Debugf("applying alias key %v", keyNode.Value)
|
log.Debugf("applying merge map key %v", keyNode.Value)
|
||||||
valueNode := alias.Content[index+1]
|
valueNode := mergeMap.Content[index+1]
|
||||||
err := overrideEntry(node, keyNode, valueNode, aliasIndex, newContent)
|
err := overrideEntry(node, keyNode, valueNode, aliasIndex, newContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@ -45,8 +45,8 @@ bar:
|
|||||||
foobarList:
|
foobarList:
|
||||||
b: bar_b
|
b: bar_b
|
||||||
thing: foo_thing
|
thing: foo_thing
|
||||||
c: foobarList_c
|
|
||||||
a: foo_a
|
a: foo_a
|
||||||
|
c: foobarList_c
|
||||||
foobar:
|
foobar:
|
||||||
c: foo_c
|
c: foo_c
|
||||||
a: foo_a
|
a: foo_a
|
||||||
@ -58,7 +58,7 @@ var anchorOperatorScenarios = []expressionScenario{
|
|||||||
skipDoc: true,
|
skipDoc: true,
|
||||||
description: "merge anchor not map",
|
description: "merge anchor not map",
|
||||||
document: "a: &a\n - 0\nc:\n <<: [*a]\n",
|
document: "a: &a\n - 0\nc:\n <<: [*a]\n",
|
||||||
expectedError: "merge anchor only supports maps, got !!seq instead",
|
expectedError: "can only use merge anchors with maps (!!map) or sequences (!!seq) of maps, but got sequence containing !!seq",
|
||||||
expression: "explode(.)",
|
expression: "explode(.)",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -80,7 +80,7 @@ var anchorOperatorScenarios = []expressionScenario{
|
|||||||
subdescription: "see https://yaml.org/type/merge.html",
|
subdescription: "see https://yaml.org/type/merge.html",
|
||||||
document: specDocument + "- << : [ *BIG, *LEFT, *SMALL ]\n x: 1\n",
|
document: specDocument + "- << : [ *BIG, *LEFT, *SMALL ]\n x: 1\n",
|
||||||
expression: ".[4] | explode(.)",
|
expression: ".[4] | explode(.)",
|
||||||
expected: []string{"D0, P[4], (!!map)::r: 10\nx: 1\ny: 2\n"},
|
expected: []string{"D0, P[4], (!!map)::r: 10\ny: 2\nx: 1\n"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Get anchor",
|
description: "Get anchor",
|
||||||
@ -254,7 +254,7 @@ var anchorOperatorScenarios = []expressionScenario{
|
|||||||
expression: `.foo* | explode(.) | (. style="flow")`,
|
expression: `.foo* | explode(.) | (. style="flow")`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
|
"D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
|
||||||
"D0, P[foobarList], (!!map)::{b: bar_b, thing: foo_thing, c: foobarList_c, a: foo_a}\n",
|
"D0, P[foobarList], (!!map)::{b: bar_b, thing: foo_thing, a: foo_a, c: foobarList_c}\n",
|
||||||
"D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n",
|
"D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -264,7 +264,7 @@ var anchorOperatorScenarios = []expressionScenario{
|
|||||||
expression: `.foo* | explode(explode(.)) | (. style="flow")`,
|
expression: `.foo* | explode(explode(.)) | (. style="flow")`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
|
"D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
|
||||||
"D0, P[foobarList], (!!map)::{b: bar_b, thing: foo_thing, c: foobarList_c, a: foo_a}\n",
|
"D0, P[foobarList], (!!map)::{b: bar_b, thing: foo_thing, a: foo_a, c: foobarList_c}\n",
|
||||||
"D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n",
|
"D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -283,6 +283,30 @@ var anchorOperatorScenarios = []expressionScenario{
|
|||||||
expression: `.thingOne |= explode(.) * {"value": false}`,
|
expression: `.thingOne |= explode(.) * {"value": false}`,
|
||||||
expected: []string{expectedUpdatedArrayRef},
|
expected: []string{expectedUpdatedArrayRef},
|
||||||
},
|
},
|
||||||
|
{ // Merge anchor with inline map
|
||||||
|
skipDoc: true,
|
||||||
|
document: `{<<: {a: 42}}`,
|
||||||
|
expression: `explode(.)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!map)::{a: 42}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // Merge anchor with sequence with inline map
|
||||||
|
skipDoc: true,
|
||||||
|
document: `{<<: [{a: 42}]}`,
|
||||||
|
expression: `explode(.)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!map)::{a: 42}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // Merge anchor with aliased sequence with inline map
|
||||||
|
skipDoc: true,
|
||||||
|
document: `{s: &s [{a: 42}], m: {<<: *s}}`,
|
||||||
|
expression: `.m | explode(.)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[m], (!!map)::{a: 42}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAnchorAliasOperatorScenarios(t *testing.T) {
|
func TestAnchorAliasOperatorScenarios(t *testing.T) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user