predicated 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -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
|
data/lib/predicated/predicate.rb
CHANGED
@@ -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
|
8
|
+
extend Shorthand
|
9
9
|
result = instance_eval(&block)
|
10
10
|
end
|
11
11
|
result
|
12
12
|
end
|
13
13
|
|
14
|
-
class
|
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
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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"
|
data/lib/predicated/print.rb
CHANGED
@@ -1,20 +1,16 @@
|
|
1
1
|
module Predicated
|
2
|
-
|
3
|
-
def inspect
|
4
|
-
|
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
|
13
|
-
part_to_str(thing) {|thing| thing.
|
8
|
+
def part_to_s(thing)
|
9
|
+
part_to_str(thing) {|thing| thing.to_s}
|
14
10
|
end
|
15
11
|
|
16
|
-
def
|
17
|
-
part_to_str(thing, indent) {|thing| thing.
|
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
|
46
|
+
def inspect(indent="")
|
35
47
|
next_indent = indent + " " + " "
|
36
48
|
|
37
49
|
str = "#{indent}#{self.class.shorthand}(\n"
|
38
|
-
str << "#{
|
39
|
-
str << "#{
|
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
|