diff --git a/pkg/yqlib/doc/Anchor and Alias Operators.md b/pkg/yqlib/doc/Anchor and Alias Operators.md index e3682dc..dfbdc92 100644 --- a/pkg/yqlib/doc/Anchor and Alias Operators.md +++ b/pkg/yqlib/doc/Anchor and Alias Operators.md @@ -3,6 +3,97 @@ Use the `alias` and `anchor` operators to read and write yaml aliases and anchor `yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag. +## Merge one map +see https://yaml.org/type/merge.html + +Given a sample.yml file of: +```yaml +- &CENTER + x: 1 + y: 2 +- &LEFT + x: 0 + y: 2 +- &BIG + r: 10 +- &SMALL + r: 1 +- !!merge <<: *CENTER + r: 10 +``` +then +```bash +yq eval '.[4] | explode(.)' sample.yml +``` +will output +```yaml +x: 1 +y: 2 +r: 10 +``` + +## Merge multiple maps +see https://yaml.org/type/merge.html + +Given a sample.yml file of: +```yaml +- &CENTER + x: 1 + y: 2 +- &LEFT + x: 0 + y: 2 +- &BIG + r: 10 +- &SMALL + r: 1 +- !!merge <<: + - *CENTER + - *BIG +``` +then +```bash +yq eval '.[4] | explode(.)' sample.yml +``` +will output +```yaml +r: 10 +x: 1 +y: 2 +``` + +## Override +see https://yaml.org/type/merge.html + +Given a sample.yml file of: +```yaml +- &CENTER + x: 1 + y: 2 +- &LEFT + x: 0 + y: 2 +- &BIG + r: 10 +- &SMALL + r: 1 +- !!merge <<: + - *BIG + - *LEFT + - *SMALL + x: 1 +``` +then +```bash +yq eval '.[4] | explode(.)' sample.yml +``` +will output +```yaml +r: 10 +x: 1 +y: 2 +``` + ## Get anchor Given a sample.yml file of: ```yaml @@ -183,9 +274,9 @@ bar: c: bar_c foobarList: b: bar_b - a: foo_a - thing: bar_thing + thing: foo_thing c: foobarList_c + a: foo_a foobar: c: foo_c a: foo_a diff --git a/pkg/yqlib/operator_anchors_aliases.go b/pkg/yqlib/operator_anchors_aliases.go index c1566bc..41e9712 100644 --- a/pkg/yqlib/operator_anchors_aliases.go +++ b/pkg/yqlib/operator_anchors_aliases.go @@ -176,7 +176,7 @@ func explodeNode(node *yaml.Node, context Context) error { } else { if valueNode.Kind == yaml.SequenceNode { log.Debugf("an alias merge list!") - for index := 0; index < len(valueNode.Content); index = index + 1 { + for index := len(valueNode.Content) - 1; index >= 0; index = index - 1 { aliasNode := valueNode.Content[index] err := applyAlias(node, aliasNode.Alias, index, context.ChildContext(newContent)) if err != nil { diff --git a/pkg/yqlib/operator_anchors_aliases_test.go b/pkg/yqlib/operator_anchors_aliases_test.go index 2af815d..fb64f85 100644 --- a/pkg/yqlib/operator_anchors_aliases_test.go +++ b/pkg/yqlib/operator_anchors_aliases_test.go @@ -4,7 +4,36 @@ import ( "testing" ) +var specDocument = `- &CENTER { x: 1, y: 2 } +- &LEFT { x: 0, y: 2 } +- &BIG { r: 10 } +- &SMALL { r: 1 } +` + +var expectedSpecResult = "D0, P[4], (!!map)::x: 1\ny: 2\nr: 10\n" + var anchorOperatorScenarios = []expressionScenario{ + { + description: "Merge one map", + subdescription: "see https://yaml.org/type/merge.html", + document: specDocument + "- << : *CENTER\n r: 10\n", + expression: ".[4] | explode(.)", + expected: []string{expectedSpecResult}, + }, + { + description: "Merge multiple maps", + subdescription: "see https://yaml.org/type/merge.html", + document: specDocument + "- << : [ *CENTER, *BIG ]\n", + expression: ".[4] | explode(.)", + expected: []string{"D0, P[4], (!!map)::r: 10\nx: 1\ny: 2\n"}, + }, + { + description: "Override", + subdescription: "see https://yaml.org/type/merge.html", + document: specDocument + "- << : [ *BIG, *LEFT, *SMALL ]\n x: 1\n", + expression: ".[4] | explode(.)", + expected: []string{"D0, P[4], (!!map)::r: 10\nx: 1\ny: 2\n"}, + }, { description: "Get anchor", document: `a: &billyBob cat`, @@ -91,9 +120,9 @@ bar: c: bar_c foobarList: b: bar_b - a: foo_a - thing: bar_thing + thing: foo_thing c: foobarList_c + a: foo_a foobar: c: foo_c a: foo_a @@ -106,7 +135,7 @@ foobar: expression: `.foo* | explode(.) | (. style="flow")`, expected: []string{ "D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n", - "D0, P[foobarList], (!!map)::{b: bar_b, a: foo_a, thing: bar_thing, c: foobarList_c}\n", + "D0, P[foobarList], (!!map)::{b: bar_b, thing: foo_thing, c: foobarList_c, a: foo_a}\n", "D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n", }, }, @@ -116,7 +145,7 @@ foobar: expression: `.foo* | explode(explode(.)) | (. style="flow")`, expected: []string{ "D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n", - "D0, P[foobarList], (!!map)::{b: bar_b, a: foo_a, thing: bar_thing, c: foobarList_c}\n", + "D0, P[foobarList], (!!map)::{b: bar_b, thing: foo_thing, c: foobarList_c, a: foo_a}\n", "D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n", }, }, diff --git a/pkg/yqlib/operators_test.go b/pkg/yqlib/operators_test.go index cd5cbbe..6c1e3f0 100644 --- a/pkg/yqlib/operators_test.go +++ b/pkg/yqlib/operators_test.go @@ -71,7 +71,7 @@ func testScenario(t *testing.T, s *expressionScenario) { t.Error(fmt.Errorf("%v: %v", err, s.expression)) return } - test.AssertResultComplexWithContext(t, s.expected, resultsToString(context.MatchingNodes), fmt.Sprintf("exp: %v\ndoc: %v", s.expression, s.document)) + test.AssertResultComplexWithContext(t, s.expected, resultsToString(context.MatchingNodes), fmt.Sprintf("desc: %v\nexp: %v\ndoc: %v", s.description, s.expression, s.document)) } func resultsToString(results *list.List) []string {