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