rools 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,17 +1,27 @@
1
- - 0.1.4
1
+ = Rools CHANGELOG
2
+
3
+ == Rools - 0.1.5 released 2007/04/27
4
+ * todo #1282 Added unit tests for baseline
5
+ * todo #1283 Added rule priority (not completed)
6
+ * todo #1284 Added XML rule format
7
+ * todo #1286 Added facts support
8
+ * todo #1287 Added decision table capability
9
+ * todo #1288 Added logging capability to trace engine
10
+ * todo #1292 Major Rakefile upgrade for site auto-generation (Thanks to John Metttraux)
11
+
12
+ == Rools - 0.1.4 released 2005/12/15
2
13
  * Removed "examples" folder since my RDoc kung-fu cannot be denied and the examples were redundant
3
14
  * Added Rools::open to rools.rb
4
15
  * Added pscp.rb to create SshPublishers on Win32 with PuttySCP
5
16
  * Added Rools::RuleSet#assert return codes, :pass and :fail
6
- * Added stop and fail methods to RuleSet, Rules, and DefaultParameterProc
7
- * Built Gem under Ruby 1.8.2 to fix yaml bug for RubyForge distribution
17
+ * Added stop and fail methods to RuleSet, Rules, and DefaultParameterProc
8
18
 
9
- - 0.1.3
19
+ == Rools - 0.1.3
10
20
  Finished most documentation
11
21
  Tweaked the rakefile
12
22
 
13
- - 0.1.2
23
+ == Rools - 0.1.2
14
24
  Added RAKEFILE, README, CHANGELOG
15
25
 
16
- - 0.1.1
26
+ == Rools - 0.1.1
17
27
  Added RuleConsequenceError
data/RAKEFILE CHANGED
@@ -1,35 +1,77 @@
1
1
  require 'rubygems'
2
2
  require 'rake'
3
- require 'rake/rdoctask'
3
+ require 'rake/clean'
4
+ require 'rake/packagetask'
4
5
  require 'rake/gempackagetask'
6
+ require 'rake/rdoctask'
7
+ require 'rake/testtask'
8
+
5
9
  require 'rake/contrib/rubyforgepublisher'
6
- require 'pscp'
7
10
 
8
- PACKAGE_VERSION = '0.1.4'
11
+ #require 'pscp'
12
+ require 'rote'
13
+ require 'rote/filters'
14
+ require 'rote/filters/redcloth'
15
+ require 'rote/filters/tidy'
16
+ require 'rote/format/html'
17
+ require 'rote/extratasks'
18
+
19
+ include Rote
20
+
21
+ load 'lib/rools/version.rb'
22
+
23
+ PACKAGE_VERSION = Rools::ROOLS_VERSION
24
+
25
+ CLEAN.include("pkg", "html", "rdoc", "engine.log")
9
26
 
10
27
  PACKAGE_FILES = FileList[
11
28
  'README',
12
29
  'CHANGELOG',
13
30
  'RAKEFILE',
31
+ 'lib/rools.rb',
14
32
  'lib/**/*.rb'
15
33
  ].to_a
16
34
 
17
35
  PROJECT = 'rools'
18
36
 
19
- ENV['RUBYFORGE_USER'] = "ssmoot@rubyforge.org"
37
+ ENV['RUBYFORGE_USER'] = "cappelaere@rubyforge.org"
20
38
  ENV['RUBYFORGE_PROJECT'] = "/var/www/gforge-projects/#{PROJECT}"
21
39
 
22
40
  desc 'Release Files'
23
- task :default => [:rdoc]
41
+ task :default => [:rdoc, :doc, :gem]
24
42
 
25
43
  # Generate the RDoc documentation
26
44
  rd = Rake::RDocTask.new do |rdoc|
27
- rdoc.rdoc_dir = 'doc'
45
+ rdoc.rdoc_dir = 'html/doc'
28
46
  rdoc.title = 'Rools -- A Pure Ruby Rules Engine'
29
- rdoc.options << '--line-numbers --inline-source --main README'
30
47
  rdoc.rdoc_files.include(PACKAGE_FILES)
48
+ rdoc.options << '-N'
49
+ rdoc.options << '-S'
50
+ end
51
+
52
+ # Create a task to build the static docs (html)
53
+ ws = Rote::DocTask.new(:doc) do |site|
54
+ site.output_dir = 'html'
55
+ site.layout_dir = 'doc/layouts'
56
+ site.pages.dir = 'doc/pages'
57
+ site.pages.include('**/*')
58
+
59
+ site.ext_mapping(/thtml|textile/, 'html') do |page|
60
+ page.extend Format::HTML
61
+ page.page_filter Filters::RedCloth.new
62
+ page.page_filter Filters::Syntax.new
63
+ end
64
+
65
+ site.res.dir = 'doc/res'
66
+ site.res.include('**/*.png')
67
+ site.res.include('**/*.gif')
68
+ site.res.include('**/*.jpg')
69
+ site.res.include('**/*.css')
31
70
  end
32
71
 
72
+ # Add rdoc deps to doc task
73
+ task :doc => [:rdoc]
74
+
33
75
  gem_spec = Gem::Specification.new do |s|
34
76
  s.platform = Gem::Platform::RUBY
35
77
  s.name = PROJECT
@@ -37,10 +79,10 @@ gem_spec = Gem::Specification.new do |s|
37
79
  s.description = "Can be used for program-flow, ideally suited to processing applications"
38
80
  s.version = PACKAGE_VERSION
39
81
 
40
- s.authors = 'Sam Smoot', 'Scott Bauer'
41
- s.email = 'ssmoot@gmail.com; bauer.mail@gmail.com'
82
+ s.authors = 'Sam Smoot', 'Scott Bauer', 'Pat Cappelaere'
83
+ s.email = 'ssmoot@gmail.com; bauer.mail@gmail.com cappelaere@gmail.com'
42
84
  s.rubyforge_project = PROJECT
43
- s.homepage = 'http://substantiality.net'
85
+ s.homepage = 'http://rools.rubyforge.org/'
44
86
 
45
87
  s.files = PACKAGE_FILES
46
88
 
@@ -53,13 +95,79 @@ gem_spec = Gem::Specification.new do |s|
53
95
  s.extra_rdoc_files = rd.rdoc_files.reject { |fn| fn =~ /\.rb$/ }.to_a
54
96
  end
55
97
 
98
+ #
99
+ # Create a task for creating a ruby gem
100
+ #
56
101
  Rake::GemPackageTask.new(gem_spec) do |p|
57
102
  p.gem_spec = gem_spec
58
103
  p.need_tar = true
59
104
  p.need_zip = true
60
105
  end
61
106
 
107
+ #
108
+ # Packaging the source
109
+ #
110
+ Rake::PackageTask.new("rools", Rools::ROOLS_VERSION) do |pkg|
111
+ pkg.need_zip = true
112
+ pkg.package_files = FileList[
113
+ "RAKEFILE",
114
+ "CHANGELOG",
115
+ "README",
116
+ "*.txt",
117
+ "doc/**/*",
118
+ "examples/**/*",
119
+ "lib/**/*",
120
+ "test/**/*",
121
+ ].to_a
122
+ pkg.package_files.delete("rc.txt")
123
+ pkg.package_files.delete("MISC.txt")
124
+ class << pkg
125
+ def package_name
126
+ "#{@name}-#{@version}-src"
127
+ end
128
+ end
129
+ end
130
+
62
131
  desc "Publish RDOC to RubyForge"
63
132
  task :rubyforge => [:rdoc, :gem] do
64
- Rake::SshDirPublisher.new(ENV['RUBYFORGE_USER'], ENV['RUBYFORGE_PROJECT'], 'doc').upload
133
+ Rake::SshDirPublisher.new(ENV['RUBYFORGE_USER'], ENV['RUBYFORGE_PROJECT'], 'html').upload
134
+ Rake::SshFilePublisher.new(ENV['RUBYFORGE_USER'], ENV['RUBYFORGE_PROJECT'], 'pkg', "#{PROJECT}-#{PACKAGE_VERSION}.gem").upload
135
+ end
136
+
137
+ # Builds the website and uploads it to Rubyforge.org
138
+ task :upload_website => [:doc] do
139
+ sh """
140
+ rsync -azv -e ssh \
141
+ html/ \
142
+ ENV['RUBYFORGE_USER']:ENV['RUBYFORGE_PROJECT']
143
+ """
144
+ sh """
145
+ rsync -azv -e ssh \
146
+ --exclude='.svn' --delete-excluded \
147
+ doc/res/defs \
148
+ ENV['RUBYFORGE_USER']:ENV['RUBYFORGE_PROJECT']
149
+ """
150
+ sh """
151
+ rsync -azv -e ssh \
152
+ --exclude='.svn' --delete-excluded \
153
+ examples \
154
+ ENV['RUBYFORGE_USER']:ENV['RUBYFORGE_PROJECT']
155
+ """
156
+ end
157
+
158
+ #
159
+ # TEST TASKS
160
+ #
161
+ class RoolsTestTask < Rake::TestTask
162
+ def initialize (name=nil)
163
+ File.delete "engine.log" if File.exist? "engine.log"
164
+ super(name)
165
+ end
166
+ end
167
+
168
+ # Create a task for handling Unit Tests
169
+ RoolsTestTask.new(:test) do |t|
170
+ t.libs << "test"
171
+ t.test_files = FileList['test/rake_test.rb']
172
+ t.verbose = true
65
173
  end
data/README CHANGED
@@ -3,36 +3,35 @@
3
3
 
4
4
  Rools is a rules engine for abstracting business logic and program-flow. It's ideally suited to processing applications where the business logic undergoes frequent modification.
5
5
 
6
+ == RubyForge
7
+ This documentation can be found at http://rools.rubyforge.org
8
+ The project page can be found at http://rubyforge.org/projects/rools
9
+
6
10
  == Example
7
11
 
8
12
  require 'rools'
9
13
 
10
14
  rules = Rools::RuleSet.new do
11
15
 
12
- rule 'Is it a String?' do
16
+ rule 'Hello?' do
13
17
  parameter String
14
- consequence { puts "Yes, it's a String" }
15
- end
16
-
17
- rule 'Is it a country?' do
18
- condition { Country.find_all.collect { |c| c.name }.include?(string) }
19
- consequence { puts "Yes, #{string} is in the country list"}
18
+ consequence { puts "Hello, Rools!" }
20
19
  end
21
20
  end
22
21
 
23
- rules.assert 'Japan'
22
+ rules.assert 'Heya'
24
23
 
25
- > Yes, it's a String
26
- > Yes, Japan is in the country list
24
+ > Hello, Rools!
25
+
27
26
 
28
- You can also store your rules in a seperate file, and pass a path to Rools::RuleSet#new instead of a block. e.g.
27
+ You can also store your rules in a separate file, and pass a path to Rools::RuleSet#new instead of a block. e.g.
29
28
 
30
29
  require 'rools'
31
30
 
32
- rules = Rools::RuleSet.new 'countries.rules'
33
- rules.assert 'Japan'
34
-
35
- == Parameter
31
+ rules = Rools::RuleSet.new 'test/data/hello.rules'
32
+ rules.assert 'heya'
33
+
34
+ === Parameter
36
35
 
37
36
  The +parameter+ method accepts Constants and/or Symbols. Every constant in the list is called with :is_a? on the asserted object, while every symbol in the list is passed to :respond_to? on the asserted object. In other words:
38
37
 
@@ -42,9 +41,9 @@ Is effectively the same as:
42
41
 
43
42
  condition { object.is_a?(Person) && object.responds_to?(:name) && object.responds_to?(:occupation) }
44
43
 
45
- The +parameter+ method is obviously preferred for it's conciseness, and because the working set of rules can be optimized to only include for evaluation (to-do), those rules whose parameters match the asserted object.
44
+ The +parameter+ method is obviously preferred for it's conciseness, and because the working set of rules can be optimized to only include for evaluation those rules whose parameters match the asserted object.
46
45
 
47
- == Condition
46
+ === Condition
48
47
 
49
48
  The +condition+ method is used to evaluate the asserted object. You can have any number of conditions. Rools::DefaultParameterProc#method_missing is used so that you can refer to the asserted object by practically any +lower_case_underscore+ name. Generally you'll want to use names that make sense in the context of the +Rule+, and be consistent through-out the +Rule+. Here's an example:
50
49
 
@@ -62,7 +61,7 @@ Here's an example of something you might want to avoid:
62
61
 
63
62
  Both examples are syntactically correct, but the first is easier to read.
64
63
 
65
- == Consequence
64
+ === Consequence
66
65
 
67
66
  A consequence is a block of code that executes if the conditions evaluate to true. You can have one or more consequences. In the above examples we're doing something simple such as just printing something to the string in the consequences. Usually the consequence is something that will actually change the state of the asserted object in some way however.
68
67
 
@@ -74,7 +73,7 @@ A consequence is a block of code that executes if the conditions evaluate to tru
74
73
 
75
74
  Nevermind that this is application logic you'd probably keep in your domain model. The intent isn't to show you best-practices here, only to make the point that consequences usually modify state.
76
75
 
77
- == Assert
76
+ === Assert
78
77
 
79
78
  What happens if in a +consequence+, you need to create other objects though? You can use the +assert+ method in a +consequence+. Consider the following:
80
79
 
@@ -98,7 +97,7 @@ The first rule asserted a new +Referral+ object into the RuleSet. You could also
98
97
 
99
98
  consequence { RuleSet.new('referral.rules').assert Referral.new(message) }
100
99
 
101
- == Extend
100
+ === Extend
102
101
 
103
102
  Problem: You have a sequence of rules that depend on each other:
104
103
 
@@ -0,0 +1,13 @@
1
+ module Rools
2
+ class Base
3
+ @@logger = nil
4
+
5
+ def logger
6
+ return @@logger
7
+ end
8
+
9
+ def self.logger= (newlogger)
10
+ @@logger = newlogger
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,85 @@
1
+ #require 'rubygems'
2
+ #require_gem 'fastercsv'
3
+ require 'csv'
4
+ require 'rools/base'
5
+
6
+ module Rools
7
+
8
+ class CsvTable < Base
9
+ attr_reader :rules
10
+
11
+ #
12
+ # return quoted String or Number
13
+ # There is probably a more elagant way of doing this but...
14
+ #
15
+ def quote( str )
16
+ return str if (str.to_i.to_s == str)
17
+ return str if (str.to_f.to_s == str)
18
+ '"' + str + '"'
19
+ end
20
+
21
+ def initialize( fileName )
22
+ csv_data = IO.read( fileName)
23
+ arrs = []
24
+ CSV::Reader.parse(csv_data, ",", "\r") do |row|
25
+ #puts "row:#{row.inspect}"
26
+ arrs << row
27
+ end
28
+
29
+ # get rule parameter
30
+ parameter = arrs[1][1]
31
+ #puts "parameter:#{parameter}"
32
+
33
+ # get rule elements
34
+ rule_elements = arrs[2]
35
+
36
+ # get code
37
+ rule_code = arrs[3]
38
+
39
+ # get headers
40
+ headers = arrs[4]
41
+
42
+ #get number of rules
43
+ num_rules = arrs.size-5
44
+ #puts "num rules: #{num_rules}"
45
+
46
+ index = 0
47
+ @rules = ""
48
+ arrs[5..arrs.size].each { |arr|
49
+ rule_name = "rule_#{index}"
50
+ #puts "arr:#{arr}"
51
+
52
+ @rules << "rule '#{rule_name}' do \n"
53
+ @rules << " parameter #{parameter}\n"
54
+ column = 0
55
+ rule_elements.each do |element|
56
+
57
+ field = headers[column].downcase
58
+ str = arr[column]
59
+
60
+ if str != nil
61
+ #puts ("eval: #{field} = '#{str}'")
62
+ #eval( "#{field} = '#{str}'" )
63
+
64
+ @rules << "\t" + element.downcase + "{ "
65
+ pattern = "\#\{#{field}\}"
66
+
67
+ statement = rule_code[column].gsub(pattern,quote(str))
68
+
69
+ #puts "statement:#{statement}"
70
+
71
+ @rules << statement
72
+
73
+ @rules << "}\n"
74
+ end
75
+ column += 1
76
+ end
77
+ @rules << "end\n"
78
+ index += 1
79
+ }
80
+ end
81
+ end
82
+
83
+
84
+
85
+ end
@@ -1,9 +1,10 @@
1
+ require 'rools/base'
1
2
  module Rools
2
3
 
3
4
  # The DefaultParameterProc binds to a Rule and
4
5
  # is allows the block to use method_missing to
5
6
  # refer to the asserted object.
6
- class DefaultParameterProc
7
+ class DefaultParameterProc < Base
7
8
 
8
9
  # Determines whether a method is vital to the functionality
9
10
  # of the class.
@@ -44,7 +45,21 @@ module Rools
44
45
  # Parameterless method calls by the attached block are assumed to
45
46
  # be references to the working object
46
47
  def method_missing(sym, *args)
48
+ # puts "method missing: #{sym}"
49
+ # check if it is a fact first
50
+ begin
51
+ facts = @rule.rule_set.get_facts
52
+ if facts.has_key?( sym.to_s )
53
+ #puts "return fact #{rsfacts[sym.to_s].fact_value}"
54
+ return facts[sym.to_s].fact_value
55
+ else
56
+ #puts "#{sym} not in facts"
57
+ end
58
+ rescue Exception => e
59
+ logger.error "miss exception #{e} #{e.backtrace.join("\n")}" if logger
60
+ end
47
61
  return @working_object if @working_object && args.size == 0
62
+ return nil
48
63
  end
49
64
 
50
65
  # Stops the current assertion. Does not indicate failure.
@@ -0,0 +1,15 @@
1
+ require 'rools/errors'
2
+ require 'rools/default_parameter_proc'
3
+ require 'rools/base'
4
+
5
+ module Rools
6
+ class Facts < Base
7
+ attr_reader :name, :fact_value
8
+
9
+ def initialize(rule_set, name, b)
10
+ @name = name
11
+ @fact_value = instance_eval( &b )
12
+ logger.debug "New Facts: #{@fact_value}" if logger
13
+ end
14
+ end
15
+ end
@@ -1,21 +1,23 @@
1
1
  require 'rools/errors'
2
2
  require 'rools/default_parameter_proc'
3
+ require 'rools/base'
3
4
 
4
5
  module Rools
5
- class Rule
6
- attr_reader :name
6
+ class Rule < Base
7
+ attr_reader :name, :priority, :rule_set
8
+
7
9
 
8
10
  # A Rule requires a Rools::RuleSet, a name, and an associated block
9
11
  # which will be executed at initialization
10
- def initialize(rule_set, name, b)
12
+ def initialize(rule_set, name, priority, b)
11
13
  @rule_set = rule_set
12
- @name = name
13
-
14
- @conditions = []
14
+ @name = name
15
+ @priority = priority
16
+ @conditions = []
15
17
  @consequences = []
16
- @parameters = []
18
+ @parameters = []
17
19
 
18
- instance_eval(&b)
20
+ instance_eval(&b) if b
19
21
  end
20
22
 
21
23
  # Adds a condition to the Rule.
@@ -63,9 +65,11 @@ module Rools
63
65
  # Checks to see if this Rule's parameters match the asserted object
64
66
  def parameters_match?(obj)
65
67
  @parameters.each do |p|
68
+ logger.debug( "match p:#{p} obj:#{obj} sym:#{Symbol}") if logger
66
69
  if p.is_a?(Symbol)
67
70
  return false unless obj.respond_to?(p)
68
71
  else
72
+ logger.debug( "is_a p:#{p} obj:#{obj} #{obj.is_a?(p)}") if logger
69
73
  return false unless obj.is_a?(p)
70
74
  end
71
75
  end
@@ -79,6 +83,7 @@ module Rools
79
83
  begin
80
84
  @conditions.each { |c| return false unless c.call(obj) }
81
85
  rescue StandardError => e
86
+ logger.error( "rule StandardError #{e} #{e.backtrace.join("\n")}") if logger
82
87
  raise RuleCheckError.new(self, e)
83
88
  end
84
89
 
@@ -113,5 +118,9 @@ module Rools
113
118
  def fail(message = nil)
114
119
  @rule_set.fail(message)
115
120
  end
121
+
122
+ def to_s
123
+ @name
124
+ end
116
125
  end
117
126
  end
@@ -1,26 +1,106 @@
1
1
  require 'rools/errors'
2
2
  require 'rools/rule'
3
+ require 'rools/base'
4
+ require 'rools/facts'
5
+ require 'rools/csv_table'
6
+
7
+ require 'rexml/document'
3
8
 
4
9
  module Rools
5
- class RuleSet
6
-
10
+ class RuleSet < Base
11
+ attr_reader :num_executed, :num_evaluated, :facts
12
+
7
13
  PASS = :pass
8
14
  FAIL = :fail
9
15
 
16
+
10
17
  # You can pass a set of Rools::Rules with a block parameter,
11
18
  # or you can pass a file-path to evaluate.
12
19
  def initialize(file = nil, &b)
13
20
 
14
21
  @rules = {}
22
+ @facts = {}
15
23
  @dependencies = {}
16
24
 
17
25
  if block_given?
18
26
  instance_eval(&b)
19
27
  else
20
- instance_eval(File::open(file).read)
21
- end
28
+ # loading a file, check extension
29
+ name,ext = file.split(".")
30
+ logger.debug("loading ext: #{ext}") if logger
31
+ case ext
32
+ when 'csv'
33
+ load_csv( file )
34
+
35
+ when 'xml'
36
+ load_xml( file )
37
+
38
+ when 'rb'
39
+ load_rb( file )
40
+
41
+ when 'rules' # for backwards compatibility
42
+ load_rb(file)
43
+
44
+ else
45
+ raise "invalid file extension: #{ext}"
46
+ end
47
+ end
22
48
  end
23
49
 
50
+ #
51
+ # Loads decision table
52
+ #
53
+ def load_csv( file )
54
+ csv = CsvTable.new( file )
55
+ logger.debug "csv rules: #{csv.rules}" if logger
56
+ instance_eval(csv.rules)
57
+ end
58
+
59
+ #
60
+ # XML File format loading
61
+ #
62
+ def load_xml( fileName )
63
+ file = File.new( fileName )
64
+ doc = REXML::Document.new file
65
+ doc.elements.each( "rule-set") { |rs|
66
+ facts = rs.elements.each( "facts") { |f|
67
+ facts( f.attributes["name"] ) do f.text.strip end
68
+ }
69
+
70
+ rules = rs.elements.each( "rule") { |rule_node|
71
+ rule_name = rule_node.attributes["name"]
72
+ priority = rule_node.attributes["priority"]
73
+
74
+ rule = Rule.new(self, rule_name, priority, nil)
75
+
76
+ parameters = rule_node.elements.each("parameter") { |param|
77
+ rule.parameter do eval(param.text.strip) end
78
+ }
79
+
80
+ conditions = rule_node.elements.each("condition") { |cond|
81
+ rule.condition do eval(cond.text.strip) end
82
+ }
83
+
84
+ consequences = rule_node.elements.each("consequence") { |cons|
85
+ rule.consequence do eval(cons.text.strip) end
86
+ }
87
+
88
+ @rules[rule_name] = rule
89
+ }
90
+ }
91
+ end
92
+
93
+ #
94
+ # Ruby File format
95
+ #
96
+ def load_rb( file )
97
+ instance_eval(File::open(file).read)
98
+ end
99
+
100
+ def get_facts
101
+ @facts
102
+ end
103
+
24
104
  # rule creates a Rools::Rule. Make sure to use a descriptive name or symbol.
25
105
  # For the purposes of extending Rules, all names are converted to
26
106
  # strings and downcased.
@@ -29,11 +109,38 @@ module Rools
29
109
  # condition { language.name.downcase == 'ruby' }
30
110
  # consequence { "#{language.name} is the best!" }
31
111
  # end
32
- def rule(name, &b)
112
+ def rule(name, priority=0, &b)
33
113
  name.to_s.downcase!
34
- @rules[name] = Rule.new(self, name, b)
114
+ @rules[name] = Rule.new(self, name, priority, b)
35
115
  end
36
116
 
117
+ # facts can be created in a similar manner to rules
118
+ # all names are converted to strings and downcased.
119
+ # Facts name is equivalent to a Class Name
120
+ # ==Example
121
+ # require 'rools'
122
+ #
123
+ # rules = Rools::RuleSet.new do
124
+ #
125
+ # facts 'Countries' do
126
+ # ["China", "USSR", "France", "Great Britain", "USA"]
127
+ # end
128
+ #
129
+ # rule 'Is it on Security Council?' do
130
+ # parameter String
131
+ # condition { countries.include?(string) }
132
+ # consequence { puts "Yes, #{string} is in the country list"}
133
+ # end
134
+ # end
135
+ #
136
+ # rules.assert 'France'
137
+ #
138
+ def facts(name, &b)
139
+ name.to_s.downcase!
140
+ @facts[name] = Facts.new(self, name, b)
141
+ end
142
+
143
+
37
144
  # Use in conjunction with Rools::RuleSet#with to create a Rools::Rule dependent on
38
145
  # another. Dependencies are created through names (converted to
39
146
  # strings and downcased), so lax naming can get you into trouble with
@@ -72,6 +179,8 @@ module Rools
72
179
  def assert(obj)
73
180
  @status = PASS
74
181
  @assert = true
182
+ @num_executed = 0;
183
+ @num_evaluated = 0;
75
184
 
76
185
  # create a working-set of all parameter-matching, non-dependent rules
77
186
  available_rules = @rules.values.select { |rule| rule.parameters_match?(obj) }
@@ -84,12 +193,15 @@ module Rools
84
193
 
85
194
  # the loop condition is reset to break by default after every iteration
86
195
  matches = false
87
-
196
+ #logger.debug("available rules: #{available_rules.size.to_s}") if logger
88
197
  available_rules.each do |rule|
89
198
  # RuleCheckErrors are caught and swallowed and the rule that
90
199
  # raised the error is removed from the working-set.
200
+ logger.debug("evaluating: #{rule}") if logger
91
201
  begin
202
+ @num_evaluated += 1
92
203
  if rule.conditions_match?(obj)
204
+ logger.debug("rule #{rule} matched") if logger
93
205
  matches = true
94
206
 
95
207
  # remove the rule from the working-set so it's not re-evaluated
@@ -104,7 +216,9 @@ module Rools
104
216
  end
105
217
 
106
218
  # execute this rule
219
+ logger.debug("executing rule #{rule}") if logger
107
220
  rule.call(obj)
221
+ @num_executed += 1
108
222
 
109
223
  # break the current iteration and start back from the first rule defined.
110
224
  break
@@ -113,6 +227,7 @@ module Rools
113
227
  rescue RuleCheckError => e
114
228
  # log da error or sumpin
115
229
  available_rules.delete(e.rule)
230
+ @status = fail
116
231
  end # begin/rescue
117
232
 
118
233
  end # available_rules.each
@@ -122,12 +237,13 @@ module Rools
122
237
  rescue RuleConsequenceError => rce
123
238
  # RuleConsequenceErrors are allowed to break out of the current assertion,
124
239
  # then the inner error is bubbled-up to the asserting code.
240
+ @status = fail
125
241
  raise rce.inner_error
126
242
  end
127
243
 
128
244
  @assert = false
129
245
 
130
- return status
246
+ return @status
131
247
  end # def assert
132
248
 
133
249
  end # class RuleSet
@@ -0,0 +1,3 @@
1
+ module Rools
2
+ ROOLS_VERSION = '0.1.5'
3
+ end
metadata CHANGED
@@ -1,53 +1,64 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.8.10
2
+ 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.4
7
- date: 2005-12-14
6
+ version: 0.1.5
7
+ date: 2007-04-27 00:00:00 -04:00
8
8
  summary: A Rules Engine written in Ruby
9
9
  require_paths:
10
- - lib
11
- email: ssmoot@gmail.com; bauer.mail@gmail.com
12
- homepage: http://substantiality.net
10
+ - lib
11
+ email: ssmoot@gmail.com; bauer.mail@gmail.com cappelaere@gmail.com
12
+ homepage: http://rools.rubyforge.org/
13
13
  rubyforge_project: rools
14
- description: "Can be used for program-flow, ideally suited to processing applications"
14
+ description: Can be used for program-flow, ideally suited to processing applications
15
15
  autorequire: rools
16
16
  default_executable:
17
17
  bindir: bin
18
18
  has_rdoc: true
19
19
  required_ruby_version: !ruby/object:Gem::Version::Requirement
20
20
  requirements:
21
- -
22
- - ">"
23
- - !ruby/object:Gem::Version
24
- version: 0.0.0
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
25
24
  version:
26
25
  platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
27
29
  authors:
28
- - Sam Smoot
29
- - Scott Bauer
30
+ - Sam Smoot
31
+ - Scott Bauer
32
+ - Pat Cappelaere
30
33
  files:
31
- - README
32
- - CHANGELOG
33
- - RAKEFILE
34
- - lib/rools.rb
35
- - lib/rools/default_parameter_proc.rb
36
- - lib/rools/errors.rb
37
- - lib/rools/rule.rb
38
- - lib/rools/rule_set.rb
34
+ - README
35
+ - CHANGELOG
36
+ - RAKEFILE
37
+ - lib/rools.rb
38
+ - lib/rools/base.rb
39
+ - lib/rools/csv_table.rb
40
+ - lib/rools/default_parameter_proc.rb
41
+ - lib/rools/errors.rb
42
+ - lib/rools/facts.rb
43
+ - lib/rools/rule.rb
44
+ - lib/rools/rule_set.rb
45
+ - lib/rools/version.rb
39
46
  test_files: []
47
+
40
48
  rdoc_options:
41
- - "--line-numbers"
42
- - "--inline-source"
43
- - "--main"
44
- - README
49
+ - --line-numbers
50
+ - --inline-source
51
+ - --main
52
+ - README
45
53
  extra_rdoc_files:
46
- - README
47
- - CHANGELOG
48
- - RAKEFILE
54
+ - README
55
+ - CHANGELOG
56
+ - RAKEFILE
49
57
  executables: []
58
+
50
59
  extensions: []
60
+
51
61
  requirements:
52
- - none
53
- dependencies: []
62
+ - none
63
+ dependencies: []
64
+