From 6002604251392047f5e458727df0b2e4cffa8277 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 12 Sep 2021 21:52:02 +1000 Subject: [PATCH] Added with operator --- examples/data1.yaml | 8 +++++--- pkg/yqlib/doc/Style.md | 4 +--- pkg/yqlib/doc/Variable Operators.md | 2 ++ pkg/yqlib/doc/With.md | 20 +++++++++++++++++++ pkg/yqlib/doc/headers/With.md | 1 + pkg/yqlib/expression_tokeniser.go | 2 ++ pkg/yqlib/lib.go | 2 ++ pkg/yqlib/operator_style_test.go | 7 +++---- pkg/yqlib/operator_variables_test.go | 7 ++++--- pkg/yqlib/operator_with.go | 30 ++++++++++++++++++++++++++++ pkg/yqlib/operator_with_test.go | 21 +++++++++++++++++++ 11 files changed, 91 insertions(+), 13 deletions(-) create mode 100644 pkg/yqlib/doc/With.md create mode 100644 pkg/yqlib/doc/headers/With.md create mode 100644 pkg/yqlib/operator_with.go create mode 100644 pkg/yqlib/operator_with_test.go diff --git a/examples/data1.yaml b/examples/data1.yaml index 2d1f6f4f..bc87e31c 100644 --- a/examples/data1.yaml +++ b/examples/data1.yaml @@ -1,3 +1,5 @@ -# a1 -a: 1 -# a2 \ No newline at end of file +Resources: + S3Bucket: + Type: AWS::CloudFormation::Stack + Properties: + BucketName: !Ref MyBucketNameA \ No newline at end of file diff --git a/pkg/yqlib/doc/Style.md b/pkg/yqlib/doc/Style.md index d7c07673..1f0f0939 100644 --- a/pkg/yqlib/doc/Style.md +++ b/pkg/yqlib/doc/Style.md @@ -18,8 +18,6 @@ a: ``` ## Update and set style of a particular node using path variables -You can use a variable reference to re-use a path - Given a sample.yml file of: ```yaml a: @@ -28,7 +26,7 @@ a: ``` then ```bash -yq eval '.a.b ref $x | $x = "new" | $x style="double"' sample.yml +yq eval 'with(.a.b ; . = "new" | . style="double")' sample.yml ``` will output ```yaml diff --git a/pkg/yqlib/doc/Variable Operators.md b/pkg/yqlib/doc/Variable Operators.md index c434a0c5..311382a6 100644 --- a/pkg/yqlib/doc/Variable Operators.md +++ b/pkg/yqlib/doc/Variable Operators.md @@ -75,6 +75,8 @@ b: a_value ``` ## Use ref to reference a path repeatedly +Note: You may find the `with` operator more useful. + Given a sample.yml file of: ```yaml a: diff --git a/pkg/yqlib/doc/With.md b/pkg/yqlib/doc/With.md new file mode 100644 index 00000000..5e21cc6b --- /dev/null +++ b/pkg/yqlib/doc/With.md @@ -0,0 +1,20 @@ +Use the `with` operator to conveniently make multiple updates to a deeply nested path. + +## Update and style +Given a sample.yml file of: +```yaml +a: + deeply: + nested: value +``` +then +```bash +yq eval 'with(.a.deeply.nested ; . = "newValue" | . style="single")' sample.yml +``` +will output +```yaml +a: + deeply: + nested: 'newValue' +``` + diff --git a/pkg/yqlib/doc/headers/With.md b/pkg/yqlib/doc/headers/With.md new file mode 100644 index 00000000..13b440e0 --- /dev/null +++ b/pkg/yqlib/doc/headers/With.md @@ -0,0 +1 @@ +Use the `with` operator to conveniently make multiple updates to a deeply nested path. diff --git a/pkg/yqlib/expression_tokeniser.go b/pkg/yqlib/expression_tokeniser.go index 09929136..e5183b80 100644 --- a/pkg/yqlib/expression_tokeniser.go +++ b/pkg/yqlib/expression_tokeniser.go @@ -315,6 +315,8 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`from_entries`), opToken(fromEntriesOpType)) lexer.Add([]byte(`with_entries`), opToken(withEntriesOpType)) + lexer.Add([]byte(`with`), opToken(withOpType)) + lexer.Add([]byte(`lineComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{LineComment: true})) lexer.Add([]byte(`headComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{HeadComment: true})) diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 9a55e53e..1a076838 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -69,6 +69,8 @@ var toEntriesOpType = &operationType{Type: "TO_ENTRIES", NumArgs: 0, Precedence: var fromEntriesOpType = &operationType{Type: "FROM_ENTRIES", NumArgs: 0, Precedence: 50, Handler: fromEntriesOperator} var withEntriesOpType = &operationType{Type: "WITH_ENTRIES", NumArgs: 1, Precedence: 50, Handler: withEntriesOperator} +var withOpType = &operationType{Type: "WITH", NumArgs: 1, Precedence: 50, Handler: withOperator} + var splitDocumentOpType = &operationType{Type: "SPLIT_DOC", NumArgs: 0, Precedence: 50, Handler: splitDocumentOperator} var getVariableOpType = &operationType{Type: "GET_VARIABLE", NumArgs: 0, Precedence: 55, Handler: getVariableOperator} var getStyleOpType = &operationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: getStyleOperator} diff --git a/pkg/yqlib/operator_style_test.go b/pkg/yqlib/operator_style_test.go index 4288240f..77d88aca 100644 --- a/pkg/yqlib/operator_style_test.go +++ b/pkg/yqlib/operator_style_test.go @@ -14,10 +14,9 @@ var styleOperatorScenarios = []expressionScenario{ }, }, { - description: "Update and set style of a particular node using path variables", - subdescription: "You can use a variable reference to re-use a path", - document: `a: {b: thing, c: something}`, - expression: `.a.b ref $x | $x = "new" | $x style="double"`, + description: "Update and set style of a particular node using path variables", + document: `a: {b: thing, c: something}`, + expression: `with(.a.b ; . = "new" | . style="double")`, expected: []string{ "D0, P[], (doc)::a: {b: \"new\", c: something}\n", }, diff --git a/pkg/yqlib/operator_variables_test.go b/pkg/yqlib/operator_variables_test.go index 69502347..2d890d88 100644 --- a/pkg/yqlib/operator_variables_test.go +++ b/pkg/yqlib/operator_variables_test.go @@ -52,9 +52,10 @@ var variableOperatorScenarios = []expressionScenario{ }, }, { - description: "Use ref to reference a path repeatedly", - document: `a: {b: thing, c: something}`, - expression: `.a.b ref $x | $x = "new" | $x style="double"`, + description: "Use ref to reference a path repeatedly", + subdescription: "Note: You may find the `with` operator more useful.", + document: `a: {b: thing, c: something}`, + expression: `.a.b ref $x | $x = "new" | $x style="double"`, expected: []string{ "D0, P[], (doc)::a: {b: \"new\", c: something}\n", }, diff --git a/pkg/yqlib/operator_with.go b/pkg/yqlib/operator_with.go new file mode 100644 index 00000000..fd277f30 --- /dev/null +++ b/pkg/yqlib/operator_with.go @@ -0,0 +1,30 @@ +package yqlib + +import "fmt" + +func withOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { + log.Debugf("-- withOperator") + // with(path, exp) + + if expressionNode.Rhs.Operation.OperationType != blockOpType { + return Context{}, fmt.Errorf("with must be given a block, got %v instead", expressionNode.Rhs.Operation.OperationType.Type) + } + + pathExp := expressionNode.Rhs.Lhs + + updateContext, err := d.GetMatchingNodes(context, pathExp) + + if err != nil { + return Context{}, err + } + + updateExp := expressionNode.Rhs.Rhs + + _, err = d.GetMatchingNodes(updateContext, updateExp) + if err != nil { + return Context{}, err + } + + return context, nil + +} diff --git a/pkg/yqlib/operator_with_test.go b/pkg/yqlib/operator_with_test.go new file mode 100644 index 00000000..c856a4b4 --- /dev/null +++ b/pkg/yqlib/operator_with_test.go @@ -0,0 +1,21 @@ +package yqlib + +import "testing" + +var withOperatorScenarios = []expressionScenario{ + { + description: "Update and style", + document: `a: {deeply: {nested: value}}`, + expression: `with(.a.deeply.nested ; . = "newValue" | . style="single")`, + expected: []string{ + "D0, P[], (doc)::a: {deeply: {nested: 'newValue'}}\n", + }, + }, +} + +func TestWithOperatorScenarios(t *testing.T) { + for _, tt := range withOperatorScenarios { + testScenario(t, &tt) + } + documentScenarios(t, "With", withOperatorScenarios) +}