diff --git a/pkg/yqlib/doc/Anchor Operators.md b/pkg/yqlib/doc/Anchor Operators.md new file mode 100644 index 00000000..16c39fec --- /dev/null +++ b/pkg/yqlib/doc/Anchor Operators.md @@ -0,0 +1,29 @@ + +## Get anchor +Given a sample.yml file of: +```yaml +a: &billyBob cat +``` +then +```bash +yq eval '.a | anchor' sample.yml +``` +will output +```yaml +billyBob +``` + +## Set anchor name +Given a sample.yml file of: +```yaml +a: cat +``` +then +```bash +yq eval '.a anchor = "foobar"' sample.yml +``` +will output +```yaml +a: &foobar cat +``` + diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 8b900a74..351e333d 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -36,6 +36,7 @@ var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Pre var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator} var AssignTag = &OperationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: AssignTagOperator} var AssignComment = &OperationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: AssignCommentsOperator} +var AssignAnchor = &OperationType{Type: "ASSIGN_ANCHOR", NumArgs: 2, Precedence: 40, Handler: AssignAnchorOperator} var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 45, Handler: MultiplyOperator} var Add = &OperationType{Type: "ADD", NumArgs: 2, Precedence: 45, Handler: AddOperator} @@ -51,6 +52,7 @@ var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handle var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: GetStyleOperator} var GetTag = &OperationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: GetTagOperator} var GetComment = &OperationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: GetCommentsOperator} +var GetAnchor = &OperationType{Type: "GET_ANCHOR", NumArgs: 0, Precedence: 50, Handler: GetAnchorOperator} var GetDocumentIndex = &OperationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: GetDocumentIndexOperator} var GetFilename = &OperationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: GetFilenameOperator} var GetFileIndex = &OperationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: GetFileIndexOperator} diff --git a/pkg/yqlib/operator_anchor_name.go b/pkg/yqlib/operator_anchor_name.go new file mode 100644 index 00000000..715445fb --- /dev/null +++ b/pkg/yqlib/operator_anchor_name.go @@ -0,0 +1,48 @@ +package yqlib + +import ( + "container/list" + + "gopkg.in/yaml.v3" +) + +func AssignAnchorOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + + log.Debugf("AssignAnchor operator!") + + rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs) + if err != nil { + return nil, err + } + anchorName := "" + if rhs.Front() != nil { + anchorName = rhs.Front().Value.(*CandidateNode).Node.Value + } + + lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) + + if err != nil { + return nil, err + } + + for el := lhs.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + log.Debugf("Setting anchorName of : %v", candidate.GetKey()) + candidate.Node.Anchor = anchorName + } + return matchingNodes, nil +} + +func GetAnchorOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + log.Debugf("GetAnchor operator!") + var results = list.New() + + for el := matchingNodes.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + anchor := candidate.Node.Anchor + node := &yaml.Node{Kind: yaml.ScalarNode, Value: anchor, Tag: "!!str"} + lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} + results.PushBack(lengthCand) + } + return results, nil +} diff --git a/pkg/yqlib/operator_anchor_name_test.go b/pkg/yqlib/operator_anchor_name_test.go new file mode 100644 index 00000000..fa6a651b --- /dev/null +++ b/pkg/yqlib/operator_anchor_name_test.go @@ -0,0 +1,31 @@ +package yqlib + +import ( + "testing" +) + +var anchorOperatorScenarios = []expressionScenario{ + { + description: "Get anchor", + document: `a: &billyBob cat`, + expression: `.a | anchor`, + expected: []string{ + "D0, P[a], (!!str)::billyBob\n", + }, + }, + { + description: "Set anchor name", + document: `a: cat`, + expression: `.a anchor = "foobar"`, + expected: []string{ + "D0, P[], (doc)::a: &foobar cat\n", + }, + }, +} + +func TestAnchorOperatorScenarios(t *testing.T) { + for _, tt := range anchorOperatorScenarios { + testScenario(t, &tt) + } + documentScenarios(t, "Anchor Operators", anchorOperatorScenarios) +} diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index 59cfe476..8aa521a7 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -208,6 +208,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`style`), opAssignableToken(GetStyle, AssignStyle)) lexer.Add([]byte(`tag`), opAssignableToken(GetTag, AssignTag)) + lexer.Add([]byte(`anchor`), opAssignableToken(GetAnchor, AssignAnchor)) lexer.Add([]byte(`filename`), opToken(GetFilename)) lexer.Add([]byte(`fileIndex`), opToken(GetFileIndex)) lexer.Add([]byte(`path`), opToken(GetPath))