diff --git a/pkg/yqlib/doc/Anchor Operators.md b/pkg/yqlib/doc/Anchor Operators.md deleted file mode 100644 index 16c39fec..00000000 --- a/pkg/yqlib/doc/Anchor Operators.md +++ /dev/null @@ -1,29 +0,0 @@ - -## 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/doc/Explode.md b/pkg/yqlib/doc/Anchor and Aliases Operators.md similarity index 52% rename from pkg/yqlib/doc/Explode.md rename to pkg/yqlib/doc/Anchor and Aliases Operators.md index 379d44a0..b7a6c707 100644 --- a/pkg/yqlib/doc/Explode.md +++ b/pkg/yqlib/doc/Anchor and Aliases Operators.md @@ -1,4 +1,67 @@ -Explodes (or dereferences) aliases and anchors. +Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference aliases and remove anchor names). + +`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag. + + +## 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 +Given a sample.yml file of: +```yaml +a: cat +``` +then +```bash +yq eval '.a anchor = "foobar"' sample.yml +``` +will output +```yaml +a: &foobar cat +``` + +## Get alias +Given a sample.yml file of: +```yaml +b: &billyBob meow +a: *billyBob +``` +then +```bash +yq eval '.a | alias' sample.yml +``` +will output +```yaml +billyBob +``` + +## Set alias +Given a sample.yml file of: +```yaml +b: &meow purr +a: cat +``` +then +```bash +yq eval '.a alias = "meow"' sample.yml +``` +will output +```yaml +b: &meow purr +a: *meow +``` + ## Explode alias and anchor Given a sample.yml file of: ```yaml diff --git a/pkg/yqlib/doc/headers/Anchor and Aliases Operators.md b/pkg/yqlib/doc/headers/Anchor and Aliases Operators.md new file mode 100644 index 00000000..17611342 --- /dev/null +++ b/pkg/yqlib/doc/headers/Anchor and Aliases Operators.md @@ -0,0 +1,4 @@ +Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference aliases and remove anchor names). + +`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag. + diff --git a/pkg/yqlib/doc/headers/Explode.md b/pkg/yqlib/doc/headers/Explode.md deleted file mode 100644 index 5fea8360..00000000 --- a/pkg/yqlib/doc/headers/Explode.md +++ /dev/null @@ -1 +0,0 @@ -Explodes (or dereferences) aliases and anchors. \ No newline at end of file diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 351e333d..2786d821 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -37,6 +37,7 @@ var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 4 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 AssignAlias = &OperationType{Type: "ASSIGN_ALIAS", NumArgs: 2, Precedence: 40, Handler: AssignAliasOperator} var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 45, Handler: MultiplyOperator} var Add = &OperationType{Type: "ADD", NumArgs: 2, Precedence: 45, Handler: AddOperator} @@ -53,6 +54,7 @@ var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Han 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 GetAlias = &OperationType{Type: "GET_ALIAS", NumArgs: 0, Precedence: 50, Handler: GetAliasOperator} 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 deleted file mode 100644 index 715445fb..00000000 --- a/pkg/yqlib/operator_anchor_name.go +++ /dev/null @@ -1,48 +0,0 @@ -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 deleted file mode 100644 index fa6a651b..00000000 --- a/pkg/yqlib/operator_anchor_name_test.go +++ /dev/null @@ -1,31 +0,0 @@ -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/operator_explode.go b/pkg/yqlib/operator_anchors_aliases.go similarity index 61% rename from pkg/yqlib/operator_explode.go rename to pkg/yqlib/operator_anchors_aliases.go index b678f61b..d1ae0ea1 100644 --- a/pkg/yqlib/operator_explode.go +++ b/pkg/yqlib/operator_anchors_aliases.go @@ -6,6 +6,88 @@ import ( "gopkg.in/yaml.v3" ) +func AssignAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + + log.Debugf("AssignAlias operator!") + + rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs) + if err != nil { + return nil, err + } + aliasName := "" + if rhs.Front() != nil { + aliasName = 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 aliasName : %v", candidate.GetKey()) + candidate.Node.Kind = yaml.AliasNode + candidate.Node.Value = aliasName + } + return matchingNodes, nil +} + +func GetAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { + log.Debugf("GetAlias operator!") + var results = list.New() + + for el := matchingNodes.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Node.Value, Tag: "!!str"} + lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} + results.PushBack(lengthCand) + } + return results, nil +} + +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 +} + func ExplodeOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { log.Debugf("-- ExplodeOperation") diff --git a/pkg/yqlib/operator_explode_test.go b/pkg/yqlib/operator_anchors_aliases_test.go similarity index 68% rename from pkg/yqlib/operator_explode_test.go rename to pkg/yqlib/operator_anchors_aliases_test.go index 5f3aac99..6174023b 100644 --- a/pkg/yqlib/operator_explode_test.go +++ b/pkg/yqlib/operator_anchors_aliases_test.go @@ -4,7 +4,39 @@ import ( "testing" ) -var explodeTest = []expressionScenario{ +var anchorOperatorScenarios = []expressionScenario{ + { + description: "Get anchor", + document: `a: &billyBob cat`, + expression: `.a | anchor`, + expected: []string{ + "D0, P[a], (!!str)::billyBob\n", + }, + }, + { + description: "Set anchor", + document: `a: cat`, + expression: `.a anchor = "foobar"`, + expected: []string{ + "D0, P[], (doc)::a: &foobar cat\n", + }, + }, + { + description: "Get alias", + document: `{b: &billyBob meow, a: *billyBob}`, + expression: `.a | alias`, + expected: []string{ + "D0, P[a], (!!str)::billyBob\n", + }, + }, + { + description: "Set alias", + document: `{b: &meow purr, a: cat}`, + expression: `.a alias = "meow"`, + expected: []string{ + "D0, P[], (doc)::{b: &meow purr, a: *meow}\n", + }, + }, { description: "Explode alias and anchor", document: `{f : {a: &a cat, b: *a}}`, @@ -82,9 +114,9 @@ foobar: }, } -func TestExplodeOperatorScenarios(t *testing.T) { - for _, tt := range explodeTest { +func TestAnchorAliaseOperatorScenarios(t *testing.T) { + for _, tt := range anchorOperatorScenarios { testScenario(t, &tt) } - documentScenarios(t, "Explode", explodeTest) + documentScenarios(t, "Anchor and Aliases Operators", anchorOperatorScenarios) } diff --git a/pkg/yqlib/path_tokeniser.go b/pkg/yqlib/path_tokeniser.go index 8aa521a7..6e551731 100644 --- a/pkg/yqlib/path_tokeniser.go +++ b/pkg/yqlib/path_tokeniser.go @@ -209,6 +209,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`tag`), opAssignableToken(GetTag, AssignTag)) lexer.Add([]byte(`anchor`), opAssignableToken(GetAnchor, AssignAnchor)) + lexer.Add([]byte(`alias`), opAssignableToken(GetAlias, AssignAlias)) lexer.Add([]byte(`filename`), opToken(GetFilename)) lexer.Add([]byte(`fileIndex`), opToken(GetFileIndex)) lexer.Add([]byte(`path`), opToken(GetPath))