predicate 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -0
  3. data/LICENSE.md +17 -19
  4. data/README.md +433 -0
  5. data/bin/g +2 -0
  6. data/lib/predicate.rb +15 -2
  7. data/lib/predicate/dsl.rb +138 -0
  8. data/lib/predicate/factory.rb +130 -33
  9. data/lib/predicate/grammar.rb +11 -2
  10. data/lib/predicate/grammar.sexp.yml +29 -0
  11. data/lib/predicate/nodes/${op_name}.rb.jeny +12 -0
  12. data/lib/predicate/nodes/binary_func.rb +20 -0
  13. data/lib/predicate/nodes/contradiction.rb +2 -7
  14. data/lib/predicate/nodes/dyadic_comp.rb +1 -3
  15. data/lib/predicate/nodes/empty.rb +14 -0
  16. data/lib/predicate/nodes/eq.rb +1 -3
  17. data/lib/predicate/nodes/expr.rb +1 -3
  18. data/lib/predicate/nodes/has_size.rb +14 -0
  19. data/lib/predicate/nodes/identifier.rb +1 -3
  20. data/lib/predicate/nodes/in.rb +2 -6
  21. data/lib/predicate/nodes/intersect.rb +3 -23
  22. data/lib/predicate/nodes/literal.rb +1 -3
  23. data/lib/predicate/nodes/match.rb +1 -21
  24. data/lib/predicate/nodes/nadic_bool.rb +1 -3
  25. data/lib/predicate/nodes/native.rb +1 -3
  26. data/lib/predicate/nodes/not.rb +1 -3
  27. data/lib/predicate/nodes/opaque.rb +1 -3
  28. data/lib/predicate/nodes/qualified_identifier.rb +1 -3
  29. data/lib/predicate/nodes/set_op.rb +26 -0
  30. data/lib/predicate/nodes/subset.rb +11 -0
  31. data/lib/predicate/nodes/superset.rb +11 -0
  32. data/lib/predicate/nodes/tautology.rb +2 -7
  33. data/lib/predicate/nodes/unary_func.rb +16 -0
  34. data/lib/predicate/nodes/var.rb +46 -0
  35. data/lib/predicate/processors/qualifier.rb +4 -0
  36. data/lib/predicate/processors/renamer.rb +4 -0
  37. data/lib/predicate/processors/to_s.rb +28 -0
  38. data/lib/predicate/processors/unqualifier.rb +4 -0
  39. data/lib/predicate/sequel/to_sequel.rb +3 -0
  40. data/lib/predicate/sugar.rb +47 -0
  41. data/lib/predicate/version.rb +1 -1
  42. data/spec/dsl/test_dsl.rb +204 -0
  43. data/spec/dsl/test_evaluate.rb +65 -0
  44. data/spec/dsl/test_respond_to_missing.rb +35 -0
  45. data/spec/dsl/test_to_skake_case.rb +38 -0
  46. data/spec/factory/shared/a_comparison_factory_method.rb +1 -0
  47. data/spec/factory/test_${op_name}.rb.jeny +12 -0
  48. data/spec/factory/test_empty.rb +11 -0
  49. data/spec/factory/test_has_size.rb +11 -0
  50. data/spec/factory/test_match.rb +1 -0
  51. data/spec/factory/test_set_ops.rb +18 -0
  52. data/spec/factory/test_var.rb +22 -0
  53. data/spec/factory/test_vars.rb +27 -0
  54. data/spec/nodes/${op_name}.jeny/test_evaluate.rb.jeny +19 -0
  55. data/spec/nodes/empty/test_evaluate.rb +42 -0
  56. data/spec/nodes/has_size/test_evaluate.rb +44 -0
  57. data/spec/predicate/test_and_split.rb +18 -0
  58. data/spec/predicate/test_attr_split.rb +18 -0
  59. data/spec/predicate/test_constant_variables.rb +24 -2
  60. data/spec/predicate/test_constants.rb +24 -0
  61. data/spec/predicate/test_evaluate.rb +205 -3
  62. data/spec/predicate/test_to_s.rb +37 -0
  63. data/spec/sequel/test_to_sequel.rb +16 -0
  64. data/spec/shared/a_predicate.rb +30 -0
  65. data/spec/spec_helper.rb +1 -0
  66. data/spec/test_predicate.rb +68 -33
  67. data/spec/test_readme.rb +80 -0
  68. data/spec/test_sugar.rb +48 -0
  69. data/tasks/test.rake +3 -3
  70. metadata +40 -12
  71. data/spec/factory/test_between.rb +0 -12
  72. data/spec/factory/test_intersect.rb +0 -12
@@ -1,6 +1,7 @@
1
1
  require_relative 'a_predicate_ast_node'
2
2
  shared_examples_for "a comparison factory method" do
3
3
  include Predicate::Factory
4
+ include Predicate::Sugar
4
5
 
5
6
  context 'with two operands' do
6
7
  subject{ self.send(method, true, true) }
@@ -0,0 +1,12 @@
1
+ #jeny(predicate)
2
+ require_relative 'shared/a_predicate_ast_node'
3
+ class Predicate
4
+ describe Factory, '${op_name}' do
5
+ include Factory
6
+ subject{ ${op_name}(TODO) }
7
+
8
+ it_should_behave_like "a predicate AST node"
9
+
10
+ it{ should be_a(${OpName}) }
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'shared/a_predicate_ast_node'
2
+ class Predicate
3
+ describe Factory, 'empty' do
4
+ include Factory
5
+ subject{ empty(:x) }
6
+
7
+ it_should_behave_like "a predicate AST node"
8
+
9
+ it{ should be_a(Empty) }
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'shared/a_predicate_ast_node'
2
+ class Predicate
3
+ describe Factory, 'has_size' do
4
+ include Factory
5
+ subject{ has_size(:x, 1..10) }
6
+
7
+ it_should_behave_like "a predicate AST node"
8
+
9
+ it{ should be_a(HasSize) }
10
+ end
11
+ end
@@ -2,6 +2,7 @@ require_relative 'shared/a_predicate_ast_node'
2
2
  class Predicate
3
3
  describe Factory, 'match' do
4
4
  include Factory
5
+ include Sugar
5
6
 
6
7
  context 'without options' do
7
8
  subject{ match(:name, "London") }
@@ -0,0 +1,18 @@
1
+ require_relative "shared/a_comparison_factory_method"
2
+ class Predicate
3
+ [
4
+ [ :intersect, Intersect ],
5
+ [ :subset, Subset ],
6
+ [ :superset, Superset ],
7
+ ].each do |op_name, op_class|
8
+ describe Factory, op_name do
9
+
10
+ subject{ Factory.send(op_name, :x, [2, 3]) }
11
+
12
+ it{ should be_a(op_class) }
13
+
14
+ it{ should eq([op_name, [:identifier, :x], [:literal, [2, 3]]]) }
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ require_relative 'shared/a_predicate_ast_node'
2
+ class Predicate
3
+ describe Factory, 'var' do
4
+ include Factory
5
+
6
+ context 'when used with a string' do
7
+ subject{ var("a.b.c", :dig) }
8
+
9
+ it_should_behave_like "a predicate AST node"
10
+ it{ should be_a(Var) }
11
+ it{ should eql([:var, "a.b.c", :dig]) }
12
+ end
13
+
14
+ context 'when used with an array' do
15
+ subject{ var([:a, :b, :c], :dig) }
16
+
17
+ it_should_behave_like "a predicate AST node"
18
+ it{ should be_a(Var) }
19
+ it{ should eql([:var, [:a, :b, :c], :dig]) }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,27 @@
1
+ require_relative 'shared/a_predicate_ast_node'
2
+ class Predicate
3
+ describe Factory, 'vars' do
4
+ include Factory
5
+
6
+ context 'when used without semantics' do
7
+ subject{ vars("a.b.c", "d.e.f") }
8
+
9
+ it 'works as expected' do
10
+ expect(subject).to be_a(Array)
11
+ expect(subject.size).to eql(2)
12
+ expect(subject.all?{|p| p.is_a?(Var) && p.semantics == :dig })
13
+ end
14
+ end
15
+
16
+ context 'when used with semantics' do
17
+ subject{ vars("a.b.c", "d.e.f", :jsonpath) }
18
+
19
+ it 'works as expected' do
20
+ expect(subject).to be_a(Array)
21
+ expect(subject.size).to eql(2)
22
+ expect(subject.all?{|p| p.is_a?(Var) && p.semantics == :jsonpath })
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ #jeny(predicate)
2
+ require 'spec_helper'
3
+ class Predicate
4
+ describe ${OpName}, "evaluate" do
5
+
6
+ let(:predicate){ Factory.${op_name}(TODO) }
7
+
8
+ subject{ predicate.evaluate(context) }
9
+
10
+ context "on TODO" do
11
+ let(:context){ TODO }
12
+
13
+ it 'works as expected' do
14
+ TODO
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+ class Predicate
3
+ describe Empty, "evaluate" do
4
+
5
+ let(:predicate){ Factory.empty(:x) }
6
+
7
+ subject{ predicate.evaluate(context) }
8
+
9
+ context "on an empty array" do
10
+ let(:context){ { x: [] } }
11
+
12
+ it{ expect(subject).to eq(true) }
13
+ end
14
+
15
+ context "on a non empty array" do
16
+ let(:context){ { x: [1, 2, 3] } }
17
+
18
+ it{ expect(subject).to eq(false) }
19
+ end
20
+
21
+ context "on a empty string" do
22
+ let(:context){ { x: "" } }
23
+
24
+ it{ expect(subject).to eq(true) }
25
+ end
26
+
27
+ context "on a non empty string" do
28
+ let(:context){ { x: "1233" } }
29
+
30
+ it{ expect(subject).to eq(false) }
31
+ end
32
+
33
+ context "on an object that does not respond to empty?" do
34
+ let(:context){ { x: 14567 } }
35
+
36
+ it{
37
+ expect{ subject }.to raise_error(TypeError, "Expected 14567 to respond to empty?")
38
+ }
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+ class Predicate
3
+ describe HasSize, "evaluate" do
4
+
5
+ let(:predicate){
6
+ Factory.has_size(:x, y)
7
+ }
8
+
9
+ subject{ predicate.evaluate(context) }
10
+
11
+ context 'against a Range' do
12
+ let(:y){ 1..10 }
13
+
14
+ context "on a match x" do
15
+ let(:context){ { x: "1234567" } }
16
+
17
+ it{ expect(subject).to eq(true) }
18
+ end
19
+
20
+ context "on a non matching x" do
21
+ let(:context){ { x: "1234567891011" } }
22
+
23
+ it{ expect(subject).to eq(false) }
24
+ end
25
+ end
26
+
27
+ context 'against an Integer' do
28
+ let(:y){ 10 }
29
+
30
+ context "on a match x" do
31
+ let(:context){ { x: "0123456789" } }
32
+
33
+ it{ expect(subject).to eq(true) }
34
+ end
35
+
36
+ context "on a non matching x" do
37
+ let(:context){ { x: "1234567891011" } }
38
+
39
+ it{ expect(subject).to eq(false) }
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -83,5 +83,23 @@ class Predicate
83
83
  it{ should eq([ pred, p.tautology ]) }
84
84
  end
85
85
 
86
+ context "on intersect" do
87
+ let(:pred){ p.intersect(:x, [1, 2]) }
88
+
89
+ it{ should eq([ pred, p.tautology ]) }
90
+ end
91
+
92
+ context "on subset" do
93
+ let(:pred){ p.subset(:x, [1, 2]) }
94
+
95
+ it{ should eq([ pred, p.tautology ]) }
96
+ end
97
+
98
+ context "on superset" do
99
+ let(:pred){ p.superset(:x, [1, 2]) }
100
+
101
+ it{ should eq([ pred, p.tautology ]) }
102
+ end
103
+
86
104
  end
87
105
  end
@@ -47,6 +47,24 @@ class Predicate
47
47
  it{ should eq({ x: pred }) }
48
48
  end
49
49
 
50
+ context "on intersect" do
51
+ let(:pred){ p.intersect(:x, [2]) }
52
+
53
+ it{ should eq({ x: pred }) }
54
+ end
55
+
56
+ context "on subset" do
57
+ let(:pred){ p.subset(:x, [2]) }
58
+
59
+ it{ should eq({ x: pred }) }
60
+ end
61
+
62
+ context "on superset" do
63
+ let(:pred){ p.superset(:x, [2]) }
64
+
65
+ it{ should eq({ x: pred }) }
66
+ end
67
+
50
68
  context "on match" do
51
69
  let(:pred){ p.match(:x, "London") }
52
70
 
@@ -37,14 +37,36 @@ class Predicate
37
37
  describe "on an intersect with one value" do
38
38
  let(:p){ Predicate.intersect(:x, [2]) }
39
39
 
40
- # TODO: is that correct?
41
40
  it{ expect(subject).to eql([]) }
42
41
  end
43
42
 
44
43
  describe "on an intersect with a placeholder" do
45
44
  let(:p){ Predicate.intersect(:x, Predicate.placeholder) }
46
45
 
47
- # TODO: is that correct?
46
+ it{ expect(subject).to eql([]) }
47
+ end
48
+
49
+ describe "on an subset with one value" do
50
+ let(:p){ Predicate.subset(:x, [2]) }
51
+
52
+ it{ expect(subject).to eql([]) }
53
+ end
54
+
55
+ describe "on an subset with a placeholder" do
56
+ let(:p){ Predicate.subset(:x, Predicate.placeholder) }
57
+
58
+ it{ expect(subject).to eql([]) }
59
+ end
60
+
61
+ describe "on an superset with one value" do
62
+ let(:p){ Predicate.superset(:x, [2]) }
63
+
64
+ it{ expect(subject).to eql([]) }
65
+ end
66
+
67
+ describe "on an superset with a placeholder" do
68
+ let(:p){ Predicate.superset(:x, Predicate.placeholder) }
69
+
48
70
  it{ expect(subject).to eql([]) }
49
71
  end
50
72
 
@@ -119,6 +119,30 @@ class Predicate
119
119
  it{ should eq({}) }
120
120
  end
121
121
 
122
+ context "on subset" do
123
+ let(:pred){ p.subset(:x, [4,8]) }
124
+
125
+ it{ should eq({}) }
126
+ end
127
+
128
+ context "on subset with placeholder" do
129
+ let(:pred){ p.subset(:x, p.placeholder) }
130
+
131
+ it{ should eq({}) }
132
+ end
133
+
134
+ context "on superset" do
135
+ let(:pred){ p.superset(:x, [4,8]) }
136
+
137
+ it{ should eq({}) }
138
+ end
139
+
140
+ context "on superset with placeholder" do
141
+ let(:pred){ p.superset(:x, p.placeholder) }
142
+
143
+ it{ should eq({}) }
144
+ end
145
+
122
146
  context "on or (two eqs)" do
123
147
  let(:pred){ p.eq(:x, 2) | p.eq(:y, 4) }
124
148
 
@@ -40,7 +40,7 @@ class Predicate
40
40
 
41
41
  context 'on a factored predicate' do
42
42
  let(:predicate){
43
- Predicate.new(Factory.lte(:x => 2))
43
+ Predicate.lte(:x => 2)
44
44
  }
45
45
 
46
46
  describe "on x == 2" do
@@ -80,7 +80,7 @@ class Predicate
80
80
  end
81
81
  end
82
82
 
83
- context 'on an intersect predicate' do
83
+ context 'on an intersect predicate, with array literal' do
84
84
  let(:predicate){
85
85
  Predicate.intersect(:x, [8,9])
86
86
  }
@@ -98,6 +98,168 @@ class Predicate
98
98
  end
99
99
  end
100
100
 
101
+ context 'on an intersect predicate, with two variables' do
102
+ let(:predicate){
103
+ Predicate.intersect(:x, :y)
104
+ }
105
+
106
+ describe "on x == [2]" do
107
+ let(:scope){ { :x => [2], :y => [8,9] } }
108
+
109
+ it{ expect(predicate.evaluate(scope)).to be_falsy }
110
+ end
111
+
112
+ describe "on x == [9,12]" do
113
+ let(:scope){ { :x => [9,12], :y => [8,9] } }
114
+
115
+ it{ expect(predicate.evaluate(scope)).to be_truthy }
116
+ end
117
+ end
118
+
119
+ context 'on an subset predicate, with an array literal' do
120
+ let(:predicate){
121
+ Predicate.subset(:x, [8,9])
122
+ }
123
+
124
+ describe "on x == [2]" do
125
+ let(:scope){ { :x => [2] } }
126
+
127
+ it{ expect(predicate.evaluate(scope)).to be_falsy }
128
+ end
129
+
130
+ describe "on x == []" do
131
+ let(:scope){ { :x => [] } }
132
+
133
+ it{ expect(predicate.evaluate(scope)).to be_truthy }
134
+ end
135
+
136
+ describe "on x == [9]" do
137
+ let(:scope){ { :x => [9] } }
138
+
139
+ it{ expect(predicate.evaluate(scope)).to be_truthy }
140
+ end
141
+
142
+ describe "on x == [8, 9]" do
143
+ let(:scope){ { :x => [8, 9] } }
144
+
145
+ it{ expect(predicate.evaluate(scope)).to be_truthy }
146
+ end
147
+
148
+ describe "on x == [8, 9, 10]" do
149
+ let(:scope){ { :x => [8, 9, 19] } }
150
+
151
+ it{ expect(predicate.evaluate(scope)).to be_falsy }
152
+ end
153
+ end
154
+
155
+ context 'on an subset predicate, with two variables' do
156
+ let(:predicate){
157
+ Predicate.subset(:x, :y)
158
+ }
159
+
160
+ describe "on x == [2]" do
161
+ let(:scope){ { :x => [2], :y => [8,9] } }
162
+
163
+ it{ expect(predicate.evaluate(scope)).to be_falsy }
164
+ end
165
+
166
+ describe "on x == []" do
167
+ let(:scope){ { :x => [], :y => [8,9] } }
168
+
169
+ it{ expect(predicate.evaluate(scope)).to be_truthy }
170
+ end
171
+
172
+ describe "on x == [9]" do
173
+ let(:scope){ { :x => [9], :y => [8,9] } }
174
+
175
+ it{ expect(predicate.evaluate(scope)).to be_truthy }
176
+ end
177
+
178
+ describe "on x == [8, 9]" do
179
+ let(:scope){ { :x => [8, 9], :y => [8,9] } }
180
+
181
+ it{ expect(predicate.evaluate(scope)).to be_truthy }
182
+ end
183
+
184
+ describe "on x == [8, 9, 10]" do
185
+ let(:scope){ { :x => [8, 9, 19], :y => [8,9] } }
186
+
187
+ it{ expect(predicate.evaluate(scope)).to be_falsy }
188
+ end
189
+ end
190
+
191
+ context 'on an superset predicate, with an array literal' do
192
+ let(:predicate){
193
+ Predicate.superset(:x, [8,9])
194
+ }
195
+
196
+ describe "on x == [2]" do
197
+ let(:scope){ { :x => [2] } }
198
+
199
+ it{ expect(predicate.evaluate(scope)).to be_falsy }
200
+ end
201
+
202
+ describe "on x == []" do
203
+ let(:scope){ { :x => [] } }
204
+
205
+ it{ expect(predicate.evaluate(scope)).to be_falsy }
206
+ end
207
+
208
+ describe "on x == [9]" do
209
+ let(:scope){ { :x => [9] } }
210
+
211
+ it{ expect(predicate.evaluate(scope)).to be_falsy }
212
+ end
213
+
214
+ describe "on x == [8, 9]" do
215
+ let(:scope){ { :x => [8, 9] } }
216
+
217
+ it{ expect(predicate.evaluate(scope)).to be_truthy }
218
+ end
219
+
220
+ describe "on x == [8, 9, 10]" do
221
+ let(:scope){ { :x => [8, 9, 19] } }
222
+
223
+ it{ expect(predicate.evaluate(scope)).to be_truthy }
224
+ end
225
+ end
226
+
227
+ context 'on an superset predicate, with two variables' do
228
+ let(:predicate){
229
+ Predicate.superset(:x, :y)
230
+ }
231
+
232
+ describe "on x == [2]" do
233
+ let(:scope){ { :x => [2], :y => [8,9] } }
234
+
235
+ it{ expect(predicate.evaluate(scope)).to be_falsy }
236
+ end
237
+
238
+ describe "on x == []" do
239
+ let(:scope){ { :x => [], :y => [8,9] } }
240
+
241
+ it{ expect(predicate.evaluate(scope)).to be_falsy }
242
+ end
243
+
244
+ describe "on x == [9]" do
245
+ let(:scope){ { :x => [9], :y => [8,9] } }
246
+
247
+ it{ expect(predicate.evaluate(scope)).to be_falsy }
248
+ end
249
+
250
+ describe "on x == [8, 9]" do
251
+ let(:scope){ { :x => [8, 9], :y => [8,9] } }
252
+
253
+ it{ expect(predicate.evaluate(scope)).to be_truthy }
254
+ end
255
+
256
+ describe "on x == [8, 9, 10]" do
257
+ let(:scope){ { :x => [8, 9, 19], :y => [8,9] } }
258
+
259
+ it{ expect(predicate.evaluate(scope)).to be_truthy }
260
+ end
261
+ end
262
+
101
263
  context 'on a match against a string' do
102
264
  let(:predicate){
103
265
  Predicate.match(:x, "12")
@@ -228,12 +390,52 @@ class Predicate
228
390
 
229
391
  context 'has a call alias' do
230
392
  let(:predicate){
231
- Predicate.new(Factory.gte(:x => 0))
393
+ Predicate.gte(:x => 0)
232
394
  }
233
395
 
234
396
  let(:scope){ { x: 2 } }
235
397
 
236
398
  it{ expect(predicate.call(scope)).to be(true) }
237
399
  end
400
+
401
+ context "on a var, build with a dotted string" do
402
+ let(:predicate){
403
+ Predicate.var("x.0.y")
404
+ }
405
+
406
+ let(:scope){ { x: [{ y: 2 }] } }
407
+
408
+ it{ expect(predicate.call(scope)).to eql(2) }
409
+ end
410
+
411
+ context "on a var, build with a . string" do
412
+ let(:predicate){
413
+ Predicate.var(".")
414
+ }
415
+
416
+ let(:scope){ { x: [{ y: 2 }] } }
417
+
418
+ it{ expect(predicate.call(scope)).to be(scope) }
419
+ end
420
+
421
+ context "on a var, build with an array for terms" do
422
+ let(:predicate){
423
+ Predicate.var([:x, 0, :y])
424
+ }
425
+
426
+ let(:scope){ { x: [{ y: 2 }] } }
427
+
428
+ it{ expect(predicate.call(scope)).to eql(2) }
429
+ end
430
+
431
+ context "on a var, build with an empty array" do
432
+ let(:predicate){
433
+ Predicate.var([])
434
+ }
435
+
436
+ let(:scope){ { x: [{ y: 2 }] } }
437
+
438
+ it{ expect(predicate.call(scope)).to be(scope) }
439
+ end
238
440
  end
239
441
  end