Support negative matches
This adds support for != and !~ operators giving more flexibility in comparing values
This commit is contained in:
parent
a59b97d74b
commit
8fa1ab4c33
9 changed files with 221 additions and 6 deletions
5
changelogs/unreleased/support-negative-matches.yml
Normal file
5
changelogs/unreleased/support-negative-matches.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Support negative matches
|
||||
merge_request:
|
||||
author:
|
||||
type: added
|
|
@ -434,8 +434,9 @@ Below you can find supported syntax reference:
|
|||
1. Equality matching using a string
|
||||
|
||||
> Example: `$VARIABLE == "some value"`
|
||||
> Example: `$VARIABLE != "some value"` _(added in 11.11)_
|
||||
|
||||
You can use equality operator `==` to compare a variable content to a
|
||||
You can use equality operator `==` or `!=` to compare a variable content to a
|
||||
string. We support both, double quotes and single quotes to define a string
|
||||
value, so both `$VARIABLE == "some value"` and `$VARIABLE == 'some value'`
|
||||
are supported. `"some value" == $VARIABLE` is correct too.
|
||||
|
@ -443,22 +444,26 @@ Below you can find supported syntax reference:
|
|||
1. Checking for an undefined value
|
||||
|
||||
> Example: `$VARIABLE == null`
|
||||
> Example: `$VARIABLE != null` _(added in 11.11)_
|
||||
|
||||
It sometimes happens that you want to check whether a variable is defined
|
||||
or not. To do that, you can compare a variable to `null` keyword, like
|
||||
`$VARIABLE == null`. This expression is going to evaluate to truth if
|
||||
variable is not defined.
|
||||
variable is not defined when `==` is used, or to falsey if `!=` is used.
|
||||
|
||||
1. Checking for an empty variable
|
||||
|
||||
> Example: `$VARIABLE == ""`
|
||||
> Example: `$VARIABLE != ""` _(added in 11.11)_
|
||||
|
||||
If you want to check whether a variable is defined, but is empty, you can
|
||||
simply compare it against an empty string, like `$VAR == ''`.
|
||||
simply compare it against an empty string, like `$VAR == ''` or non-empty
|
||||
string `$VARIABLE != ""`.
|
||||
|
||||
1. Comparing two variables
|
||||
|
||||
> Example: `$VARIABLE_1 == $VARIABLE_2`
|
||||
> Example: `$VARIABLE_1 != $VARIABLE_2` _(added in 11.11)_
|
||||
|
||||
It is possible to compare two variables. This is going to compare values
|
||||
of these variables.
|
||||
|
@ -477,9 +482,11 @@ Below you can find supported syntax reference:
|
|||
1. Pattern matching _(added in 11.0)_
|
||||
|
||||
> Example: `$VARIABLE =~ /^content.*/`
|
||||
> Example: `$VARIABLE_1 !~ /^content.*/` _(added in 11.11)_
|
||||
|
||||
It is possible perform pattern matching against a variable and regular
|
||||
expression. Expression like this evaluates to truth if matches are found.
|
||||
expression. Expression like this evaluates to truth if matches are found
|
||||
when using `=~`. It evaluates to truth if matches are not found when `!~` is used.
|
||||
|
||||
Pattern matching is case-sensitive by default. Use `i` flag modifier, like
|
||||
`/pattern/i` to make a pattern case-insensitive.
|
||||
|
|
28
lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb
Normal file
28
lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb
Normal file
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Ci
|
||||
module Pipeline
|
||||
module Expression
|
||||
module Lexeme
|
||||
class NotEquals < Lexeme::Operator
|
||||
PATTERN = /!=/.freeze
|
||||
|
||||
def initialize(left, right)
|
||||
@left = left
|
||||
@right = right
|
||||
end
|
||||
|
||||
def evaluate(variables = {})
|
||||
@left.evaluate(variables) != @right.evaluate(variables)
|
||||
end
|
||||
|
||||
def self.build(_value, behind, ahead)
|
||||
new(behind, ahead)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
31
lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb
Normal file
31
lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb
Normal file
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Ci
|
||||
module Pipeline
|
||||
module Expression
|
||||
module Lexeme
|
||||
class NotMatches < Lexeme::Operator
|
||||
PATTERN = /\!~/.freeze
|
||||
|
||||
def initialize(left, right)
|
||||
@left = left
|
||||
@right = right
|
||||
end
|
||||
|
||||
def evaluate(variables = {})
|
||||
text = @left.evaluate(variables)
|
||||
regexp = @right.evaluate(variables)
|
||||
|
||||
regexp.scan(text.to_s).none?
|
||||
end
|
||||
|
||||
def self.build(_value, behind, ahead)
|
||||
new(behind, ahead)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -15,7 +15,9 @@ module Gitlab
|
|||
Expression::Lexeme::Pattern,
|
||||
Expression::Lexeme::Null,
|
||||
Expression::Lexeme::Equals,
|
||||
Expression::Lexeme::Matches
|
||||
Expression::Lexeme::Matches,
|
||||
Expression::Lexeme::NotEquals,
|
||||
Expression::Lexeme::NotMatches
|
||||
].freeze
|
||||
|
||||
MAX_TOKENS = 100
|
||||
|
|
|
@ -8,13 +8,24 @@ module Gitlab
|
|||
StatementError = Class.new(Expression::ExpressionError)
|
||||
|
||||
GRAMMAR = [
|
||||
# presence matchers
|
||||
%w[variable],
|
||||
|
||||
# positive matchers
|
||||
%w[variable equals string],
|
||||
%w[variable equals variable],
|
||||
%w[variable equals null],
|
||||
%w[string equals variable],
|
||||
%w[null equals variable],
|
||||
%w[variable matches pattern]
|
||||
%w[variable matches pattern],
|
||||
|
||||
# negative matchers
|
||||
%w[variable notequals string],
|
||||
%w[variable notequals variable],
|
||||
%w[variable notequals null],
|
||||
%w[string notequals variable],
|
||||
%w[null notequals variable],
|
||||
%w[variable notmatches pattern]
|
||||
].freeze
|
||||
|
||||
def initialize(statement, variables = {})
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotEquals do
|
||||
let(:left) { double('left') }
|
||||
let(:right) { double('right') }
|
||||
|
||||
describe '.build' do
|
||||
it 'creates a new instance of the token' do
|
||||
expect(described_class.build('!=', left, right))
|
||||
.to be_a(described_class)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.type' do
|
||||
it 'is an operator' do
|
||||
expect(described_class.type).to eq :operator
|
||||
end
|
||||
end
|
||||
|
||||
describe '#evaluate' do
|
||||
it 'returns true when left and right are not equal' do
|
||||
allow(left).to receive(:evaluate).and_return(1)
|
||||
allow(right).to receive(:evaluate).and_return(2)
|
||||
|
||||
operator = described_class.new(left, right)
|
||||
|
||||
expect(operator.evaluate(VARIABLE: 3)).to eq true
|
||||
end
|
||||
|
||||
it 'returns false when left and right are equal' do
|
||||
allow(left).to receive(:evaluate).and_return(1)
|
||||
allow(right).to receive(:evaluate).and_return(1)
|
||||
|
||||
operator = described_class.new(left, right)
|
||||
|
||||
expect(operator.evaluate(VARIABLE: 3)).to eq false
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,80 @@
|
|||
require 'fast_spec_helper'
|
||||
require_dependency 're2'
|
||||
|
||||
describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotMatches do
|
||||
let(:left) { double('left') }
|
||||
let(:right) { double('right') }
|
||||
|
||||
describe '.build' do
|
||||
it 'creates a new instance of the token' do
|
||||
expect(described_class.build('!~', left, right))
|
||||
.to be_a(described_class)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.type' do
|
||||
it 'is an operator' do
|
||||
expect(described_class.type).to eq :operator
|
||||
end
|
||||
end
|
||||
|
||||
describe '#evaluate' do
|
||||
it 'returns true when left and right do not match' do
|
||||
allow(left).to receive(:evaluate).and_return('my-string')
|
||||
allow(right).to receive(:evaluate)
|
||||
.and_return(Gitlab::UntrustedRegexp.new('something'))
|
||||
|
||||
operator = described_class.new(left, right)
|
||||
|
||||
expect(operator.evaluate).to eq true
|
||||
end
|
||||
|
||||
it 'returns false when left and right match' do
|
||||
allow(left).to receive(:evaluate).and_return('my-awesome-string')
|
||||
allow(right).to receive(:evaluate)
|
||||
.and_return(Gitlab::UntrustedRegexp.new('awesome.string$'))
|
||||
|
||||
operator = described_class.new(left, right)
|
||||
|
||||
expect(operator.evaluate).to eq false
|
||||
end
|
||||
|
||||
it 'supports matching against a nil value' do
|
||||
allow(left).to receive(:evaluate).and_return(nil)
|
||||
allow(right).to receive(:evaluate)
|
||||
.and_return(Gitlab::UntrustedRegexp.new('pattern'))
|
||||
|
||||
operator = described_class.new(left, right)
|
||||
|
||||
expect(operator.evaluate).to eq true
|
||||
end
|
||||
|
||||
it 'supports multiline strings' do
|
||||
allow(left).to receive(:evaluate).and_return <<~TEXT
|
||||
My awesome contents
|
||||
|
||||
My-text-string!
|
||||
TEXT
|
||||
|
||||
allow(right).to receive(:evaluate)
|
||||
.and_return(Gitlab::UntrustedRegexp.new('text-string'))
|
||||
|
||||
operator = described_class.new(left, right)
|
||||
|
||||
expect(operator.evaluate).to eq false
|
||||
end
|
||||
|
||||
it 'supports regexp flags' do
|
||||
allow(left).to receive(:evaluate).and_return <<~TEXT
|
||||
My AWESOME content
|
||||
TEXT
|
||||
|
||||
allow(right).to receive(:evaluate)
|
||||
.and_return(Gitlab::UntrustedRegexp.new('(?i)awesome'))
|
||||
|
||||
operator = described_class.new(left, right)
|
||||
|
||||
expect(operator.evaluate).to eq false
|
||||
end
|
||||
end
|
||||
end
|
|
@ -101,6 +101,18 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
|
|||
"$EMPTY_VARIABLE =~ /var.*/" | false
|
||||
"$UNDEFINED_VARIABLE =~ /var.*/" | false
|
||||
"$PRESENT_VARIABLE =~ /VAR.*/i" | true
|
||||
'$PRESENT_VARIABLE != "my variable"' | false
|
||||
'"my variable" != $PRESENT_VARIABLE' | false
|
||||
'$PRESENT_VARIABLE != null' | true
|
||||
'$EMPTY_VARIABLE != null' | true
|
||||
'"" != $EMPTY_VARIABLE' | false
|
||||
'$UNDEFINED_VARIABLE != null' | false
|
||||
'null != $UNDEFINED_VARIABLE' | false
|
||||
"$PRESENT_VARIABLE !~ /var.*e$/" | false
|
||||
"$PRESENT_VARIABLE !~ /^var.*/" | true
|
||||
"$EMPTY_VARIABLE !~ /var.*/" | true
|
||||
"$UNDEFINED_VARIABLE !~ /var.*/" | true
|
||||
"$PRESENT_VARIABLE !~ /VAR.*/i" | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
|
Loading…
Reference in a new issue