travis-conditions 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'parslet'
4
+
5
+ group :test do
6
+ gem 'rspec'
7
+ end
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
@@ -0,0 +1,5 @@
1
+ module Travis
2
+ module Conditions
3
+ VERSION = '0.0.1'
4
+ end
5
+ 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
@@ -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
@@ -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: []