summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKamil Trzciński <ayufan@ayufan.eu>2019-04-19 13:17:42 +0200
committerKamil Trzciński <ayufan@ayufan.eu>2019-04-19 14:30:31 +0200
commit7afb6244fb6eaac823696ca5c039db06212a5ef5 (patch)
tree8ac588abafc873a0a09546f0c0b733e0e12e375d
parentef82859d7d8ea70b29f600193fc18bdf5aea895e (diff)
downloadgitlab-ce-support-negative-matches.tar.gz
Support negative matchessupport-negative-matches
This adds support for != and !~ operators giving more flexibility in comparing values
-rw-r--r--changelogs/unreleased/support-negative-matches.yml5
-rw-r--r--doc/ci/variables/README.md15
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb28
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb31
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexer.rb4
-rw-r--r--lib/gitlab/ci/pipeline/expression/statement.rb13
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb39
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb80
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb12
9 files changed, 221 insertions, 6 deletions
diff --git a/changelogs/unreleased/support-negative-matches.yml b/changelogs/unreleased/support-negative-matches.yml
new file mode 100644
index 00000000000..8d3f2d3cbae
--- /dev/null
+++ b/changelogs/unreleased/support-negative-matches.yml
@@ -0,0 +1,5 @@
+---
+title: Support negative matches
+merge_request:
+author:
+type: added
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 61d1a904f76..8cc457dab24 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -425,8 +425,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.
@@ -434,22 +435,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.
@@ -468,9 +473,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 `=~`, or truth if non matches are found if `!~` is used.
Pattern matching is case-sensitive by default. Use `i` flag modifier, like
`/pattern/i` to make a pattern case-insensitive.
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb b/lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb
new file mode 100644
index 00000000000..5fcc9406cc8
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb
@@ -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
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb b/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb
new file mode 100644
index 00000000000..14544d33e25
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb
@@ -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
diff --git a/lib/gitlab/ci/pipeline/expression/lexer.rb b/lib/gitlab/ci/pipeline/expression/lexer.rb
index f26542361a2..e14edfae51d 100644
--- a/lib/gitlab/ci/pipeline/expression/lexer.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexer.rb
@@ -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
diff --git a/lib/gitlab/ci/pipeline/expression/statement.rb b/lib/gitlab/ci/pipeline/expression/statement.rb
index b03611f756e..ab5ae9caeea 100644
--- a/lib/gitlab/ci/pipeline/expression/statement.rb
+++ b/lib/gitlab/ci/pipeline/expression/statement.rb
@@ -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 = {})
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
new file mode 100644
index 00000000000..9aa2f4efd67
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
new file mode 100644
index 00000000000..fa3b9651fb4
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
index 11e73294f18..a9fd809409b 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
@@ -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