ruler 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "rspec", "~> 2.3.0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.5.2"
12
+ gem "rcov", ">= 0"
13
+ gem "mocha"
14
+ end
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Joshua Smith
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,59 @@
1
+ = ruler
2
+
3
+ Ruler is a simple module providing a DSL that helps with defining facts and rules. While ruler is not a prolog or a
4
+ full-fledged production rule system, it might become one someday.
5
+
6
+ == How To Use It
7
+
8
+ You use it whenever you would have a long set of nested if/else blocks or other kinds of conditional logic.
9
+ require 'rubygems'
10
+ require 'ruler'
11
+ class TeaDrinker
12
+ include Ruler
13
+ attr_accessor :tea
14
+
15
+ def make_iced_tea
16
+ puts "Making tea"
17
+ end
18
+
19
+ def drink_iced_tea
20
+ puts "Ahhhhhhh"
21
+ end
22
+
23
+ def thirsty?
24
+ true
25
+ end
26
+
27
+ def tea_check outside_temp
28
+ ruleset do
29
+ fact :it_is_hot, outside_temp >= 100.0
30
+ fact :iced_tea_made, true
31
+ fact :no_iced_tea, notf(:iced_tea_made)
32
+ fact :am_thirsty, self.thirsty?
33
+
34
+ rule [:it_is_hot, :am_thirsty, :no_iced_tea] do
35
+ make_iced_tea
36
+ end
37
+
38
+ rule [:it_is_hot, :am_thirsty, :iced_tea_made] do
39
+ drink_iced_tea
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ == Contributing to ruler
46
+
47
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
48
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
49
+ * Fork the project
50
+ * Start a feature/bugfix branch
51
+ * Commit and push until you are happy with your contribution
52
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
53
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
54
+
55
+ == Copyright
56
+
57
+ Copyright (c) 2011 Joshua Smith. See LICENSE.txt for
58
+ further details.
59
+
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "ruler"
16
+ gem.homepage = "http://github.com/BlueFrogGaming/ruler"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{Ruby DSL to help with rules and facts}
19
+ gem.description = %Q{Longer description of your gem}
20
+ gem.email = "kognate@gmail.com"
21
+ gem.authors = ["Joshua Smith"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rspec/core'
30
+ require 'rspec/core/rake_task'
31
+ RSpec::Core::RakeTask.new(:spec) do |spec|
32
+ spec.pattern = FileList['spec/**/*_spec.rb']
33
+ end
34
+
35
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
36
+ spec.pattern = 'spec/**/*_spec.rb'
37
+ spec.rcov = true
38
+ end
39
+
40
+ task :default => :spec
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
45
+
46
+ rdoc.rdoc_dir = 'rdoc'
47
+ rdoc.title = "ruler #{version}"
48
+ rdoc.rdoc_files.include('README*')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,44 @@
1
+
2
+ require 'rubygems'
3
+ require 'lib/ruler'
4
+
5
+ class TeaDrinker
6
+ include Ruler
7
+ attr_accessor :tea
8
+
9
+ def initialize
10
+ @DEBUG = true
11
+ end
12
+
13
+ def make_iced_tea
14
+ puts "Making tea"
15
+ end
16
+
17
+ def drink_iced_tea
18
+ puts "Ahhhhhhh"
19
+ end
20
+
21
+ def thirsty?
22
+ true
23
+ end
24
+
25
+ def tea_check outside_temp
26
+ ruleset do
27
+ fact :it_is_hot do outside_temp >= 100.0 end
28
+ fact :iced_tea_made, true
29
+ fact :no_iced_tea, notf(:iced_tea_made)
30
+ fact :am_thirsty, self.thirsty?
31
+
32
+ rule [:it_is_hot, :am_thirsty, :no_iced_tea] do
33
+ make_iced_tea
34
+ end
35
+
36
+ rule [:it_is_hot, :am_thirsty, :iced_tea_made] do
37
+ drink_iced_tea
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ t = TeaDrinker.new
44
+ puts t.tea_check 190.0
@@ -0,0 +1,133 @@
1
+ # This module provides a set of methods to manage and run sets of facts and rules.
2
+ # These rules take an array of fact names and all of the facts are true
3
+ # the block passed to the rule is executed. By default, only one rule can fire in a given ruleset.
4
+ # Each ruleset can have a default_rule which is executed if no rule fires (and the ruleset only allows
5
+ # one rule to fire)
6
+
7
+ # Author:: Josh Smith (josh@bluefroggaming.com)
8
+ # Copyright:: (c) 2011 Blue Frog Gaming, All Rights Reserved
9
+ # License:: This file has no public license.
10
+
11
+ class BadDefaultRule < StandardError
12
+ end
13
+
14
+ module Ruler
15
+ # This module uses thread local storage and should be threadsafe.
16
+ #
17
+ # A ruleset is the method that wraps all calls to fact and rule.
18
+ # A ruleset takes two arguments, the first is a boolean that determins if
19
+ # more than one rule can fire. If this value is true, that ruleset allows only one
20
+ # rule to fire (this is the default). If this value is false, all rules that evaluate to
21
+ # true will fire. rulesets can also include a default_rule, which behaves as a normal rule
22
+ # that is always true. The default_rule obeys the ruleset's singletary status
23
+ # A short example:
24
+ #
25
+ # result = ruleset do
26
+ # fact :is_true, true
27
+ # fact :water_is_wet, true
28
+ # fact :cannot_push_on_a_rope, true
29
+ #
30
+ # fact :do_not_mess_with_jim do
31
+ # false
32
+ # end
33
+ # fact :pi_greater_than_four do
34
+ # Math::PI > 4
35
+ # end
36
+ #
37
+ # rule [:is_true, :water_is_wet, :do_not_mess_with_jim] do
38
+ # rand()
39
+ # end
40
+ #
41
+ # rule [:cannot_push_on_a_rope, :water_is_wet, :is_true] do
42
+ # 42
43
+ # end
44
+ #
45
+ # default_rule do
46
+ # 100
47
+ # end
48
+ # end
49
+ # puts result
50
+
51
+ def ruleset singletary = true,&blk
52
+ Thread.current[:singletary] = singletary
53
+ Thread.current[:rulematched] = nil
54
+ Thread.current[:working_memory] = {}
55
+ yield
56
+ end
57
+
58
+ # multi_ruleset is a helper function to define rulesets that allow
59
+ # more than one rule to fire
60
+ def multi_ruleset &blk
61
+ ruleset false,&blk
62
+ end
63
+
64
+ # a fact takes a symbol name and either a value or a block.
65
+ # for example:
66
+ # fact :factname, true
67
+ # fact :otherfactname do
68
+ # some_computation_that_evaluates_to_true_or_false
69
+ # end
70
+ #
71
+ # Facts may be defined anywhere in the ruleset. due to the
72
+ # evaluation order, facts that appear after the last rule
73
+ # will never be used.
74
+ def fact name, dval = nil, &blk
75
+ if dval.nil?
76
+ Thread.current[:working_memory][name] = yield
77
+ else
78
+ Thread.current[:working_memory][name] = dval
79
+ end
80
+ end
81
+
82
+
83
+ # allows for a fact to be NOT another fact.
84
+ # for example:
85
+ # fact :one, 10 == 10
86
+ # fact :notfone, not(:one)
87
+ def notf name
88
+ not(Thread.current[:working_memory][name])
89
+ end
90
+
91
+ # a rule takes a list of fact names and a block. Rules are evaluated in the order
92
+ # they appear in the ruleset and are evaluated at execution time. If all of the facts are true, then
93
+ # that rule is fired. If the ruleset is singletary, only one rule (the first rule to be true)
94
+ # may fire.
95
+ # rules may also have docstrings. These aren't really used yet,
96
+ # but they will be and they provide a nice alternative to commenting.
97
+ # For example:
98
+ # rule [:one_fact, :two_facts], "this is the docstring" do
99
+ # some_method
100
+ # end
101
+ # there is no check to see if fact names are valid, and facts can be (re)defined
102
+ #inside of rules. Fact names are false if they are not defined.
103
+ def rule vlist,docstr = nil,&blk
104
+ dbg = lambda {|va| puts "|=-\t#{va} = #{Thread.current[:working_memory][va]}" }
105
+ if @DEBUG
106
+ puts "---------------------------------------"
107
+ puts vlist.join(" & ")
108
+ puts "======================================="
109
+ vlist.each {|v| dbg.call(v) }
110
+ puts "---------------------------------------"
111
+ end
112
+ if Thread.current[:singletary] && Thread.current[:rulematched]
113
+ Thread.current[:rulematched]
114
+ else
115
+
116
+ Thread.current[:rulematched] = if vlist.inject(true) {|k,v| k ? k && Thread.current[:working_memory][v] : false }
117
+ yield
118
+ end
119
+ end
120
+ end
121
+
122
+ # the default_rule is simple a rule that is always true. This is mostly syntactic-sugar to represent
123
+ # a rule that should fire if no others fire. You cannot have a default rule if you allow more
124
+ # than one match. The BadDefaultRule exception is raised.
125
+ def default_rule &blk
126
+ raise BadDefaultRule.new("Can't have a default rule when multiple matches are allowed") unless Thread.current[:singletary]
127
+ if Thread.current[:singletary] && !Thread.current[:rulematched]
128
+ yield
129
+ else
130
+ Thread.current[:rulematched]
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,191 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'ruler'
3
+
4
+ class Rules
5
+ include Ruler
6
+ def initialize
7
+ #@DEBUG = true
8
+ end
9
+
10
+ def test_one
11
+ ruleset do
12
+
13
+ fact :firstone, true
14
+
15
+ rule [:firstone] do
16
+ true
17
+ end
18
+
19
+ default_rule do
20
+ false
21
+ end
22
+ end
23
+ end
24
+
25
+ def test_two
26
+ ruleset do
27
+ fact :firstone, true
28
+
29
+ rule [:firstone] do
30
+ self.should_fire
31
+ "this should be here"
32
+ end
33
+
34
+ rule [:firstone] do
35
+ self.should_not_fire
36
+ "this should not be here"
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ def test_three
43
+ ruleset do
44
+
45
+ fact :firstone, true
46
+ fact :secondone, true
47
+ fact :thirdone, false
48
+ fact :fourthone, (1 == 1)
49
+ fact :fifthone, (2 == 2)
50
+ fact :sixthone, true
51
+
52
+ rule [:firstone,:secondone,:thirdone, :fourthone, :fifthone] do
53
+ false
54
+ end
55
+
56
+ rule [:firstone,:secondone,:sixthone, :fourthone, :fifthone] do
57
+ true
58
+ end
59
+ end
60
+ end
61
+
62
+ def test_four
63
+ ruleset do
64
+ fact :firstone do
65
+ true || true || false
66
+ end
67
+ fact :secondone, true
68
+ fact :thirdone, true
69
+
70
+ rule [:firstone, :secondone, :thirdone] do
71
+ true
72
+ end
73
+ end
74
+ end
75
+
76
+ def test_five
77
+ ruleset do
78
+ fact :firstone, false
79
+
80
+ rule [:firstone] do
81
+ false
82
+ end
83
+
84
+ default_rule do
85
+ true
86
+ end
87
+ end
88
+ end
89
+
90
+ def test_six
91
+ ruleset do
92
+ fact :firstone, false
93
+ fact :secondone, notf(:firstone)
94
+
95
+ rule [:secondone] do
96
+ true
97
+ end
98
+ end
99
+ end
100
+
101
+ def test_seven
102
+ multi_ruleset do
103
+ fact :firstone, true
104
+ fact :secondone, true
105
+
106
+ rule [:firstone] do
107
+ self.test_seven_multi
108
+ true
109
+ end
110
+
111
+ rule [:secondone] do
112
+ self.test_seven_multi
113
+ true
114
+ end
115
+ end
116
+ end
117
+
118
+ def test_eight
119
+ ruleset do
120
+ fact :firstone, true
121
+
122
+ rule [:firstone],"""
123
+ This is the documentation for this rule.
124
+ It can be on multiple lines if you
125
+ do it right. This is a ruby limitation,
126
+ I think.
127
+ """ do
128
+ true
129
+ end
130
+ end
131
+ end
132
+
133
+ def test_nine
134
+ multi_ruleset do
135
+ default_rule do
136
+ true
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ describe Rules do
143
+ it "should process a simple rule" do
144
+ r = Rules.new
145
+ r.test_one.should be(true)
146
+ end
147
+
148
+ it "should only process the first matching rule" do
149
+ r = Rules.new
150
+ r.expects(:should_fire)
151
+ r.test_two.should == "this should be here"
152
+ end
153
+
154
+ it "should process rules with several conditions" do
155
+ r = Rules.new
156
+ r.test_three.should be(true)
157
+ end
158
+
159
+ it "should handle ORs" do
160
+ r = Rules.new
161
+ r.test_four.should be(true)
162
+ end
163
+
164
+ it "should respect the default rule" do
165
+ r = Rules.new
166
+ r.test_five.should be(true)
167
+ end
168
+
169
+ it "should run correctly with notf" do
170
+ r = Rules.new
171
+ r.test_six.should be(true)
172
+ end
173
+
174
+ it "should run multi_rulesets" do
175
+ r = Rules.new
176
+ r.expects(:test_seven_multi).twice
177
+ r.test_seven.should be(true)
178
+ end
179
+
180
+ it "should run even with docstrings" do
181
+ r = Rules.new
182
+ r.test_eight.should be(true)
183
+ end
184
+
185
+ it "should throw if there is a defualt rule in a multimatch" do
186
+ r = Rules.new
187
+ lambda { r.test_nine.should}.should raise_error
188
+ end
189
+
190
+ end
191
+
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'ruler'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+ config.mock_with :mocha
12
+ end
metadata ADDED
@@ -0,0 +1,154 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruler
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Joshua Smith
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-01-31 00:00:00 -05:00
19
+ default_executable: example.rb
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ prerelease: false
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
+ - 2
31
+ - 3
32
+ - 0
33
+ version: 2.3.0
34
+ name: rspec
35
+ requirement: *id001
36
+ type: :development
37
+ - !ruby/object:Gem::Dependency
38
+ prerelease: false
39
+ version_requirements: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 23
45
+ segments:
46
+ - 1
47
+ - 0
48
+ - 0
49
+ version: 1.0.0
50
+ name: bundler
51
+ requirement: *id002
52
+ type: :development
53
+ - !ruby/object:Gem::Dependency
54
+ prerelease: false
55
+ version_requirements: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ hash: 7
61
+ segments:
62
+ - 1
63
+ - 5
64
+ - 2
65
+ version: 1.5.2
66
+ name: jeweler
67
+ requirement: *id003
68
+ type: :development
69
+ - !ruby/object:Gem::Dependency
70
+ prerelease: false
71
+ version_requirements: &id004 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 3
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ name: rcov
81
+ requirement: *id004
82
+ type: :development
83
+ - !ruby/object:Gem::Dependency
84
+ prerelease: false
85
+ version_requirements: &id005 !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ hash: 3
91
+ segments:
92
+ - 0
93
+ version: "0"
94
+ name: mocha
95
+ requirement: *id005
96
+ type: :development
97
+ description: Longer description of your gem
98
+ email: kognate@gmail.com
99
+ executables:
100
+ - example.rb
101
+ extensions: []
102
+
103
+ extra_rdoc_files:
104
+ - LICENSE.txt
105
+ - README.rdoc
106
+ files:
107
+ - .document
108
+ - .rspec
109
+ - Gemfile
110
+ - LICENSE.txt
111
+ - README.rdoc
112
+ - Rakefile
113
+ - VERSION
114
+ - bin/example.rb
115
+ - lib/ruler.rb
116
+ - spec/ruler_spec.rb
117
+ - spec/spec_helper.rb
118
+ has_rdoc: true
119
+ homepage: http://github.com/BlueFrogGaming/ruler
120
+ licenses:
121
+ - MIT
122
+ post_install_message:
123
+ rdoc_options: []
124
+
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ hash: 3
133
+ segments:
134
+ - 0
135
+ version: "0"
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ hash: 3
142
+ segments:
143
+ - 0
144
+ version: "0"
145
+ requirements: []
146
+
147
+ rubyforge_project:
148
+ rubygems_version: 1.3.7
149
+ signing_key:
150
+ specification_version: 3
151
+ summary: Ruby DSL to help with rules and facts
152
+ test_files:
153
+ - spec/ruler_spec.rb
154
+ - spec/spec_helper.rb