rools 0.1.6 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
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 = Rools::ROOLS_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
@@ -24,4 +24,8 @@ module Rools
24
24
  class RuleConsequenceError < RuleError
25
25
  end
26
26
 
27
+ # See: Rools::RuleError (RuleLoadingError is only a default derivation)
28
+ class RuleLoadingError < StandardError
29
+ end
30
+
27
31
  end
data/lib/rools/facts.rb CHANGED
@@ -22,5 +22,9 @@ module Rools
22
22
  @fact_value
23
23
  end
24
24
  end
25
+
26
+ def to_s
27
+ "facts: #{name} #{fact_value.to_s}"
28
+ end
25
29
  end
26
30
  end
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
- instance_eval(&b) if b
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.assert(obj)
110
+ @rule_set.rule_assert(obj)
106
111
  end
107
112
 
108
113
  # Execute each consequence.
@@ -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
- else
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( "laoded #{rules.size} rules") if logger
94
+ logger.debug( "loaded #{rules.size} rules") if logger
95
95
  }
96
96
  rescue Exception => e
97
- puts "Load XML Exception: #{e.to_s}"
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
- # Used to create a working-set of rules for an object, and evaluate it
218
- # against them. Returns a status, simply PASS or FAIL
219
- def assert_1(obj)
220
- @status = PASS
221
- @assert = true
222
- @num_executed = 0;
223
- @num_evaluated = 0;
224
-
225
- # create a working-set of all parameter-matching, non-dependent rules
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
- # get all relevant rules for all specified facts
303
- def get_relevant_rules
304
- @relevant_rules = Array.new
305
- @facts.each { |k,f|
306
- @rules.values.select { |rule|
307
- if !@relevant_rules.include?( rule)
308
- if rule.parameters_match?(f.value)
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
- end
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
- #if @dependencies.has_key?(rule.name)
361
- # @relevant_rules += @dependencies[rule.name].select do |dependency|
362
- # dependency.parameters_match?(obj)
363
- # end
364
- #end
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
- logger.debug( "RuleCheckError")
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
@@ -1,3 +1,4 @@
1
1
  module Rools
2
- ROOLS_VERSION = '0.1.6'
2
+ ROOLS_VERSION = '0.2'
3
+ ROOLS_COVERAGE = 95.1
3
4
  end
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.1.6
7
- date: 2007-05-20 00:00:00 -04:00
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