predicate 2.3.0 → 2.5.0

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.
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