From 49acb79ff187e62de7ce32fedb03aa19e3e3b8ff Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 15 Apr 2023 14:48:35 +1000 Subject: [PATCH] aliases --- pkg/yqlib/candidate_node_yaml.go | 6 +- .../operators/anchor-and-alias-operators.md | 296 ++++++++++++++ pkg/yqlib/operator_anchors_aliases_test.go | 367 +++++++++--------- 3 files changed, 481 insertions(+), 188 deletions(-) diff --git a/pkg/yqlib/candidate_node_yaml.go b/pkg/yqlib/candidate_node_yaml.go index 3a6c56d2..c1621686 100644 --- a/pkg/yqlib/candidate_node_yaml.go +++ b/pkg/yqlib/candidate_node_yaml.go @@ -54,10 +54,8 @@ func (o *CandidateNode) copyFromYamlNode(node *yaml.Node, anchorMap map[string]* log.Debug("set anchor %v to %v", o.Anchor, NodeToString(o)) } - // o.Alias = TODO - find Alias in our own structure - // might need to be a post process thing - - if node.Alias != nil && node.Alias.Value != "" { + // its a single alias + if node.Alias != nil && node.Alias.Anchor != "" { o.Alias = anchorMap[node.Alias.Anchor] log.Debug("set alias to %v", NodeToString(anchorMap[node.Alias.Anchor])) } diff --git a/pkg/yqlib/doc/operators/anchor-and-alias-operators.md b/pkg/yqlib/doc/operators/anchor-and-alias-operators.md index 80e22f85..bf100c2b 100644 --- a/pkg/yqlib/doc/operators/anchor-and-alias-operators.md +++ b/pkg/yqlib/doc/operators/anchor-and-alias-operators.md @@ -5,3 +5,299 @@ 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 '.[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 '.[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 '.[4] | explode(.)' sample.yml +``` +will output +```yaml +r: 10 +x: 1 +y: 2 +``` + +## Get anchor +Given a sample.yml file of: +```yaml +a: &billyBob cat +``` +then +```bash +yq '.a | anchor' sample.yml +``` +will output +```yaml +billyBob +``` + +## Set anchor +Given a sample.yml file of: +```yaml +a: cat +``` +then +```bash +yq '.a anchor = "foobar"' sample.yml +``` +will output +```yaml +a: &foobar cat +``` + +## Set anchor relatively using assign-update +Given a sample.yml file of: +```yaml +a: + b: cat +``` +then +```bash +yq '.a anchor |= .b' sample.yml +``` +will output +```yaml +a: &cat + b: cat +``` + +## Get alias +Given a sample.yml file of: +```yaml +b: &billyBob meow +a: *billyBob +``` +then +```bash +yq '.a | alias' sample.yml +``` +will output +```yaml +billyBob +``` + +## Set alias +Given a sample.yml file of: +```yaml +b: &meow purr +a: cat +``` +then +```bash +yq '.a alias = "meow"' sample.yml +``` +will output +```yaml +b: &meow purr +a: *meow +``` + +## Set alias to blank does nothing +Given a sample.yml file of: +```yaml +b: &meow purr +a: cat +``` +then +```bash +yq '.a alias = ""' sample.yml +``` +will output +```yaml +b: &meow purr +a: cat +``` + +## Set alias relatively using assign-update +Given a sample.yml file of: +```yaml +b: &meow purr +a: + f: meow +``` +then +```bash +yq '.a alias |= .f' sample.yml +``` +will output +```yaml +b: &meow purr +a: *meow +``` + +## Explode alias and anchor +Given a sample.yml file of: +```yaml +f: + a: &a cat + b: *a +``` +then +```bash +yq 'explode(.f)' sample.yml +``` +will output +```yaml +f: + a: cat + b: cat +``` + +## Explode with no aliases or anchors +Given a sample.yml file of: +```yaml +a: mike +``` +then +```bash +yq 'explode(.a)' sample.yml +``` +will output +```yaml +a: mike +``` + +## Explode with alias keys +Given a sample.yml file of: +```yaml +f: + a: &a cat + *a: b +``` +then +```bash +yq 'explode(.f)' sample.yml +``` +will output +```yaml +f: + a: cat + cat: b +``` + +## Explode with merge anchors +Given a sample.yml file of: +```yaml +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 + !!merge <<: + - *foo + - *bar + c: foobarList_c +foobar: + c: foobar_c + !!merge <<: *foo + thing: foobar_thing +``` +then +```bash +yq 'explode(.)' sample.yml +``` +will output +```yaml +foo: + a: foo_a + thing: foo_thing + c: foo_c +bar: + b: bar_b + thing: bar_thing + c: bar_c +foobarList: + b: bar_b + thing: foo_thing + c: foobarList_c + a: foo_a +foobar: + c: foo_c + a: foo_a + thing: foobar_thing +``` + diff --git a/pkg/yqlib/operator_anchors_aliases_test.go b/pkg/yqlib/operator_anchors_aliases_test.go index 7bc90e05..743bea44 100644 --- a/pkg/yqlib/operator_anchors_aliases_test.go +++ b/pkg/yqlib/operator_anchors_aliases_test.go @@ -38,209 +38,208 @@ var anchorOperatorScenarios = []expressionScenario{ { skipDoc: true, description: "merge anchor not map", - document: "a: &a\n - 0\nc:\n <<: [*a]\n", + document: "a: &a\n - 500\nc:\n <<: [*a]\n", expectedError: "merge anchor only supports maps, got !!seq instead", expression: "explode(.)", }, + { + 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`, + expression: `.a | anchor`, + expected: []string{ + "D0, P[a], (!!str)::billyBob\n", + }, + }, + { + description: "Set anchor", + document: `a: cat`, + expression: `.a anchor = "foobar"`, + expected: []string{ + "D0, P[], (doc)::a: &foobar cat\n", + }, + }, + { + description: "Set anchor relatively using assign-update", + document: `a: {b: cat}`, + expression: `.a anchor |= .b`, + expected: []string{ + "D0, P[], (doc)::a: &cat {b: cat}\n", + }, + }, + { + skipDoc: true, + document: `a: {c: cat}`, + expression: `.a anchor |= .b`, + expected: []string{ + "D0, P[], (doc)::a: {c: cat}\n", + }, + }, + { + skipDoc: true, + document: `a: {c: cat}`, + expression: `.a anchor = .b`, + expected: []string{ + "D0, P[], (doc)::a: {c: cat}\n", + }, + }, + { + description: "Get alias", + document: `{b: &billyBob meow, a: *billyBob}`, + expression: `.a | alias`, + expected: []string{ + "D0, P[a], (!!str)::billyBob\n", + }, + }, + { + description: "Set alias", + document: `{b: &meow purr, a: cat}`, + expression: `.a alias = "meow"`, + expected: []string{ + "D0, P[], (doc)::{b: &meow purr, a: *meow}\n", + }, + }, + { + description: "Set alias to blank does nothing", + document: `{b: &meow purr, a: cat}`, + expression: `.a alias = ""`, + expected: []string{ + "D0, P[], (doc)::{b: &meow purr, a: cat}\n", + }, + }, + { + skipDoc: true, + document: `{b: &meow purr, a: cat}`, + expression: `.a alias = .c`, + expected: []string{ + "D0, P[], (doc)::{b: &meow purr, a: cat}\n", + }, + }, + { + skipDoc: true, + document: `{b: &meow purr, a: cat}`, + expression: `.a alias |= .c`, + expected: []string{ + "D0, P[], (doc)::{b: &meow purr, a: cat}\n", + }, + }, + { + description: "Set alias relatively using assign-update", + document: `{b: &meow purr, a: {f: meow}}`, + expression: `.a alias |= .f`, + expected: []string{ + "D0, P[], (doc)::{b: &meow purr, a: *meow}\n", + }, + }, + { + description: "Explode alias and anchor", + document: `{f : {a: &a cat, b: *a}}`, + expression: `explode(.f)`, + expected: []string{ + "D0, P[], (doc)::{f: {a: cat, b: cat}}\n", + }, + }, + { + description: "Explode with no aliases or anchors", + document: `a: mike`, + expression: `explode(.a)`, + expected: []string{ + "D0, P[], (doc)::a: mike\n", + }, + }, + { + description: "Explode with alias keys", + document: `{f : {a: &a cat, *a: b}}`, + expression: `explode(.f)`, + expected: []string{ + "D0, P[], (doc)::{f: {a: cat, cat: b}}\n", + }, + }, + { + description: "Explode with merge anchors", + document: mergeDocSample, + expression: `explode(.)`, + expected: []string{`D0, P[], (doc)::foo: + a: foo_a + thing: foo_thing + c: foo_c + + bar: + + b: bar_b + thing: bar_thing + c: bar_c + + foobarList: + + b: bar_b + thing: foo_thing + c: foobarList_c + a: foo_a + + foobar: + + c: foo_c + a: foo_a + thing: foobar_thing + + `}, + }, // { - // 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`, - // expression: `.a | anchor`, + // skipDoc: true, + // document: mergeDocSample, + // expression: `.foo* | explode(.) | (. style="flow")`, // expected: []string{ - // "D0, P[a], (!!str)::billyBob\n", - // }, - // }, - // { - // description: "Set anchor", - // document: `a: cat`, - // expression: `.a anchor = "foobar"`, - // expected: []string{ - // "D0, P[], (doc)::a: &foobar cat\n", - // }, - // }, - // { - // description: "Set anchor relatively using assign-update", - // document: `a: {b: cat}`, - // expression: `.a anchor |= .b`, - // expected: []string{ - // "D0, P[], (doc)::a: &cat {b: cat}\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[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n", // }, // }, // { // skipDoc: true, - // document: `a: {c: cat}`, - // expression: `.a anchor |= .b`, + // document: mergeDocSample, + // expression: `.foo* | explode(explode(.)) | (. style="flow")`, // expected: []string{ - // "D0, P[], (doc)::a: {c: cat}\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[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n", // }, // }, // { // skipDoc: true, - // document: `a: {c: cat}`, - // expression: `.a anchor = .b`, + // document: `{f : {a: &a cat, b: &b {f: *a}, *a: *b}}`, + // expression: `explode(.f)`, // expected: []string{ - // "D0, P[], (doc)::a: {c: cat}\n", + // "D0, P[], (doc)::{f: {a: cat, b: {f: cat}, cat: {f: cat}}}\n", // }, // }, // { - // description: "Get alias", - // document: `{b: &billyBob meow, a: *billyBob}`, - // expression: `.a | alias`, - // expected: []string{ - // "D0, P[a], (!!str)::billyBob\n", - // }, + // description: "Dereference and update a field", + // subdescription: "Use explode with multiply to dereference an object", + // document: simpleArrayRef, + // expression: `.thingOne |= explode(.) * {"value": false}`, + // expected: []string{expectedUpdatedArrayRef}, // }, - // { - // description: "Set alias", - // document: `{b: &meow purr, a: cat}`, - // expression: `.a alias = "meow"`, - // expected: []string{ - // "D0, P[], (doc)::{b: &meow purr, a: *meow}\n", - // }, - // }, - // { - // description: "Set alias to blank does nothing", - // document: `{b: &meow purr, a: cat}`, - // expression: `.a alias = ""`, - // expected: []string{ - // "D0, P[], (doc)::{b: &meow purr, a: cat}\n", - // }, - // }, - // { - // skipDoc: true, - // document: `{b: &meow purr, a: cat}`, - // expression: `.a alias = .c`, - // expected: []string{ - // "D0, P[], (doc)::{b: &meow purr, a: cat}\n", - // }, - // }, - // { - // skipDoc: true, - // document: `{b: &meow purr, a: cat}`, - // expression: `.a alias |= .c`, - // expected: []string{ - // "D0, P[], (doc)::{b: &meow purr, a: cat}\n", - // }, - // }, - // { - // description: "Set alias relatively using assign-update", - // document: `{b: &meow purr, a: {f: meow}}`, - // expression: `.a alias |= .f`, - // expected: []string{ - // "D0, P[], (doc)::{b: &meow purr, a: *meow}\n", - // }, - // }, - // { - // description: "Explode alias and anchor", - // document: `{f : {a: &a cat, b: *a}}`, - // expression: `explode(.f)`, - // expected: []string{ - // "D0, P[], (doc)::{f: {a: cat, b: cat}}\n", - // }, - // }, - // { - // description: "Explode with no aliases or anchors", - // document: `a: mike`, - // expression: `explode(.a)`, - // expected: []string{ - // "D0, P[], (doc)::a: mike\n", - // }, - // }, - // { - // description: "Explode with alias keys", - // document: `{f : {a: &a cat, *a: b}}`, - // expression: `explode(.f)`, - // expected: []string{ - // "D0, P[], (doc)::{f: {a: cat, cat: b}}\n", - // }, - // }, - // { - // description: "Explode with merge anchors", - // document: mergeDocSample, - // expression: `explode(.)`, - // expected: []string{`D0, P[], (doc)::foo: - // a: foo_a - // thing: foo_thing - // c: foo_c - // - // bar: - // - // b: bar_b - // thing: bar_thing - // c: bar_c - // - // foobarList: - // - // b: bar_b - // thing: foo_thing - // c: foobarList_c - // a: foo_a - // - // foobar: - // - // c: foo_c - // a: foo_a - // thing: foobar_thing - // - // `}, - // - // }, - // { - // skipDoc: true, - // document: mergeDocSample, - // 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, thing: foo_thing, c: foobarList_c, a: foo_a}\n", - // "D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n", - // }, - // }, - // { - // skipDoc: true, - // document: mergeDocSample, - // 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, thing: foo_thing, c: foobarList_c, a: foo_a}\n", - // "D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n", - // }, - // }, - // { - // skipDoc: true, - // document: `{f : {a: &a cat, b: &b {f: *a}, *a: *b}}`, - // expression: `explode(.f)`, - // expected: []string{ - // "D0, P[], (doc)::{f: {a: cat, b: {f: cat}, cat: {f: cat}}}\n", - // }, - // }, - // { - // description: "Dereference and update a field", - // subdescription: "Use explode with multiply to dereference an object", - // document: simpleArrayRef, - // expression: `.thingOne |= explode(.) * {"value": false}`, - // expected: []string{expectedUpdatedArrayRef}, - // }, } func TestAnchorAliasOperatorScenarios(t *testing.T) {