Can assign-update comments

This commit is contained in:
Mike Farah 2021-01-06 20:22:50 +11:00
parent 3d5a5e0360
commit a7975df7cd
9 changed files with 113 additions and 23 deletions

View File

@ -13,6 +13,22 @@ will output
a: cat # single a: cat # single
``` ```
## Use update assign to perform relative updates
Given a sample.yml file of:
```yaml
a: cat
b: dog
```
then
```bash
yq eval '.. lineComment |= .' sample.yml
```
will output
```yaml
a: cat # cat
b: dog # dog
```
## Set head comment ## Set head comment
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml

View File

@ -86,6 +86,7 @@ type Operation struct {
StringValue string StringValue string
CandidateNode *CandidateNode // used for Value Path elements CandidateNode *CandidateNode // used for Value Path elements
Preferences interface{} Preferences interface{}
UpdateAssign bool // used for assign ops, when true it means we evaluate the rhs given the lhs (instead of matching nodes)
} }
func CreateValueOperation(value interface{}, stringValue string) *Operation { func CreateValueOperation(value interface{}, stringValue string) *Operation {

View File

@ -16,7 +16,7 @@ func createSelfAddOp(rhs *PathTreeNode) *PathTreeNode {
func AddAssignOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { func AddAssignOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
assignmentOp := &Operation{OperationType: Assign} assignmentOp := &Operation{OperationType: Assign}
assignmentOp.Preferences = &AssignOpPreferences{true} assignmentOp.UpdateAssign = true
assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: pathNode.Lhs, Rhs: createSelfAddOp(pathNode.Rhs)} assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: pathNode.Lhs, Rhs: createSelfAddOp(pathNode.Rhs)}
return d.GetMatchingNodes(matchingNodes, assignmentOpNode) return d.GetMatchingNodes(matchingNodes, assignmentOpNode)

View File

@ -2,26 +2,20 @@ package yqlib
import "container/list" import "container/list"
type AssignOpPreferences struct {
UpdateAssign bool
}
func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
preferences := pathNode.Operation.Preferences.(*AssignOpPreferences)
var rhs *list.List var rhs *list.List
if !preferences.UpdateAssign { if !pathNode.Operation.UpdateAssign {
rhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Rhs) rhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
} }
for el := lhs.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
if preferences.UpdateAssign { if pathNode.Operation.UpdateAssign {
rhs, err = d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs) rhs, err = d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
} }

View File

@ -4,7 +4,7 @@ import (
"container/list" "container/list"
"strings" "strings"
"gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
type CommentOpPreferences struct { type CommentOpPreferences struct {
@ -17,15 +17,6 @@ func AssignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, path
log.Debugf("AssignComments operator!") 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) lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil { if err != nil {
@ -34,8 +25,32 @@ func AssignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, path
preferences := pathNode.Operation.Preferences.(*CommentOpPreferences) preferences := pathNode.Operation.Preferences.(*CommentOpPreferences)
comment := ""
if !pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
comment = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
for el := lhs.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
if pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
comment = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
log.Debugf("Setting comment of : %v", candidate.GetKey()) log.Debugf("Setting comment of : %v", candidate.GetKey())
if preferences.LineComment { if preferences.LineComment {
candidate.Node.LineComment = comment candidate.Node.LineComment = comment

View File

@ -13,6 +13,39 @@ var commentOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::a: cat # single\n", "D0, P[], (doc)::a: cat # single\n",
}, },
}, },
{
skipDoc: true,
document: "a: cat\nb: dog",
expression: `.a lineComment=.b`,
expected: []string{
"D0, P[], (doc)::a: cat # dog\nb: dog\n",
},
},
{
skipDoc: true,
document: "a: cat\n---\na: dog",
expression: `.a lineComment |= documentIndex`,
expected: []string{
"D0, P[], (doc)::a: cat # 0\n",
"D1, P[], (doc)::a: dog # 1\n",
},
},
{
description: "Use update assign to perform relative updates",
document: "a: cat\nb: dog",
expression: `.. lineComment |= .`,
expected: []string{
"D0, P[], (!!map)::a: cat # cat\nb: dog # dog\n",
},
},
{
skipDoc: true,
document: "a: cat\nb: dog",
expression: `.. comments |= .`,
expected: []string{
"D0, P[], (!!map)::a: cat # cat\n# cat\n\n# cat\nb: dog # dog\n# dog\n\n# dog\n",
},
},
{ {
description: "Set head comment", description: "Set head comment",
document: `a: cat`, document: `a: cat`,

View File

@ -115,7 +115,7 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid
assignmentOp := &Operation{OperationType: AssignAttributes} assignmentOp := &Operation{OperationType: AssignAttributes}
if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode { if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode {
assignmentOp.OperationType = Assign assignmentOp.OperationType = Assign
assignmentOp.Preferences = &AssignOpPreferences{false} assignmentOp.UpdateAssign = false
} else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode { } else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode {
assignmentOp.OperationType = AddAssign assignmentOp.OperationType = AddAssign
} }

View File

@ -137,6 +137,11 @@ var pathTests = []struct {
append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"), append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"),
append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"), append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"),
}, },
{
`. lineComment |= "str"`,
append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"),
append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"),
},
{ {
`.a.b tag="!!str"`, `.a.b tag="!!str"`,
append(make([]interface{}, 0), "a", "SHORT_PIPE", "b", "ASSIGN_TAG", "!!str (string)"), append(make([]interface{}, 0), "a", "SHORT_PIPE", "b", "ASSIGN_TAG", "!!str (string)"),

View File

@ -92,6 +92,15 @@ func opAssignableToken(opType *OperationType, assignOpType *OperationType) lex.A
return opTokenWithPrefs(opType, assignOpType, nil) return opTokenWithPrefs(opType, assignOpType, nil)
} }
func assignOpToken(updateAssign bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
log.Debug("assignOpToken %v", string(m.Bytes))
value := string(m.Bytes)
op := &Operation{OperationType: Assign, Value: Assign.Type, StringValue: value, UpdateAssign: updateAssign}
return &Token{TokenType: OperationToken, Operation: op}, nil
}
}
func opTokenWithPrefs(op *OperationType, assignOpType *OperationType, preferences interface{}) lex.Action { func opTokenWithPrefs(op *OperationType, assignOpType *OperationType, preferences interface{}) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
log.Debug("opTokenWithPrefs %v", string(m.Bytes)) log.Debug("opTokenWithPrefs %v", string(m.Bytes))
@ -105,6 +114,21 @@ func opTokenWithPrefs(op *OperationType, assignOpType *OperationType, preference
} }
} }
func assignAllCommentsOp(updateAssign bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
log.Debug("assignAllCommentsOp %v", string(m.Bytes))
value := string(m.Bytes)
op := &Operation{
OperationType: AssignComment,
Value: AssignComment.Type,
StringValue: value,
UpdateAssign: updateAssign,
Preferences: &CommentOpPreferences{LineComment: true, HeadComment: true, FootComment: true},
}
return &Token{TokenType: OperationToken, Operation: op}, nil
}
}
func literalToken(pType TokenType, checkForPost bool) lex.Action { func literalToken(pType TokenType, checkForPost bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
return &Token{TokenType: pType, CheckForPostTraverse: checkForPost}, nil return &Token{TokenType: pType, CheckForPostTraverse: checkForPost}, nil
@ -210,16 +234,17 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`footComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{FootComment: true})) lexer.Add([]byte(`footComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{FootComment: true}))
lexer.Add([]byte(`comments\s*=`), opTokenWithPrefs(AssignComment, nil, &CommentOpPreferences{LineComment: true, HeadComment: true, FootComment: true})) lexer.Add([]byte(`comments\s*=`), assignAllCommentsOp(false))
lexer.Add([]byte(`comments\s*\|=`), assignAllCommentsOp(true))
lexer.Add([]byte(`collect`), opToken(Collect)) lexer.Add([]byte(`collect`), opToken(Collect))
lexer.Add([]byte(`\s*==\s*`), opToken(Equals)) lexer.Add([]byte(`\s*==\s*`), opToken(Equals))
lexer.Add([]byte(`\s*=\s*`), opTokenWithPrefs(Assign, nil, &AssignOpPreferences{false})) lexer.Add([]byte(`\s*=\s*`), assignOpToken(false))
lexer.Add([]byte(`del`), opToken(DeleteChild)) lexer.Add([]byte(`del`), opToken(DeleteChild))
lexer.Add([]byte(`\s*\|=\s*`), opTokenWithPrefs(Assign, nil, &AssignOpPreferences{true})) lexer.Add([]byte(`\s*\|=\s*`), assignOpToken(true))
lexer.Add([]byte("( |\t|\n|\r)+"), skip) lexer.Add([]byte("( |\t|\n|\r)+"), skip)
@ -326,6 +351,7 @@ func (p *pathTokeniser) handleToken(tokens []*Token, index int, postProcessedTok
tokens[index+1].TokenType == OperationToken && tokens[index+1].TokenType == OperationToken &&
tokens[index+1].Operation.OperationType == Assign { tokens[index+1].Operation.OperationType == Assign {
token.Operation = token.AssignOperation token.Operation = token.AssignOperation
token.Operation.UpdateAssign = tokens[index+1].Operation.UpdateAssign
skipNextToken = true skipNextToken = true
} }