ruler 1.2.0 → 1.4.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -0
- data/VERSION +1 -1
- data/lib/ruler.rb +37 -17
- data/spec/ruler_spec.rb +44 -0
- metadata +27 -13
data/Gemfile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.2
|
1
|
+
1.4.2
|
data/lib/ruler.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'uuid'
|
1
3
|
# This module provides a set of methods to manage and run sets of facts and rules.
|
2
4
|
# These rules take an array of fact names and all of the facts are true
|
3
5
|
# the block passed to the rule is executed. By default, only one rule can fire in a given ruleset.
|
@@ -17,6 +19,12 @@ end
|
|
17
19
|
class BadNotCall < RulerError
|
18
20
|
end
|
19
21
|
|
22
|
+
class BadFact < RulerError
|
23
|
+
end
|
24
|
+
|
25
|
+
class UnknownFact < RulerError
|
26
|
+
end
|
27
|
+
|
20
28
|
module Ruler
|
21
29
|
# This module uses thread local storage and should be threadsafe.
|
22
30
|
#
|
@@ -55,10 +63,17 @@ module Ruler
|
|
55
63
|
# puts result
|
56
64
|
|
57
65
|
def ruleset singletary = true,&blk
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
66
|
+
@rulesetids ||= []
|
67
|
+
@rulesetids << UUID.generate
|
68
|
+
puts "Ruleset: #{@rulesetids.last} of #{@rulesetids.length}" if @DEBUG
|
69
|
+
Thread.current[@rulesetids.last] = {:singletary => singletary, :rulematched => nil, :working_memory => {}}
|
70
|
+
_rval = nil
|
71
|
+
begin
|
72
|
+
_rval = yield
|
73
|
+
ensure
|
74
|
+
@rulesetids.pop
|
75
|
+
end
|
76
|
+
_rval
|
62
77
|
end
|
63
78
|
|
64
79
|
# multi_ruleset is a helper function to define rulesets that allow
|
@@ -79,16 +94,21 @@ module Ruler
|
|
79
94
|
# will never be used.
|
80
95
|
def fact name, dval = nil, &blk
|
81
96
|
if dval.nil?
|
82
|
-
|
97
|
+
_res = begin
|
98
|
+
yield
|
99
|
+
rescue
|
100
|
+
raise BadFact.new($!)
|
101
|
+
end
|
102
|
+
Thread.current[@rulesetids.last][:working_memory][name] = {:value => _res }
|
83
103
|
else
|
84
|
-
Thread.current[:working_memory][name] = {:value => dval }
|
104
|
+
Thread.current[@rulesetids.last][:working_memory][name] = {:value => dval }
|
85
105
|
end
|
86
106
|
end
|
87
107
|
|
88
108
|
# a dynamic_fact is evaulated every time the fact is checked. Unlike a normal fact, which
|
89
109
|
# is only evaluated once, dynamic facts are evaluated once for each rule they appear in
|
90
110
|
def dynamic_fact name, &blk
|
91
|
-
Thread.current[:working_memory][name] = {:transient => true, :block => blk }
|
111
|
+
Thread.current[@rulesetids.last][:working_memory][name] = {:transient => true, :block => blk }
|
92
112
|
end
|
93
113
|
|
94
114
|
# allows for a fact to be NOT another fact. notf cannot be used with a dynamic_fact
|
@@ -96,10 +116,10 @@ module Ruler
|
|
96
116
|
# fact :one, 10 == 10
|
97
117
|
# fact :notfone, not(:one)
|
98
118
|
def notf name
|
99
|
-
if Thread.current[:working_memory][name][:transient]
|
119
|
+
if Thread.current[@rulesetids.last][:working_memory][name][:transient]
|
100
120
|
raise BadNotCall.new("Cannot call notf on dynamic fact")
|
101
121
|
else
|
102
|
-
not(Thread.current[:working_memory][name][:value])
|
122
|
+
not(Thread.current[@rulesetids.last][:working_memory][name][:value])
|
103
123
|
end
|
104
124
|
end
|
105
125
|
|
@@ -116,7 +136,8 @@ module Ruler
|
|
116
136
|
# there is no check to see if fact names are valid, and facts can be (re)defined
|
117
137
|
#inside of rules. Fact names are false if they are not defined.
|
118
138
|
def rule vlist,docstr = nil,&blk
|
119
|
-
|
139
|
+
conditional_call = lambda {|n| begin Thread.current[@rulesetids.last][:working_memory][n][:transient].nil? ? Thread.current[@rulesetids.last][:working_memory][n][:value] : Thread.current[@rulesetids.last][:working_memory][n][:block].call() rescue false end }
|
140
|
+
dbg = lambda {|va| puts begin "|=-\t#{va} = #{conditional_call.call(va)}" rescue "|>=- ERROR: #{$!}" end }
|
120
141
|
if @DEBUG
|
121
142
|
puts "---------------------------------------"
|
122
143
|
puts vlist.join(" & ")
|
@@ -124,11 +145,10 @@ module Ruler
|
|
124
145
|
vlist.each {|v| dbg.call(v) }
|
125
146
|
puts "---------------------------------------"
|
126
147
|
end
|
127
|
-
if Thread.current[:singletary] && Thread.current[:rulematched]
|
128
|
-
Thread.current[:rulematched]
|
148
|
+
if Thread.current[@rulesetids.last][:singletary] && Thread.current[@rulesetids.last][:rulematched]
|
149
|
+
Thread.current[@rulesetids.last][:rulematched]
|
129
150
|
else
|
130
|
-
|
131
|
-
Thread.current[:rulematched] = if vlist.inject(true) {|k,v| k ? k && conditional_call.call(v) : false }
|
151
|
+
Thread.current[@rulesetids.last][:rulematched] = if vlist.inject(true) {|k,v| raise UnknownFact.new("Fact: #{v} is unknown") if Thread.current[@rulesetids.last][:working_memory][v].nil? ;k ? k && conditional_call.call(v) : false }
|
132
152
|
yield
|
133
153
|
end
|
134
154
|
end
|
@@ -138,11 +158,11 @@ module Ruler
|
|
138
158
|
# a rule that should fire if no others fire. You cannot have a default rule if you allow more
|
139
159
|
# than one match. The BadDefaultRule exception is raised.
|
140
160
|
def default_rule &blk
|
141
|
-
raise BadDefaultRule.new("Can't have a default rule when multiple matches are allowed") unless Thread.current[:singletary]
|
142
|
-
if Thread.current[:singletary] && !Thread.current[:rulematched]
|
161
|
+
raise BadDefaultRule.new("Can't have a default rule when multiple matches are allowed") unless Thread.current[@rulesetids.last][:singletary]
|
162
|
+
if Thread.current[@rulesetids.last][:singletary] && !Thread.current[@rulesetids.last][:rulematched]
|
143
163
|
yield
|
144
164
|
else
|
145
|
-
Thread.current[:rulematched]
|
165
|
+
Thread.current[@rulesetids.last][:rulematched]
|
146
166
|
end
|
147
167
|
end
|
148
168
|
end
|
data/spec/ruler_spec.rb
CHANGED
@@ -170,6 +170,35 @@ I think.
|
|
170
170
|
end
|
171
171
|
end
|
172
172
|
end
|
173
|
+
|
174
|
+
def test_twelve
|
175
|
+
ruleset do
|
176
|
+
fact :one do
|
177
|
+
raise StandardError.new("This Should be Caught")
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def test_thirteen
|
183
|
+
ruleset do
|
184
|
+
fact :one,true
|
185
|
+
fact :two, test_one
|
186
|
+
|
187
|
+
rule [:one,:two] do
|
188
|
+
"Nested Works"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def test_fourteen
|
194
|
+
ruleset do
|
195
|
+
fact :one, true
|
196
|
+
rule [:noe] do
|
197
|
+
"Should Break"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
173
202
|
end
|
174
203
|
|
175
204
|
describe Rules do
|
@@ -231,5 +260,20 @@ describe Rules do
|
|
231
260
|
lambda { r.test_eleven }.should raise_error
|
232
261
|
end
|
233
262
|
|
263
|
+
it "should catch an exception thrown in a fact" do
|
264
|
+
r = Rules.new
|
265
|
+
lambda {r.test_twelve}.should raise_error(BadFact)
|
266
|
+
end
|
267
|
+
|
268
|
+
it "should support nested rulesets" do
|
269
|
+
r = Rules.new
|
270
|
+
r.test_thirteen.should == "Nested Works"
|
271
|
+
end
|
272
|
+
|
273
|
+
it "should thrown an error if an unknown fact is checked" do
|
274
|
+
r = Rules.new
|
275
|
+
lambda { r.test_fourteen }.should raise_error(UnknownFact)
|
276
|
+
end
|
277
|
+
|
234
278
|
end
|
235
279
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 3
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
+
- 4
|
8
9
|
- 2
|
9
|
-
|
10
|
-
version: 1.2.0
|
10
|
+
version: 1.4.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Joshua Smith
|
@@ -15,12 +15,26 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-01
|
18
|
+
date: 2011-02-01 00:00:00 -05:00
|
19
19
|
default_executable: example.rb
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
name: uuid
|
33
|
+
requirement: *id001
|
34
|
+
type: :runtime
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
24
38
|
none: false
|
25
39
|
requirements:
|
26
40
|
- - ~>
|
@@ -32,11 +46,11 @@ dependencies:
|
|
32
46
|
- 0
|
33
47
|
version: 2.3.0
|
34
48
|
name: rspec
|
35
|
-
requirement: *
|
49
|
+
requirement: *id002
|
36
50
|
type: :development
|
37
51
|
- !ruby/object:Gem::Dependency
|
38
52
|
prerelease: false
|
39
|
-
version_requirements: &
|
53
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
40
54
|
none: false
|
41
55
|
requirements:
|
42
56
|
- - ~>
|
@@ -48,11 +62,11 @@ dependencies:
|
|
48
62
|
- 0
|
49
63
|
version: 1.0.0
|
50
64
|
name: bundler
|
51
|
-
requirement: *
|
65
|
+
requirement: *id003
|
52
66
|
type: :development
|
53
67
|
- !ruby/object:Gem::Dependency
|
54
68
|
prerelease: false
|
55
|
-
version_requirements: &
|
69
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
56
70
|
none: false
|
57
71
|
requirements:
|
58
72
|
- - ~>
|
@@ -64,11 +78,11 @@ dependencies:
|
|
64
78
|
- 2
|
65
79
|
version: 1.5.2
|
66
80
|
name: jeweler
|
67
|
-
requirement: *
|
81
|
+
requirement: *id004
|
68
82
|
type: :development
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
prerelease: false
|
71
|
-
version_requirements: &
|
85
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
72
86
|
none: false
|
73
87
|
requirements:
|
74
88
|
- - ">="
|
@@ -78,11 +92,11 @@ dependencies:
|
|
78
92
|
- 0
|
79
93
|
version: "0"
|
80
94
|
name: rcov
|
81
|
-
requirement: *
|
95
|
+
requirement: *id005
|
82
96
|
type: :development
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
prerelease: false
|
85
|
-
version_requirements: &
|
99
|
+
version_requirements: &id006 !ruby/object:Gem::Requirement
|
86
100
|
none: false
|
87
101
|
requirements:
|
88
102
|
- - ">="
|
@@ -92,7 +106,7 @@ dependencies:
|
|
92
106
|
- 0
|
93
107
|
version: "0"
|
94
108
|
name: mocha
|
95
|
-
requirement: *
|
109
|
+
requirement: *id006
|
96
110
|
type: :development
|
97
111
|
description: Ruler module implements a DSL that makes it easy to write a set of facts and rules. If you have some tricky conditional logic, Ruler can help clear it up.
|
98
112
|
email: kognate@gmail.com
|