predicated 0.1.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.
@@ -0,0 +1,50 @@
1
+ module Predicated
2
+ class Binary
3
+ def inspect
4
+ "#{self.class.shorthand}(#{part_inspect(left)},#{part_inspect(right)})"
5
+ end
6
+
7
+ def to_s(indent="")
8
+ indent + inspect
9
+ end
10
+
11
+ private
12
+ def part_inspect(thing)
13
+ part_to_str(thing) {|thing| thing.inspect}
14
+ end
15
+
16
+ def part_to_s(thing, indent="")
17
+ part_to_str(thing, indent) {|thing| thing.to_s(indent)}
18
+ end
19
+
20
+ def part_to_str(thing, indent="")
21
+ if thing.is_a?(String)
22
+ "'#{thing}'"
23
+ elsif thing.is_a?(Numeric) || thing.is_a?(TrueClass) || thing.is_a?(FalseClass)
24
+ thing.to_s
25
+ elsif thing.is_a?(Binary)
26
+ yield(thing)
27
+ else
28
+ "#{thing.class.name}{'#{thing.to_s}'}"
29
+ end
30
+ end
31
+ end
32
+
33
+ module ContainerToString
34
+ def to_s(indent="")
35
+ next_indent = indent + " " + " "
36
+
37
+ str = "#{indent}#{self.class.shorthand}(\n"
38
+ str << "#{part_to_s(left, next_indent)},\n"
39
+ str << "#{part_to_s(right, next_indent)}\n"
40
+ str << "#{indent})"
41
+ str << "\n" if indent == ""
42
+
43
+ str
44
+ end
45
+ end
46
+
47
+ class And; include ContainerToString end
48
+ class Or; include ContainerToString end
49
+
50
+ end
@@ -0,0 +1,55 @@
1
+ module Predicated
2
+ module Selector
3
+ #this is no doubt totally non-performant with all the extends, and
4
+ #could probably be just done better / more elegantly
5
+ #seek help.
6
+
7
+ def self.SelectorEnumerable(key_to_selecting_proc)
8
+ Module.new do
9
+ @key_to_selecting_proc = key_to_selecting_proc
10
+
11
+ def self.extended(base)
12
+ sym = "@key_to_selecting_proc".to_sym
13
+ hash = base.instance_variable_get(sym) || {}
14
+ base.instance_variable_set(sym, hash.merge(@key_to_selecting_proc))
15
+ end
16
+
17
+ def self.included(base)
18
+ extended(base)
19
+ end
20
+
21
+ #ugh ugh
22
+ def key_to_selecting_proc
23
+ @key_to_selecting_proc || self.class.instance_variable_get("@key_to_selecting_proc".to_sym)
24
+ end
25
+
26
+ def select(*keys, &block)
27
+ key = keys.shift if keys.length>=1
28
+ result =
29
+ if key
30
+ selecting_proc = key_to_selecting_proc[key]
31
+ raise "no selector found for '#{key}'. current selectors: [#{key_to_selecting_proc.collect{|k,v|k.to_s}.join(",")}]" unless selecting_proc
32
+ memos_for(:select)[key] ||= super(&selecting_proc)
33
+ else
34
+ super(&block)
35
+ end
36
+ #ugh
37
+ result.extend(Predicated::Selector.SelectorEnumerable(key_to_selecting_proc))
38
+ keys.length>=1 ? result.select(*keys, &block) : result
39
+ end
40
+
41
+ private
42
+ def memos_for(group)
43
+ @memos ||= {}
44
+ @memos[group] ||= {}
45
+ end
46
+ end
47
+ end
48
+
49
+ #ugh
50
+ def SelectorEnumerable(key_to_selecting_proc)
51
+ Predicated::Selector.SelectorEnumerable(key_to_selecting_proc)
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,35 @@
1
+ raise "this doesn't work in 1.8.6 because the arel gem is 1.8.7-only" if RUBY_VERSION=="1.8.6"
2
+
3
+ require "predicated/predicate"
4
+
5
+ module Predicated
6
+
7
+ require_gem_version("arel", "0.4.0")
8
+
9
+ {And => Arel::Predicates::And,
10
+ Or => Arel::Predicates::Or}.each do |predicated_class, arel_class|
11
+
12
+ predicated_class.class_eval %{
13
+ def to_arel
14
+ #{arel_class.name}.new(left.to_arel, right.to_arel)
15
+ end
16
+ }
17
+
18
+
19
+ end
20
+
21
+ {Equal => Arel::Predicates::Equality,
22
+ GreaterThan => Arel::Predicates::GreaterThan,
23
+ LessThan => Arel::Predicates::LessThan,
24
+ GreaterThanOrEqualTo => Arel::Predicates::GreaterThanOrEqualTo,
25
+ LessThanOrEqualTo => Arel::Predicates::LessThanOrEqualTo}.each do |predicated_class, arel_class|
26
+
27
+ predicated_class.class_eval %{
28
+ def to_arel
29
+ #{arel_class.name}.new(left, right)
30
+ end
31
+ }
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,91 @@
1
+ require "predicated/predicate"
2
+ require "predicated/evaluate"
3
+
4
+ module Predicated
5
+
6
+ module ContainerSentence
7
+ def to_sentence
8
+ left.to_sentence + "#{joining_str}" + right.to_sentence
9
+ end
10
+
11
+ def to_negative_sentence
12
+ "This is not true: " + to_sentence
13
+ end
14
+ end
15
+
16
+ class And; include ContainerSentence; def joining_str; " and " end; end
17
+ class Or; include ContainerSentence; def joining_str; " or " end;end
18
+
19
+ class Operation
20
+
21
+ def self.register_verb_phrase(method_sym,
22
+ positive_verb_phrase,
23
+ negative_verb_phrase,
24
+ accepts_object=true)
25
+ @@method_sym_to_phrase_info[method_sym] = {
26
+ :positive => positive_verb_phrase,
27
+ :negative => negative_verb_phrase,
28
+ :accepts_object => accepts_object
29
+ }
30
+ end
31
+
32
+ def self.reset_verb_phrases
33
+ @@method_sym_to_phrase_info = {}
34
+
35
+ register_verb_phrase(:==, "is equal to", "is not equal to")
36
+ register_verb_phrase(:>, "is greater than", "is not greater than")
37
+ register_verb_phrase(:<, "is less than", "is not less than")
38
+ register_verb_phrase(:>=, "is greater than or equal to", "is not greater than or equal to")
39
+ register_verb_phrase(:<=, "is less than or equal to", "is not less than or equal to")
40
+
41
+ register_verb_phrase(:include?, "includes", "does not include")
42
+ register_verb_phrase(:is_a?, "is a", "is not a")
43
+ register_verb_phrase(:nil?, "is nil", "is not nil", accepts_object=false)
44
+
45
+ nil
46
+ end
47
+
48
+ reset_verb_phrases
49
+
50
+
51
+ def to_sentence
52
+ sentence(verb_phrase[:positive])
53
+ end
54
+
55
+ def to_negative_sentence
56
+ sentence(verb_phrase[:negative])
57
+ end
58
+
59
+ private
60
+
61
+ def sentence(verb_phrase_str)
62
+ left_str = format_value(left)
63
+ right_str = format_value(right)
64
+
65
+ str = left_str + " " + verb_phrase_str
66
+ str << " " + right_str if verb_phrase[:accepts_object]
67
+ str
68
+ end
69
+
70
+ def verb_phrase
71
+ @@method_sym_to_phrase_info[method_sym] || {
72
+ :positive => "is " + rudimentary=method_sym.to_s.gsub("_", " ").gsub("?", ""),
73
+ :negative => "is not " + rudimentary,
74
+ :accepts_object => true
75
+ }
76
+ end
77
+
78
+ def format_value(value)
79
+ if value.is_a?(String)
80
+ "'" + value.to_s + "'"
81
+ elsif value.nil?
82
+ "nil"
83
+ elsif value.is_a?(Numeric) || value.is_a?(TrueClass) || value.is_a?(FalseClass)
84
+ value.to_s
85
+ else
86
+ "'" + value.inspect + "'"
87
+ end
88
+ end
89
+ end
90
+
91
+ end
@@ -0,0 +1,3 @@
1
+ module Predicated
2
+ VERSION = "0.1.0" unless defined?(Predicated::VERSION)
3
+ end
@@ -0,0 +1,77 @@
1
+ require "test/test_helper_with_wrong"
2
+
3
+ require "predicated/predicate"
4
+ require "predicated/constrain"
5
+ include Predicated
6
+
7
+ apropos %{constraints are rules about the content and structure of predicates.
8
+ a predicate might violate a constraint} do
9
+
10
+ before do
11
+ @value_not_equal_to_two =
12
+ Constraint.new(:name => "Value can't be two",
13
+ :selectors => [Operation],
14
+ :check_that => proc{|predicate, ancestors| predicate.right!=2})
15
+
16
+ @not_more_than_two_levels_deep =
17
+ Constraint.new(:name => "Limited to two levels deep",
18
+ :check_that => proc{|predicate, ancestors| ancestors.length<=2})
19
+ end
20
+
21
+ test "apply to each predicate - simple" do
22
+ constraints = Constraints.new.add(@value_not_equal_to_two)
23
+
24
+ assert{ constraints.check(Predicate{Eq(1,1)}).pass? }
25
+ deny { constraints.check(Predicate{Eq(1,2)}).pass? }
26
+
27
+ assert{ constraints.check(Predicate{And(Eq(1,1), Eq(1,3))}).pass? }
28
+ deny { constraints.check(Predicate{And(Eq(1,1), Eq(1,2))}).pass? }
29
+ end
30
+
31
+ test "apply each to each predicate - many constraints" do
32
+ constraints =
33
+ Constraints.new.
34
+ add(@value_not_equal_to_two).
35
+ add(@not_more_than_two_levels_deep)
36
+
37
+ assert{ constraints.check(Predicate{And(Eq(1,1), Eq(3,3))}).pass? }
38
+ deny { constraints.check(Predicate{And(Eq(1,1), Eq(2,2))}).pass? }
39
+
40
+ assert{ constraints.check(Predicate{And(Eq(1,1), Eq(3,3))}).pass? }
41
+ deny { constraints.check(Predicate{Or(Or(And(Eq(1,1), Eq(3,3)), Eq(4,4)), Eq(5,5))}).pass? }
42
+ end
43
+
44
+ test "equality" do
45
+ one = Constraint.new(:name => "Value can't be two",
46
+ :selectors => [Operation],
47
+ :check_that => proc{|predicate, ancestors| predicate.right!=2})
48
+ two = Constraint.new(:name => "Value can't be two",
49
+ :selectors => [Operation],
50
+ :check_that => proc{|predicate, ancestors| predicate.right!=2})
51
+ three = Constraint.new(:name => "Some other constraint",
52
+ :check_that => proc{|predicate, ancestors| false})
53
+
54
+ assert{ one == two }
55
+ deny { one == three }
56
+ end
57
+
58
+ test %{result contains information about whether the checks passed,
59
+ which constraints were violated,
60
+ along with the offending predicates} do
61
+ constraints = Constraints.new.add(@value_not_equal_to_two)
62
+
63
+ result = constraints.check(Predicate{Eq(1,1)})
64
+ assert{ result.pass? }
65
+ assert{ result.violations == {} }
66
+
67
+ result = constraints.check(Predicate{And(Eq(1,1), And(Eq(2,2), Eq(3,2)))})
68
+ deny { result.pass? }
69
+ assert{
70
+ result.violations == {
71
+ @value_not_equal_to_two => [Equal.new(2,2), Equal.new(3,2)]
72
+ }
73
+ }
74
+ end
75
+
76
+
77
+ end
@@ -0,0 +1,60 @@
1
+ require "test/test_helper_with_wrong"
2
+
3
+ require "predicated/predicate"
4
+ include Predicated
5
+
6
+ apropos "you can flip through the predicate tree, like any enumerable. a list of ancestors of each node are provided" do
7
+
8
+ test "simple" do
9
+ assert { Predicate { Eq(1, 2) }.to_a == [[Predicate { Eq(1, 2) }, []]] }
10
+ end
11
+
12
+ test "complex" do
13
+ the_top = Predicate { And(Eq(1, 2), Or(Eq(3, 4), Eq(5, 6))) }
14
+ the_or = Predicate { Or(Eq(3, 4), Eq(5, 6)) }
15
+ assert { the_top.to_a ==
16
+ [
17
+ [the_top, []],
18
+ [Predicate { Eq(1, 2) }, [the_top]],
19
+ [Predicate { Or(Eq(3, 4), Eq(5, 6)) }, [the_top]],
20
+ [Predicate { Eq(3, 4) }, [the_top, the_or]],
21
+ [Predicate { Eq(5, 6) }, [the_top, the_or]]
22
+ ]
23
+ }
24
+ end
25
+
26
+ end
27
+
28
+ apropos "there are convenient selectors defined for getting things out of a predicate" do
29
+ class Array
30
+ def predicates
31
+ collect{|p, a|p}
32
+ end
33
+ end
34
+
35
+ it "gets predicate parts by type" do
36
+ root = Predicate { And(Eq(1, 2), Or(Eq(3, 4), Eq(5, 6))) }
37
+ the_or = Predicate { Or(Eq(3, 4), Eq(5, 6)) }
38
+
39
+ assert{ root.select(:all).predicates == [root, Equal.new(1, 2), the_or, Equal.new(3, 4), Equal.new(5, 6)] }
40
+
41
+ assert{ root.select(And).predicates == [root] }
42
+ assert{ root.select(Or).predicates == [the_or] }
43
+ assert{ root.select(Equal).predicates == [Equal.new(1, 2), Equal.new(3, 4), Equal.new(5, 6)] }
44
+ assert{ root.select(GreaterThan).predicates == [] }
45
+
46
+ gt_lt = Predicate { And(Gt(1, 2), Lt(3, 4)) }
47
+ assert{ gt_lt.select(GreaterThan).predicates == [GreaterThan.new(1, 2)] }
48
+ assert{ gt_lt.select(LessThan).predicates == [LessThan.new(3, 4)] }
49
+
50
+ gte_lte = Predicate { And(Gte(1, 2), Lte(3, 4)) }
51
+ assert{ gte_lte.select(GreaterThanOrEqualTo).predicates == [GreaterThanOrEqualTo.new(1, 2)] }
52
+ assert{ gte_lte.select(LessThanOrEqualTo).predicates == [LessThanOrEqualTo.new(3, 4)] }
53
+
54
+ mixed = Predicate { And(Eq(1, 2), Or(Gt(3, 4), Lt(5, 6))) }
55
+ mixed_or = Predicate { Or(Gt(3, 4), Lt(5, 6)) }
56
+ assert{ mixed.select(Operation).predicates == [Equal.new(1, 2), GreaterThan.new(3, 4), LessThan.new(5, 6)] }
57
+ assert{ mixed.select(Operation).select(Equal).predicates == [Equal.new(1, 2)] }
58
+ assert{ mixed.select(Binary).predicates == [mixed, Equal.new(1, 2), mixed_or, GreaterThan.new(3, 4), LessThan.new(5, 6)] }
59
+ end
60
+ end
@@ -0,0 +1,21 @@
1
+ require "test/test_helper_with_wrong"
2
+
3
+ require "predicated/predicate"
4
+ include Predicated
5
+
6
+ apropos "prove value equality" do
7
+
8
+ test "simple" do
9
+ assert { Predicate { Eq(1, 1) } == Predicate { Eq(1, 1) } }
10
+ deny { Predicate { Eq(1, 1) } == Predicate { Eq(1, 99) } }
11
+ end
12
+
13
+ test "complex" do
14
+ assert { Predicate { And(Eq(1, 1), Or(Eq(2, 2), Eq(3, 3))) } ==
15
+ Predicate { And(Eq(1, 1), Or(Eq(2, 2), Eq(3, 3))) } }
16
+
17
+ deny { Predicate { And(Eq(1, 1), Or(Eq(2, 2), Eq(3, 3))) } ==
18
+ Predicate { And(Eq(1, 1), Or(Eq(2, 99), Eq(3, 3))) } }
19
+ end
20
+
21
+ end
@@ -0,0 +1,144 @@
1
+ require "test/test_helper_with_wrong"
2
+
3
+ require "predicated/evaluate"
4
+ include Predicated
5
+
6
+ apropos "evaluate a predicate as boolean logic in ruby. change the context by providing and optional binding." do
7
+
8
+ apropos "proving out basic operations" do
9
+ test "equals" do
10
+ assert { Predicate { Eq(1, 1) }.evaluate }
11
+ deny { Predicate { Eq(1, 2) }.evaluate }
12
+ end
13
+
14
+ test "less than" do
15
+ assert { Predicate { Lt(1, 2) }.evaluate }
16
+ deny { Predicate { Lt(2, 2) }.evaluate }
17
+ deny { Predicate { Lt(3, 2) }.evaluate }
18
+ end
19
+
20
+ test "greater than" do
21
+ deny { Predicate { Gt(1, 2) }.evaluate }
22
+ deny { Predicate { Gt(2, 2) }.evaluate }
23
+ assert { Predicate { Gt(3, 2) }.evaluate }
24
+ end
25
+
26
+ test "less than or equal to" do
27
+ assert { Predicate { Lte(1, 2) }.evaluate }
28
+ assert { Predicate { Lte(2, 2) }.evaluate }
29
+ deny { Predicate { Lte(3, 2) }.evaluate }
30
+ end
31
+
32
+ test "greater than or equal to" do
33
+ deny { Predicate { Gte(1, 2) }.evaluate }
34
+ assert { Predicate { Gte(2, 2) }.evaluate }
35
+ assert { Predicate { Gte(3, 2) }.evaluate }
36
+ end
37
+ end
38
+
39
+ apropos "comparing values of different data types" do
40
+ test "strings" do
41
+ assert { Predicate { Eq("1", "1") }.evaluate }
42
+ deny { Predicate { Eq("1", 1) }.evaluate }
43
+ deny { Predicate { Eq("1", nil) }.evaluate }
44
+ end
45
+
46
+ test "booleans" do
47
+ assert { Predicate { Eq(true, true) }.evaluate }
48
+ deny { Predicate { Eq(false, true) }.evaluate }
49
+
50
+ deny { Predicate { Eq(false, nil) }.evaluate }
51
+ deny { Predicate { Eq(true, nil) }.evaluate }
52
+
53
+ deny { Predicate { Eq("false", false) }.evaluate }
54
+ deny { Predicate { Eq("true", true) }.evaluate }
55
+ end
56
+
57
+ test "numbers" do
58
+ assert { Predicate { Eq(1, 1) }.evaluate }
59
+ assert { Predicate { Eq(1, 1.0) }.evaluate }
60
+ assert { Predicate { Eq(1.0, 1.0) }.evaluate }
61
+ deny { Predicate { Eq(1, 2) }.evaluate }
62
+ deny { Predicate { Eq(1, nil) }.evaluate }
63
+ end
64
+
65
+ class Color
66
+ attr_reader :name
67
+ def initialize(name)
68
+ @name = name
69
+ end
70
+
71
+ def ==(other)
72
+ other.is_a?(Color) && @name == other.name
73
+ end
74
+ end
75
+
76
+ test "objects" do
77
+ assert { Predicate { Eq(Color.new("red"), Color.new("red")) }.evaluate }
78
+ deny { Predicate { Eq(Color.new("red"), Color.new("BLUE")) }.evaluate }
79
+ deny { Predicate { Eq(Color.new("red"), 2) }.evaluate }
80
+ deny { Predicate { Eq(Color.new("red"), "red") }.evaluate }
81
+ deny { Predicate { Eq(Color.new("red"), nil) }.evaluate }
82
+ end
83
+ end
84
+
85
+
86
+ apropos "and" do
87
+ test "left and right must be true" do
88
+ assert { Predicate { And( Eq(1, 1), Eq(2, 2) ) }.evaluate }
89
+ deny { Predicate { And( Eq(99, 1), Eq(2, 2) ) }.evaluate }
90
+ deny { Predicate { And( Eq(1, 1), Eq(99, 2) ) }.evaluate }
91
+ end
92
+
93
+ test "simple true and false work too" do
94
+ assert { Predicate { And( true, true ) }.evaluate }
95
+ assert { Predicate { And( true, Eq(2, 2) ) }.evaluate }
96
+ deny { Predicate { And( true, false ) }.evaluate }
97
+ deny { Predicate { And( Eq(2, 2), false ) }.evaluate }
98
+ end
99
+
100
+ test "nested" do
101
+ assert { Predicate { And( true, And(true, true) ) }.evaluate }
102
+ deny { Predicate { And( false, And(true, true) ) }.evaluate }
103
+ deny { Predicate { And( true, And(true, false) ) }.evaluate }
104
+ end
105
+ end
106
+
107
+ apropos "or" do
108
+ test "one of left or right must be true" do
109
+ assert { Predicate { Or(true, true) }.evaluate }
110
+ assert { Predicate { Or(true, false) }.evaluate }
111
+ assert { Predicate { Or(false, true) }.evaluate }
112
+ deny { Predicate { Or(false, false) }.evaluate }
113
+ end
114
+ end
115
+
116
+ apropos "evaluate adds a generic 'call' class. that is, object.message(args)" do
117
+ test "evaluate simple calls" do
118
+ assert { Predicate { Call("abc", :include?, "bc") }.evaluate }
119
+ deny { Predicate { Call("abc", :include?, "ZZ") }.evaluate }
120
+ end
121
+
122
+ test "nil call. call defaults to no args if none are specified" do
123
+ assert { Predicate { Call(nil, :nil?, []) }.evaluate }
124
+ deny { Predicate { Call("abc", :nil?, []) }.evaluate }
125
+
126
+ assert { Predicate { Call(nil, :nil?) }.evaluate }
127
+ deny { Predicate { Call("abc", :nil?) }.evaluate }
128
+ end
129
+
130
+ test "inspect" do
131
+ assert{ Call.new("abc", :include?, "bc").inspect == "Call('abc'.include?('bc'))" }
132
+ end
133
+
134
+ test "call equality" do
135
+ assert { Call.new("abc", :include?, "bc") == Call.new("abc", :include?, "bc") }
136
+ deny { Call.new("ZZZ", :include?, "bc") == Call.new("abc", :include?, "bc") }
137
+ deny { Call.new("abc", :zzz, "bc") == Call.new("abc", :include?, "bc") }
138
+ deny { Call.new("abc", :include?, "ZZZ") == Call.new("abc", :include?, "bc") }
139
+ end
140
+
141
+
142
+ end
143
+
144
+ end