rools 0.1.6 → 0.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/CHANGELOG +5 -0
- data/RAKEFILE +36 -3
- data/lib/rools/default_parameter_proc.rb +3 -3
- data/lib/rools/errors.rb +4 -0
- data/lib/rools/facts.rb +4 -0
- data/lib/rools/rule.rb +7 -2
- data/lib/rools/rule_set.rb +64 -104
- data/lib/rools/version.rb +2 -1
- metadata +2 -2
data/CHANGELOG
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
= Rools CHANGELOG
|
2
2
|
|
3
|
+
== Rools - 0.2 released 2007/05/22
|
4
|
+
* specifications have been created using RSpec
|
5
|
+
* fix bug #10985 regarding the support of rule extension
|
6
|
+
* fixed assertion of a fact within a rule
|
7
|
+
|
3
8
|
== Rools - 0.1.6 released 2007/05/20
|
4
9
|
* todo #1319 use Rails conventions for rules parameters.
|
5
10
|
WARNING: no more free for all.
|
data/RAKEFILE
CHANGED
@@ -16,11 +16,15 @@ require 'rote/filters/tidy'
|
|
16
16
|
require 'rote/format/html'
|
17
17
|
require 'rote/extratasks'
|
18
18
|
|
19
|
+
require 'spec/rake/spectask'
|
20
|
+
require 'spec/rake/verify_rcov'
|
21
|
+
|
19
22
|
include Rote
|
20
23
|
|
21
24
|
load 'lib/rools/version.rb'
|
22
25
|
|
23
|
-
PACKAGE_VERSION
|
26
|
+
PACKAGE_VERSION = Rools::ROOLS_VERSION
|
27
|
+
CURRENT_COVERAGE = Rools::ROOLS_COVERAGE
|
24
28
|
|
25
29
|
CLEAN.include("pkg", "html", "rdoc", "engine.log")
|
26
30
|
|
@@ -38,7 +42,7 @@ ENV['RUBYFORGE_USER'] = "cappelaere@rubyforge.org"
|
|
38
42
|
ENV['RUBYFORGE_PROJECT'] = "/var/www/gforge-projects/#{PROJECT}"
|
39
43
|
|
40
44
|
desc 'Release Files'
|
41
|
-
task :default => [:rdoc, :doc, :gem]
|
45
|
+
task :default => [:rcov, :rdoc, :doc, :gem, :package]
|
42
46
|
|
43
47
|
# Generate the RDoc documentation
|
44
48
|
rd = Rake::RDocTask.new do |rdoc|
|
@@ -118,6 +122,8 @@ Rake::PackageTask.new("rools", Rools::ROOLS_VERSION) do |pkg|
|
|
118
122
|
"examples/**/*",
|
119
123
|
"lib/**/*",
|
120
124
|
"test/**/*",
|
125
|
+
"specs/**/*",
|
126
|
+
"html/**/*"
|
121
127
|
].to_a
|
122
128
|
pkg.package_files.delete("rc.txt")
|
123
129
|
pkg.package_files.delete("MISC.txt")
|
@@ -170,4 +176,31 @@ RoolsTestTask.new(:test) do |t|
|
|
170
176
|
t.libs << "test"
|
171
177
|
t.test_files = FileList['test/rake_test.rb']
|
172
178
|
t.verbose = true
|
173
|
-
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Check Rools specifications using rspec
|
182
|
+
Spec::Rake::SpecTask.new('specs') do |t|
|
183
|
+
t.spec_opts = ['--format', 'specdoc']
|
184
|
+
t.spec_files = FileList['specs/**/*.rb']
|
185
|
+
end
|
186
|
+
|
187
|
+
Spec::Rake::SpecTask.new('spec_coverage') do |t|
|
188
|
+
t.spec_opts = ['--format', 'specdoc']
|
189
|
+
t.spec_files = FileList['specs/**/*.rb']
|
190
|
+
t.rcov = true
|
191
|
+
t.rcov_dir = './html/output/coverage'
|
192
|
+
t.rcov_opts = ['--exclude', 'specs']
|
193
|
+
end
|
194
|
+
|
195
|
+
desc "Run all specs and store html output in html/output/report.html"
|
196
|
+
Spec::Rake::SpecTask.new('spec_html') do |t|
|
197
|
+
t.spec_files = FileList['specs/**/*_spec.rb']
|
198
|
+
t.spec_opts = ['--format html:html/output/rspec_report.html','--backtrace']
|
199
|
+
end
|
200
|
+
|
201
|
+
RCov::VerifyTask.new(:verify_rcov => :spec_coverage) do |t|
|
202
|
+
t.threshold = CURRENT_COVERAGE # Make sure you have rcov 0.7 or higher!
|
203
|
+
t.index_html = './html/output/coverage/index.html'
|
204
|
+
end
|
205
|
+
|
206
|
+
task :rcov => [:verify_rcov, :spec_html]
|
@@ -52,15 +52,15 @@ module Rools
|
|
52
52
|
if facts.has_key?( sym.to_s )
|
53
53
|
#puts "return fact #{facts[sym.to_s].value}"
|
54
54
|
return facts[sym.to_s].value
|
55
|
-
else
|
55
|
+
#else
|
56
56
|
#puts "#{sym} not in facts"
|
57
57
|
end
|
58
58
|
rescue Exception => e
|
59
59
|
#logger.error "miss exception #{e} #{e.backtrace.join("\n")}" if logger
|
60
60
|
#puts "miss exception #{e} #{e.backtrace.join("\n")}"
|
61
61
|
end
|
62
|
-
return @working_object if @working_object && args.size == 0
|
63
|
-
return nil
|
62
|
+
#return @working_object if @working_object && args.size == 0
|
63
|
+
#return nil
|
64
64
|
end
|
65
65
|
|
66
66
|
# Stops the current assertion. Does not indicate failure.
|
data/lib/rools/errors.rb
CHANGED
data/lib/rools/facts.rb
CHANGED
data/lib/rools/rule.rb
CHANGED
@@ -5,6 +5,7 @@ require 'rools/base'
|
|
5
5
|
module Rools
|
6
6
|
class Rule < Base
|
7
7
|
attr_reader :name, :priority, :rule_set
|
8
|
+
attr_reader :parameters, :conditions, :consequences
|
8
9
|
|
9
10
|
|
10
11
|
# A Rule requires a Rools::RuleSet, a name, and an associated block
|
@@ -17,7 +18,11 @@ module Rools
|
|
17
18
|
@consequences = []
|
18
19
|
@parameters = []
|
19
20
|
|
20
|
-
|
21
|
+
begin
|
22
|
+
instance_eval(&b) if b
|
23
|
+
rescue Exception => e
|
24
|
+
raise RuleCheckError.new( self, e)
|
25
|
+
end
|
21
26
|
end
|
22
27
|
|
23
28
|
# Adds a condition to the Rule.
|
@@ -102,7 +107,7 @@ module Rools
|
|
102
107
|
|
103
108
|
# Calls Rools::RuleSet#assert in the bound working-set
|
104
109
|
def assert(obj)
|
105
|
-
@rule_set.
|
110
|
+
@rule_set.rule_assert(obj)
|
106
111
|
end
|
107
112
|
|
108
113
|
# Execute each consequence.
|
data/lib/rools/rule_set.rb
CHANGED
@@ -8,11 +8,11 @@ require 'rexml/document'
|
|
8
8
|
|
9
9
|
module Rools
|
10
10
|
class RuleSet < Base
|
11
|
-
attr_reader :num_executed, :num_evaluated, :facts
|
11
|
+
attr_reader :num_executed, :num_evaluated, :facts, :status
|
12
12
|
|
13
13
|
PASS = :pass
|
14
14
|
FAIL = :fail
|
15
|
-
|
15
|
+
UNDETERMINED = :undetermined
|
16
16
|
|
17
17
|
# You can pass a set of Rools::Rules with a block parameter,
|
18
18
|
# or you can pass a file-path to evaluate.
|
@@ -24,7 +24,7 @@ module Rools
|
|
24
24
|
|
25
25
|
if block_given?
|
26
26
|
instance_eval(&b)
|
27
|
-
|
27
|
+
elsif file
|
28
28
|
# loading a file, check extension
|
29
29
|
name,ext = file.split(".")
|
30
30
|
logger.debug("loading ext: #{name}.#{ext}") if logger
|
@@ -42,7 +42,7 @@ module Rools
|
|
42
42
|
load_rb(file)
|
43
43
|
|
44
44
|
else
|
45
|
-
raise "invalid file extension: #{ext}"
|
45
|
+
raise RuleLoadingError, "invalid file extension: #{ext}"
|
46
46
|
end
|
47
47
|
end
|
48
48
|
end
|
@@ -91,11 +91,10 @@ module Rools
|
|
91
91
|
|
92
92
|
@rules[rule_name] = rule
|
93
93
|
}
|
94
|
-
logger.debug( "
|
94
|
+
logger.debug( "loaded #{rules.size} rules") if logger
|
95
95
|
}
|
96
96
|
rescue Exception => e
|
97
|
-
|
98
|
-
puts e.backtrace.join("\n")
|
97
|
+
raise RuleLoadingError, "loading xml file"
|
99
98
|
end
|
100
99
|
|
101
100
|
end
|
@@ -107,10 +106,20 @@ module Rools
|
|
107
106
|
instance_eval(File::open(file).read)
|
108
107
|
end
|
109
108
|
|
109
|
+
#
|
110
|
+
# returns an array of facts
|
111
|
+
#
|
110
112
|
def get_facts
|
111
113
|
@facts
|
112
114
|
end
|
113
|
-
|
115
|
+
|
116
|
+
#
|
117
|
+
# returns all the rules defined for that set
|
118
|
+
#
|
119
|
+
def get_rules
|
120
|
+
@rules
|
121
|
+
end
|
122
|
+
|
114
123
|
# rule creates a Rools::Rule. Make sure to use a descriptive name or symbol.
|
115
124
|
# For the purposes of extending Rules, all names are converted to
|
116
125
|
# strings and downcased.
|
@@ -196,9 +205,10 @@ module Rools
|
|
196
205
|
# condition { language.age > 15 }
|
197
206
|
# consequence { "In the year 2008 Ruby conquered the known universe" }
|
198
207
|
# end
|
199
|
-
def with(name, &b)
|
208
|
+
def with(name, prio=0, &b)
|
200
209
|
name.to_s.downcase!
|
201
|
-
(@dependencies[@extend_rule_name] ||= []) << Rule.new(self, name, b)
|
210
|
+
(@dependencies[@extend_rule_name] ||= []) << Rule.new(self, name, prio, b)
|
211
|
+
#@rules[name] = Rule.new(self, name, prio, b)
|
202
212
|
end
|
203
213
|
|
204
214
|
# Stops the current assertion. Does not indicate failure.
|
@@ -212,83 +222,17 @@ module Rools
|
|
212
222
|
@assert = false
|
213
223
|
end
|
214
224
|
|
215
|
-
|
216
|
-
|
217
|
-
#
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
available_rules = @rules.values.select { |rule| rule.parameters_match?(obj) }
|
227
|
-
|
228
|
-
available_rules = available_rules.sort do |r1, r2|
|
229
|
-
r2.priority <=> r1.priority
|
230
|
-
end
|
231
|
-
|
232
|
-
begin
|
233
|
-
|
234
|
-
# loop through the available_rules, evaluating each one,
|
235
|
-
# until there are no more matching rules available
|
236
|
-
begin # loop
|
237
|
-
|
238
|
-
# the loop condition is reset to break by default after every iteration
|
239
|
-
matches = false
|
240
|
-
#logger.debug("available rules: #{available_rules.size.to_s}") if logger
|
241
|
-
available_rules.each do |rule|
|
242
|
-
# RuleCheckErrors are caught and swallowed and the rule that
|
243
|
-
# raised the error is removed from the working-set.
|
244
|
-
logger.debug("evaluating: #{rule}") if logger
|
245
|
-
begin
|
246
|
-
@num_evaluated += 1
|
247
|
-
if rule.conditions_match?(obj)
|
248
|
-
logger.debug("rule #{rule} matched") if logger
|
249
|
-
matches = true
|
250
|
-
|
251
|
-
# remove the rule from the working-set so it's not re-evaluated
|
252
|
-
available_rules.delete(rule)
|
253
|
-
|
254
|
-
# find all parameter-matching dependencies of this rule and
|
255
|
-
# add them to the working-set.
|
256
|
-
if @dependencies.has_key?(rule.name)
|
257
|
-
available_rules += @dependencies[rule.name].select do |dependency|
|
258
|
-
dependency.parameters_match?(obj)
|
259
|
-
end
|
260
|
-
end
|
261
|
-
|
262
|
-
# execute this rule
|
263
|
-
logger.debug("executing rule #{rule}") if logger
|
264
|
-
rule.call(obj)
|
265
|
-
@num_executed += 1
|
266
|
-
|
267
|
-
# break the current iteration and start back from the first rule defined.
|
268
|
-
break
|
269
|
-
end # if rule.conditions_match?(obj)
|
270
|
-
|
271
|
-
rescue RuleCheckError => e
|
272
|
-
# log da error or sumpin
|
273
|
-
available_rules.delete(e.rule)
|
274
|
-
@status = fail
|
275
|
-
end # begin/rescue
|
276
|
-
|
277
|
-
end # available_rules.each
|
278
|
-
|
279
|
-
end while(matches && @assert)
|
280
|
-
|
281
|
-
rescue RuleConsequenceError => rce
|
282
|
-
# RuleConsequenceErrors are allowed to break out of the current assertion,
|
283
|
-
# then the inner error is bubbled-up to the asserting code.
|
284
|
-
@status = fail
|
285
|
-
raise rce.inner_error
|
286
|
-
end
|
287
|
-
|
288
|
-
@assert = false
|
289
|
-
|
290
|
-
return @status
|
291
|
-
end # def assert
|
225
|
+
#
|
226
|
+
# an assert has been made within a rule
|
227
|
+
#
|
228
|
+
def rule_assert( obj )
|
229
|
+
# add object as a new fact
|
230
|
+
f = fact(obj)
|
231
|
+
# get_relevant_rules
|
232
|
+
logger.debug( "Check if we need to add more rules") if logger
|
233
|
+
add_relevant_rules_for_fact(f)
|
234
|
+
sort_relevant_rules
|
235
|
+
end
|
292
236
|
|
293
237
|
# Turn passed object into facts and evaluate all relevant rules
|
294
238
|
# Previous facts of same type are removed
|
@@ -299,28 +243,42 @@ module Rools
|
|
299
243
|
return evaluate()
|
300
244
|
end
|
301
245
|
|
302
|
-
#
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
246
|
+
#
|
247
|
+
# for a particular fact, we need to retrieve the relevant rules
|
248
|
+
# and add them to the relevant list
|
249
|
+
#
|
250
|
+
def add_relevant_rules_for_fact fact
|
251
|
+
@rules.values.select { |rule|
|
252
|
+
if !@relevant_rules.include?( rule)
|
253
|
+
if rule.parameters_match?(fact.value)
|
309
254
|
@relevant_rules << rule
|
310
255
|
logger.debug "#{rule} is relevant" if logger
|
311
256
|
else
|
312
257
|
logger.debug "#{rule} is not relevant" if logger
|
313
258
|
end
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
259
|
+
end
|
260
|
+
}
|
261
|
+
end
|
262
|
+
|
263
|
+
#
|
264
|
+
# relevant rules need to be sorted in priority order
|
265
|
+
#
|
266
|
+
def sort_relevant_rules
|
318
267
|
# sort array in rule priority order
|
319
268
|
@relevant_rules = @relevant_rules.sort do |r1, r2|
|
320
269
|
r2.priority <=> r1.priority
|
321
270
|
end
|
322
271
|
end
|
323
272
|
|
273
|
+
# get all relevant rules for all specified facts
|
274
|
+
def get_relevant_rules
|
275
|
+
@relevant_rules = Array.new
|
276
|
+
@facts.each { |k,f|
|
277
|
+
add_relevant_rules_for_fact f
|
278
|
+
}
|
279
|
+
sort_relevant_rules
|
280
|
+
end
|
281
|
+
|
324
282
|
# evaluate all relevant rules for specified facts
|
325
283
|
def evaluate
|
326
284
|
@status = PASS
|
@@ -357,11 +315,12 @@ module Rools
|
|
357
315
|
|
358
316
|
# find all parameter-matching dependencies of this rule and
|
359
317
|
# add them to the working-set.
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
318
|
+
if @dependencies.has_key?(rule.name)
|
319
|
+
logger.debug( "found dependant rules to #{rule}") if logger
|
320
|
+
@relevant_rules += @dependencies[rule.name].select do |dependency|
|
321
|
+
dependency.parameters_match?(obj)
|
322
|
+
end
|
323
|
+
end
|
365
324
|
|
366
325
|
# execute this rule
|
367
326
|
logger.debug("executing rule #{rule}") if logger
|
@@ -373,7 +332,8 @@ module Rools
|
|
373
332
|
end # if rule.conditions_match?(obj)
|
374
333
|
|
375
334
|
rescue RuleCheckError => e
|
376
|
-
|
335
|
+
#puts "evaluate RuleCheckError: #{e.to_s}"
|
336
|
+
logger.error( "RuleCheckError") if logger
|
377
337
|
@relevant_rules.delete(e.rule)
|
378
338
|
@status = fail
|
379
339
|
end # begin/rescue
|
data/lib/rools/version.rb
CHANGED
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
|
|
3
3
|
specification_version: 1
|
4
4
|
name: rools
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date: 2007-05-
|
6
|
+
version: "0.2"
|
7
|
+
date: 2007-05-22 00:00:00 -04:00
|
8
8
|
summary: A Rules Engine written in Ruby
|
9
9
|
require_paths:
|
10
10
|
- lib
|