travis-conditions 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +28 -0
- data/README.md +46 -0
- data/lib/travis/conditions.rb +26 -0
- data/lib/travis/conditions/data.rb +45 -0
- data/lib/travis/conditions/eval.rb +68 -0
- data/lib/travis/conditions/parser.rb +202 -0
- data/lib/travis/conditions/version.rb +5 -0
- data/spec/data_spec.rb +20 -0
- data/spec/eval_spec.rb +148 -0
- data/spec/parser_spec.rb +275 -0
- data/spec/spec_helper.rb +1 -0
- metadata +68 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c260f529b4432a50705b9296e7f5bff49fdb199a
|
4
|
+
data.tar.gz: 75623d8e139dcd3c7b34e152a72e80d7b49ffeb7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 14a0df58f59d5d04c76830f506c4f080e3b50c6fa0b95d21612786a25c499f7fc64526792983b544b85bb5e260adf3ecaac40bc6a8a772dbe23e718db3c287f6
|
7
|
+
data.tar.gz: 1a4f2b466d2c85c858d018a88dd2bfdd02f973756d880ffa69d058a6724ddaf4606cd0fbed7e48846b31d48b7d437a97af040e7103bdc6718c64c88672940e60
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
diff-lcs (1.3)
|
5
|
+
parslet (1.8.0)
|
6
|
+
rspec (3.6.0)
|
7
|
+
rspec-core (~> 3.6.0)
|
8
|
+
rspec-expectations (~> 3.6.0)
|
9
|
+
rspec-mocks (~> 3.6.0)
|
10
|
+
rspec-core (3.6.0)
|
11
|
+
rspec-support (~> 3.6.0)
|
12
|
+
rspec-expectations (3.6.0)
|
13
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
14
|
+
rspec-support (~> 3.6.0)
|
15
|
+
rspec-mocks (3.6.0)
|
16
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
17
|
+
rspec-support (~> 3.6.0)
|
18
|
+
rspec-support (3.6.0)
|
19
|
+
|
20
|
+
PLATFORMS
|
21
|
+
ruby
|
22
|
+
|
23
|
+
DEPENDENCIES
|
24
|
+
parslet
|
25
|
+
rspec
|
26
|
+
|
27
|
+
BUNDLED WITH
|
28
|
+
1.15.4
|
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Boolean language for conditional builds, stages, jobs (draft)
|
2
|
+
|
3
|
+
```
|
4
|
+
(NOT [term] OR [term]) AND [term]
|
5
|
+
```
|
6
|
+
|
7
|
+
A term can be can be:
|
8
|
+
|
9
|
+
#### Equality
|
10
|
+
|
11
|
+
```
|
12
|
+
branch = master
|
13
|
+
env(foo) = bar
|
14
|
+
```
|
15
|
+
|
16
|
+
#### Match
|
17
|
+
|
18
|
+
```
|
19
|
+
branch =~ ^master$
|
20
|
+
env(foo) =~ ^bar$
|
21
|
+
```
|
22
|
+
|
23
|
+
#### Include
|
24
|
+
|
25
|
+
```
|
26
|
+
branch IN (master, dev)
|
27
|
+
env(foo) IN (bar, baz)
|
28
|
+
```
|
29
|
+
|
30
|
+
#### Presence
|
31
|
+
|
32
|
+
```
|
33
|
+
branch IS present
|
34
|
+
branch IS blank
|
35
|
+
env(foo) IS present
|
36
|
+
env(foo) IS blank
|
37
|
+
```
|
38
|
+
|
39
|
+
## Usage
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
str = 'branch IN (foo, bar) AND env(baz) =~ ^baz- OR tag IS present'
|
43
|
+
data = { branch: 'foo', env: { baz: 'baz-1' }, tag: 'v.1.0.0' }
|
44
|
+
Travis::Conditions.apply(str, data)
|
45
|
+
# => true
|
46
|
+
```
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'travis/conditions/data'
|
2
|
+
require 'travis/conditions/eval'
|
3
|
+
require 'travis/conditions/parser'
|
4
|
+
|
5
|
+
module Travis
|
6
|
+
module Conditions
|
7
|
+
class << self
|
8
|
+
def eval(str, data)
|
9
|
+
Eval.new(parse(str, keys: data.keys), Data.new(data)).apply
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse(str, opts = {})
|
13
|
+
tree = parser(opts).parse(str)
|
14
|
+
Transform.new.apply(tree)
|
15
|
+
end
|
16
|
+
|
17
|
+
def parser(opts)
|
18
|
+
parsers[opts] ||= Parser.new(opts)
|
19
|
+
end
|
20
|
+
|
21
|
+
def parsers
|
22
|
+
@parsers ||= {}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Travis
|
2
|
+
module Conditions
|
3
|
+
class Data < Struct.new(:data)
|
4
|
+
def initialize(data)
|
5
|
+
data = symbolize(data)
|
6
|
+
data[:env] = symbolize(to_h(data[:env] || {}))
|
7
|
+
super(data)
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](key)
|
11
|
+
data[key.to_sym]
|
12
|
+
end
|
13
|
+
|
14
|
+
def env(key)
|
15
|
+
data.fetch(:env, {})[key.to_sym]
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def to_h(obj)
|
21
|
+
case obj
|
22
|
+
when Hash
|
23
|
+
obj
|
24
|
+
else
|
25
|
+
Array(obj).map { |value| split(value.to_s) }.to_h
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def split(str)
|
30
|
+
str.split('=', 2)
|
31
|
+
end
|
32
|
+
|
33
|
+
def symbolize(value)
|
34
|
+
case value
|
35
|
+
when Hash
|
36
|
+
value.map { |key, value| [key.to_sym, symbolize(value)] }.to_h
|
37
|
+
when Array
|
38
|
+
value.map { |value| symbolize(value) }
|
39
|
+
else
|
40
|
+
value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Travis
|
2
|
+
module Conditions
|
3
|
+
class Eval < Struct.new(:sexp, :data)
|
4
|
+
def apply
|
5
|
+
!!evl(sexp)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def evl(value)
|
11
|
+
case value
|
12
|
+
when Array
|
13
|
+
send(*value)
|
14
|
+
else
|
15
|
+
data[value]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def not(lft)
|
20
|
+
!evl(lft)
|
21
|
+
end
|
22
|
+
|
23
|
+
def or(lft, rgt)
|
24
|
+
evl(lft) || evl(rgt)
|
25
|
+
end
|
26
|
+
|
27
|
+
def and(lft, rgt)
|
28
|
+
evl(lft) && evl(rgt)
|
29
|
+
end
|
30
|
+
|
31
|
+
def eq(lft, rgt)
|
32
|
+
evl(lft) == rgt
|
33
|
+
end
|
34
|
+
|
35
|
+
def not_eq(lft, rgt)
|
36
|
+
not eq(lft, rgt)
|
37
|
+
end
|
38
|
+
|
39
|
+
def match(lft, rgt)
|
40
|
+
evl(lft) =~ Regexp.new(rgt)
|
41
|
+
end
|
42
|
+
|
43
|
+
def not_match(lft, rgt)
|
44
|
+
not match(lft, rgt)
|
45
|
+
end
|
46
|
+
|
47
|
+
def in(lft, rgt)
|
48
|
+
rgt.include?(evl(lft))
|
49
|
+
end
|
50
|
+
|
51
|
+
def is(lft, rgt)
|
52
|
+
send(rgt, evl(lft))
|
53
|
+
end
|
54
|
+
|
55
|
+
def env(key)
|
56
|
+
data.env(key)
|
57
|
+
end
|
58
|
+
|
59
|
+
def present(value)
|
60
|
+
value.respond_to?(:empty?) && !value.empty?
|
61
|
+
end
|
62
|
+
|
63
|
+
def blank(value)
|
64
|
+
!present(value)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
require 'parslet'
|
2
|
+
|
3
|
+
module Travis
|
4
|
+
module Conditions
|
5
|
+
class Parser < Struct.new(:opts)
|
6
|
+
FUNCS = %w(env)
|
7
|
+
PRESENCE = %w(present blank)
|
8
|
+
|
9
|
+
def parse(str)
|
10
|
+
parser.parse(str)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def parser
|
16
|
+
@parser ||= define_parser(opts[:keys]).new
|
17
|
+
end
|
18
|
+
|
19
|
+
def define_parser(keywords)
|
20
|
+
Class.new(Parslet::Parser) do
|
21
|
+
root :expr_or
|
22
|
+
|
23
|
+
rule :expr_or do
|
24
|
+
spaced(expr_and.as(:lft), op_or, expr_or.as(:rgt)).as(:or) | expr_and
|
25
|
+
end
|
26
|
+
|
27
|
+
rule :expr_and do
|
28
|
+
spaced(expr_inner.as(:lft), op_and, expr_and.as(:rgt)).as(:and) | expr_inner
|
29
|
+
end
|
30
|
+
|
31
|
+
rule :expr_inner do
|
32
|
+
lnot(parens(expr_or) | expr_incl | expr_is | expr_regex | expr_cmp)
|
33
|
+
end
|
34
|
+
|
35
|
+
rule :expr_cmp do
|
36
|
+
spaced(lhs.as(:lft), op_cmp.as(:op), value.as(:rgt)).as(:cmp)
|
37
|
+
end
|
38
|
+
|
39
|
+
rule :expr_regex do
|
40
|
+
spaced(lhs.as(:lft), op_regex.as(:op), regex.as(:rgt)).as(:cmp)
|
41
|
+
end
|
42
|
+
|
43
|
+
rule :expr_incl do
|
44
|
+
spaced(lhs.as(:lft), op_incl, parens(list).as(:rgt)).as(:incl)
|
45
|
+
end
|
46
|
+
|
47
|
+
rule :expr_is do
|
48
|
+
spaced(lhs.as(:lft), op_is, presence.as(:rgt)).as(:is)
|
49
|
+
end
|
50
|
+
|
51
|
+
def lnot(node)
|
52
|
+
(stri('not').maybe >> space >> node.as(:rgt)).as(:not) | node
|
53
|
+
end
|
54
|
+
|
55
|
+
rule :list do
|
56
|
+
(value >> (ts(str(',')) >> value).repeat)
|
57
|
+
end
|
58
|
+
|
59
|
+
rule :lhs do
|
60
|
+
func | keyword
|
61
|
+
end
|
62
|
+
|
63
|
+
rule :keyword do
|
64
|
+
stris(*keywords)
|
65
|
+
end
|
66
|
+
|
67
|
+
rule :func do
|
68
|
+
(stris(*FUNCS).as(:name) >> parens(word)).as(:func)
|
69
|
+
end
|
70
|
+
|
71
|
+
rule :word do
|
72
|
+
match('[\w_\-]').repeat(1).as(:str)
|
73
|
+
end
|
74
|
+
|
75
|
+
rule :regex do
|
76
|
+
quoted('/') | match('[\S]').repeat(1).as(:str)
|
77
|
+
end
|
78
|
+
|
79
|
+
rule :value do
|
80
|
+
unquoted | quoted('"') | quoted("'")
|
81
|
+
end
|
82
|
+
|
83
|
+
rule :unquoted do
|
84
|
+
match('[^\s\"\'\(\),]').repeat(1).as(:str)
|
85
|
+
end
|
86
|
+
|
87
|
+
def quoted(chr)
|
88
|
+
str(chr) >> match("[^#{chr}]").repeat.as(:str) >> str(chr)
|
89
|
+
end
|
90
|
+
|
91
|
+
rule :presence do
|
92
|
+
(stris(*PRESENCE)).as(:str)
|
93
|
+
end
|
94
|
+
|
95
|
+
rule :op_is do
|
96
|
+
stri('is')
|
97
|
+
end
|
98
|
+
|
99
|
+
rule :op_cmp do
|
100
|
+
str('=') | str('!=')
|
101
|
+
end
|
102
|
+
|
103
|
+
rule :op_regex do
|
104
|
+
str('=~') | str('!~')
|
105
|
+
end
|
106
|
+
|
107
|
+
rule :op_incl do
|
108
|
+
stri('in')
|
109
|
+
end
|
110
|
+
|
111
|
+
rule :op_or do
|
112
|
+
stri('or')
|
113
|
+
end
|
114
|
+
|
115
|
+
rule :op_and do
|
116
|
+
stri('and')
|
117
|
+
end
|
118
|
+
|
119
|
+
rule :space do
|
120
|
+
match('\s').repeat(1)
|
121
|
+
end
|
122
|
+
|
123
|
+
rule :space? do
|
124
|
+
space.maybe
|
125
|
+
end
|
126
|
+
|
127
|
+
def stris(*strs)
|
128
|
+
strs.inject(stri(strs.shift)) { |node, str| node | stri(str) }
|
129
|
+
end
|
130
|
+
|
131
|
+
def stri(str)
|
132
|
+
str(str) | str(str.upcase)
|
133
|
+
end
|
134
|
+
|
135
|
+
def parens?(node)
|
136
|
+
spaced?(str('(').maybe, node, str(')').maybe)
|
137
|
+
end
|
138
|
+
|
139
|
+
def parens(node)
|
140
|
+
spaced?(str('('), node, str(')'))
|
141
|
+
end
|
142
|
+
|
143
|
+
def spaced?(*nodes)
|
144
|
+
nodes.zip([space?] * nodes.size).flatten[0..-2].inject(&:>>)
|
145
|
+
end
|
146
|
+
|
147
|
+
def spaced(*nodes)
|
148
|
+
# nodes.zip([space] * nodes.size).flatten[0..-2].inject(&:>>)
|
149
|
+
nodes.zip([space?] * nodes.size).flatten[0..-2].inject(&:>>)
|
150
|
+
end
|
151
|
+
|
152
|
+
def ls(node)
|
153
|
+
space? >> node
|
154
|
+
end
|
155
|
+
|
156
|
+
def ts(node)
|
157
|
+
node >> space?
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
class Transform < Parslet::Transform
|
164
|
+
OP = {
|
165
|
+
'=' => :eq,
|
166
|
+
'!=' => :not_eq,
|
167
|
+
'=~' => :match,
|
168
|
+
'!~' => :not_match,
|
169
|
+
}
|
170
|
+
|
171
|
+
str = ->(node) { node.is_a?(Hash) ? node[:str].to_s : node.to_s }
|
172
|
+
sym = ->(node) { str.(node).downcase.to_sym }
|
173
|
+
func = ->(node) { [sym.(node[:func][:name]), str.(node[:func])] }
|
174
|
+
list = ->(node) { node.is_a?(Array) ? node.map { |v| str.(v) } : [str.(node)] }
|
175
|
+
lhs = ->(node) { node.is_a?(Hash) && node.key?(:func) ? func.(node) : str.(node) }
|
176
|
+
|
177
|
+
rule not: { rgt: subtree(:rgt) } do
|
178
|
+
[:not, rgt]
|
179
|
+
end
|
180
|
+
|
181
|
+
rule or: { lft: subtree(:lft), rgt: subtree(:rgt) } do
|
182
|
+
[:or, lft, rgt]
|
183
|
+
end
|
184
|
+
|
185
|
+
rule and: { lft: subtree(:lft), rgt: subtree(:rgt) } do
|
186
|
+
[:and, lft, rgt]
|
187
|
+
end
|
188
|
+
|
189
|
+
rule cmp: { op: simple(:op), lft: subtree(:lft), rgt: subtree(:rgt) } do
|
190
|
+
[OP[op.to_s], lhs.(lft), str.(rgt)]
|
191
|
+
end
|
192
|
+
|
193
|
+
rule incl: { lft: subtree(:lft), rgt: subtree(:rgt) } do
|
194
|
+
[:in, lhs.(lft), list.(rgt)]
|
195
|
+
end
|
196
|
+
|
197
|
+
rule is: { lft: subtree(:lft), rgt: subtree(:rgt) } do
|
198
|
+
[:is, lhs.(lft), sym.(rgt)]
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
data/spec/data_spec.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
describe Travis::Conditions::Data do
|
2
|
+
let(:env) { nil }
|
3
|
+
let(:data) { { branch: 'branch', env: env } }
|
4
|
+
subject { described_class.new(data) }
|
5
|
+
|
6
|
+
it { expect(subject[:branch]).to eq 'branch' }
|
7
|
+
it { expect(subject['branch']).to eq 'branch' }
|
8
|
+
|
9
|
+
describe 'given an env hash' do
|
10
|
+
let(:env) { { foo: 'FOO' } }
|
11
|
+
it { expect(subject.env(:foo)).to eq 'FOO' }
|
12
|
+
it { expect(subject.env('foo')).to eq 'FOO' }
|
13
|
+
end
|
14
|
+
|
15
|
+
describe 'given an env array' do
|
16
|
+
let(:env) { ['foo=FOO'] }
|
17
|
+
it { expect(subject.env(:foo)).to eq 'FOO' }
|
18
|
+
it { expect(subject.env('foo')).to eq 'FOO' }
|
19
|
+
end
|
20
|
+
end
|
data/spec/eval_spec.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
describe Travis::Conditions, 'eval' do
|
2
|
+
let(:tag) { nil }
|
3
|
+
let(:data) { { branch: 'master', tag: tag, env: { foo: 'foo' } } }
|
4
|
+
subject { described_class.eval(str, data) }
|
5
|
+
|
6
|
+
describe 'expressions' do
|
7
|
+
context do
|
8
|
+
let(:str) { 'NOT branch = foo AND (env(foo) = foo OR tag = wat)' }
|
9
|
+
it { should be true }
|
10
|
+
end
|
11
|
+
|
12
|
+
context do
|
13
|
+
let(:str) { 'branch = foo OR env(foo) = foo AND NOT tag = wat' }
|
14
|
+
it { should be true }
|
15
|
+
end
|
16
|
+
|
17
|
+
context do
|
18
|
+
let(:str) { 'branch = foo OR env(foo) = foo AND tag = wat' }
|
19
|
+
it { should be false }
|
20
|
+
end
|
21
|
+
|
22
|
+
context do
|
23
|
+
let(:tag) { '0.0.1' }
|
24
|
+
let(:str) { 'tag =~ /^(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:\.(0|[1-9]\d*))?(?:-([\w.-]+))?(?:\+([\w.-]+))?$ AND type IN (push, api)/' }
|
25
|
+
it { should be false }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'eq' do
|
30
|
+
context do
|
31
|
+
let(:str) { 'branch = master' }
|
32
|
+
it { should be true }
|
33
|
+
end
|
34
|
+
|
35
|
+
context do
|
36
|
+
let(:str) { 'branch = foo' }
|
37
|
+
it { should be false }
|
38
|
+
end
|
39
|
+
|
40
|
+
context do
|
41
|
+
let(:str) { 'env(foo) = foo' }
|
42
|
+
it { should be true }
|
43
|
+
end
|
44
|
+
|
45
|
+
context do
|
46
|
+
let(:str) { 'env(foo) = bar' }
|
47
|
+
it { should be false }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'not eq' do
|
52
|
+
context do
|
53
|
+
let(:str) { 'branch != master' }
|
54
|
+
it { should be false }
|
55
|
+
end
|
56
|
+
|
57
|
+
context do
|
58
|
+
let(:str) { 'branch != foo' }
|
59
|
+
it { should be true }
|
60
|
+
end
|
61
|
+
|
62
|
+
context do
|
63
|
+
let(:str) { 'env(foo) != foo' }
|
64
|
+
it { should be false }
|
65
|
+
end
|
66
|
+
|
67
|
+
context do
|
68
|
+
let(:str) { 'env(foo) != bar' }
|
69
|
+
it { should be true }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe 'match' do
|
74
|
+
context do
|
75
|
+
let(:str) { 'branch =~ ^ma.*$' }
|
76
|
+
it { should be true }
|
77
|
+
end
|
78
|
+
|
79
|
+
context do
|
80
|
+
let(:str) { 'branch =~ ^foo.*$' }
|
81
|
+
it { should be false }
|
82
|
+
end
|
83
|
+
|
84
|
+
context do
|
85
|
+
let(:str) { 'env(foo) =~ ^foo.*$' }
|
86
|
+
it { should be true }
|
87
|
+
end
|
88
|
+
|
89
|
+
context do
|
90
|
+
let(:str) { 'env(foo) =~ ^bar.*$' }
|
91
|
+
it { should be false }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe 'in' do
|
96
|
+
context do
|
97
|
+
let(:str) { 'branch IN (foo, master, bar)' }
|
98
|
+
it { should be true }
|
99
|
+
end
|
100
|
+
|
101
|
+
context do
|
102
|
+
let(:str) { 'branch IN (foo, bar)' }
|
103
|
+
it { should be false }
|
104
|
+
end
|
105
|
+
|
106
|
+
context do
|
107
|
+
let(:str) { 'env(foo) IN (foo, bar, baz)' }
|
108
|
+
it { should be true }
|
109
|
+
end
|
110
|
+
|
111
|
+
context do
|
112
|
+
let(:str) { 'env(foo) IN (bar, baz)' }
|
113
|
+
it { should be false }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe 'is' do
|
118
|
+
context do
|
119
|
+
let(:str) { 'branch IS present' }
|
120
|
+
it { should be true }
|
121
|
+
end
|
122
|
+
|
123
|
+
context do
|
124
|
+
let(:str) { 'tag IS present' }
|
125
|
+
it { should be false }
|
126
|
+
end
|
127
|
+
|
128
|
+
context do
|
129
|
+
let(:str) { 'branch IS blank' }
|
130
|
+
it { should be false }
|
131
|
+
end
|
132
|
+
|
133
|
+
context do
|
134
|
+
let(:str) { 'tag IS blank' }
|
135
|
+
it { should be true }
|
136
|
+
end
|
137
|
+
|
138
|
+
context do
|
139
|
+
let(:str) { 'env(foo) IS present' }
|
140
|
+
it { should be true }
|
141
|
+
end
|
142
|
+
|
143
|
+
context do
|
144
|
+
let(:str) { 'env(bar) IS blank' }
|
145
|
+
it { should be true }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
data/spec/parser_spec.rb
ADDED
@@ -0,0 +1,275 @@
|
|
1
|
+
describe Travis::Conditions do
|
2
|
+
let(:keys) { [:foo] }
|
3
|
+
subject { described_class.parse(str, keys: keys) }
|
4
|
+
|
5
|
+
describe 'eq' do
|
6
|
+
context do
|
7
|
+
let(:str) { 'foo = bar' }
|
8
|
+
it { should eq [:eq, 'foo', 'bar'] }
|
9
|
+
end
|
10
|
+
|
11
|
+
context do
|
12
|
+
let(:str) { 'foo =bar' }
|
13
|
+
it { should eq [:eq, 'foo', 'bar'] }
|
14
|
+
end
|
15
|
+
|
16
|
+
context do
|
17
|
+
let(:str) { 'foo= bar' }
|
18
|
+
it { should eq [:eq, 'foo', 'bar'] }
|
19
|
+
end
|
20
|
+
|
21
|
+
context do
|
22
|
+
let(:str) { 'foo=bar' }
|
23
|
+
it { should eq [:eq, 'foo', 'bar'] }
|
24
|
+
end
|
25
|
+
|
26
|
+
context do
|
27
|
+
let(:str) { 'foo = "bar baz"' }
|
28
|
+
it { should eq [:eq, 'foo', 'bar baz'] }
|
29
|
+
end
|
30
|
+
|
31
|
+
context do
|
32
|
+
let(:str) { 'foo="bar baz"' }
|
33
|
+
it { should eq [:eq, 'foo', 'bar baz'] }
|
34
|
+
end
|
35
|
+
|
36
|
+
context do
|
37
|
+
let(:str) { 'env(foo) = "bar baz"' }
|
38
|
+
it { should eq [:eq, [:env, 'foo'], 'bar baz'] }
|
39
|
+
end
|
40
|
+
|
41
|
+
context do
|
42
|
+
let(:str) { 'foo = true' }
|
43
|
+
it { should eq [:eq, 'foo', 'true'] }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'not eq' do
|
48
|
+
context do
|
49
|
+
let(:str) { 'foo != bar' }
|
50
|
+
it { should eq [:not_eq, 'foo', 'bar'] }
|
51
|
+
end
|
52
|
+
|
53
|
+
context do
|
54
|
+
let(:str) { 'foo !=bar' }
|
55
|
+
it { should eq [:not_eq, 'foo', 'bar'] }
|
56
|
+
end
|
57
|
+
|
58
|
+
context do
|
59
|
+
let(:str) { 'foo!= bar' }
|
60
|
+
it { should eq [:not_eq, 'foo', 'bar'] }
|
61
|
+
end
|
62
|
+
|
63
|
+
context do
|
64
|
+
let(:str) { 'foo!=bar' }
|
65
|
+
it { should eq [:not_eq, 'foo', 'bar'] }
|
66
|
+
end
|
67
|
+
|
68
|
+
context do
|
69
|
+
let(:str) { 'foo != "bar baz"' }
|
70
|
+
it { should eq [:not_eq, 'foo', 'bar baz'] }
|
71
|
+
end
|
72
|
+
|
73
|
+
context do
|
74
|
+
let(:str) { 'foo!="bar baz"' }
|
75
|
+
it { should eq [:not_eq, 'foo', 'bar baz'] }
|
76
|
+
end
|
77
|
+
|
78
|
+
context do
|
79
|
+
let(:str) { 'env(foo) != "bar baz"' }
|
80
|
+
it { should eq [:not_eq, [:env, 'foo'], 'bar baz'] }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe 'match' do
|
85
|
+
context do
|
86
|
+
let(:str) { 'foo =~ ^v[0-9]$' }
|
87
|
+
it { should eq [:match, 'foo', '^v[0-9]$'] }
|
88
|
+
end
|
89
|
+
|
90
|
+
context do
|
91
|
+
let(:str) { 'env(foo) =~ ^v[0-9]$' }
|
92
|
+
it { should eq [:match, [:env, 'foo'], '^v[0-9]$'] }
|
93
|
+
end
|
94
|
+
|
95
|
+
context do
|
96
|
+
let(:str) { 'foo =~ /^v[0-9]$/' }
|
97
|
+
it { should eq [:match, 'foo', '^v[0-9]$'] }
|
98
|
+
end
|
99
|
+
|
100
|
+
context do
|
101
|
+
let(:str) { 'NOT (foo =~ /^v[0-9]$/)' }
|
102
|
+
it { should eq [:not, [:match, 'foo', '^v[0-9]$']] }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe 'not match' do
|
107
|
+
context do
|
108
|
+
let(:str) { 'foo !~ ^v[0-9]$' }
|
109
|
+
it { should eq [:not_match, 'foo', '^v[0-9]$'] }
|
110
|
+
end
|
111
|
+
|
112
|
+
context do
|
113
|
+
let(:str) { 'env(foo) !~ ^v[0-9]$' }
|
114
|
+
it { should eq [:not_match, [:env, 'foo'], '^v[0-9]$'] }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe 'in' do
|
119
|
+
context do
|
120
|
+
let(:str) { 'foo IN (bar)' }
|
121
|
+
it { should eq [:in, 'foo', ['bar']] }
|
122
|
+
end
|
123
|
+
|
124
|
+
context do
|
125
|
+
let(:str) { 'foo IN (bar, baz, buz)' }
|
126
|
+
it { should eq [:in, 'foo', ['bar', 'baz', 'buz']] }
|
127
|
+
end
|
128
|
+
|
129
|
+
context do
|
130
|
+
let(:str) { 'foo IN (bar, "baz, buz")' }
|
131
|
+
it { should eq [:in, 'foo', ['bar', 'baz, buz']] }
|
132
|
+
end
|
133
|
+
|
134
|
+
context do
|
135
|
+
let(:str) { 'env(foo) IN (bar, "baz, buz")' }
|
136
|
+
it { should eq [:in, [:env, 'foo'], ['bar', 'baz, buz']] }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe 'is' do
|
141
|
+
context do
|
142
|
+
let(:str) { 'foo IS present' }
|
143
|
+
it { should eq [:is, 'foo', :present] }
|
144
|
+
end
|
145
|
+
|
146
|
+
context do
|
147
|
+
let(:str) { 'foo IS PRESENT' }
|
148
|
+
it { should eq [:is, 'foo', :present] }
|
149
|
+
end
|
150
|
+
|
151
|
+
context do
|
152
|
+
let(:str) { 'foo IS blank' }
|
153
|
+
it { should eq [:is, 'foo', :blank] }
|
154
|
+
end
|
155
|
+
|
156
|
+
context do
|
157
|
+
let(:str) { 'foo IS BLANK' }
|
158
|
+
it { should eq [:is, 'foo', :blank] }
|
159
|
+
end
|
160
|
+
|
161
|
+
context do
|
162
|
+
let(:str) { 'env(foo) IS BLANK' }
|
163
|
+
it { should eq [:is, [:env, 'foo'], :blank] }
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe 'boolean' do
|
168
|
+
let(:keys) { [:a, :b, :c, :d] }
|
169
|
+
|
170
|
+
context do
|
171
|
+
let(:str) { 'a=1 OR b=2 AND c=3 OR d=4' }
|
172
|
+
it { should eq [:or, [:eq, 'a', '1'], [:or, [:and, [:eq, 'b', '2'], [:eq, 'c', '3']], [:eq, 'd', '4']]] }
|
173
|
+
end
|
174
|
+
|
175
|
+
context do
|
176
|
+
let(:str) { '(a=1) OR (b=2) AND (c=3) OR (d=4)' }
|
177
|
+
it { should eq [:or, [:eq, 'a', '1'], [:or, [:and, [:eq, 'b', '2'], [:eq, 'c', '3']], [:eq, 'd', '4']]] }
|
178
|
+
end
|
179
|
+
|
180
|
+
context do
|
181
|
+
let(:str) { '(a=1 OR b=2) AND (c=3 OR d=4)' }
|
182
|
+
it { should eq [:and, [:or, [:eq, 'a', '1'], [:eq, 'b', '2']], [:or, [:eq, 'c', '3'], [:eq, 'd', '4']]] }
|
183
|
+
end
|
184
|
+
|
185
|
+
context do
|
186
|
+
let(:str) { 'env(a)=1 OR env(b)=2 AND c=3' }
|
187
|
+
it { should eq [:or, [:eq, [:env, 'a'], '1'], [:and, [:eq, [:env, 'b'], '2'], [:eq, 'c', '3']]] }
|
188
|
+
end
|
189
|
+
|
190
|
+
context do
|
191
|
+
let(:str) { '(env(a)=1) OR (env(b)=2) AND (c=3)' }
|
192
|
+
it { should eq [:or, [:eq, [:env, 'a'], '1'], [:and, [:eq, [:env, 'b'], '2'], [:eq, 'c', '3']]] }
|
193
|
+
end
|
194
|
+
|
195
|
+
context do
|
196
|
+
let(:str) { '(env(a)=1 OR env(b)=2) AND c=3' }
|
197
|
+
it { should eq [:and, [:or, [:eq, [:env, 'a'], '1'], [:eq, [:env, 'b'], '2']], [:eq, 'c', '3']] }
|
198
|
+
end
|
199
|
+
|
200
|
+
context do
|
201
|
+
let(:str) { 'a IN (1) OR b IN (2) AND c IN (3)' }
|
202
|
+
it { should eq [:or, [:in, 'a', ['1']], [:and, [:in, 'b', ['2']], [:in, 'c', ['3']]]] }
|
203
|
+
end
|
204
|
+
|
205
|
+
context do
|
206
|
+
let(:str) { '(a IN (1) OR b IN (2)) AND (c IN (3))' }
|
207
|
+
it { should eq [:and, [:or, [:in, 'a', ['1']], [:in, 'b', ['2']]], [:in, 'c', ['3']]] }
|
208
|
+
end
|
209
|
+
|
210
|
+
context do
|
211
|
+
let(:str) { 'a IS present OR b IS blank AND c IS present' }
|
212
|
+
it { should eq [:or, [:is, 'a', :present], [:and, [:is, 'b', :blank], [:is, 'c', :present]]] }
|
213
|
+
end
|
214
|
+
|
215
|
+
context do
|
216
|
+
let(:str) { '(a IS present OR (b IS blank)) AND (c IS present)' }
|
217
|
+
it { should eq [:and, [:or, [:is, 'a', :present], [:is, 'b', :blank]], [:is, 'c', :present]] }
|
218
|
+
end
|
219
|
+
|
220
|
+
context do
|
221
|
+
let(:str) { 'NOT a=1' }
|
222
|
+
it { should eq [:not, [:eq, 'a', '1']] }
|
223
|
+
end
|
224
|
+
|
225
|
+
context do
|
226
|
+
let(:str) { 'NOT (a=1)' }
|
227
|
+
it { should eq [:not, [:eq, 'a', '1']] }
|
228
|
+
end
|
229
|
+
|
230
|
+
context do
|
231
|
+
let(:str) { 'NOT a=1 OR b=2' }
|
232
|
+
it { should eq [:or, [:not, [:eq, 'a', '1']], [:eq, 'b', '2']] }
|
233
|
+
end
|
234
|
+
|
235
|
+
context do
|
236
|
+
let(:str) { 'NOT (a=1 OR b=2)' }
|
237
|
+
it { should eq [:not, [:or, [:eq, 'a', '1'], [:eq, 'b', '2']]] }
|
238
|
+
end
|
239
|
+
|
240
|
+
context do
|
241
|
+
let(:str) { 'NOT a=1 OR NOT b=2' }
|
242
|
+
it { should eq [:or, [:not, [:eq, 'a', '1']], [:not, [:eq, 'b', '2']]] }
|
243
|
+
end
|
244
|
+
|
245
|
+
context do
|
246
|
+
let(:str) { 'NOT a=1 AND b=2' }
|
247
|
+
it { should eq [:and, [:not, [:eq, 'a', '1']], [:eq, 'b', '2']] }
|
248
|
+
end
|
249
|
+
|
250
|
+
context do
|
251
|
+
let(:str) { 'NOT (a=1 OR b=2)' }
|
252
|
+
it { should eq [:not, [:or, [:eq, 'a', '1'], [:eq, 'b', '2']]] }
|
253
|
+
end
|
254
|
+
|
255
|
+
context do
|
256
|
+
let(:str) { 'NOT (NOT (a=1) OR b=2) AND NOT (c=3) OR d=4' }
|
257
|
+
it { should eq [:or, [:and, [:not, [:or, [:not, [:eq, 'a', '1']], [:eq, 'b', '2']]], [:not, [:eq, 'c', '3']]], [:eq, 'd', '4']] }
|
258
|
+
end
|
259
|
+
|
260
|
+
context do
|
261
|
+
let(:str) { 'NOT env(a)=1' }
|
262
|
+
it { should eq [:not, [:eq, [:env, 'a'], '1']] }
|
263
|
+
end
|
264
|
+
|
265
|
+
context do
|
266
|
+
let(:str) { 'NOT (env(a)=1)' }
|
267
|
+
it { should eq [:not, [:eq, [:env, 'a'], '1']] }
|
268
|
+
end
|
269
|
+
|
270
|
+
context do
|
271
|
+
let(:str) { 'NOT (env(a)=1 OR env(b)=2)' }
|
272
|
+
it { should eq [:not, [:or, [:eq, [:env, 'a'], '1'],[:eq, [:env, 'b'], '2']]] }
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'travis/conditions'
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: travis-conditions
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Travis CI
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-10-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: parslet
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description:
|
28
|
+
email: contact@travis-ci.org
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- Gemfile
|
34
|
+
- Gemfile.lock
|
35
|
+
- README.md
|
36
|
+
- lib/travis/conditions.rb
|
37
|
+
- lib/travis/conditions/data.rb
|
38
|
+
- lib/travis/conditions/eval.rb
|
39
|
+
- lib/travis/conditions/parser.rb
|
40
|
+
- lib/travis/conditions/version.rb
|
41
|
+
- spec/data_spec.rb
|
42
|
+
- spec/eval_spec.rb
|
43
|
+
- spec/parser_spec.rb
|
44
|
+
- spec/spec_helper.rb
|
45
|
+
homepage: https://github.com/travis-ci/travis-conditions
|
46
|
+
licenses: []
|
47
|
+
metadata: {}
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 2.6.13
|
65
|
+
signing_key:
|
66
|
+
specification_version: 4
|
67
|
+
summary: Boolean language for conditional builds, stages, jobs
|
68
|
+
test_files: []
|