From d00153de7143e20f140a876df71d89512a46ba29 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 4 Feb 2022 09:24:48 +1100 Subject: [PATCH] Adding to array copies styling of previous elements #722 --- pkg/yqlib/doc/operators/add.md | 133 ++++++++++----------------------- pkg/yqlib/operator_add.go | 32 ++++++-- pkg/yqlib/operator_add_test.go | 76 +++++++++---------- 3 files changed, 102 insertions(+), 139 deletions(-) diff --git a/pkg/yqlib/doc/operators/add.md b/pkg/yqlib/doc/operators/add.md index 7872d8d1..6ef6139a 100644 --- a/pkg/yqlib/doc/operators/add.md +++ b/pkg/yqlib/doc/operators/add.md @@ -9,29 +9,6 @@ Add behaves differently according to the type of the LHS: Use `+=` as a relative append assign for things like increment. Note that `.a += .x` is equivalent to running `.a = .a + .x`. -## Concatenate and assign arrays -Given a sample.yml file of: -```yaml -a: - val: thing - b: - - cat - - dog -``` -then -```bash -yq '.a.b += ["cow"]' sample.yml -``` -will output -```yaml -a: - val: thing - b: - - cat - - dog - - cow -``` - ## Concatenate arrays Given a sample.yml file of: ```yaml @@ -54,6 +31,28 @@ will output - 4 ``` +## Concatenate to existing array +Note that the styling of `a` is kept. + +Given a sample.yml file of: +```yaml +a: [1,2] +b: + - 3 + - 4 +``` +then +```bash +yq '.a += .b' sample.yml +``` +will output +```yaml +a: [1, 2, 3, 4] +b: + - 3 + - 4 +``` + ## Concatenate null to array Given a sample.yml file of: ```yaml @@ -71,6 +70,22 @@ will output - 2 ``` +## Append to existing array +Note that the styling is copied from existing array elements + +Given a sample.yml file of: +```yaml +a: ['dog'] +``` +then +```bash +yq '.a += "cat"' sample.yml +``` +will output +```yaml +a: ['dog', 'cat'] +``` + ## Add new object to array Given a sample.yml file of: ```yaml @@ -87,76 +102,6 @@ will output - cat: meow ``` -## Add string to array -Given a sample.yml file of: -```yaml -a: - - 1 - - 2 -``` -then -```bash -yq '.a + "hello"' sample.yml -``` -will output -```yaml -- 1 -- 2 -- hello -``` - -## Append to array -Given a sample.yml file of: -```yaml -a: - - 1 - - 2 -b: - - 3 - - 4 -``` -then -```bash -yq '.a = .a + .b' sample.yml -``` -will output -```yaml -a: - - 1 - - 2 - - 3 - - 4 -b: - - 3 - - 4 -``` - -## Append another array using += -Given a sample.yml file of: -```yaml -a: - - 1 - - 2 -b: - - 3 - - 4 -``` -then -```bash -yq '.a += .b' sample.yml -``` -will output -```yaml -a: - - 1 - - 2 - - 3 - - 4 -b: - - 3 - - 4 -``` - ## Relative append Given a sample.yml file of: ```yaml @@ -195,7 +140,7 @@ b: meow ``` then ```bash -yq '.a = .a + .b' sample.yml +yq '.a += .b' sample.yml ``` will output ```yaml diff --git a/pkg/yqlib/operator_add.go b/pkg/yqlib/operator_add.go index 9f8dcc1a..8e678f0d 100644 --- a/pkg/yqlib/operator_add.go +++ b/pkg/yqlib/operator_add.go @@ -18,16 +18,23 @@ func addAssignOperator(d *dataTreeNavigator, context Context, expressionNode *Ex return compoundAssignFunction(d, context, expressionNode, createAddOp) } -func toNodes(candidate *CandidateNode) []*yaml.Node { +func toNodes(candidate *CandidateNode, lhs *CandidateNode) ([]*yaml.Node, error) { if candidate.Node.Tag == "!!null" { - return []*yaml.Node{} + return []*yaml.Node{}, nil + } + clone, err := candidate.Copy() + if err != nil { + return nil, err } switch candidate.Node.Kind { case yaml.SequenceNode: - return candidate.Node.Content + return clone.Node.Content, nil default: - return []*yaml.Node{candidate.Node} + if len(lhs.Node.Content) > 0 { + clone.Node.Style = lhs.Node.Content[0].Style + } + return []*yaml.Node{clone.Node}, nil } } @@ -54,7 +61,10 @@ func add(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *Candida case yaml.MappingNode: addMaps(target, lhs, rhs) case yaml.SequenceNode: - addSequences(target, lhs, rhs) + if err := addSequences(target, lhs, rhs); err != nil { + return nil, err + } + case yaml.ScalarNode: if rhs.Node.Kind != yaml.ScalarNode { return nil, fmt.Errorf("%v (%v) cannot be added to a %v", rhs.Node.Tag, rhs.Path, lhsNode.Tag) @@ -139,7 +149,7 @@ func addScalars(target *CandidateNode, lhs *yaml.Node, rhs *yaml.Node) error { return nil } -func addSequences(target *CandidateNode, lhs *CandidateNode, rhs *CandidateNode) { +func addSequences(target *CandidateNode, lhs *CandidateNode, rhs *CandidateNode) error { target.Node.Kind = yaml.SequenceNode if len(lhs.Node.Content) > 0 { target.Node.Style = lhs.Node.Style @@ -147,7 +157,15 @@ func addSequences(target *CandidateNode, lhs *CandidateNode, rhs *CandidateNode) target.Node.Tag = lhs.Node.Tag target.Node.Content = make([]*yaml.Node, len(lhs.Node.Content)) copy(target.Node.Content, lhs.Node.Content) - target.Node.Content = append(target.Node.Content, toNodes(rhs)...) + + extraNodes, err := toNodes(rhs, lhs) + if err != nil { + return err + } + + target.Node.Content = append(target.Node.Content, extraNodes...) + return nil + } func addMaps(target *CandidateNode, lhsC *CandidateNode, rhsC *CandidateNode) { diff --git a/pkg/yqlib/operator_add_test.go b/pkg/yqlib/operator_add_test.go index a7ef0f37..7f62303f 100644 --- a/pkg/yqlib/operator_add_test.go +++ b/pkg/yqlib/operator_add_test.go @@ -30,14 +30,7 @@ var addOperatorScenarios = []expressionScenario{ "D0, P[], (doc)::a: 0\n", }, }, - { - description: "Concatenate and assign arrays", - document: `{a: {val: thing, b: [cat,dog]}}`, - expression: ".a.b += [\"cow\"]", - expected: []string{ - "D0, P[], (doc)::{a: {val: thing, b: [cat, dog, cow]}}\n", - }, - }, + { description: "Concatenate arrays", document: `{a: [1,2], b: [3,4]}`, @@ -46,6 +39,16 @@ var addOperatorScenarios = []expressionScenario{ "D0, P[a], (!!seq)::[1, 2, 3, 4]\n", }, }, + { + description: "Concatenate to existing array", + subdescription: "Note that the styling of `a` is kept.", + document: "a: [1,2]\nb:\n - 3\n - 4", + dontFormatInputForDoc: true, + expression: `.a += .b`, + expected: []string{ + "D0, P[], (doc)::a: [1, 2, 3, 4]\nb:\n - 3\n - 4\n", + }, + }, { skipDoc: true, expression: `[1] + ([2], [3])`, @@ -72,12 +75,23 @@ var addOperatorScenarios = []expressionScenario{ }, }, { - skipDoc: true, - description: "Concatenate to existing array", - document: `{a: [dog]}`, - expression: `.a + "cat"`, + description: "Append to existing array", + subdescription: "Note that the styling is copied from existing array elements", + dontFormatInputForDoc: true, + document: `a: ['dog']`, + expression: `.a += "cat"`, expected: []string{ - "D0, P[a], (!!seq)::[dog, cat]\n", + "D0, P[], (doc)::a: ['dog', 'cat']\n", + }, + }, + { + skipDoc: true, + description: "Concatenate to existing array", + subdescription: "does not modify original", + document: `{a: ['dog'], b: cat}`, + expression: `.a = .a + .b`, + expected: []string{ + "D0, P[], (doc)::{a: ['dog', 'cat'], b: cat}\n", }, }, { @@ -116,6 +130,16 @@ var addOperatorScenarios = []expressionScenario{ "D0, P[a], (!!map)::{c: dog, b: cat}\n", }, }, + { + skipDoc: true, + description: "Concatenate to existing object", + subdescription: "matches stylig", + document: "a:\n c: dog", + expression: `.a + {"b": "cat"}`, + expected: []string{ + "D0, P[a], (!!map)::c: dog\nb: cat\n", + }, + }, { skipDoc: true, description: "Concatenate to empty object in place", @@ -142,30 +166,6 @@ var addOperatorScenarios = []expressionScenario{ "D0, P[a], (!!seq)::[{dog: woof}, {cat: meow}]\n", }, }, - { - description: "Add string to array", - document: `{a: [1,2]}`, - expression: `.a + "hello"`, - expected: []string{ - "D0, P[a], (!!seq)::[1, 2, hello]\n", - }, - }, - { - description: "Append to array", - document: `{a: [1,2], b: [3,4]}`, - expression: `.a = .a + .b`, - expected: []string{ - "D0, P[], (doc)::{a: [1, 2, 3, 4], b: [3, 4]}\n", - }, - }, - { - description: "Append another array using +=", - document: `{a: [1,2], b: [3,4]}`, - expression: `.a += .b`, - expected: []string{ - "D0, P[], (doc)::{a: [1, 2, 3, 4], b: [3, 4]}\n", - }, - }, { description: "Relative append", document: `a: { a1: {b: [cat]}, a2: {b: [dog]}, a3: {} }`, @@ -177,7 +177,7 @@ var addOperatorScenarios = []expressionScenario{ { description: "String concatenation", document: `{a: cat, b: meow}`, - expression: `.a = .a + .b`, + expression: `.a += .b`, expected: []string{ "D0, P[], (doc)::{a: catmeow, b: meow}\n", },