From 66ec487792a713a964493eb1274333bc78ab97dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Dec 2025 03:01:10 +0000 Subject: [PATCH 1/4] Bump github.com/goccy/go-yaml from 1.19.0 to 1.19.1 Bumps [github.com/goccy/go-yaml](https://github.com/goccy/go-yaml) from 1.19.0 to 1.19.1. - [Release notes](https://github.com/goccy/go-yaml/releases) - [Changelog](https://github.com/goccy/go-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/goccy/go-yaml/compare/v1.19.0...v1.19.1) --- updated-dependencies: - dependency-name: github.com/goccy/go-yaml dependency-version: 1.19.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 82f328ea..d6475be2 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 21539f44..da642b33 100644 --- a/go.sum +++ b/go.sum @@ -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= From ea40e14fb1579cd58e1b1e04b7b6af744b645309 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 20 Dec 2025 15:02:22 +1100 Subject: [PATCH 2/4] Create *.instructions.md --- .github/instructions/*.instructions.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/instructions/*.instructions.md diff --git a/.github/instructions/*.instructions.md b/.github/instructions/*.instructions.md new file mode 100644 index 00000000..cb6ad7db --- /dev/null +++ b/.github/instructions/*.instructions.md @@ -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. From c6ecad15465542da46016d01061515b818730257 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 20 Dec 2025 14:59:26 +1100 Subject: [PATCH 3/4] Support negative parent indices --- pkg/yqlib/doc/operators/parent.md | 40 ++++++++++++++++++++++++++++ pkg/yqlib/lexer.go | 2 +- pkg/yqlib/lexer_participle.go | 4 +-- pkg/yqlib/operator_parent.go | 23 +++++++++++++++-- pkg/yqlib/operator_parent_test.go | 43 +++++++++++++++++++++++++++++++ 5 files changed, 107 insertions(+), 5 deletions(-) diff --git a/pkg/yqlib/doc/operators/parent.md b/pkg/yqlib/doc/operators/parent.md index 02375f87..7870aa3f 100644 --- a/pkg/yqlib/doc/operators/parent.md +++ b/pkg/yqlib/doc/operators/parent.md @@ -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. diff --git a/pkg/yqlib/lexer.go b/pkg/yqlib/lexer.go index 8e04761d..509d07b0 100644 --- a/pkg/yqlib/lexer.go +++ b/pkg/yqlib/lexer.go @@ -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 { diff --git a/pkg/yqlib/lexer_participle.go b/pkg/yqlib/lexer_participle.go index f7c9d056..d4451257 100644 --- a/pkg/yqlib/lexer_participle.go +++ b/pkg/yqlib/lexer_participle.go @@ -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), diff --git a/pkg/yqlib/operator_parent.go b/pkg/yqlib/operator_parent.go index a85c9ca5..3644db0f 100644 --- a/pkg/yqlib/operator_parent.go +++ b/pkg/yqlib/operator_parent.go @@ -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++ diff --git a/pkg/yqlib/operator_parent_test.go b/pkg/yqlib/operator_parent_test.go index a1709895..0f0295ce 100644 --- a/pkg/yqlib/operator_parent_test.go +++ b/pkg/yqlib/operator_parent_test.go @@ -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.", From 9fa353b1235ab32055a205e4b8ea7b8499e68fdb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 04:21:42 +0000 Subject: [PATCH 4/4] Add test coverage for parent(0) and parent(-3) edge cases Co-authored-by: mikefarah <1151925+mikefarah@users.noreply.github.com> --- pkg/yqlib/operator_parent_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pkg/yqlib/operator_parent_test.go b/pkg/yqlib/operator_parent_test.go index 0f0295ce..8a1eb700 100644 --- a/pkg/yqlib/operator_parent_test.go +++ b/pkg/yqlib/operator_parent_test.go @@ -65,6 +65,15 @@ var parentOperatorScenarios = []expressionScenario{ "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, @@ -74,6 +83,15 @@ var parentOperatorScenarios = []expressionScenario{ "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,