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.
Files changed (51) hide show
  1. data/README.markdown +183 -1
  2. data/lib/predicated.rb +1 -3
  3. data/lib/predicated/constrain.rb +2 -0
  4. data/lib/predicated/evaluate.rb +25 -6
  5. data/lib/predicated/from/callable_object.rb +7 -8
  6. data/lib/predicated/from/json.rb +59 -0
  7. data/lib/predicated/from/{ruby_string.rb → ruby_code_string.rb} +13 -5
  8. data/lib/predicated/from/{url_fragment.rb → url_part.rb} +17 -10
  9. data/lib/predicated/from/xml.rb +61 -0
  10. data/lib/predicated/predicate.rb +62 -43
  11. data/lib/predicated/print.rb +28 -16
  12. data/lib/predicated/selectable.rb +102 -0
  13. data/lib/predicated/simple_templated_predicate.rb +79 -0
  14. data/lib/predicated/string_utils.rb +20 -0
  15. data/lib/predicated/to/arel.rb +28 -22
  16. data/lib/predicated/to/json.rb +48 -0
  17. data/lib/predicated/to/sentence.rb +17 -14
  18. data/lib/predicated/to/solr.rb +15 -0
  19. data/lib/predicated/to/xml.rb +67 -0
  20. data/lib/predicated/version.rb +2 -2
  21. data/test/canonical_transform_cases.rb +67 -0
  22. data/test/constrain_test.rb +20 -11
  23. data/test/enumerable_test.rb +6 -34
  24. data/test/equality_test.rb +15 -4
  25. data/test/evaluate_test.rb +31 -26
  26. data/test/from/callable_object_canonical_test.rb +43 -0
  27. data/test/from/callable_object_test.rb +16 -40
  28. data/test/from/json_test.rb +83 -0
  29. data/test/from/ruby_code_string_canonical_test.rb +37 -0
  30. data/test/from/ruby_code_string_test.rb +103 -0
  31. data/test/from/{url_fragment_parser_test.rb → url_part_parser_test.rb} +20 -13
  32. data/test/from/url_part_test.rb +48 -0
  33. data/test/from/xml_test.rb +57 -0
  34. data/test/json_conversion_test.rb +33 -0
  35. data/test/print_test.rb +26 -25
  36. data/test/selectable_test.rb +123 -0
  37. data/test/simple_templated_predicate_test.rb +39 -0
  38. data/test/suite.rb +2 -4
  39. data/test/test_helper.rb +26 -4
  40. data/test/test_helper_with_wrong.rb +3 -2
  41. data/test/to/arel_test.rb +71 -31
  42. data/test/to/json_test.rb +74 -0
  43. data/test/to/sentence_test.rb +41 -34
  44. data/test/to/solr_test.rb +39 -0
  45. data/test/to/xml_test.rb +72 -0
  46. data/test/xml_conversion_test.rb +34 -0
  47. metadata +44 -16
  48. data/lib/predicated/selector.rb +0 -55
  49. data/test/from/ruby_string_test.rb +0 -135
  50. data/test/from/url_fragment_test.rb +0 -37
  51. data/test/selector_test.rb +0 -82
@@ -1,8 +1,190 @@
1
- Predicated is a simple predicate model for Ruby.
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
 
@@ -1,6 +1,4 @@
1
1
  $LOAD_PATH.unshift(File.dirname(__FILE__))
2
2
 
3
3
  require "predicated/gem_check"
4
- require "predicated/predicate"
5
- require "predicated/evaluate"
6
- require "predicated/constrain"
4
+ require "predicated/predicate"
@@ -1,3 +1,5 @@
1
+ require "predicated/selectable"
2
+
1
3
  module Predicated
2
4
  class Constraints
3
5
  def initialize
@@ -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
- left.send(@method_sym, *right)
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 inspect
40
- "Call(#{self.send(:part_inspect,left)}.#{method_sym.to_s}(#{self.send(:part_inspect, right)}))"
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
- Predicate.module_eval(%{
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
- end
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/ruby_string"
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
- module Predicate
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
- from_ruby_string(TranslateToRubyString.convert(callable_object), context)
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
- ruby_string = Ruby2Ruby.translate(temp_class, :serializable)
36
- ruby_string.sub(/^def serializable\n /, "").sub(/\nend$/, "")
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
- module Predicate
10
- def self.from_ruby_string(ruby_predicate_string, context=binding())
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
- eval(Ruby2Ruby.new.process(upper_sexp), @context)
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 = eval(Ruby2Ruby.new.process(left_sexp), @context)
40
- right = eval(Ruby2Ruby.new.process(right_sexp), @context)
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
- module Predicate
8
- def self.from_url_fragment(url_fragment_string)
9
- TreetopUrlFragmentParser.new.parse(url_fragment_string).to_predicate
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 TreetopUrlFragment
13
+ module TreetopUrlPart
14
14
  Treetop.load_from_string(%{
15
15
 
16
- grammar TreetopUrlFragment
16
+ grammar TreetopUrlPart
17
17
 
18
- include Predicated::TreetopUrlFragment
18
+ include Predicated::TreetopUrlPart
19
19
 
20
20
  rule or
21
- ( and "|" or <OrNode>) / and
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[1] end
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