From 7d49d408ef4358824c16a4c9e40ec0941bce43dc Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 9 Sep 2025 20:27:11 +1000 Subject: [PATCH] Added parents operator --- pkg/yqlib/doc/operators/parent.md | 23 +++++++++++++++++++++++ pkg/yqlib/lexer_participle.go | 1 + pkg/yqlib/operation.go | 1 + pkg/yqlib/operator_parent.go | 20 ++++++++++++++++++++ pkg/yqlib/operator_parent_test.go | 9 +++++++++ 5 files changed, 54 insertions(+) diff --git a/pkg/yqlib/doc/operators/parent.md b/pkg/yqlib/doc/operators/parent.md index 94fd3fa4..02375f87 100644 --- a/pkg/yqlib/doc/operators/parent.md +++ b/pkg/yqlib/doc/operators/parent.md @@ -56,6 +56,29 @@ will output sam ``` +## Get parents +Match all parents + +Given a sample.yml file of: +```yaml +a: + b: + c: cat +``` +then +```bash +yq '.a.b.c | parents' sample.yml +``` +will output +```yaml +- c: cat +- b: + c: cat +- a: + b: + c: cat +``` + ## N-th parent You can optionally supply the number of levels to go up for the parent, the default being 1. diff --git a/pkg/yqlib/lexer_participle.go b/pkg/yqlib/lexer_participle.go index 870b39cf..2a47c97d 100644 --- a/pkg/yqlib/lexer_participle.go +++ b/pkg/yqlib/lexer_participle.go @@ -130,6 +130,7 @@ var participleYqRules = []*participleYqRule{ simpleOp("contains", containsOpType), simpleOp("split", splitStringOpType), + simpleOp("parents", getParentsOpType), {"ParentWithLevel", `parent\([0-9]+\)`, parentWithLevel(), 0}, {"ParentWithDefaultLevel", `parent`, parentWithDefaultLevel(), 0}, diff --git a/pkg/yqlib/operation.go b/pkg/yqlib/operation.go index 6ed55abc..77bdf206 100644 --- a/pkg/yqlib/operation.go +++ b/pkg/yqlib/operation.go @@ -128,6 +128,7 @@ var getKindOpType = &operationType{Type: "GET_KIND", NumArgs: 0, Precedence: 50, var getKeyOpType = &operationType{Type: "GET_KEY", NumArgs: 0, Precedence: 50, Handler: getKeyOperator} var isKeyOpType = &operationType{Type: "IS_KEY", NumArgs: 0, Precedence: 50, Handler: isKeyOperator} var getParentOpType = &operationType{Type: "GET_PARENT", NumArgs: 0, Precedence: 50, Handler: getParentOperator} +var getParentsOpType = &operationType{Type: "GET_PARENTS", NumArgs: 0, Precedence: 50, Handler: getParentsOperator} var getCommentOpType = &operationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: getCommentsOperator} var getAnchorOpType = &operationType{Type: "GET_ANCHOR", NumArgs: 0, Precedence: 50, Handler: getAnchorOperator} diff --git a/pkg/yqlib/operator_parent.go b/pkg/yqlib/operator_parent.go index 3133d843..a85c9ca5 100644 --- a/pkg/yqlib/operator_parent.go +++ b/pkg/yqlib/operator_parent.go @@ -6,6 +6,26 @@ type parentOpPreferences struct { Level int } +func getParentsOperator(_ *dataTreeNavigator, context Context, _ *ExpressionNode) (Context, error) { + log.Debugf("getParentsOperator") + + var results = list.New() + + for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + parentsList := &CandidateNode{Kind: SequenceNode, Tag: "!!seq"} + parent := candidate.Parent + for parent != nil { + parentsList.AddChild(parent) + parent = parent.Parent + } + results.PushBack(parentsList) + } + + return context.ChildContext(results), nil + +} + func getParentOperator(_ *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { log.Debugf("getParentOperator") diff --git a/pkg/yqlib/operator_parent_test.go b/pkg/yqlib/operator_parent_test.go index 11d256c7..a1709895 100644 --- a/pkg/yqlib/operator_parent_test.go +++ b/pkg/yqlib/operator_parent_test.go @@ -29,6 +29,15 @@ var parentOperatorScenarios = []expressionScenario{ "D0, P[b name], (!!str)::sam\n", }, }, + { + description: "Get parents", + subdescription: "Match all parents", + document: "{a: {b: {c: cat} } }", + expression: `.a.b.c | parents`, + expected: []string{ + "D0, P[], (!!seq)::- {c: cat}\n- {b: {c: cat}}\n- {a: {b: {c: cat}}}\n", + }, + }, { description: "N-th parent", subdescription: "You can optionally supply the number of levels to go up for the parent, the default being 1.",