baby_bots 0.0.6 → 0.0.7

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/lib/baby_bots.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  require 'baby_bots/version'
2
- require 'baby_bots/baby_bot'
3
2
  require 'baby_bots/state'
3
+ require 'baby_bots/baby_bot'
4
+
@@ -1,10 +1,5 @@
1
- # A tiny finite-state automata library.
2
- #
3
- # Author:: Justin Hamilton (mailto:justinanthonyhamilton@gmail.com)
4
- # Copyright :: BSD2 (see LICENSE for more details)
5
-
6
1
  module BabyBots
7
-
2
+
8
3
  # Error to handle attempts to transition to a state that does not exist.
9
4
  class NoSuchStateException < Exception
10
5
  end
@@ -13,24 +8,33 @@ module BabyBots
13
8
  class NoSuchTransitionException < Exception
14
9
  end
15
10
 
16
- # # Error to handle incorrect arity on the post_state methods.
17
- # class PostMethodArgumentError < ArgumentError
18
- # end
19
-
20
11
  # A tiny finite-state automata class.
21
12
  class BabyBot
22
- attr_accessor :curr, :states, :start
13
+ attr_accessor :curr, :start
23
14
 
24
15
  # Accepts an optional hash of states.
25
16
  def initialize(states={})
26
17
  # Hash of state names to state objects
27
18
  @states = {}
28
19
  if !states.empty? then build states end
20
+
21
+ # Add our default error state to provide user-based 'error' handling.
22
+ add_state(ERRSTATE)
23
+
29
24
  # Initial state
30
25
  @start = nil
31
26
  # Current state
32
27
  @curr = nil
33
28
  end
29
+
30
+ # Provides the states, minus the default error state (because it isn't
31
+ # really a state in the true sense). Note that this is NOT a reference
32
+ # to the BabyBot's actual states, but rather a duplicate.
33
+ def states
34
+ new_states = @states.dup
35
+ new_states.delete(ERR)
36
+ return new_states
37
+ end
34
38
 
35
39
  # Adds a new state to the finite-state automata, also accepts
36
40
  # an optional start flag, which will set the supplied state as the initial
@@ -51,6 +55,12 @@ module BabyBots
51
55
  end
52
56
  end
53
57
 
58
+ # Since we implement a default error state, this empty will check
59
+ # if there are any other states aside from that one.
60
+ def empty?
61
+ @states.keys.find_all { |k| k != ERR }.empty?
62
+ end
63
+
54
64
  # Build up this machine's states with a given state table. The format of
55
65
  # this table is assumed to be {state_name => {event1 => transition1, ...}}.
56
66
  # build assumes that the first state provided is the start state, although
@@ -79,6 +89,11 @@ module BabyBots
79
89
  @curr.state
80
90
  end
81
91
 
92
+ # Return the name of the current state
93
+ def state_name
94
+ @curr.state.state
95
+ end
96
+
82
97
  # Process the finite state automata with the given event.
83
98
  # This will first see if the finite state automata has a defined method
84
99
  # named "pre_{current_state}", and if so "cook" the input event with this
@@ -113,23 +128,30 @@ module BabyBots
113
128
  # if the event is nil, and there is no :else clause,
114
129
  # throw an exception
115
130
  if next_state.nil?
116
- raise NoSuchTransitionException,
117
- "No valid transition #{event} for #{@curr.state}"
131
+ transition_error(event, @curr.state)
118
132
  end
119
-
120
- # check if we need to postprocess the event, this will act
133
+
134
+ # first, check if we need to handle an error function for this state,
135
+ # then check if we need to postprocess the event, this will act
121
136
  # as the "return" from any state transition (even self-looping transitions)
122
- if respond_to?("post_#{@curr.state}")
137
+ if next_state == ERRSTATE
138
+ if respond_to?("error_#{@curr.state}")
139
+ ret_val = my_send("error_#{@curr.state}", event)
140
+ else
141
+ raise NoSuchTransitionException,
142
+ "No valid transition #{event} for #{@curr.state}"
143
+ end
144
+ elsif respond_to?("post_#{@curr.state}")
123
145
  ret_val = my_send("post_#{@curr.state}", event)
124
146
  elsif respond_to?("post_cooked_#{curr.state}")
125
147
  ret_val = my_send("post_#{@curr.state}", cooked_event)
126
148
  end
127
149
 
128
150
  # actually transition, and make sure such a transition exists
129
- @curr = next_state
151
+ @curr = next_state unless next_state == ERRSTATE
152
+
130
153
  if @curr.nil?
131
- raise NoSuchStateException,
132
- "No valid state #{@curr} for transition #{event} from #{curr_state}"
154
+ transition_error(event, @curr.state)
133
155
  end
134
156
 
135
157
  return ret_val
@@ -154,8 +176,23 @@ module BabyBots
154
176
  return true
155
177
  end
156
178
 
179
+ # Check if a method call is checking that state name.
180
+ def method_missing(m, *a, &b)
181
+ if m =~ /^([a-zA-Z_]+)\?$/
182
+ state == m.to_s.gsub("?","").to_sym
183
+ else
184
+ raise NoMethodError
185
+ end
186
+ end
187
+
157
188
  private
158
189
 
190
+ # Wrapper for our NoSuchTransitionException
191
+ def transition_error(event, state)
192
+ raise NoSuchTransitionException,
193
+ "No valid transition #{event} for #{state}"
194
+ end
195
+
159
196
  # A wrapper around send, using the arity restrictions assumed by BabyBots.
160
197
  def my_send(method_name, event=nil)
161
198
  m_arity = method(method_name).arity
@@ -2,6 +2,9 @@ module BabyBots
2
2
  # Transition state used to remove events from a transition table.
3
3
  NOWHERE = :nowhere__
4
4
 
5
+ # Defualt Error Transition
6
+ ERR = :err__
7
+
5
8
  # The state contained within the BabyBots Finite State Automata.
6
9
  # States have an event that transitions to a new state. They may also
7
10
  # have an event named :else which will be the transition used by the
@@ -50,4 +53,6 @@ module BabyBots
50
53
  end
51
54
  end
52
55
 
56
+ ERRSTATE = State.new(:err__)
57
+
53
58
  end
@@ -1,4 +1,4 @@
1
1
  module BabyBots
2
2
  # Current development version
3
- VERSION = "0.0.6"
3
+ VERSION = "0.0.7"
4
4
  end
@@ -6,8 +6,25 @@ $run = BabyBots::State.new(:run, {:else => :run})
6
6
 
7
7
 
8
8
  TEST_MACHINE1 = { :loading => {1 => :ready, :else => :loading},
9
- :ready => {1 => :run, :else => :loading},
10
- :run => {:else => :run} }
9
+ :ready => {1 => :run, :else => :loading},
10
+ :run => {:else => :run} }
11
+
12
+ class ERRTEST < BabyBots::BabyBot
13
+ def initialize
14
+ super
15
+ build({ :loading => {1 => :ready, :else => BabyBots::ERR},
16
+ :ready => {0 => :loading, 1 => :run, :else => BabyBots::ERR},
17
+ :run => {:else => :run} })
18
+ end
19
+
20
+ def error_loading
21
+ "Error loading"
22
+ end
23
+
24
+ def error_ready
25
+ "Error ready"
26
+ end
27
+ end
11
28
 
12
29
  class BB < BabyBots::BabyBot
13
30
  def initialize
@@ -52,117 +69,144 @@ end
52
69
 
53
70
 
54
71
  describe BabyBots::BabyBot do
72
+ before(:each) do
73
+ @test = nil
74
+ @test2 = nil
75
+ end
76
+
77
+ after(:each) do
78
+ @test = nil
79
+ @test2 = nil
80
+ end
81
+
55
82
  it "should be able to be initiated with no additional initialization" do
56
- test = BabyBots::BabyBot.new
57
- test.states.should == {}
83
+ @test = BabyBots::BabyBot.new
84
+ @test.empty?.should == true
85
+ @test = nil
58
86
  end
59
87
 
60
88
  it "should be able to be initialized with a state table" do
61
- test = BabyBots::BabyBot.new(TEST_MACHINE1)
62
- test.states.should == {:loading => $loading, :ready => $ready, :run => $run}
89
+ @test = BabyBots::BabyBot.new(TEST_MACHINE1)
90
+ @test.states.should == {:loading => $loading, :ready => $ready, :run => $run}
63
91
  end
64
92
 
65
93
  it "should have states be able to be added using add_state" do
66
- test = BabyBots::BabyBot.new
67
- test.add_state($loading)
68
- test.states.should == {:loading => $loading}
69
- test.add_state($ready)
70
- test.states.should == {:loading => $loading, :ready => $ready}
71
- test.add_state($run)
72
- test.states.should == {:loading => $loading, :ready => $ready, :run => $run}
94
+ @test = BabyBots::BabyBot.new
95
+ @test.add_state($loading)
96
+ @test.states.should == {:loading => $loading}
97
+ @test.add_state($ready)
98
+ @test.states.should == {:loading => $loading, :ready => $ready}
99
+ @test.add_state($run)
100
+ @test.states.should == {:loading => $loading, :ready => $ready, :run => $run}
73
101
  end
74
102
 
75
103
  it "should be able to have states built using the build method" do
76
- test = BabyBots::BabyBot.new
77
- test.build(TEST_MACHINE1)
104
+ @test = BabyBots::BabyBot.new
105
+ @test.build(TEST_MACHINE1)
78
106
  end
79
107
 
80
108
  it "should allow states to be overwritten using build" do
81
- test = BabyBots::BabyBot.new(TEST_MACHINE1)
82
- test.build({ :loading => {1 => :run, 2 => :loading} })
109
+ @test = BabyBots::BabyBot.new(TEST_MACHINE1)
110
+ @test.build({ :loading => {1 => :run, 2 => :loading} })
83
111
 
84
- test2 = BabyBots::BabyBot.new({ :loading => {1 => :run, 2 => :loading},
112
+ @test2 = BabyBots::BabyBot.new({ :loading => {1 => :run, 2 => :loading},
85
113
  :ready => {1 => :run, :else => :loading},
86
114
  :run => {:else => :run} })
87
- test.states.should == test2.states
115
+ @test.states.should == @test2.states
88
116
  end
89
117
 
90
- it "should run the example, with the starting state being loading" do
91
- test = BB.new
92
- test.start.state.should == :loading
118
+ it "should provide state-based error handling" do
119
+ @test = ERRTEST.new
120
+ msg = @test.process(42)
121
+ msg.should == "Error loading"
122
+ @test.state.should == :loading
123
+ @test.process(1)
124
+ @test.state.should == :ready
125
+ msg = @test.process(42)
126
+ msg.should == "Error ready"
127
+ end
128
+
129
+ it "should run the example, with the starting state being 'loading'" do
130
+ @test = BB.new
131
+ @test.start.state.should == :loading
93
132
  end
94
133
 
95
134
  it "should run the example, with the current state being loading" do
96
- test = BB.new
97
- test.state.should == :loading
135
+ @test = BB.new
136
+ @test.state.should == :loading
98
137
  end
99
138
 
100
139
  it "should run the example, iterating from loading to ready on inputs 1" do
101
- test = BB.new
102
- test.process(1)
103
- test.state.should == :ready
140
+ @test = BB.new
141
+ @test.process(1)
142
+ @test.state.should == :ready
104
143
  end
105
144
 
106
145
  it "should run the example, using pre_loading to convert \"1\" to 1" do
107
- test = BB.new
108
- test.process("1")
109
- test.state.should == :ready
146
+ @test = BB.new
147
+ @test.process("1")
148
+ @test.state.should == :ready
110
149
  end
111
150
 
112
151
  it "should run the example, using pre_loading to convert \"1\" to 1" do
113
- test = BB.new
114
- test.process("1")
115
- test.state.should == :ready
152
+ @test = BB.new
153
+ @test.process("1")
154
+ @test.state.should == :ready
116
155
  end
117
156
 
118
157
  it "should run the example, use pre_loading to convert \"1\" to 1, but use post_loading to return \"1\"" do
119
- test = BB.new
120
- final_var = test.process("1")
121
- test.state.should == :ready
158
+ @test = BB.new
159
+ final_var = @test.process("1")
160
+ @test.state.should == :ready
122
161
  final_var.should == "1"
123
162
  end
124
163
 
125
164
  it "should run the example, and be able to be reset using the restart method" do
126
- test = BB.new
127
- test.process(1)
128
- test.state.should == :ready
129
- test.restart
130
- test.state.should == :loading
165
+ @test = BB.new
166
+ @test.process(1)
167
+ @test.state.should == :ready
168
+ @test.restart
169
+ @test.state.should == :loading
131
170
  end
132
171
 
133
172
  it "should run the example, where anything other than \"1\" or \"0\" looping in :ready" do
134
- test = BB.new
135
- test.process(1)
136
- test.state.should == :ready
137
- test.process("banana phone")
138
- test.state.should == :ready
139
- test.process(99)
140
- test.state.should == :ready
141
- test.process
142
- test.state.should == :ready
173
+ @test = BB.new
174
+ @test.process(1)
175
+ @test.state.should == :ready
176
+ @test.process("banana phone")
177
+ @test.state.should == :ready
178
+ @test.process(99)
179
+ @test.state.should == :ready
180
+ @test.process
181
+ @test.state.should == :ready
143
182
  end
144
183
 
145
184
  it "should run the example, lacking a pre_run should not have any ill side-effects" do
146
- test = BB.new
147
- test.process(1)
148
- test.process(1)
149
- ret1 = test.process("chocolate rain")
150
- ret2 = test.process("gournal")
185
+ @test = BB.new
186
+ @test.process(1)
187
+ @test.process(1)
188
+ ret1 = @test.process("chocolate rain")
189
+ ret2 = @test.process("gournal")
151
190
  ret1.should == ret2
152
191
  ret2.should == true
153
192
  end
154
193
 
155
194
  it "should run the example, and not have issue with a zero-arity method" do
156
- test = BB2.new
157
- test.process(1)
158
- test.process(1)
159
- test.state.should == :run
195
+ @test = BB2.new
196
+ @test.process(1)
197
+ @test.process(1)
198
+ @test.state.should == :run
160
199
  end
161
200
 
162
201
  it "should run the example, and raise an ArgumentError on arity error" do
163
- test = BB3.new
164
- lambda{ test.process(1)}.should raise_error ArgumentError
202
+ @test = BB3.new
203
+ lambda{ @test.process(1)}.should raise_error ArgumentError
165
204
  end
166
205
 
206
+ it "should allow the current state to be checked with the message {state_name}?" do
207
+ @test = BB.new
208
+ @test.loading?.should == true
209
+ @test.not_ready?.should == false
210
+ end
167
211
 
168
212
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: baby_bots
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-07 00:00:00.000000000Z
12
+ date: 2012-05-08 00:00:00.000000000Z
13
13
  dependencies: []
14
14
  description: A tiny finite-state automata library.
15
15
  email: justinanthonyhamilton@gmail.com