Support negative parent indices

This commit is contained in:
Mike Farah 2025-12-20 14:59:26 +11:00
parent ea40e14fb1
commit c6ecad1546
5 changed files with 107 additions and 5 deletions

View File

@ -79,6 +79,46 @@ will output
c: cat
```
## Get the top (root) parent
Use negative numbers to get the top parents
Given a sample.yml file of:
```yaml
a:
b:
c: cat
```
then
```bash
yq '.a.b.c | parent(-1)' sample.yml
```
will output
```yaml
a:
b:
c: cat
```
## Root
Alias for parent(-1), returns the top level parent. This is usually the document node.
Given a sample.yml file of:
```yaml
a:
b:
c: cat
```
then
```bash
yq '.a.b.c | root' sample.yml
```
will output
```yaml
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.

View File

@ -61,7 +61,7 @@ func unwrap(value string) string {
}
func extractNumberParameter(value string) (int, error) {
parameterParser := regexp.MustCompile(`.*\(([0-9]+)\)`)
parameterParser := regexp.MustCompile(`.*\((-?[0-9]+)\)`)
matches := parameterParser.FindStringSubmatch(value)
var indent, errParsingInt = parseInt(matches[1])
if errParsingInt != nil {

View File

@ -57,7 +57,7 @@ var participleYqRules = []*participleYqRule{
simpleOp("sort_?keys", sortKeysOpType),
{"ArrayToMap", "array_?to_?map", expressionOpToken(`(.[] | select(. != null) ) as $i ireduce({}; .[$i | key] = $i)`), 0},
{"Root", "root", expressionOpToken(`parent(-1)`), 0},
{"YamlEncodeWithIndent", `to_?yaml\([0-9]+\)`, encodeParseIndent(YamlFormat), 0},
{"XMLEncodeWithIndent", `to_?xml\([0-9]+\)`, encodeParseIndent(XMLFormat), 0},
{"JSONEncodeWithIndent", `to_?json\([0-9]+\)`, encodeParseIndent(JSONFormat), 0},
@ -132,7 +132,7 @@ var participleYqRules = []*participleYqRule{
simpleOp("split", splitStringOpType),
simpleOp("parents", getParentsOpType),
{"ParentWithLevel", `parent\([0-9]+\)`, parentWithLevel(), 0},
{"ParentWithLevel", `parent\(-?[0-9]+\)`, parentWithLevel(), 0},
{"ParentWithDefaultLevel", `parent`, parentWithDefaultLevel(), 0},
simpleOp("keys", keysOpType),

View File

@ -35,9 +35,28 @@ func getParentOperator(_ *dataTreeNavigator, context Context, expressionNode *Ex
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
// Handle negative levels: count total parents first
levelsToGoUp := prefs.Level
if prefs.Level < 0 {
// Count all parents
totalParents := 0
temp := candidate.Parent
for temp != nil {
totalParents++
temp = temp.Parent
}
// Convert negative index to positive
// -1 means last parent (root), -2 means second to last, etc.
levelsToGoUp = totalParents + prefs.Level + 1
if levelsToGoUp < 0 {
levelsToGoUp = 0
}
}
currentLevel := 0
for currentLevel < prefs.Level && candidate != nil {
log.Debugf("currentLevel: %v, desired: %v", currentLevel, prefs.Level)
for currentLevel < levelsToGoUp && candidate != nil {
log.Debugf("currentLevel: %v, desired: %v", currentLevel, levelsToGoUp)
log.Debugf("candidate: %v", NodeToString(candidate))
candidate = candidate.Parent
currentLevel++

View File

@ -38,6 +38,49 @@ var parentOperatorScenarios = []expressionScenario{
"D0, P[], (!!seq)::- {c: cat}\n- {b: {c: cat}}\n- {a: {b: {c: cat}}}\n",
},
},
{
description: "Get the top (root) parent",
subdescription: "Use negative numbers to get the top parents",
document: "a:\n b:\n c: cat\n",
expression: `.a.b.c | parent(-1)`,
expected: []string{
"D0, P[], (!!map)::a:\n b:\n c: cat\n",
},
},
{
description: "Root",
subdescription: "Alias for parent(-1), returns the top level parent. This is usually the document node.",
document: "a:\n b:\n c: cat\n",
expression: `.a.b.c | root`,
expected: []string{
"D0, P[], (!!map)::a:\n b:\n c: cat\n",
},
},
{
description: "N-th negative",
skipDoc: true,
document: "a:\n b:\n c: cat\n",
expression: `.a.b.c | parent(-2)`,
expected: []string{
"D0, P[a], (!!map)::b:\n c: cat\n",
},
},
{
description: "large negative",
skipDoc: true,
document: "a:\n b:\n c: cat\n",
expression: `.a.b.c | parent(-10)`,
expected: []string{
"D0, P[a b c], (!!str)::cat\n",
},
},
{
description: "large positive",
skipDoc: true,
document: "a:\n b:\n c: cat\n",
expression: `.a.b.c | parent(10)`,
expected: []string{},
},
{
description: "N-th parent",
subdescription: "You can optionally supply the number of levels to go up for the parent, the default being 1.",