From 5ab584afac4c973b752ce6eeadbad7e4fec39454 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 6 Nov 2020 11:23:26 +1100 Subject: [PATCH] comment ops! --- pkg/yqlib/doc/.gitignore | 1 + pkg/yqlib/doc/Equal Operator.md | 64 ----------------------------- pkg/yqlib/lib.go | 5 +-- pkg/yqlib/operator_comments.go | 47 +++++++++++++++++++++ pkg/yqlib/operator_comments_test.go | 55 +++++++++++++++++++++++++ pkg/yqlib/operator_equals_test.go | 2 +- pkg/yqlib/operatory_style.go | 14 ++++++- pkg/yqlib/operatory_style_test.go | 9 ++++ pkg/yqlib/path_tokeniser.go | 12 +++++- 9 files changed, 138 insertions(+), 71 deletions(-) create mode 100644 pkg/yqlib/doc/.gitignore delete mode 100644 pkg/yqlib/doc/Equal Operator.md create mode 100644 pkg/yqlib/operator_comments.go create mode 100644 pkg/yqlib/operator_comments_test.go diff --git a/pkg/yqlib/doc/.gitignore b/pkg/yqlib/doc/.gitignore new file mode 100644 index 00000000..dd449725 --- /dev/null +++ b/pkg/yqlib/doc/.gitignore @@ -0,0 +1 @@ +*.md diff --git a/pkg/yqlib/doc/Equal Operator.md b/pkg/yqlib/doc/Equal Operator.md deleted file mode 100644 index c8c94687..00000000 --- a/pkg/yqlib/doc/Equal Operator.md +++ /dev/null @@ -1,64 +0,0 @@ -# Equal Operator -## Examples -### Example 0 -sample.yml: -```yaml -[cat,goat,dog] -``` -Expression -```bash -yq '.[] | (. == "*at")' < sample.yml -``` -Result -```yaml -true -true -false -``` -### Example 1 -sample.yml: -```yaml -[3, 4, 5] -``` -Expression -```bash -yq '.[] | (. == 4)' < sample.yml -``` -Result -```yaml -false -true -false -``` -### Example 2 -sample.yml: -```yaml -a: { cat: {b: apple, c: whatever}, pat: {b: banana} } -``` -Expression -```bash -yq '.a | (.[].b == "apple")' < sample.yml -``` -Result -```yaml -true -false -``` -### Example 3 -Expression -```bash -yq 'null == null' < sample.yml -``` -Result -```yaml -true -``` -### Example 4 -Expression -```bash -yq 'null == ~' < sample.yml -``` -Result -```yaml -true -``` diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index f229c136..6ae95f84 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -18,14 +18,12 @@ type OperationType struct { // operators TODO: // - generator doc from operator tests -// - stripComments not recursive +// - set comments not recursive // - documentIndex - retrieves document index, can be used with select // - mergeAppend (merges and appends arrays) // - mergeEmpty (sets only if the document is empty, do I do that now?) // - updateTag - not recursive -// - select by tag (tag==) // - get tag (tag) -// - select by style (style==) // - compare ?? // - validate ?? // - exists @@ -38,6 +36,7 @@ var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: U var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator} var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator} var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator} +var AssignComment = &OperationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: AssignCommentsOperator} var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 40, Handler: MultiplyOperator} diff --git a/pkg/yqlib/operator_comments.go b/pkg/yqlib/operator_comments.go new file mode 100644 index 00000000..e3aeedb1 --- /dev/null +++ b/pkg/yqlib/operator_comments.go @@ -0,0 +1,47 @@ +package yqlib + +import "container/list" + +type AssignCommentPreferences struct { + LineComment bool + HeadComment bool + FootComment bool +} + +func AssignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + + log.Debugf("AssignComments operator!") + + rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs) + if err != nil { + return nil, err + } + comment := "" + if rhs.Front() != nil { + comment = rhs.Front().Value.(*CandidateNode).Node.Value + } + + lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) + + if err != nil { + return nil, err + } + + preferences := pathNode.Operation.Preferences.(*AssignCommentPreferences) + + for el := lhs.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + log.Debugf("Setting comment of : %v", candidate.GetKey()) + if preferences.LineComment { + candidate.Node.LineComment = comment + } + if preferences.HeadComment { + candidate.Node.HeadComment = comment + } + if preferences.FootComment { + candidate.Node.FootComment = comment + } + + } + return matchingNodes, nil +} diff --git a/pkg/yqlib/operator_comments_test.go b/pkg/yqlib/operator_comments_test.go new file mode 100644 index 00000000..221a227b --- /dev/null +++ b/pkg/yqlib/operator_comments_test.go @@ -0,0 +1,55 @@ +package yqlib + +import ( + "testing" +) + +var commentOperatorScenarios = []expressionScenario{ + { + description: "Add line comment", + document: `a: cat`, + expression: `.a lineComment="single"`, + expected: []string{ + "D0, P[], (doc)::a: cat # single\n", + }, + }, + { + description: "Add head comment", + document: `a: cat`, + expression: `. headComment="single"`, + expected: []string{ + "D0, P[], (doc)::# single\n\na: cat\n", + }, + }, + { + description: "Add foot comment, using an expression", + document: `a: cat`, + expression: `. footComment=.a`, + expected: []string{ + "D0, P[], (doc)::a: cat\n\n# cat\n", + }, + }, + { + description: "Remove comment", + document: "a: cat # comment\nb: dog # leave this", + expression: `.a lineComment=""`, + expected: []string{ + "D0, P[], (doc)::a: cat\nb: dog # leave this\n", + }, + }, + { + description: "Remove all comments", + document: "# hi\n\na: cat # comment\n\n# great\n", + expression: `.. comments=""`, + expected: []string{ + "D0, P[], (!!map)::a: cat\n", + }, + }, +} + +func TestCommentOperatorScenarios(t *testing.T) { + for _, tt := range commentOperatorScenarios { + testScenario(t, &tt) + } + documentScenarios(t, "Comments Operator", commentOperatorScenarios) +} diff --git a/pkg/yqlib/operator_equals_test.go b/pkg/yqlib/operator_equals_test.go index 6db4199d..39e05456 100644 --- a/pkg/yqlib/operator_equals_test.go +++ b/pkg/yqlib/operator_equals_test.go @@ -49,5 +49,5 @@ func TestEqualOperatorScenarios(t *testing.T) { for _, tt := range equalsOperatorScenarios { testScenario(t, &tt) } - documentScenarios(t, "Equal Operator", equalsOperatorScenarios) + documentScenarios(t, "Equals Operator", equalsOperatorScenarios) } diff --git a/pkg/yqlib/operatory_style.go b/pkg/yqlib/operatory_style.go index a0d4aab0..872b0ada 100644 --- a/pkg/yqlib/operatory_style.go +++ b/pkg/yqlib/operatory_style.go @@ -8,8 +8,18 @@ import ( ) func AssignStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { - customStyle := pathNode.Rhs.Operation.StringValue - log.Debugf("AssignStyleOperator: %v", customStyle) + + log.Debugf("AssignStyleOperator: %v") + + rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs) + if err != nil { + return nil, err + } + customStyle := "" + + if rhs.Front() != nil { + customStyle = rhs.Front().Value.(*CandidateNode).Node.Value + } var style yaml.Style if customStyle == "tagged" { diff --git a/pkg/yqlib/operatory_style_test.go b/pkg/yqlib/operatory_style_test.go index b2aa40bc..28909899 100644 --- a/pkg/yqlib/operatory_style_test.go +++ b/pkg/yqlib/operatory_style_test.go @@ -12,6 +12,14 @@ var styleOperatorScenarios = []expressionScenario{ "D0, P[], (doc)::{a: 'cat'}\n", }, }, + { + description: "Set style using a path", + document: `{a: cat, b: double}`, + expression: `.a style=.b`, + expected: []string{ + "D0, P[], (doc)::{a: \"cat\", b: double}\n", + }, + }, { document: `{a: "cat", b: 'dog'}`, expression: `.. style=""`, @@ -42,4 +50,5 @@ func TestStyleOperatorScenarios(t *testing.T) { for _, tt := range styleOperatorScenarios { testScenario(t, &tt) } + documentScenarios(t, "Style Operator", styleOperatorScenarios) } diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index 4c5b44a0..53575cdd 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -84,9 +84,13 @@ func documentToken() lex.Action { } func opToken(op *OperationType) lex.Action { + return opTokenWithPrefs(op, nil) +} + +func opTokenWithPrefs(op *OperationType, preferences interface{}) lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { value := string(m.Bytes) - op := &Operation{OperationType: op, Value: op.Type, StringValue: value} + op := &Operation{OperationType: op, Value: op.Type, StringValue: value, Preferences: preferences} return &Token{TokenType: OperationToken, Operation: op}, nil } } @@ -190,6 +194,12 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`style\s*=`), opToken(AssignStyle)) lexer.Add([]byte(`style`), opToken(GetStyle)) + lexer.Add([]byte(`lineComment\s*=`), opTokenWithPrefs(AssignComment, &AssignCommentPreferences{LineComment: true})) + lexer.Add([]byte(`headComment\s*=`), opTokenWithPrefs(AssignComment, &AssignCommentPreferences{HeadComment: true})) + lexer.Add([]byte(`footComment\s*=`), opTokenWithPrefs(AssignComment, &AssignCommentPreferences{FootComment: true})) + lexer.Add([]byte(`comments\s*=`), opTokenWithPrefs(AssignComment, &AssignCommentPreferences{LineComment: true, HeadComment: true, FootComment: true})) + // lexer.Add([]byte(`style`), opToken(GetStyle)) + // lexer.Add([]byte(`and`), opToken()) lexer.Add([]byte(`collect`), opToken(Collect))