predicated 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,61 @@
1
+ require "predicated/predicate"
2
+
3
+ module Predicated
4
+
5
+ require_gem_version("nokogiri", "1.4.3")
6
+
7
+ class Predicate
8
+ def self.from_xml(xml_str)
9
+ NodeToPredicate.convert(Nokogiri::XML(xml_str).root)
10
+ end
11
+
12
+ module NodeToPredicate
13
+ OPERATION_TAG_TO_CLASS = {
14
+ "equal" => Equal,
15
+ "greaterThan" => GreaterThan,
16
+ "lessThan" => LessThan,
17
+ "greaterThanOrEqualTo" => GreaterThanOrEqualTo,
18
+ "lessThanOrEqualTo" => LessThanOrEqualTo
19
+ }
20
+
21
+ def self.convert(node)
22
+
23
+ node = next_non_text_node(node)
24
+
25
+ if node.name == "and"
26
+ left = next_non_text_node(node.children[0])
27
+ right = next_non_text_node(left.next)
28
+ And.new(convert(left), convert(right))
29
+ elsif node.name == "or"
30
+ left = next_non_text_node(node.children[0])
31
+ right = next_non_text_node(left.next)
32
+ Or.new(convert(left), convert(right))
33
+ elsif node.name == "not"
34
+ inner = next_non_text_node(node.children[0])
35
+ Not.new(convert(inner))
36
+ elsif operation_class=OPERATION_TAG_TO_CLASS[node.name]
37
+ left = next_non_text_node(node.children[0])
38
+ right = next_non_text_node(left.next)
39
+ operation_class.new(left.text, right.text)
40
+ else
41
+ raise DontKnowWhatToDoWithThisXmlTag.new(node.name)
42
+ end
43
+ end
44
+
45
+ def self.next_non_text_node(node)
46
+ while node.text?
47
+ node = node.next
48
+ end
49
+ node
50
+ end
51
+
52
+ end
53
+
54
+ class DontKnowWhatToDoWithThisXmlTag < StandardError
55
+ def initialize(xml_tag)
56
+ super("don't know what to do with #{xml_tag}")
57
+ end
58
+ end
59
+
60
+ end
61
+ end
@@ -1,17 +1,45 @@
1
1
  require "predicated/gem_check"
2
- require "predicated/selector"
3
2
 
4
3
  module Predicated
4
+
5
5
  def Predicate(&block)
6
6
  result = nil
7
7
  Module.new do
8
- extend Predicate
8
+ extend Shorthand
9
9
  result = instance_eval(&block)
10
10
  end
11
11
  result
12
12
  end
13
13
 
14
- class Binary
14
+ class Predicate; end #marker class
15
+
16
+ class Unary < Predicate
17
+ attr_accessor :inner
18
+
19
+ def initialize(inner)
20
+ @inner = inner
21
+ end
22
+
23
+ module FlipThroughMe
24
+ def each(ancestors=[], &block)
25
+ yield([self, ancestors])
26
+ ancestors_including_me = ancestors.dup + [self]
27
+ inner.each(ancestors_including_me) { |item| block.call(item) } if inner.is_a?(Enumerable)
28
+ end
29
+ end
30
+ include FlipThroughMe
31
+ include Enumerable
32
+
33
+ module ValueEquality
34
+ def ==(other)
35
+ self.class == other.class &&
36
+ self.inner == other.inner
37
+ end
38
+ end
39
+ include ValueEquality
40
+ end
41
+
42
+ class Binary < Predicate
15
43
  attr_accessor :left, :right
16
44
 
17
45
  def initialize(left, right)
@@ -45,48 +73,39 @@ module Predicated
45
73
  include ValueEquality
46
74
  end
47
75
 
76
+
77
+
78
+
79
+ class And < Binary; def self.shorthand; :And end end
80
+ class Or < Binary; def self.shorthand; :Or end end
81
+ class Not < Unary; def self.shorthand; :Not end end
82
+
83
+
48
84
  class Operation < Binary; end
49
85
 
50
- module Predicate
51
- extend Predicated::Selector
52
-
53
- CLASS_INFO = [
54
- [:And, :And, Class.new(Binary)],
55
- [:Or, :Or, Class.new(Binary)],
56
- [:Equal, :Eq, Class.new(Operation)],
57
- [:LessThan, :Lt, Class.new(Operation)],
58
- [:GreaterThan, :Gt, Class.new(Operation)],
59
- [:LessThanOrEqualTo, :Lte, Class.new(Operation)],
60
- [:GreaterThanOrEqualTo, :Gte, Class.new(Operation)]
61
- ]
62
-
63
- #not great
64
- base_selector_enumerable = SelectorEnumerable(
65
- (CLASS_INFO.collect{|class_sym, sh, class_obj|class_obj} + [Binary, Operation]).
66
- inject({:all => proc{|predicate, enumerable|true}}) do |h, class_obj|
67
- h[class_obj] = proc{|predicate, enumerable|predicate.is_a?(class_obj)}
68
- h
69
- end
70
- )
71
-
72
- CLASS_INFO.each do |operation_class_name, shorthand, class_object|
73
- Predicated.const_set(operation_class_name, class_object)
74
- class_object.instance_variable_set("@shorthand".to_sym, shorthand)
75
- class_object.class_eval do
76
-
77
- def self.shorthand
78
- @shorthand
79
- end
80
-
81
- include base_selector_enumerable
82
- end
83
- module_eval(%{
84
- def #{shorthand}(left, right)
85
- ::Predicated::#{operation_class_name}.new(left, right)
86
- end
87
- })
88
- end
86
+ class Equal < Operation; def self.shorthand; :Eq end end
87
+ class LessThan < Operation; def self.shorthand; :Lt end end
88
+ class GreaterThan < Operation; def self.shorthand; :Gt end end
89
+ class GreaterThanOrEqualTo < Operation; def self.shorthand; :Gte end end
90
+ class LessThanOrEqualTo < Operation; def self.shorthand; :Lte end end
91
+
92
+
93
+ module Shorthand
94
+ def And(left, right) ::Predicated::And.new(left, right) end
95
+ def Or(left, right) ::Predicated::Or.new(left, right) end
96
+ def Not(inner) ::Predicated::Not.new(inner) end
97
+
98
+ def Eq(left, right) ::Predicated::Equal.new(left, right) end
99
+ def Lt(left, right) ::Predicated::LessThan.new(left, right) end
100
+ def Gt(left, right) ::Predicated::GreaterThan.new(left, right) end
101
+ def Lte(left, right) ::Predicated::LessThanOrEqualTo.new(left, right) end
102
+ def Gte(left, right) ::Predicated::GreaterThanOrEqualTo.new(left, right) end
89
103
  end
104
+
105
+ ALL_PREDICATE_CLASSES = [
106
+ And, Or, Not,
107
+ Equal, LessThan, GreaterThan, LessThanOrEqualTo, GreaterThanOrEqualTo
108
+ ]
90
109
  end
91
110
 
92
- require "predicated/print"
111
+ require "predicated/print"
@@ -1,20 +1,16 @@
1
1
  module Predicated
2
- class Binary
3
- def inspect
4
- "#{self.class.shorthand}(#{part_inspect(left)},#{part_inspect(right)})"
2
+ module PrintSupport
3
+ def inspect(indent="")
4
+ indent + to_s
5
5
  end
6
-
7
- def to_s(indent="")
8
- indent + inspect
9
- end
10
-
6
+
11
7
  private
12
- def part_inspect(thing)
13
- part_to_str(thing) {|thing| thing.inspect}
8
+ def part_to_s(thing)
9
+ part_to_str(thing) {|thing| thing.to_s}
14
10
  end
15
11
 
16
- def part_to_s(thing, indent="")
17
- part_to_str(thing, indent) {|thing| thing.to_s(indent)}
12
+ def part_inspect(thing, indent="")
13
+ part_to_str(thing, indent) {|thing| thing.inspect(indent)}
18
14
  end
19
15
 
20
16
  def part_to_str(thing, indent="")
@@ -24,19 +20,35 @@ module Predicated
24
20
  thing.to_s
25
21
  elsif thing.is_a?(Binary)
26
22
  yield(thing)
23
+ elsif thing.nil?
24
+ "nil"
27
25
  else
28
26
  "#{thing.class.name}{'#{thing.to_s}'}"
29
27
  end
30
28
  end
31
29
  end
30
+
31
+ class Unary < Predicate
32
+ include PrintSupport
33
+ def to_s
34
+ "#{self.class.shorthand}(#{part_to_s(inner)})"
35
+ end
36
+ end
37
+
38
+ class Binary < Predicate
39
+ include PrintSupport
40
+ def to_s
41
+ "#{self.class.shorthand}(#{part_to_s(left)},#{part_to_s(right)})"
42
+ end
43
+ end
32
44
 
33
45
  module ContainerToString
34
- def to_s(indent="")
46
+ def inspect(indent="")
35
47
  next_indent = indent + " " + " "
36
48
 
37
49
  str = "#{indent}#{self.class.shorthand}(\n"
38
- str << "#{part_to_s(left, next_indent)},\n"
39
- str << "#{part_to_s(right, next_indent)}\n"
50
+ str << "#{part_inspect(left, next_indent)},\n"
51
+ str << "#{part_inspect(right, next_indent)}\n"
40
52
  str << "#{indent})"
41
53
  str << "\n" if indent == ""
42
54
 
@@ -47,4 +59,4 @@ module Predicated
47
59
  class And; include ContainerToString end
48
60
  class Or; include ContainerToString end
49
61
 
50
- end
62
+ end
@@ -0,0 +1,102 @@
1
+
2
+ class Object
3
+ # todo: make this definition conditional
4
+ # todo: move this to a monkey patch file
5
+ def singleton_class
6
+ class << self
7
+ self
8
+ end
9
+ end
10
+ end
11
+
12
+ module Predicated
13
+ module Selectable
14
+ # Make an Enumerable instance into a Selectable.
15
+ # This does for instances what "include Selectable" does for classes.
16
+ # todo: rename?
17
+ def self.bless_enumerable(enumerable, selectors)
18
+ enumerable.singleton_class.instance_eval do
19
+ include Selectable
20
+ selector selectors
21
+ end
22
+ end
23
+
24
+ # merge several hashes into one, skipping nils
25
+ # todo: unit test
26
+ # todo: move onto Hash?
27
+ def self.merge_many(*hashes)
28
+ result = {}
29
+ hashes.compact.each do |hash|
30
+ result.merge! hash
31
+ end
32
+ result
33
+ end
34
+
35
+ SELECTORS = :@_predicated_selectors
36
+
37
+ def self.included(base)
38
+ base.extend ClassMethods
39
+ end
40
+
41
+ module ClassMethods
42
+ def selector(hash)
43
+ instance_variable_set(SELECTORS, Selectable.merge_many(instance_variable_get(SELECTORS), hash))
44
+ end
45
+ end
46
+
47
+ def selectors
48
+ class_selectors = self.class.instance_variable_get(SELECTORS)
49
+ singleton_selectors = self.singleton_class.instance_variable_get(SELECTORS)
50
+ instance_selectors = self.instance_variable_get(SELECTORS)
51
+ Selectable.merge_many(class_selectors, singleton_selectors, instance_selectors)
52
+ end
53
+
54
+ def select(*keys, &block)
55
+ if block_given?
56
+ super
57
+ else
58
+ key = keys.shift
59
+ result =
60
+ if key
61
+ selecting_proc = selectors[key]
62
+ raise "no selector found for '#{key}'. current selectors: [#{selectors.collect { |k, v| k.to_s }.join(",")}]" unless selecting_proc
63
+ memos_for(:select)[key] ||= begin
64
+ super(&selecting_proc)
65
+ end
66
+ else
67
+ raise "select must be called with either a key or a block"
68
+ end
69
+
70
+ Selectable.bless_enumerable(result, selectors)
71
+
72
+ if keys.length >= 1
73
+ result.select(*keys, &block)
74
+ else
75
+ result
76
+ end
77
+ end
78
+ end
79
+
80
+ private
81
+ def memos_for(group)
82
+ @_predicated_memos ||= {}
83
+ @_predicated_memos[group] ||= {}
84
+ end
85
+ end
86
+
87
+ all_basic_selectors = {:all => proc{|predicate, enumerable|true}}.merge(
88
+ ([Unary, Binary, Operation] + ALL_PREDICATE_CLASSES).inject({}) do |h, klass|
89
+ h[klass] = proc{|predicate, enumerable|predicate.is_a?(klass)}
90
+ h
91
+ end
92
+ )
93
+
94
+ ALL_PREDICATE_CLASSES.each do |klass|
95
+ klass.class_eval do
96
+ klass.send(:include, Selectable)
97
+ klass.selector all_basic_selectors
98
+ end
99
+ end
100
+ end
101
+
102
+
@@ -0,0 +1,79 @@
1
+ require "predicated/predicate"
2
+ require "predicated/evaluate"
3
+
4
+ module Predicated
5
+ def SimpleTemplatedPredicate(&block)
6
+ result = nil
7
+ Module.new do
8
+ extend SimpleTemplatedShorthand
9
+ result = instance_eval(&block)
10
+ end
11
+ result
12
+ end
13
+
14
+ class And
15
+ def fill_in(placeholder_replacement)
16
+ And.new(left.fill_in(placeholder_replacement), right.fill_in(placeholder_replacement))
17
+ end
18
+ end
19
+
20
+ class Or
21
+ def fill_in(placeholder_replacement)
22
+ Or.new(left.fill_in(placeholder_replacement), right.fill_in(placeholder_replacement))
23
+ end
24
+ end
25
+
26
+ class Not
27
+ def fill_in(placeholder_replacement)
28
+ Not.new(inner.fill_in(placeholder_replacement))
29
+ end
30
+ end
31
+
32
+ class Operation
33
+ def fill_in(placeholder_replacement)
34
+ self.class.new(placeholder_replacement, right)
35
+ end
36
+ end
37
+
38
+ class Call
39
+ def fill_in(placeholder_replacement)
40
+ self.class.new(placeholder_replacement, method_sym, right)
41
+ end
42
+ end
43
+
44
+ class Placeholder; end
45
+
46
+ module SimpleTemplatedShorthand
47
+ include Shorthand
48
+
49
+ def Eq(right) ::Predicated::Equal.new(Placeholder, right) end
50
+ def Lt(right) ::Predicated::LessThan.new(Placeholder, right) end
51
+ def Gt(right) ::Predicated::GreaterThan.new(Placeholder, right) end
52
+ def Lte(right) ::Predicated::LessThanOrEqualTo.new(Placeholder, right) end
53
+ def Gte(right) ::Predicated::GreaterThanOrEqualTo.new(Placeholder, right) end
54
+
55
+ def Call(method_sym, right=[]) ::Predicated::Call.new(Placeholder, method_sym, right) end
56
+ end
57
+
58
+ class Operation < Binary
59
+ def to_s
60
+ if left == Placeholder
61
+ "#{self.class.shorthand}(#{part_to_s(right)})"
62
+ else
63
+ super
64
+ end
65
+ end
66
+ end
67
+
68
+ class Call < Operation
69
+ def to_s
70
+ if left == Placeholder
71
+ "Call(#{method_sym.to_s}(#{part_to_s(right)}))"
72
+ else
73
+ super
74
+ end
75
+ end
76
+ end
77
+
78
+
79
+ end
@@ -0,0 +1,20 @@
1
+ module Predicated
2
+ module StringUtils
3
+
4
+ #Ripped from activesupport.
5
+ #If I start using a bunch of code from that project I'll create a dependency.
6
+ #I hope this is not bad form.
7
+ def self.underscore(camel_cased_word)
8
+ camel_cased_word.to_s.gsub(/::/, '/').
9
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
10
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
11
+ tr("-", "_").
12
+ downcase
13
+ end
14
+
15
+ def self.uppercamelize(lower_case_and_underscored_word)
16
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
17
+ end
18
+
19
+ end
20
+ end