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 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