ruler 1.2.0 → 1.4.2
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/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
|