travis-conditions 0.0.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -0
  3. data/Gemfile.lock +1 -1
  4. data/NOTES.md +107 -0
  5. data/README.md +261 -13
  6. data/bin/travis-conditions +34 -0
  7. data/lib/travis/conditions.rb +10 -16
  8. data/lib/travis/conditions/v0.rb +30 -0
  9. data/lib/travis/conditions/v0/data.rb +57 -0
  10. data/lib/travis/conditions/v0/eval.rb +70 -0
  11. data/lib/travis/conditions/v0/parser.rb +204 -0
  12. data/lib/travis/conditions/v1.rb +19 -0
  13. data/lib/travis/conditions/v1/boolean.rb +71 -0
  14. data/lib/travis/conditions/v1/data.rb +75 -0
  15. data/lib/travis/conditions/v1/eval.rb +114 -0
  16. data/lib/travis/conditions/v1/helper.rb +30 -0
  17. data/lib/travis/conditions/v1/parser.rb +214 -0
  18. data/lib/travis/conditions/version.rb +1 -1
  19. data/spec/conditions_spec.rb +15 -0
  20. data/spec/v0/conditions_spec.rb +15 -0
  21. data/spec/{data_spec.rb → v0/data_spec.rb} +6 -1
  22. data/spec/{eval_spec.rb → v0/eval_spec.rb} +1 -1
  23. data/spec/v0/fixtures/failures.txt +342 -0
  24. data/spec/v0/fixtures/passes.txt +1685 -0
  25. data/spec/{parser_spec.rb → v0/parser_spec.rb} +1 -1
  26. data/spec/v1/conditions_spec.rb +44 -0
  27. data/spec/v1/data_spec.rb +30 -0
  28. data/spec/v1/eval_spec.rb +349 -0
  29. data/spec/v1/fixtures/failures.txt +336 -0
  30. data/spec/v1/fixtures/passes.txt +1634 -0
  31. data/spec/v1/parser/boolean_spec.rb +215 -0
  32. data/spec/v1/parser/call_spec.rb +68 -0
  33. data/spec/v1/parser/comma_spec.rb +28 -0
  34. data/spec/v1/parser/cont_spec.rb +41 -0
  35. data/spec/v1/parser/eq_spec.rb +16 -0
  36. data/spec/v1/parser/in_list_spec.rb +60 -0
  37. data/spec/v1/parser/is_pred_spec.rb +24 -0
  38. data/spec/v1/parser/list_spec.rb +36 -0
  39. data/spec/v1/parser/operand_spec.rb +16 -0
  40. data/spec/v1/parser/parens_spec.rb +28 -0
  41. data/spec/v1/parser/quoted_spec.rb +24 -0
  42. data/spec/v1/parser/re_spec.rb +16 -0
  43. data/spec/v1/parser/regex_spec.rb +12 -0
  44. data/spec/v1/parser/space_spec.rb +40 -0
  45. data/spec/v1/parser/term_spec.rb +84 -0
  46. data/spec/v1/parser/val_spec.rb +24 -0
  47. data/spec/v1/parser/var_spec.rb +16 -0
  48. data/spec/v1/parser_spec.rb +486 -0
  49. data/spec/v1/user_spec.rb +223 -0
  50. metadata +48 -9
  51. data/lib/travis/conditions/data.rb +0 -45
  52. data/lib/travis/conditions/eval.rb +0 -68
  53. data/lib/travis/conditions/parser.rb +0 -202
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $: << 'lib'
4
+
5
+ require 'json'
6
+ require 'optparse'
7
+ require 'travis/conditions'
8
+
9
+ def help
10
+ abort <<~help
11
+ Usage:
12
+ travis-conditions "branch = master" --data '{"branch": "master"}'
13
+ travis-conditions echo '{"branch": "master"}' | travis-conditions "branch = master"
14
+
15
+ The given data JSON hash can include known attributes (such as branch, tag,
16
+ repo) and an "env" key that can either hold a hash, or an array of strings:
17
+
18
+ {"env": {"foo": "bar"}}
19
+ {"env": ["foo=bar"]}
20
+ help
21
+ end
22
+
23
+ data = $stdin.read unless $stdin.tty?
24
+
25
+ ARGV.options do |opts|
26
+ opts.on("-d", "--data DATA") { |val| data = val }
27
+ opts.on_tail("-h", "--help") { help }
28
+ opts.parse!
29
+ end
30
+
31
+ cond = ARGV.shift
32
+ abort help unless cond && data
33
+
34
+ p Travis::Conditions.eval(cond, JSON.parse(data), version: :v1)
@@ -1,29 +1,23 @@
1
- require 'travis/conditions/data'
2
- require 'travis/conditions/eval'
3
- require 'travis/conditions/parser'
1
+ require 'travis/conditions/v0'
2
+ require 'travis/conditions/v1'
4
3
 
5
4
  module Travis
6
5
  module Conditions
7
- ParseError = Class.new(StandardError)
6
+ Error = Class.new(::ArgumentError)
7
+ ArgumentError = Class.new(Error)
8
+ ParseError = Class.new(Error)
8
9
 
9
10
  class << self
10
- def eval(str, data)
11
- Eval.new(parse(str, keys: data.keys), Data.new(data)).apply
11
+ def eval(str, data, opts = {})
12
+ const(opts).eval(str, data)
12
13
  end
13
14
 
14
15
  def parse(str, opts = {})
15
- tree = parser(opts).parse(str)
16
- Transform.new.apply(tree)
17
- rescue Parslet::ParseFailed
18
- raise ParseError
16
+ const(opts).parse(str, opts)
19
17
  end
20
18
 
21
- def parser(opts)
22
- parsers[opts] ||= Parser.new(opts)
23
- end
24
-
25
- def parsers
26
- @parsers ||= {}
19
+ def const(opts)
20
+ opts[:version] == :v1 ? V1 : V0
27
21
  end
28
22
  end
29
23
  end
@@ -0,0 +1,30 @@
1
+ require 'travis/conditions/v0/data'
2
+ require 'travis/conditions/v0/eval'
3
+ require 'travis/conditions/v0/parser'
4
+
5
+ module Travis
6
+ module Conditions
7
+ module V0
8
+ class << self
9
+ def eval(str, data, opts = {})
10
+ Eval.new(parse(str, keys: data.keys), Data.new(data)).apply
11
+ end
12
+
13
+ def parse(str, opts = {})
14
+ tree = parser(opts).parse(str)
15
+ Transform.new.apply(tree)
16
+ rescue Parslet::ParseFailed
17
+ raise ParseError
18
+ end
19
+
20
+ def parser(opts)
21
+ parsers[opts] ||= Parser.new(opts)
22
+ end
23
+
24
+ def parsers
25
+ @parsers ||= {}
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,57 @@
1
+ module Travis
2
+ module Conditions
3
+ module V0
4
+ class Data < Struct.new(:data)
5
+ def initialize(data)
6
+ super(normalize(data))
7
+ end
8
+
9
+ def [](key)
10
+ data[key.to_sym]
11
+ end
12
+
13
+ def env(key)
14
+ data.fetch(:env, {})[key.to_sym]
15
+ end
16
+
17
+ private
18
+
19
+ def split(str)
20
+ str.split('=', 2)
21
+ end
22
+
23
+ def symbolize(value)
24
+ case value
25
+ when Hash
26
+ value.map { |key, value| [key.to_sym, symbolize(value)] }.to_h
27
+ when Array
28
+ value.map { |value| symbolize(value) }
29
+ else
30
+ value
31
+ end
32
+ end
33
+
34
+ def normalize(data)
35
+ data = symbolize(data)
36
+ data[:env] = normalize_env(data[:env])
37
+ data
38
+ end
39
+
40
+ def normalize_env(env)
41
+ symbolize(to_h(env || {}))
42
+ rescue ::ArgumentError
43
+ raise ArgumentError.new("Cannot normalize data[:env] (#{env.inspect} given)")
44
+ end
45
+
46
+ def to_h(obj)
47
+ case obj
48
+ when Hash
49
+ obj
50
+ else
51
+ Array(obj).map { |obj| split(obj.to_s) }.to_h
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,70 @@
1
+ module Travis
2
+ module Conditions
3
+ module V0
4
+ class Eval < Struct.new(:sexp, :data)
5
+ def apply
6
+ !!evl(sexp)
7
+ end
8
+
9
+ private
10
+
11
+ def evl(value)
12
+ case value
13
+ when Array
14
+ send(*value)
15
+ else
16
+ data[value]
17
+ end
18
+ end
19
+
20
+ def not(lft)
21
+ !evl(lft)
22
+ end
23
+
24
+ def or(lft, rgt)
25
+ evl(lft) || evl(rgt)
26
+ end
27
+
28
+ def and(lft, rgt)
29
+ evl(lft) && evl(rgt)
30
+ end
31
+
32
+ def eq(lft, rgt)
33
+ evl(lft) == rgt
34
+ end
35
+
36
+ def not_eq(lft, rgt)
37
+ not eq(lft, rgt)
38
+ end
39
+
40
+ def match(lft, rgt)
41
+ evl(lft) =~ Regexp.new(rgt)
42
+ end
43
+
44
+ def not_match(lft, rgt)
45
+ not match(lft, rgt)
46
+ end
47
+
48
+ def in(lft, rgt)
49
+ rgt.include?(evl(lft))
50
+ end
51
+
52
+ def is(lft, rgt)
53
+ send(rgt, evl(lft))
54
+ end
55
+
56
+ def env(key)
57
+ data.env(key)
58
+ end
59
+
60
+ def present(value)
61
+ value.respond_to?(:empty?) && !value.empty?
62
+ end
63
+
64
+ def blank(value)
65
+ !present(value)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,204 @@
1
+ require 'parslet'
2
+
3
+ module Travis
4
+ module Conditions
5
+ module V0
6
+ class Parser < Struct.new(:opts)
7
+ FUNCS = %w(env)
8
+ PRESENCE = %w(present blank)
9
+
10
+ def parse(str)
11
+ parser.parse(str)
12
+ end
13
+
14
+ private
15
+
16
+ def parser
17
+ @parser ||= define_parser(opts[:keys]).new
18
+ end
19
+
20
+ def define_parser(keywords)
21
+ Class.new(Parslet::Parser) do
22
+ root :expr_or
23
+
24
+ rule :expr_or do
25
+ spaced(expr_and.as(:lft), op_or, expr_or.as(:rgt)).as(:or) | expr_and
26
+ end
27
+
28
+ rule :expr_and do
29
+ spaced(expr_inner.as(:lft), op_and, expr_and.as(:rgt)).as(:and) | expr_inner
30
+ end
31
+
32
+ rule :expr_inner do
33
+ lnot(parens(expr_or) | expr_incl | expr_is | expr_regex | expr_cmp)
34
+ end
35
+
36
+ rule :expr_cmp do
37
+ spaced(lhs.as(:lft), op_cmp.as(:op), value.as(:rgt)).as(:cmp)
38
+ end
39
+
40
+ rule :expr_regex do
41
+ spaced(lhs.as(:lft), op_regex.as(:op), regex.as(:rgt)).as(:cmp)
42
+ end
43
+
44
+ rule :expr_incl do
45
+ spaced(lhs.as(:lft), op_incl, parens(list).as(:rgt)).as(:incl)
46
+ end
47
+
48
+ rule :expr_is do
49
+ spaced(lhs.as(:lft), op_is, presence.as(:rgt)).as(:is)
50
+ end
51
+
52
+ def lnot(node)
53
+ (stri('not').maybe >> space >> node.as(:rgt)).as(:not) | node
54
+ end
55
+
56
+ rule :list do
57
+ (value >> (ts(str(',')) >> value).repeat)
58
+ end
59
+
60
+ rule :lhs do
61
+ func | keyword
62
+ end
63
+
64
+ rule :keyword do
65
+ stris(*keywords)
66
+ end
67
+
68
+ rule :func do
69
+ (stris(*FUNCS).as(:name) >> parens(word)).as(:func)
70
+ end
71
+
72
+ rule :word do
73
+ match('[\w_\-]').repeat(1).as(:str)
74
+ end
75
+
76
+ rule :regex do
77
+ quoted('/') | match('[\S]').repeat(1).as(:str)
78
+ end
79
+
80
+ rule :value do
81
+ unquoted | quoted('"') | quoted("'")
82
+ end
83
+
84
+ rule :unquoted do
85
+ match('[^\s\"\'\(\),]').repeat(1).as(:str)
86
+ end
87
+
88
+ def quoted(chr)
89
+ str(chr) >> match("[^#{chr}]").repeat.as(:str) >> str(chr)
90
+ end
91
+
92
+ rule :presence do
93
+ (stris(*PRESENCE)).as(:str)
94
+ end
95
+
96
+ rule :op_is do
97
+ stri('is')
98
+ end
99
+
100
+ rule :op_cmp do
101
+ str('=') | str('!=')
102
+ end
103
+
104
+ rule :op_regex do
105
+ str('=~') | str('!~')
106
+ end
107
+
108
+ rule :op_incl do
109
+ stri('in')
110
+ end
111
+
112
+ rule :op_or do
113
+ stri('or')
114
+ end
115
+
116
+ rule :op_and do
117
+ stri('and')
118
+ end
119
+
120
+ rule :space do
121
+ match('\s').repeat(1)
122
+ end
123
+
124
+ rule :space? do
125
+ space.maybe
126
+ end
127
+
128
+ def stris(*strs)
129
+ strs.inject(stri(strs.shift)) { |node, str| node | stri(str) }
130
+ end
131
+
132
+ def stri(str)
133
+ str(str) | str(str.upcase)
134
+ end
135
+
136
+ def parens?(node)
137
+ spaced?(str('(').maybe, node, str(')').maybe)
138
+ end
139
+
140
+ def parens(node)
141
+ spaced?(str('('), node, str(')'))
142
+ end
143
+
144
+ def spaced?(*nodes)
145
+ nodes.zip([space?] * nodes.size).flatten[0..-2].inject(&:>>)
146
+ end
147
+
148
+ def spaced(*nodes)
149
+ # nodes.zip([space] * nodes.size).flatten[0..-2].inject(&:>>)
150
+ nodes.zip([space?] * nodes.size).flatten[0..-2].inject(&:>>)
151
+ end
152
+
153
+ def ls(node)
154
+ space? >> node
155
+ end
156
+
157
+ def ts(node)
158
+ node >> space?
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ class Transform < Parslet::Transform
165
+ OP = {
166
+ '=' => :eq,
167
+ '!=' => :not_eq,
168
+ '=~' => :match,
169
+ '!~' => :not_match,
170
+ }
171
+
172
+ str = ->(node) { node.is_a?(Hash) ? node[:str].to_s : node.to_s }
173
+ sym = ->(node) { str.(node).downcase.to_sym }
174
+ func = ->(node) { [sym.(node[:func][:name]), str.(node[:func])] }
175
+ list = ->(node) { node.is_a?(Array) ? node.map { |v| str.(v) } : [str.(node)] }
176
+ lhs = ->(node) { node.is_a?(Hash) && node.key?(:func) ? func.(node) : str.(node) }
177
+
178
+ rule not: { rgt: subtree(:rgt) } do
179
+ [:not, rgt]
180
+ end
181
+
182
+ rule or: { lft: subtree(:lft), rgt: subtree(:rgt) } do
183
+ [:or, lft, rgt]
184
+ end
185
+
186
+ rule and: { lft: subtree(:lft), rgt: subtree(:rgt) } do
187
+ [:and, lft, rgt]
188
+ end
189
+
190
+ rule cmp: { op: simple(:op), lft: subtree(:lft), rgt: subtree(:rgt) } do
191
+ [OP[op.to_s], lhs.(lft), str.(rgt)]
192
+ end
193
+
194
+ rule incl: { lft: subtree(:lft), rgt: subtree(:rgt) } do
195
+ [:in, lhs.(lft), list.(rgt)]
196
+ end
197
+
198
+ rule is: { lft: subtree(:lft), rgt: subtree(:rgt) } do
199
+ [:is, lhs.(lft), sym.(rgt)]
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,19 @@
1
+ require 'travis/conditions/v1/data'
2
+ require 'travis/conditions/v1/eval'
3
+ require 'travis/conditions/v1/parser'
4
+
5
+ module Travis
6
+ module Conditions
7
+ module V1
8
+ class << self
9
+ def eval(str, data)
10
+ Eval.new(parse(str), Data.new(data)).apply
11
+ end
12
+
13
+ def parse(str, _ = nil)
14
+ Parser.new(str).parse
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end