diff --git a/pkg/yqlib/doc/Equals.md b/pkg/yqlib/doc/Equals.md index fda10922..6d2ebbdf 100644 --- a/pkg/yqlib/doc/Equals.md +++ b/pkg/yqlib/doc/Equals.md @@ -29,6 +29,24 @@ true false ``` +## Don't match string +Given a sample.yml file of: +```yaml +- cat +- goat +- dog +``` +then +```bash +yq eval '.[] | (. != "*at")' sample.yml +``` +will output +```yaml +false +false +true +``` + ## Match number Given a sample.yml file of: ```yaml @@ -47,6 +65,24 @@ true false ``` +## Dont match number +Given a sample.yml file of: +```yaml +- 3 +- 4 +- 5 +``` +then +```bash +yq eval '.[] | (. != 4)' sample.yml +``` +will output +```yaml +true +false +true +``` + ## Match nulls Running ```bash diff --git a/pkg/yqlib/expression_tokeniser.go b/pkg/yqlib/expression_tokeniser.go index a08bea50..5f589850 100644 --- a/pkg/yqlib/expression_tokeniser.go +++ b/pkg/yqlib/expression_tokeniser.go @@ -284,6 +284,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`collect`), opToken(collectOpType)) lexer.Add([]byte(`\s*==\s*`), opToken(equalsOpType)) + lexer.Add([]byte(`\s*!=\s*`), opToken(notEqualsOpType)) lexer.Add([]byte(`\s*=\s*`), assignOpToken(false)) lexer.Add([]byte(`del`), opToken(deleteChildOpType)) diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index d33ce8d3..0f253bff 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -46,6 +46,7 @@ var addOpType = &operationType{Type: "ADD", NumArgs: 2, Precedence: 42, Handler: var alternativeOpType = &operationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 42, Handler: alternativeOperator} var equalsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: equalsOperator} +var notEqualsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: notEqualsOperator} var createMapOpType = &operationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: createMapOperator} var shortPipeOpType = &operationType{Type: "SHORT_PIPE", NumArgs: 2, Precedence: 45, Handler: pipeOperator} diff --git a/pkg/yqlib/operator_equals.go b/pkg/yqlib/operator_equals.go index 5c6d9596..2f444284 100644 --- a/pkg/yqlib/operator_equals.go +++ b/pkg/yqlib/operator_equals.go @@ -4,20 +4,30 @@ import "gopkg.in/yaml.v3" func equalsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { log.Debugf("-- equalsOperation") - return crossFunction(d, context, expressionNode, isEquals) + return crossFunction(d, context, expressionNode, isEquals(false)) } -func isEquals(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { - value := false +func isEquals(flip bool) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { + return func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { + value := false - lhsNode := unwrapDoc(lhs.Node) - rhsNode := unwrapDoc(rhs.Node) + lhsNode := unwrapDoc(lhs.Node) + rhsNode := unwrapDoc(rhs.Node) - if lhsNode.Tag == "!!null" { - value = (rhsNode.Tag == "!!null") - } else if lhsNode.Kind == yaml.ScalarNode && rhsNode.Kind == yaml.ScalarNode { - value = matchKey(lhsNode.Value, rhsNode.Value) + if lhsNode.Tag == "!!null" { + value = (rhsNode.Tag == "!!null") + } else if lhsNode.Kind == yaml.ScalarNode && rhsNode.Kind == yaml.ScalarNode { + value = matchKey(lhsNode.Value, rhsNode.Value) + } + log.Debugf("%v == %v ? %v", NodeToString(lhs), NodeToString(rhs), value) + if flip { + value = !value + } + return createBooleanCandidate(lhs, value), nil } - log.Debugf("%v == %v ? %v", NodeToString(lhs), NodeToString(rhs), value) - return createBooleanCandidate(lhs, value), nil +} + +func notEqualsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { + log.Debugf("-- equalsOperation") + return crossFunction(d, context, expressionNode, isEquals(true)) } diff --git a/pkg/yqlib/operator_equals_test.go b/pkg/yqlib/operator_equals_test.go index 822c031b..291d6916 100644 --- a/pkg/yqlib/operator_equals_test.go +++ b/pkg/yqlib/operator_equals_test.go @@ -31,7 +31,18 @@ var equalsOperatorScenarios = []expressionScenario{ "D0, P[1], (!!bool)::true\n", "D0, P[2], (!!bool)::false\n", }, - }, { + }, + { + description: "Don't match string", + document: `[cat,goat,dog]`, + expression: `.[] | (. != "*at")`, + expected: []string{ + "D0, P[0], (!!bool)::false\n", + "D0, P[1], (!!bool)::false\n", + "D0, P[2], (!!bool)::true\n", + }, + }, + { description: "Match number", document: `[3, 4, 5]`, expression: `.[] | (. == 4)`, @@ -40,7 +51,18 @@ var equalsOperatorScenarios = []expressionScenario{ "D0, P[1], (!!bool)::true\n", "D0, P[2], (!!bool)::false\n", }, - }, { + }, + { + description: "Dont match number", + document: `[3, 4, 5]`, + expression: `.[] | (. != 4)`, + expected: []string{ + "D0, P[0], (!!bool)::true\n", + "D0, P[1], (!!bool)::false\n", + "D0, P[2], (!!bool)::true\n", + }, + }, + { skipDoc: true, document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`, expression: `.a | (.[].b == "apple")`,