Commit 951ffd9d authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'support-negative-matches' into 'master'

Support negative matches

See merge request gitlab-org/gitlab-ce!27510
parents 3796bbd1 8fa1ab4c
---
title: Support negative matches
merge_request:
author:
type: added
...@@ -434,8 +434,9 @@ Below you can find supported syntax reference: ...@@ -434,8 +434,9 @@ Below you can find supported syntax reference:
1. Equality matching using a string 1. Equality matching using a string
> Example: `$VARIABLE == "some value"` > 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 string. We support both, double quotes and single quotes to define a string
value, so both `$VARIABLE == "some value"` and `$VARIABLE == 'some value'` value, so both `$VARIABLE == "some value"` and `$VARIABLE == 'some value'`
are supported. `"some value" == $VARIABLE` is correct too. are supported. `"some value" == $VARIABLE` is correct too.
...@@ -443,22 +444,26 @@ Below you can find supported syntax reference: ...@@ -443,22 +444,26 @@ Below you can find supported syntax reference:
1. Checking for an undefined value 1. Checking for an undefined value
> Example: `$VARIABLE == null` > Example: `$VARIABLE == null`
> Example: `$VARIABLE != null` _(added in 11.11)_
It sometimes happens that you want to check whether a variable is defined 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 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 == 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 1. Checking for an empty variable
> Example: `$VARIABLE == ""` > Example: `$VARIABLE == ""`
> Example: `$VARIABLE != ""` _(added in 11.11)_
If you want to check whether a variable is defined, but is empty, you can 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 1. Comparing two variables
> Example: `$VARIABLE_1 == $VARIABLE_2` > 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 It is possible to compare two variables. This is going to compare values
of these variables. of these variables.
...@@ -477,9 +482,11 @@ Below you can find supported syntax reference: ...@@ -477,9 +482,11 @@ Below you can find supported syntax reference:
1. Pattern matching _(added in 11.0)_ 1. Pattern matching _(added in 11.0)_
> Example: `$VARIABLE =~ /^content.*/` > Example: `$VARIABLE =~ /^content.*/`
> Example: `$VARIABLE_1 !~ /^content.*/` _(added in 11.11)_
It is possible perform pattern matching against a variable and regular 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 matching is case-sensitive by default. Use `i` flag modifier, like
`/pattern/i` to make a pattern case-insensitive. `/pattern/i` to make a pattern case-insensitive.
......
# 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
# 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 ...@@ -15,7 +15,9 @@ module Gitlab
Expression::Lexeme::Pattern, Expression::Lexeme::Pattern,
Expression::Lexeme::Null, Expression::Lexeme::Null,
Expression::Lexeme::Equals, Expression::Lexeme::Equals,
Expression::Lexeme::Matches Expression::Lexeme::Matches,
Expression::Lexeme::NotEquals,
Expression::Lexeme::NotMatches
].freeze ].freeze
MAX_TOKENS = 100 MAX_TOKENS = 100
......
...@@ -8,13 +8,24 @@ module Gitlab ...@@ -8,13 +8,24 @@ module Gitlab
StatementError = Class.new(Expression::ExpressionError) StatementError = Class.new(Expression::ExpressionError)
GRAMMAR = [ GRAMMAR = [
# presence matchers
%w[variable], %w[variable],
# positive matchers
%w[variable equals string], %w[variable equals string],
%w[variable equals variable], %w[variable equals variable],
%w[variable equals null], %w[variable equals null],
%w[string equals variable], %w[string equals variable],
%w[null 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 ].freeze
def initialize(statement, variables = {}) def initialize(statement, variables = {})
......
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
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 ...@@ -101,6 +101,18 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
"$EMPTY_VARIABLE =~ /var.*/" | false "$EMPTY_VARIABLE =~ /var.*/" | false
"$UNDEFINED_VARIABLE =~ /var.*/" | false "$UNDEFINED_VARIABLE =~ /var.*/" | false
"$PRESENT_VARIABLE =~ /VAR.*/i" | true "$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 end
with_them do with_them do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment