predicated 0.1.0 → 0.2.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.
- data/README.markdown +183 -1
- data/lib/predicated.rb +1 -3
- data/lib/predicated/constrain.rb +2 -0
- data/lib/predicated/evaluate.rb +25 -6
- data/lib/predicated/from/callable_object.rb +7 -8
- data/lib/predicated/from/json.rb +59 -0
- data/lib/predicated/from/{ruby_string.rb → ruby_code_string.rb} +13 -5
- data/lib/predicated/from/{url_fragment.rb → url_part.rb} +17 -10
- data/lib/predicated/from/xml.rb +61 -0
- data/lib/predicated/predicate.rb +62 -43
- data/lib/predicated/print.rb +28 -16
- data/lib/predicated/selectable.rb +102 -0
- data/lib/predicated/simple_templated_predicate.rb +79 -0
- data/lib/predicated/string_utils.rb +20 -0
- data/lib/predicated/to/arel.rb +28 -22
- data/lib/predicated/to/json.rb +48 -0
- data/lib/predicated/to/sentence.rb +17 -14
- data/lib/predicated/to/solr.rb +15 -0
- data/lib/predicated/to/xml.rb +67 -0
- data/lib/predicated/version.rb +2 -2
- data/test/canonical_transform_cases.rb +67 -0
- data/test/constrain_test.rb +20 -11
- data/test/enumerable_test.rb +6 -34
- data/test/equality_test.rb +15 -4
- data/test/evaluate_test.rb +31 -26
- data/test/from/callable_object_canonical_test.rb +43 -0
- data/test/from/callable_object_test.rb +16 -40
- data/test/from/json_test.rb +83 -0
- data/test/from/ruby_code_string_canonical_test.rb +37 -0
- data/test/from/ruby_code_string_test.rb +103 -0
- data/test/from/{url_fragment_parser_test.rb → url_part_parser_test.rb} +20 -13
- data/test/from/url_part_test.rb +48 -0
- data/test/from/xml_test.rb +57 -0
- data/test/json_conversion_test.rb +33 -0
- data/test/print_test.rb +26 -25
- data/test/selectable_test.rb +123 -0
- data/test/simple_templated_predicate_test.rb +39 -0
- data/test/suite.rb +2 -4
- data/test/test_helper.rb +26 -4
- data/test/test_helper_with_wrong.rb +3 -2
- data/test/to/arel_test.rb +71 -31
- data/test/to/json_test.rb +74 -0
- data/test/to/sentence_test.rb +41 -34
- data/test/to/solr_test.rb +39 -0
- data/test/to/xml_test.rb +72 -0
- data/test/xml_conversion_test.rb +34 -0
- metadata +44 -16
- data/lib/predicated/selector.rb +0 -55
- data/test/from/ruby_string_test.rb +0 -135
- data/test/from/url_fragment_test.rb +0 -37
- data/test/selector_test.rb +0 -82
data/README.markdown
CHANGED
@@ -1,8 +1,190 @@
|
|
1
|
-
|
1
|
+
## Abstract ##
|
2
|
+
|
3
|
+
Predicated is a simple predicate model for Ruby. It provides useful predicate transformations and operations.
|
2
4
|
|
3
5
|
Tracker project:
|
4
6
|
[http://www.pivotaltracker.com/projects/95014](http://www.pivotaltracker.com/projects/95014)
|
5
7
|
|
8
|
+
## Transformations ##
|
9
|
+
|
10
|
+
- From:
|
11
|
+
- json
|
12
|
+
- xml
|
13
|
+
- url part, ex: "!(a=1&b=2|c=3)"
|
14
|
+
- callable objects (lambdas/procs, and therefore blocks) - ruby 1.8.x only
|
15
|
+
- ruby code - ruby 1.8.x only
|
16
|
+
- To:
|
17
|
+
- json
|
18
|
+
- xml
|
19
|
+
- sql where clause via [arel](http://github.com/rails/arel)
|
20
|
+
- [solr](http://lucene.apache.org/solr/) query string
|
21
|
+
- english sentence, ex: "'a' is not equal to 'b'"
|
22
|
+
|
23
|
+
## Usage ##
|
24
|
+
|
25
|
+
Note: The test suite acts as a comprehensive usage guide.
|
26
|
+
|
27
|
+
|
28
|
+
Evaluate a predicate:
|
29
|
+
|
30
|
+
require "predicated/evaluate"
|
31
|
+
include Predicated
|
32
|
+
|
33
|
+
Predicate { Eq(1, 2) }.evaluate == false
|
34
|
+
Predicate { Lt(1, 2) }.evaluate == true
|
35
|
+
Predicate { Or(Lt(1, 2),Eq(1, 2)) }.evaluate == true
|
36
|
+
|
37
|
+
x = 1
|
38
|
+
Predicate { Lt(x, 2) }.evaluate == true
|
39
|
+
|
40
|
+
|
41
|
+
Parse a predicate from part of a url and then turn it into a sql where clause:
|
42
|
+
|
43
|
+
require "predicated/from/url_part"
|
44
|
+
require "predicated/to/arel"
|
45
|
+
|
46
|
+
predicate = Predicated::Predicate.from_url_part("(color=red|color=green)&size=large")
|
47
|
+
|
48
|
+
predicate.inspect ==
|
49
|
+
"And(Or(Eq('color','red'),Eq('color','green')),Eq('size','large'))"
|
50
|
+
|
51
|
+
predicate.to_arel(Table(:shirt)).to_sql ==
|
52
|
+
%{(("shirt"."color" = 'red' OR "shirt"."color" = 'green') AND "shirt"."size" = 'large')}
|
53
|
+
|
54
|
+
|
55
|
+
Parse a predicate from json and then turn it into a solr query string:
|
56
|
+
|
57
|
+
require "predicated/from/json"
|
58
|
+
require "predicated/to/solr"
|
59
|
+
|
60
|
+
predicate = Predicated::Predicate.from_json_str(%{
|
61
|
+
{"and":[{"or":[["color","==","red"],["color","==","green"]]},["size","==","large"]]}
|
62
|
+
})
|
63
|
+
|
64
|
+
predicate.inspect == "And(Or(Eq('color','red'),Eq('color','green')),Eq('size','large'))"
|
65
|
+
|
66
|
+
predicate.to_solr == "((color:red OR color:green) AND size:large)"
|
67
|
+
|
68
|
+
|
69
|
+
From json:
|
70
|
+
|
71
|
+
require "predicated/from/json"
|
72
|
+
|
73
|
+
Predicated::Predicate.from_json_str(%{
|
74
|
+
{"and":[
|
75
|
+
{"or":[
|
76
|
+
["color","==","red"],
|
77
|
+
["color","==","green"]
|
78
|
+
]},
|
79
|
+
["size","==","large"]
|
80
|
+
]}
|
81
|
+
}).inspect == "And(Or(Eq('color','red'),Eq('color','green')),Eq('size','large'))"
|
82
|
+
|
83
|
+
|
84
|
+
From xml:
|
85
|
+
|
86
|
+
require "predicated/from/xml"
|
87
|
+
|
88
|
+
Predicated::Predicate.from_xml(%{
|
89
|
+
<and>
|
90
|
+
<or>
|
91
|
+
<equal><left>color</left><right>red</right></equal>
|
92
|
+
<equal><left>color</left><right>green</right></equal>
|
93
|
+
</or>
|
94
|
+
<equal><left>size</left><right>large</right></equal>
|
95
|
+
</and>
|
96
|
+
}).inspect == "And(Or(Eq('color','red'),Eq('color','green')),Eq('size','large'))"
|
97
|
+
|
98
|
+
|
99
|
+
From url part:
|
100
|
+
|
101
|
+
require "predicated/from/url_part"
|
102
|
+
|
103
|
+
Predicated::Predicate.from_url_part("(color=red|color=green)&size=large").inspect ==
|
104
|
+
"And(Or(Eq('color','red'),Eq('color','green')),Eq('size','large'))"
|
105
|
+
|
106
|
+
|
107
|
+
From callable object:
|
108
|
+
|
109
|
+
require "predicated/from/callable_object"
|
110
|
+
|
111
|
+
Predicated::Predicate.from_callable_object{('color'=='red' || 'color'=='green') && 'size'=='large'}.inspect ==
|
112
|
+
"And(Or(Eq('color','red'),Eq('color','green')),Eq('size','large'))"
|
113
|
+
|
114
|
+
|
115
|
+
From ruby code string:
|
116
|
+
|
117
|
+
require "predicated/from/ruby_code_string"
|
118
|
+
|
119
|
+
Predicated::Predicate.from_ruby_code_string("('color'=='red' || 'color'=='green') && 'size'=='large'").inspect ==
|
120
|
+
"And(Or(Eq('color','red'),Eq('color','green')),Eq('size','large'))"
|
121
|
+
|
122
|
+
|
123
|
+
To json:
|
124
|
+
|
125
|
+
require "predicated/to/json"
|
126
|
+
include Predicated
|
127
|
+
|
128
|
+
Predicate{And(Or(Eq('color','red'),Eq('color','green')),Eq('size','large'))}.to_json_str ==
|
129
|
+
%{
|
130
|
+
{"and":[
|
131
|
+
{"or":[
|
132
|
+
["color","==","red"],
|
133
|
+
["color","==","green"]
|
134
|
+
]},
|
135
|
+
["size","==","large"]
|
136
|
+
]}
|
137
|
+
}
|
138
|
+
|
139
|
+
|
140
|
+
To xml:
|
141
|
+
|
142
|
+
require "predicated/to/xml"
|
143
|
+
include Predicated
|
144
|
+
|
145
|
+
Predicate{And(Or(Eq('color','red'),Eq('color','green')),Eq('size','large'))}.to_xml ==
|
146
|
+
%{
|
147
|
+
<and>
|
148
|
+
<or>
|
149
|
+
<equal><left>color</left><right>red</right></equal>
|
150
|
+
<equal><left>color</left><right>green</right></equal>
|
151
|
+
</or>
|
152
|
+
<equal><left>size</left><right>large</right></equal>
|
153
|
+
</and>
|
154
|
+
}
|
155
|
+
|
156
|
+
|
157
|
+
To arel (sql where clause):
|
158
|
+
|
159
|
+
require "predicated/to/arel"
|
160
|
+
include Predicated
|
161
|
+
|
162
|
+
Predicate{And(Or(Eq('color','red'),Eq('color','green')),Eq('size','large'))}.to_arel(Table(:shirt)).to_sql ==
|
163
|
+
%{(("shirt"."color" = 'red' OR "shirt"."color" = 'green') AND "shirt"."size" = 'large')}
|
164
|
+
|
165
|
+
|
166
|
+
To solr query string:
|
167
|
+
|
168
|
+
require "predicated/to/solr"
|
169
|
+
include Predicated
|
170
|
+
|
171
|
+
Predicate{And(Or(Eq('color','red'),Eq('color','green')),Eq('size','large'))}.to_solr ==
|
172
|
+
"((color:red OR color:green) AND size:large)"
|
173
|
+
|
174
|
+
|
175
|
+
To sentence:
|
176
|
+
|
177
|
+
require "predicated/to/sentence"
|
178
|
+
include Predicated
|
179
|
+
|
180
|
+
Predicate{ And(Eq("a",1),Eq("b",2)) }.to_sentence ==
|
181
|
+
"'a' is equal to 1 and 'b' is equal to 2"
|
182
|
+
|
183
|
+
Predicate{ Gt("a",1) }.to_negative_sentence ==
|
184
|
+
"'a' is not greater than 1"
|
185
|
+
|
186
|
+
|
187
|
+
## Testing Notes ##
|
6
188
|
|
7
189
|
Right now this project makes use of Wrong for assertions. Wrong uses this project. It's kind of neat in an eat-your-own-dogfood sense, but it's possible that this will be problematic over time (particularly when changes in this project cause assertions to behave differently - if even temporarily).
|
8
190
|
|
data/lib/predicated.rb
CHANGED
data/lib/predicated/constrain.rb
CHANGED
data/lib/predicated/evaluate.rb
CHANGED
@@ -3,7 +3,7 @@ require "predicated/predicate"
|
|
3
3
|
module Predicated
|
4
4
|
|
5
5
|
|
6
|
-
class Operation
|
6
|
+
class Operation < Binary
|
7
7
|
attr_reader :method_sym
|
8
8
|
|
9
9
|
def initialize(left, method_sym, right)
|
@@ -12,7 +12,8 @@ module Predicated
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def evaluate
|
15
|
-
|
15
|
+
right_values = right.nil? ? [nil] : right #1.9 problem where nils and varargs don't play nicely
|
16
|
+
left.send(@method_sym, *right_values)
|
16
17
|
end
|
17
18
|
|
18
19
|
def ==(other)
|
@@ -36,11 +37,22 @@ module Predicated
|
|
36
37
|
super
|
37
38
|
end
|
38
39
|
|
39
|
-
def
|
40
|
-
"Call(#{
|
40
|
+
def to_s
|
41
|
+
"Call(#{left_to_s}.#{method_sym.to_s}#{right_to_s})"
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def left_to_s
|
46
|
+
part_to_s(left)
|
47
|
+
end
|
48
|
+
|
49
|
+
def right_to_s
|
50
|
+
values = right.is_a?(::Enumerable) ? right : [right]
|
51
|
+
values.empty? ? "" :
|
52
|
+
"(" + values.collect{|arg|part_to_s(arg)}.join(",") + ")"
|
41
53
|
end
|
42
54
|
end
|
43
|
-
|
55
|
+
Shorthand.module_eval(%{
|
44
56
|
def Call(left_object, method_sym, right_args=[])
|
45
57
|
::Predicated::Call.new(left_object, method_sym, right_args)
|
46
58
|
end
|
@@ -72,4 +84,11 @@ module Predicated
|
|
72
84
|
boolean_or_evaluate(left) || boolean_or_evaluate(right)
|
73
85
|
end
|
74
86
|
end
|
75
|
-
|
87
|
+
|
88
|
+
class Not
|
89
|
+
include Container
|
90
|
+
def evaluate
|
91
|
+
! boolean_or_evaluate(inner)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -1,13 +1,12 @@
|
|
1
1
|
require "predicated/predicate"
|
2
|
-
require "predicated/from/
|
2
|
+
require "predicated/from/ruby_code_string"
|
3
3
|
|
4
4
|
#Procs and lambdas are "callable objects"
|
5
|
-
|
6
5
|
module Predicated
|
7
6
|
|
8
|
-
require_gem_version("ParseTree", "3.0.5", "parse_tree")
|
7
|
+
require_gem_version("ParseTree", "3.0.5", "parse_tree") if RUBY_VERSION < "1.9"
|
9
8
|
|
10
|
-
|
9
|
+
class Predicate
|
11
10
|
|
12
11
|
#hrm
|
13
12
|
def self.from_callable_object(context_or_callable_object=nil, context=nil, &block)
|
@@ -22,7 +21,7 @@ module Predicated
|
|
22
21
|
|
23
22
|
context ||= callable_object.binding
|
24
23
|
|
25
|
-
|
24
|
+
from_ruby_code_string(TranslateToRubyString.convert(callable_object), context)
|
26
25
|
end
|
27
26
|
|
28
27
|
module TranslateToRubyString
|
@@ -32,8 +31,8 @@ module Predicated
|
|
32
31
|
temp_class.class_eval do
|
33
32
|
define_method :serializable, &callable_object
|
34
33
|
end
|
35
|
-
|
36
|
-
|
34
|
+
ruby_code_string = Ruby2Ruby.translate(temp_class, :serializable)
|
35
|
+
ruby_code_string.sub(/^def serializable\n /, "").sub(/\nend$/, "")
|
37
36
|
end
|
38
37
|
end
|
39
38
|
|
@@ -76,4 +75,4 @@ module Predicated
|
|
76
75
|
end
|
77
76
|
|
78
77
|
end
|
79
|
-
end
|
78
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "predicated/predicate"
|
2
|
+
|
3
|
+
module Predicated
|
4
|
+
|
5
|
+
require_gem_version("json", "1.1.9")
|
6
|
+
|
7
|
+
class Predicate
|
8
|
+
def self.from_json_str(json_str)
|
9
|
+
from_json_struct(JSON.parse(json_str))
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.from_json_struct(json_struct)
|
13
|
+
JsonStructToPredicate.convert(json_struct)
|
14
|
+
end
|
15
|
+
|
16
|
+
module JsonStructToPredicate
|
17
|
+
SIGN_TO_CLASS = {
|
18
|
+
"==" => Equal,
|
19
|
+
">" => GreaterThan,
|
20
|
+
"<" => LessThan,
|
21
|
+
">=" => GreaterThanOrEqualTo,
|
22
|
+
"<=" => LessThanOrEqualTo
|
23
|
+
}
|
24
|
+
|
25
|
+
def self.convert(json_struct)
|
26
|
+
if json_struct.is_a?(Array)
|
27
|
+
left, sign, right = json_struct
|
28
|
+
if operation_class=SIGN_TO_CLASS[sign]
|
29
|
+
operation_class.new(left, right)
|
30
|
+
else
|
31
|
+
raise DontKnowWhatToDoWithThisJson.new(json_struct)
|
32
|
+
end
|
33
|
+
elsif json_struct.is_a?(Hash)
|
34
|
+
if left_and_right=json_struct["and"]
|
35
|
+
left, right = left_and_right
|
36
|
+
And.new(convert(left), convert(right))
|
37
|
+
elsif left_and_right=json_struct["or"]
|
38
|
+
left, right = left_and_right
|
39
|
+
Or.new(convert(left), convert(right))
|
40
|
+
elsif inner=json_struct["not"]
|
41
|
+
Not.new(convert(inner))
|
42
|
+
else
|
43
|
+
raise DontKnowWhatToDoWithThisJson.new(json_struct)
|
44
|
+
end
|
45
|
+
else
|
46
|
+
raise DontKnowWhatToDoWithThisJson.new(json_struct)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
class DontKnowWhatToDoWithThisJson < StandardError
|
53
|
+
def initialize(json_struct)
|
54
|
+
super("don't know what to do with #{JSON.generate(json_struct)}")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -6,8 +6,8 @@ module Predicated
|
|
6
6
|
require_gem_version("ruby_parser", "2.0.4")
|
7
7
|
require_gem_version("ruby2ruby", "1.2.4")
|
8
8
|
|
9
|
-
|
10
|
-
def self.
|
9
|
+
class Predicate
|
10
|
+
def self.from_ruby_code_string(ruby_predicate_string, context=binding())
|
11
11
|
sexp = RubyParser.new.process(ruby_predicate_string.strip)
|
12
12
|
SexpToPredicate.new(context).convert(sexp)
|
13
13
|
end
|
@@ -31,13 +31,13 @@ module Predicated
|
|
31
31
|
#eval all the top lines and then treat the last one as a predicate
|
32
32
|
body_sexps = sexp.sexp_body.to_a
|
33
33
|
body_sexps.slice(0..-2).each do |upper_sexp|
|
34
|
-
|
34
|
+
eval_sexp(upper_sexp)
|
35
35
|
end
|
36
36
|
convert(body_sexps.last)
|
37
37
|
elsif first_element == :call
|
38
38
|
sym, left_sexp, method_sym, right_sexp = sexp
|
39
|
-
left =
|
40
|
-
right =
|
39
|
+
left = eval_sexp(left_sexp)
|
40
|
+
right = eval_sexp(right_sexp)
|
41
41
|
|
42
42
|
if operation_class=SIGN_TO_PREDICATE_CLASS[method_sym]
|
43
43
|
operation_class.new(left, right)
|
@@ -50,12 +50,20 @@ module Predicated
|
|
50
50
|
elsif first_element == :or
|
51
51
|
sym, left, right = sexp
|
52
52
|
Or.new(convert(left), convert(right))
|
53
|
+
elsif first_element == :not
|
54
|
+
sym, inner = sexp
|
55
|
+
Not.new(convert(inner))
|
53
56
|
else
|
54
57
|
raise DontKnowWhatToDoWithThisSexpError.new(sexp)
|
55
58
|
end
|
56
59
|
end
|
60
|
+
|
61
|
+
def eval_sexp(sexp)
|
62
|
+
eval(Ruby2Ruby.new.process(sexp), @context)
|
63
|
+
end
|
57
64
|
end
|
58
65
|
|
66
|
+
|
59
67
|
class DontKnowWhatToDoWithThisSexpError < StandardError
|
60
68
|
def initialize(sexp)
|
61
69
|
super("don't know what to do with #{sexp.inspect}")
|
@@ -4,21 +4,21 @@ module Predicated
|
|
4
4
|
|
5
5
|
require_gem_version("treetop", "1.4.8")
|
6
6
|
|
7
|
-
|
8
|
-
def self.
|
9
|
-
|
7
|
+
class Predicate
|
8
|
+
def self.from_url_part(url_part)
|
9
|
+
TreetopUrlPartParser.new.parse(url_part).to_predicate
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
module
|
13
|
+
module TreetopUrlPart
|
14
14
|
Treetop.load_from_string(%{
|
15
15
|
|
16
|
-
grammar
|
16
|
+
grammar TreetopUrlPart
|
17
17
|
|
18
|
-
include Predicated::
|
18
|
+
include Predicated::TreetopUrlPart
|
19
19
|
|
20
20
|
rule or
|
21
|
-
|
21
|
+
( and "|" or <OrNode>) / and
|
22
22
|
end
|
23
23
|
|
24
24
|
rule and
|
@@ -30,9 +30,14 @@ rule operation
|
|
30
30
|
end
|
31
31
|
|
32
32
|
rule parens
|
33
|
-
"(" or ")" <ParensNode>
|
33
|
+
not? "(" or ")" <ParensNode>
|
34
34
|
end
|
35
35
|
|
36
|
+
rule not
|
37
|
+
'!'
|
38
|
+
end
|
39
|
+
|
40
|
+
|
36
41
|
|
37
42
|
rule leaf
|
38
43
|
operation / parens
|
@@ -86,10 +91,12 @@ end
|
|
86
91
|
end
|
87
92
|
|
88
93
|
class ParensNode < Treetop::Runtime::SyntaxNode
|
89
|
-
def inner; elements[
|
94
|
+
def inner; elements[2] end
|
95
|
+
def not?; elements[0].text_value=="!" end
|
90
96
|
|
91
97
|
def to_predicate
|
92
|
-
inner.to_predicate
|
98
|
+
inner_predicate = inner.to_predicate
|
99
|
+
not? ? Not.new(inner_predicate) : inner_predicate
|
93
100
|
end
|
94
101
|
end
|
95
102
|
|