Merge branch 'master' into toml_encoder

This commit is contained in:
Mike Farah 2025-12-20 15:58:57 +11:00
commit 7198d16575
8 changed files with 129 additions and 8 deletions

View File

@ -0,0 +1 @@
When you find a bug - make sure to include a new test that exposes the bug, as well as the fix for the bug itself.

2
go.mod
View File

@ -9,7 +9,7 @@ require (
github.com/fatih/color v1.18.0
github.com/go-ini/ini v1.67.0
github.com/goccy/go-json v0.10.5
github.com/goccy/go-yaml v1.19.0
github.com/goccy/go-yaml v1.19.1
github.com/hashicorp/hcl/v2 v2.24.0
github.com/jinzhu/copier v0.4.0
github.com/magiconair/properties v1.8.10

4
go.sum
View File

@ -26,8 +26,8 @@ github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.0 h1:EmkZ9RIsX+Uq4DYFowegAuJo8+xdX3T/2dwNPXbxEYE=
github.com/goccy/go-yaml v1.19.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/goccy/go-yaml v1.19.1 h1:3rG3+v8pkhRqoQ/88NYNMHYVGYztCOCIZ7UQhu7H+NE=
github.com/goccy/go-yaml v1.19.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=

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,67 @@ 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: "boundary negative",
skipDoc: true,
document: "a:\n b:\n c: cat\n",
expression: `.a.b.c | parent(-3)`,
expected: []string{
"D0, P[a b], (!!map)::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: "parent zero",
skipDoc: true,
document: "a:\n b:\n c: cat\n",
expression: `.a.b.c | parent(0)`,
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.",