predicate 2.3.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) 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 +26 -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/and.rb +9 -0
  13. data/lib/predicate/nodes/binary_func.rb +20 -0
  14. data/lib/predicate/nodes/contradiction.rb +2 -7
  15. data/lib/predicate/nodes/dyadic_comp.rb +3 -5
  16. data/lib/predicate/nodes/empty.rb +14 -0
  17. data/lib/predicate/nodes/eq.rb +13 -6
  18. data/lib/predicate/nodes/expr.rb +9 -3
  19. data/lib/predicate/nodes/has_size.rb +14 -0
  20. data/lib/predicate/nodes/identifier.rb +1 -3
  21. data/lib/predicate/nodes/in.rb +9 -8
  22. data/lib/predicate/nodes/intersect.rb +3 -23
  23. data/lib/predicate/nodes/literal.rb +1 -3
  24. data/lib/predicate/nodes/match.rb +1 -21
  25. data/lib/predicate/nodes/nadic_bool.rb +1 -3
  26. data/lib/predicate/nodes/native.rb +1 -3
  27. data/lib/predicate/nodes/not.rb +1 -3
  28. data/lib/predicate/nodes/opaque.rb +1 -3
  29. data/lib/predicate/nodes/qualified_identifier.rb +2 -4
  30. data/lib/predicate/nodes/set_op.rb +26 -0
  31. data/lib/predicate/nodes/subset.rb +11 -0
  32. data/lib/predicate/nodes/superset.rb +11 -0
  33. data/lib/predicate/nodes/tautology.rb +6 -7
  34. data/lib/predicate/nodes/unary_func.rb +16 -0
  35. data/lib/predicate/nodes/var.rb +46 -0
  36. data/lib/predicate/processors.rb +1 -0
  37. data/lib/predicate/processors/qualifier.rb +4 -0
  38. data/lib/predicate/processors/renamer.rb +4 -0
  39. data/lib/predicate/processors/to_s.rb +28 -0
  40. data/lib/predicate/processors/unqualifier.rb +21 -0
  41. data/lib/predicate/sequel/to_sequel.rb +4 -1
  42. data/lib/predicate/sugar.rb +47 -0
  43. data/lib/predicate/version.rb +1 -1
  44. data/spec/dsl/test_dsl.rb +204 -0
  45. data/spec/dsl/test_evaluate.rb +65 -0
  46. data/spec/dsl/test_respond_to_missing.rb +35 -0
  47. data/spec/dsl/test_to_skake_case.rb +38 -0
  48. data/spec/factory/shared/a_comparison_factory_method.rb +1 -0
  49. data/spec/factory/test_${op_name}.rb.jeny +12 -0
  50. data/spec/factory/test_empty.rb +11 -0
  51. data/spec/factory/test_has_size.rb +11 -0
  52. data/spec/factory/test_match.rb +1 -0
  53. data/spec/factory/test_set_ops.rb +18 -0
  54. data/spec/factory/test_var.rb +22 -0
  55. data/spec/factory/test_vars.rb +27 -0
  56. data/spec/nodes/${op_name}.jeny/test_evaluate.rb.jeny +19 -0
  57. data/spec/nodes/empty/test_evaluate.rb +42 -0
  58. data/spec/nodes/eq/test_and.rb +6 -0
  59. data/spec/nodes/has_size/test_evaluate.rb +44 -0
  60. data/spec/nodes/qualified_identifier/test_and_split.rb +1 -1
  61. data/spec/nodes/qualified_identifier/test_free_variables.rb +1 -1
  62. data/spec/predicate/test_and_split.rb +18 -0
  63. data/spec/predicate/test_attr_split.rb +18 -0
  64. data/spec/predicate/test_bool_and.rb +11 -0
  65. data/spec/predicate/test_constant_variables.rb +24 -2
  66. data/spec/predicate/test_constants.rb +24 -0
  67. data/spec/predicate/test_evaluate.rb +205 -3
  68. data/spec/predicate/test_free_variables.rb +1 -1
  69. data/spec/predicate/test_to_hash.rb +40 -0
  70. data/spec/predicate/test_to_s.rb +37 -0
  71. data/spec/predicate/test_unqualify.rb +18 -0
  72. data/spec/sequel/test_to_sequel.rb +16 -0
  73. data/spec/shared/a_predicate.rb +30 -0
  74. data/spec/spec_helper.rb +1 -0
  75. data/spec/test_predicate.rb +68 -33
  76. data/spec/test_readme.rb +80 -0
  77. data/spec/test_sugar.rb +48 -0
  78. data/tasks/test.rake +3 -3
  79. metadata +43 -13
  80. data/spec/factory/test_between.rb +0 -12
  81. data/spec/factory/test_intersect.rb +0 -12
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Predicate in a curried form' do
4
+
5
+ context 'with a variable name' do
6
+ it 'supports tautology, contradiction' do
7
+ p = Predicate.currying(:x){ tautology }
8
+ expect(p).to eql(Predicate.tautology)
9
+
10
+ p = Predicate.currying(:x){ contradiction }
11
+ expect(p).to eql(Predicate.contradiction)
12
+ end
13
+
14
+ it 'support comparison operators' do
15
+ p = Predicate.currying(:x){
16
+ gt(0) & lt(12)
17
+ }
18
+ expect(p.evaluate(x: 0)).to be_falsy
19
+ expect(p.evaluate(x: 1)).to be_truthy
20
+ expect(p.evaluate(x: 11)).to be_truthy
21
+ expect(p.evaluate(x: 12)).to be_falsy
22
+ end
23
+
24
+ it 'supports between shortcut' do
25
+ p = Predicate.currying(:x){
26
+ between(1, 11)
27
+ }
28
+ expect(p.evaluate(x: 0)).to be_falsy
29
+ expect(p.evaluate(x: 1)).to be_truthy
30
+ expect(p.evaluate(x: 11)).to be_truthy
31
+ expect(p.evaluate(x: 12)).to be_falsy
32
+ end
33
+
34
+ it 'supports match' do
35
+ p = Predicate.currying(:x){
36
+ match(/a/)
37
+ }
38
+ expect(p.evaluate(x: "zzz")).to be_falsy
39
+ expect(p.evaluate(x: "abc")).to be_truthy
40
+ end
41
+ end
42
+
43
+ context 'without a variable name' do
44
+ it 'applies to the object passed' do
45
+ p = Predicate.currying{
46
+ gt(0) & lt(12)
47
+ }
48
+ expect(p.evaluate(0)).to be_falsy
49
+ expect(p.evaluate(1)).to be_truthy
50
+ expect(p.evaluate(11)).to be_truthy
51
+ expect(p.evaluate(12)).to be_falsy
52
+ end
53
+ end
54
+
55
+ context 'on shortcuts' do
56
+ it 'applies to the object passed' do
57
+ p = Predicate.currying{
58
+ min_size(5)
59
+ }
60
+ expect(p.evaluate("1")).to be_falsy
61
+ expect(p.evaluate("013456789")).to be_truthy
62
+ end
63
+ end
64
+
65
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ class Predicate
4
+ describe Dsl do
5
+ subject{
6
+ Predicate::Dsl.new(:x)
7
+ }
8
+
9
+ it 'respond to negated forms' do
10
+ [
11
+ :notSize,
12
+ :notEq
13
+ ].each do |m|
14
+ expect(subject.send(:respond_to_missing?, m)).to eql(true)
15
+ end
16
+ end
17
+
18
+ it 'respond to camelCased forms' do
19
+ [
20
+ :hasSize
21
+ ].each do |m|
22
+ expect(subject.send(:respond_to_missing?, m)).to eql(true)
23
+ end
24
+ end
25
+
26
+ it 'respond false otherwise' do
27
+ [
28
+ :nosuchone,
29
+ :notsuchone
30
+ ].each do |m|
31
+ expect(subject.send(:respond_to_missing?, m)).to eql(false)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ class Predicate
4
+ describe Dsl, "to_snake_case" do
5
+
6
+ def snake(str)
7
+ Dsl.new.__send__(:to_snake_case, str)
8
+ end
9
+
10
+ it 'works on snake case already' do
11
+ {
12
+ "snake" => "snake",
13
+ "snake_case" => "snake_case"
14
+ }.each_pair do |k,v|
15
+ expect(snake(k)).to eq(v)
16
+ end
17
+ end
18
+
19
+ it 'works on camelCase already' do
20
+ {
21
+ "camelCase" => "camel_case",
22
+ "theCamelCase" => "the_camel_case"
23
+ }.each_pair do |k,v|
24
+ expect(snake(k)).to eq(v)
25
+ end
26
+ end
27
+
28
+ it 'works on PascalCase already' do
29
+ {
30
+ "PascalCase" => "pascal_case",
31
+ "ThePascalCase" => "the_pascal_case"
32
+ }.each_pair do |k,v|
33
+ expect(snake(k)).to eq(v)
34
+ end
35
+ end
36
+
37
+ end
38
+ end
@@ -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
@@ -26,5 +26,11 @@ class Predicate
26
26
  it{ should be_a(And) }
27
27
  end
28
28
 
29
+ context 'with an IN having a placeholder' do
30
+ let(:right){ Factory.in(:x, Factory.placeholder) }
31
+
32
+ it{ should be_a(And) }
33
+ end
34
+
29
35
  end
30
36
  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
@@ -8,7 +8,7 @@ class Predicate
8
8
  subject{ predicate.and_split(list) }
9
9
 
10
10
  context 'when included' do
11
- let(:list){ [:id, :name] }
11
+ let(:list){ [:"t.id", :name] }
12
12
 
13
13
  it{ should eq([predicate, tautology]) }
14
14
  end
@@ -6,7 +6,7 @@ class Predicate
6
6
 
7
7
  subject{ expr.free_variables }
8
8
 
9
- it{ should eq([:id]) }
9
+ it{ should eq([:"t.id"]) }
10
10
 
11
11
  end
12
12
  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