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