mirror of
https://github.com/mikefarah/yq.git
synced 2026-10-12 02:19:06 +00:00
* feat: add jq style if-then-elif-else-end conditionals Adds `if A then B elif C then D else E end`, following jq semantics: - the condition is evaluated against each input, using the same truthiness as select / and / or (null and false are falsy) - each result of the condition produces a result - `else` is optional; when missing, falsy inputs are returned unchanged (jq 1.7 behaviour) - a condition with no results (e.g. a missing key) is treated as null, consistent with `and`, `or` and `//` `if` / `end` are lexed as brackets so the conditional can be followed by traversals and used anywhere an expression can. `then` / `elif` / `else` are binary ops with the lowest precedence, and the postfixer checks that the keywords are in the right order inside an if block. Path elements like `.if.then.else.end` are unaffected. Closes #95 * updating tools * build(deps): bump golang from 1.26.6 to 1.27.1 (#2855) Bumps golang from 1.26.6 to 1.27.1. --- updated-dependencies: - dependency-name: golang dependency-version: 1.27.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump github.com/mattn/go-isatty from 0.0.20 to 0.0.24 (#2866) Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.20 to 0.0.24. - [Commits](https://github.com/mattn/go-isatty/compare/v0.0.20...v0.0.24) --- updated-dependencies: - dependency-name: github.com/mattn/go-isatty dependency-version: 0.0.24 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump golang.org/x/text from 0.41.0 to 0.42.0 (#2867) Bumps [golang.org/x/text](https://github.com/golang/text) from 0.41.0 to 0.42.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.41.0...v0.42.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-version: 0.42.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump golang.org/x/mod from 0.40.0 to 0.41.0 (#2858) Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.40.0 to 0.41.0. - [Commits](https://github.com/golang/mod/compare/v0.40.0...v0.41.0) --- updated-dependencies: - dependency-name: golang.org/x/mod dependency-version: 0.41.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(toml): quote empty keys (#2870) * build(deps): bump alpine from `28bd5fe` to `294b683` (#2875) Bumps alpine from `28bd5fe` to `294b683`. --- updated-dependencies: - dependency-name: alpine dependency-version: '3' dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump golang from `512690a` to `3680233` (#2876) Bumps golang from `512690a` to `3680233`. --- updated-dependencies: - dependency-name: golang dependency-version: 1.27.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump golang.org/x/net from 0.58.0 to 0.59.0 (#2868) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.58.0 to 0.59.0. - [Commits](https://github.com/golang/net/compare/v0.58.0...v0.59.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.59.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump github.com/hashicorp/hcl/v2 from 2.24.0 to 2.25.0 (#2878) Bumps [github.com/hashicorp/hcl/v2](https://github.com/hashicorp/hcl) from 2.24.0 to 2.25.0. - [Release notes](https://github.com/hashicorp/hcl/releases) - [Changelog](https://github.com/hashicorp/hcl/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/hcl/compare/v2.24.0...v2.25.0) --- updated-dependencies: - dependency-name: github.com/hashicorp/hcl/v2 dependency-version: 2.25.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump github.com/magiconair/properties (#2877) Bumps [github.com/magiconair/properties](https://github.com/magiconair/properties) from 1.18.11 to 1.18.12. - [Release notes](https://github.com/magiconair/properties/releases) - [Commits](https://github.com/magiconair/properties/compare/v1.18.11...v1.18.12) --- updated-dependencies: - dependency-name: github.com/magiconair/properties dependency-version: 1.18.12 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix: preserve explicit assignments in read-only expressions (#2850) * fix: preserve explicit assignments in read-only expressions AI agent disclosure: This commit was prepared by Codex acting on RRXXZZYY's behalf. * fix: allow attribute assignments to create paths --------- Co-authored-by: RRXXZZYY <[email protected]> * Fixing assignment with merge (#2883) * Writable context for compound assignments * Fixing assignment with merge * test: cover explicit YAML null conditions in if-then-else Add a documented scenario showing that empty, ~, null and Null values are falsy, plus tests for a tagged !!null, a null condition with no else, and the remaining truthiness cases (only null and false are falsy). * Extracting if logic from expression_postfix --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: Mike Farah <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jake Wang <[email protected]> Co-authored-by: ZhongYu Li <[email protected]> Co-authored-by: RRXXZZYY <[email protected]>
154 lines
5.6 KiB
Go
154 lines
5.6 KiB
Go
package yqlib
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/mikefarah/yq/v4/test"
|
|
)
|
|
|
|
func getExpressionParser() ExpressionParserInterface {
|
|
InitExpressionParser()
|
|
return ExpressionParser
|
|
}
|
|
|
|
func TestParserCreateMapColonOnItsOwn(t *testing.T) {
|
|
_, err := getExpressionParser().ParseExpression(":")
|
|
test.AssertResultComplex(t, "':' expects 2 args but there is 0", err.Error())
|
|
}
|
|
|
|
func TestParserNoMatchingCloseBracket(t *testing.T) {
|
|
_, err := getExpressionParser().ParseExpression(".cat | with(.;.bob")
|
|
test.AssertResultComplex(t, "bad expression - probably missing close bracket on WITH", err.Error())
|
|
}
|
|
|
|
func TestParserNoMatchingCloseCollect(t *testing.T) {
|
|
_, err := getExpressionParser().ParseExpression("[1,2")
|
|
test.AssertResultComplex(t, "bad expression, could not find matching `]`", err.Error())
|
|
}
|
|
func TestParserNoMatchingCloseObjectInCollect(t *testing.T) {
|
|
_, err := getExpressionParser().ParseExpression(`[{"b": "c"]`)
|
|
test.AssertResultComplex(t, "bad expression, could not find matching `}`", err.Error())
|
|
}
|
|
|
|
func TestParserNoMatchingCloseInCollect(t *testing.T) {
|
|
_, err := getExpressionParser().ParseExpression(`[(.a]`)
|
|
test.AssertResultComplex(t, "bad expression, could not find matching `)`", err.Error())
|
|
}
|
|
|
|
func TestParserNoMatchingCloseCollectObject(t *testing.T) {
|
|
_, err := getExpressionParser().ParseExpression(`{"a": "b"`)
|
|
test.AssertResultComplex(t, "bad expression, could not find matching `}`", err.Error())
|
|
}
|
|
|
|
func TestParserNoMatchingCloseCollectInCollectObject(t *testing.T) {
|
|
_, err := getExpressionParser().ParseExpression(`{"b": [1}`)
|
|
test.AssertResultComplex(t, "bad expression, could not find matching `]`", err.Error())
|
|
}
|
|
|
|
func TestParserNoMatchingCloseBracketInCollectObject(t *testing.T) {
|
|
_, err := getExpressionParser().ParseExpression(`{"b": (1}`)
|
|
test.AssertResultComplex(t, "bad expression, could not find matching `)`", err.Error())
|
|
}
|
|
|
|
func TestParserNoArgsForTwoArgOp(t *testing.T) {
|
|
_, err := getExpressionParser().ParseExpression("=")
|
|
test.AssertResultComplex(t, "'=' expects 2 args but there is 0", err.Error())
|
|
}
|
|
|
|
func TestParserOneLhsArgsForTwoArgOp(t *testing.T) {
|
|
_, err := getExpressionParser().ParseExpression(".a =")
|
|
test.AssertResultComplex(t, "'=' expects 2 args but there is 1", err.Error())
|
|
}
|
|
|
|
func TestParserOneRhsArgsForTwoArgOp(t *testing.T) {
|
|
_, err := getExpressionParser().ParseExpression("= .a")
|
|
test.AssertResultComplex(t, "'=' expects 2 args but there is 1", err.Error())
|
|
}
|
|
|
|
func TestParserTwoArgsForTwoArgOp(t *testing.T) {
|
|
_, err := getExpressionParser().ParseExpression(".a = .b")
|
|
test.AssertResultComplex(t, nil, err)
|
|
}
|
|
|
|
func TestParserNoArgsForOneArgOp(t *testing.T) {
|
|
_, err := getExpressionParser().ParseExpression("explode")
|
|
test.AssertResultComplex(t, "'explode' expects 1 arg but received none", err.Error())
|
|
}
|
|
|
|
func TestParserOneArgForOneArgOp(t *testing.T) {
|
|
_, err := getExpressionParser().ParseExpression("explode(.)")
|
|
test.AssertResultComplex(t, nil, err)
|
|
}
|
|
|
|
func TestParserExtraArgs(t *testing.T) {
|
|
_, err := getExpressionParser().ParseExpression("sortKeys(.) explode(.)")
|
|
test.AssertResultComplex(t, "bad expression, please check expression syntax", err.Error())
|
|
}
|
|
|
|
func TestParserEmptyExpression(t *testing.T) {
|
|
_, err := getExpressionParser().ParseExpression("")
|
|
test.AssertResultComplex(t, nil, err)
|
|
}
|
|
|
|
func TestParserSingleOperation(t *testing.T) {
|
|
result, err := getExpressionParser().ParseExpression(".")
|
|
test.AssertResultComplex(t, nil, err)
|
|
if result == nil {
|
|
t.Fatal("Expected non-nil result for single operation")
|
|
return
|
|
}
|
|
if result.Operation == nil {
|
|
t.Fatal("Expected operation to be set")
|
|
}
|
|
}
|
|
|
|
func TestParserFirstOpWithZeroArgs(t *testing.T) {
|
|
// Test the special case where firstOpType can accept zero args
|
|
result, err := getExpressionParser().ParseExpression("first")
|
|
test.AssertResultComplex(t, nil, err)
|
|
if result == nil {
|
|
t.Fatal("Expected non-nil result for first operation with zero args")
|
|
}
|
|
}
|
|
|
|
func TestParserInvalidExpressionTree(t *testing.T) {
|
|
// This tests the createExpressionTree function with malformed postfix
|
|
parser := getExpressionParser().(*expressionParserImpl)
|
|
|
|
// Create invalid postfix operations that would leave more than one item on stack
|
|
invalidOps := []*Operation{
|
|
{OperationType: &operationType{NumArgs: 0}},
|
|
{OperationType: &operationType{NumArgs: 0}},
|
|
}
|
|
|
|
_, err := parser.createExpressionTree(invalidOps)
|
|
test.AssertResultComplex(t, "bad expression, please check expression syntax", err.Error())
|
|
}
|
|
|
|
func TestParserIfThenElseErrors(t *testing.T) {
|
|
var scenarios = []struct {
|
|
expression string
|
|
expectedError string
|
|
}{
|
|
{`if . then 1`, "bad expression, could not find matching `end`"},
|
|
{`if . then 1 )`, "bad expression, could not find matching `end`"},
|
|
{`[if . then 1]`, "bad expression, could not find matching `end`"},
|
|
{`(. + 1 end`, "bad expression, got `end` without matching `if`"},
|
|
{`. then 1`, "bad expression, `then` without matching `if`"},
|
|
{`if (. then 1) end`, "bad expression, `then` without matching `if`"},
|
|
{`if . then 1 then 2 end`, "bad expression, `then` must follow `if` or `elif`"},
|
|
{`if . else 1 end`, "bad expression, `else` must follow `then`"},
|
|
{`if . then 1 else 2 elif 3 then 4 end`, "bad expression, `elif` must follow `then`"},
|
|
{`if . end`, "bad expression, `if` must be followed by `then`"},
|
|
{`if . then 1 elif 2 end`, "bad expression, `elif` must be followed by `then`"},
|
|
}
|
|
for _, s := range scenarios {
|
|
_, err := getExpressionParser().ParseExpression(s.expression)
|
|
if err == nil {
|
|
t.Errorf("expected error for %v", s.expression)
|
|
continue
|
|
}
|
|
test.AssertResultComplexWithContext(t, s.expectedError, err.Error(), s.expression)
|
|
}
|
|
}
|