statemachine 0.2.0 → 0.3.0

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/CHANGES CHANGED
@@ -1,5 +1,25 @@
1
1
  = Statemachine Changelog
2
2
 
3
+ == Version 0.3.0
4
+
5
+ Feature enhancements
6
+ * added default transitions
7
+ * added default history for superstates
8
+ * the context method in the builder will set the context's statemachine variable if the context respond_to?(:statemachine=)
9
+
10
+ Behavior Fixes
11
+ * the entry action of the startstate is called when the statemachine starts or is reset.
12
+ * resetting the statemachine will reset the history state for each superstate.
13
+
14
+ == Version 0.2.2
15
+
16
+ Minor plugin update
17
+ * introduced before_event and after_event hooks for controllers
18
+
19
+ == Version 0.2.1
20
+
21
+ Rails Plugin.
22
+ * Rails plugin introduced
3
23
 
4
24
  == Version 0.2.0
5
25
 
data/README ADDED
@@ -0,0 +1,38 @@
1
+ = Statemachine Gem
2
+
3
+ This Ruby Library enables simple creation of full-features Finite Statemachines.
4
+
5
+ == API
6
+
7
+ Where to start:
8
+
9
+ * Statemachine.build
10
+ * Statemachine::SuperstateBuilding
11
+ * Statemachine::StateBuilding
12
+ * Statemachine::Statemachine
13
+
14
+ == Documentation
15
+
16
+ Some documentation is available here in this RDOC documentation.
17
+ You may also find useful documentation on the Statemachine website: http://statemachine.rubyforge.org
18
+
19
+ A detailed tutorial and overview of Finite State Machines and this library can be found
20
+ at http://blog.8thlight.com/articles/2006/11/17/understanding-statemachines-part-1-states-and-transitions.
21
+
22
+ == License
23
+
24
+ Copyright (C) 2006 Micah Martin
25
+
26
+ This library is free software; you can redistribute it and/or
27
+ modify it under the terms of the GNU Lesser General Public
28
+ License as published by the Free Software Foundation; either
29
+ version 2.1 of the License, or (at your option) any later version.
30
+
31
+ This library is distributed in the hope that it will be useful,
32
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
33
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
34
+ Lesser General Public License for more details.
35
+
36
+ You should have received a copy of the GNU Lesser General Public
37
+ License along with this library; if not, write to the Free Software
38
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
data/Rakefile CHANGED
@@ -7,13 +7,6 @@ require 'rake/rdoctask'
7
7
  require 'spec/rake/spectask'
8
8
  require 'statemachine'
9
9
 
10
- # Some of the tasks are in separate files since they are also part of the website documentation
11
- "load File.dirname(__FILE__) + '/tasks/examples.rake'
12
- load File.dirname(__FILE__) + '/tasks/examples_specdoc.rake'
13
- load File.dirname(__FILE__) + '/tasks/examples_with_rcov.rake'
14
- load File.dirname(__FILE__) + '/tasks/failing_examples_with_html.rake'
15
- load File.dirname(__FILE__) + '/tasks/verify_rcov.rake'"
16
-
17
10
  PKG_NAME = "statemachine"
18
11
  PKG_VERSION = Statemachine::VERSION::STRING
19
12
  PKG_TAG = Statemachine::VERSION::TAG
@@ -22,7 +15,6 @@ PKG_FILES = FileList[
22
15
  '[A-Z]*',
23
16
  'lib/**/*.rb',
24
17
  'spec/**/*.rb'
25
- # 'examples/**/*',
26
18
  ]
27
19
 
28
20
  task :default => :spec
@@ -30,52 +22,26 @@ task :default => :spec
30
22
  desc "Run all specs"
31
23
  Spec::Rake::SpecTask.new do |t|
32
24
  t.spec_files = FileList['spec/**/*_spec.rb']
33
- # t.spec_opts = ['--diff','--color']
34
- # t.rcov = true
35
- # t.rcov_dir = 'doc/output/coverage'
36
- # t.rcov_opts = ['--exclude', 'spec\/spec,bin\/spec']"
37
25
  end
38
26
 
39
- #desc 'Generate HTML documentation for website'
40
- #task :webgen do
41
- # Dir.chdir 'doc' do
42
- # output = nil
43
- # IO.popen('webgen 2>&1') do |io|
44
- # output = io.read
45
- # end
46
- # raise "ERROR while running webgen: #{output}" if output =~ /ERROR/n || $? != 0
47
- # end
48
- #end
49
-
50
- #desc 'Generate RDoc'
51
- #rd = Rake::RDocTask.new do |rdoc|
52
- # rdoc.rdoc_dir = 'doc/output/rdoc'
53
- # rdoc.options << '--title' << 'RSpec' << '--line-numbers' << '--inline-source' << '--main' << 'README'
54
- # rdoc.rdoc_files.include('README', 'CHANGES', 'EXAMPLES.rd', 'lib/**/*.rb')
55
- #end
56
- #task :rdoc => :examples_specdoc # We generate EXAMPLES.rd
27
+ desc 'Generate RDoc'
28
+ rd = Rake::RDocTask.new do |rdoc|
29
+ rdoc.rdoc_dir = 'doc/website/output/rdoc'
30
+ rdoc.options << '--title' << 'Statemachine' << '--line-numbers' << '--inline-source' << '--main' << 'README'
31
+ rdoc.rdoc_files.include('README', 'CHANGES', 'lib/**/*.rb')
32
+ end
33
+ task :rdoc
57
34
 
58
35
  spec = Gem::Specification.new do |s|
59
36
  s.name = PKG_NAME
60
37
  s.version = PKG_VERSION
61
38
  s.summary = Statemachine::VERSION::DESCRIPTION
62
- s.description = <<-EOF
63
- Statemachine is a ruby library for building Finite State Machines (FSM), also known as Finite State Automata (FSA).
64
- EOF
65
-
39
+ s.description = "Statemachine is a ruby library for building Finite State Machines (FSM), also known as Finite State Automata (FSA)."
66
40
  s.files = PKG_FILES.to_a
67
41
  s.require_path = 'lib'
68
-
69
- # s.has_rdoc = true
70
- # s.rdoc_options = rd.options
71
- # s.extra_rdoc_files = rd.rdoc_files.reject { |fn| fn =~ /\.rb$|^EXAMPLES.rd$/ }.to_a
72
-
73
42
  s.test_files = Dir.glob('spec/*_spec.rb')
74
43
  s.require_path = 'lib'
75
44
  s.autorequire = 'statemachine'
76
- # s.bindir = "bin"
77
- # s.executables = ["spec"]
78
- # s.default_executable = "spec"
79
45
  s.author = "Micah Martin"
80
46
  s.email = "statemachine-devel@rubyforge.org"
81
47
  s.homepage = "http://statemachine.rubyforge.org"
data/TODO CHANGED
@@ -1,3 +1,4 @@
1
1
 
2
- Implement default history
3
- Implement superstate end state with automatic transition
2
+
3
+ Maybe:
4
+ Implement superstate endstate with automatic transition
data/lib/statemachine.rb CHANGED
@@ -1,7 +1,25 @@
1
+ #--
2
+ # Copyright (C) 2006 Micah Martin
3
+ #
4
+ # This library is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU Lesser General Public
6
+ # License as published by the Free Software Foundation; either
7
+ # version 2.1 of the License, or (at your option) any later version.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
+ #++
18
+
1
19
  require 'statemachine/action_invokation'
2
20
  require 'statemachine/state'
3
- require 'statemachine/super_state'
21
+ require 'statemachine/superstate'
4
22
  require 'statemachine/transition'
5
- require 'statemachine/state_machine'
23
+ require 'statemachine/statemachine'
6
24
  require 'statemachine/builder'
7
25
  require 'statemachine/version'
@@ -1,6 +1,6 @@
1
1
  module Statemachine
2
2
 
3
- module ActionInvokation
3
+ module ActionInvokation #:nodoc:
4
4
 
5
5
  def invoke_action(action, args, message)
6
6
  if action.is_a? Symbol
@@ -1,13 +1,36 @@
1
1
  module Statemachine
2
-
2
+
3
+ # The starting point for building instances of Statemachine.
4
+ # The block passed in should contain all the declarations for all
5
+ # states, events, and actions with in the statemachine.
6
+ #
7
+ # Sample: Turnstyle
8
+ #
9
+ # sm = Statemachine.build do
10
+ # trans :locked, :coin, :unlocked, :unlock
11
+ # trans :unlocked, :pass, :locked, :lock
12
+ # end
13
+ #
14
+ # An optional statemachine paramter may be passed in to modify
15
+ # an existing statemachine instance.
16
+ #
17
+ # Actions:
18
+ # Where ever an action paramter is used, it may take on one of three forms:
19
+ # 1. Symbols: will execute a method by the same name on the _context_
20
+ # 2. String: Ruby code that will be executed within the binding of the _context_
21
+ # 3. Proc: Will be executed within the binding of the _context_
22
+ #
23
+ # See Statemachine::SuperstateBuilding
24
+ # See Statemachine::StateBuilding
25
+ #
3
26
  def self.build(statemachine = nil, &block)
4
27
  builder = statemachine ? StatemachineBuilder.new(statemachine) : StatemachineBuilder.new
5
28
  builder.instance_eval(&block)
6
29
  builder.statemachine.reset
7
30
  return builder.statemachine
8
31
  end
9
-
10
- class Builder
32
+
33
+ class Builder #:nodoc:
11
34
  attr_reader :statemachine
12
35
 
13
36
  def initialize(statemachine)
@@ -29,54 +52,164 @@ module Statemachine
29
52
  return state
30
53
  end
31
54
  end
32
-
55
+
56
+ # The builder module used to declare states.
33
57
  module StateBuilding
34
58
  attr_reader :subject
35
59
 
60
+ # Declares that the state responds to the spcified event.
61
+ # The +event+ paramter should be a Symbol.
62
+ # The +destination_id+, which should also be a Symbol, is the id of the state
63
+ # that will event will transition into.
64
+ #
65
+ # The 3rd +action+ paramter is optional
66
+ #
67
+ # sm = Statemachine.build do
68
+ # state :locked do
69
+ # event :coin, :unlocked, :unlock
70
+ # end
71
+ # end
72
+ #
36
73
  def event(event, destination_id, action = nil)
37
74
  @subject.add(Transition.new(@subject.id, destination_id, event, action))
38
75
  end
39
76
 
77
+ # Declare the entry action for the state.
78
+ #
79
+ # sm = Statemachine.build do
80
+ # state :locked do
81
+ # on_entry :lock
82
+ # end
83
+ # end
84
+ #
40
85
  def on_entry(entry_action)
41
86
  @subject.entry_action = entry_action
42
87
  end
43
-
88
+
89
+ # Declare the exit action for the state.
90
+ #
91
+ # sm = Statemachine.build do
92
+ # state :locked do
93
+ # on_exit :unlock
94
+ # end
95
+ # end
96
+ #
44
97
  def on_exit(exit_action)
45
98
  @subject.exit_action = exit_action
46
99
  end
100
+
101
+ # Declare a default transition for the state. Any event that is not already handled
102
+ # by the state will be handled by this transition.
103
+ #
104
+ # sm = Statemachine.build do
105
+ # state :locked do
106
+ # default :unlock, :action
107
+ # end
108
+ # end
109
+ #
110
+ def default(destination_id, action = nil)
111
+ @subject.default_transition = Transition.new(@subject.id, destination_id, nil, action)
112
+ end
47
113
  end
48
114
 
115
+ # The builder module used to declare superstates.
49
116
  module SuperstateBuilding
50
117
  attr_reader :subject
51
-
118
+
119
+ # Define a state within the statemachine or superstate.
120
+ #
121
+ # sm = Statemachine.build do
122
+ # state :locked do
123
+ # #define the state
124
+ # end
125
+ # end
126
+ #
52
127
  def state(id, &block)
53
128
  builder = StateBuilder.new(id, @subject, @statemachine)
54
129
  builder.instance_eval(&block) if block
55
130
  end
56
-
131
+
132
+ # Define a superstate within the statemachine or superstate.
133
+ #
134
+ # sm = Statemachine.build do
135
+ # superstate :operational do
136
+ # #define superstate
137
+ # end
138
+ # end
139
+ #
57
140
  def superstate(id, &block)
58
141
  builder = SuperstateBuilder.new(id, @subject, @statemachine)
59
142
  builder.instance_eval(&block)
60
143
  end
61
-
144
+
145
+ # Declares a transition within the superstate or statemachine.
146
+ # The +origin_id+, a Symbol, identifies the starting state for this transition. The state
147
+ # identified by +origin_id+ will be created within the statemachine or superstate which this
148
+ # transition is declared.
149
+ # The +event+ paramter should be a Symbol.
150
+ # The +destination_id+, which should also be a Symbol, is the id of the state that will
151
+ # event will transition into. This method will not create destination states within the
152
+ # current statemachine of superstate. If the state destination state should exist here,
153
+ # that declare with with the +state+ method or declare a transition starting at the state.
154
+ #
155
+ # sm = Statemachine.build do
156
+ # trans :locked, :coin, :unlocked, :unlock
157
+ # end
158
+ #
62
159
  def trans(origin_id, event, destination_id, action = nil)
63
160
  origin = acquire_state_in(origin_id, @subject)
64
161
  origin.add(Transition.new(origin_id, destination_id, event, action))
65
162
  end
66
163
 
164
+ # Specifies the startstate for the statemachine or superstate. The state must
165
+ # exist within the scope.
166
+ #
167
+ # sm = Statemachine.build do
168
+ # startstate :locked
169
+ # end
170
+ #
67
171
  def startstate(startstate_id)
68
172
  @subject.startstate_id = startstate_id
69
173
  end
70
174
 
175
+ # Allows the declaration of entry actions without using the +state+ method. +id+ is identifies
176
+ # the state to which the entry action will be added.
177
+ #
178
+ # sm = Statemachine.build do
179
+ # trans :locked, :coin, :unlocked
180
+ # on_entry_of :unlocked, :unlock
181
+ # end
182
+ #
71
183
  def on_entry_of(id, action)
72
184
  @statemachine.get_state(id).entry_action = action
73
185
  end
74
186
 
187
+ # Allows the declaration of exit actions without using the +state+ method. +id+ is identifies
188
+ # the state to which the exit action will be added.
189
+ #
190
+ # sm = Statemachine.build do
191
+ # trans :locked, :coin, :unlocked
192
+ # on_exit_of :locked, :unlock
193
+ # end
194
+ #
75
195
  def on_exit_of(id, action)
76
196
  @statemachine.get_state(id).exit_action = action
77
197
  end
198
+
199
+ # Used to specify the default state held by the history pseudo state of the superstate.
200
+ #
201
+ # sm = Statemachine.build do
202
+ # superstate :operational do
203
+ # default_history :state_id
204
+ # end
205
+ # end
206
+ #
207
+ def default_history(id)
208
+ @subject.default_history = @statemachine.get_state(id)
209
+ end
78
210
  end
79
211
 
212
+ # Builder class used to define states. Creates by SuperstateBuilding#state
80
213
  class StateBuilder < Builder
81
214
  include StateBuilding
82
215
 
@@ -85,7 +218,8 @@ module Statemachine
85
218
  @subject = acquire_state_in(id, superstate)
86
219
  end
87
220
  end
88
-
221
+
222
+ # Builder class used to define superstates. Creates by SuperstateBuilding#superstate
89
223
  class SuperstateBuilder < Builder
90
224
  include StateBuilding
91
225
  include SuperstateBuilding
@@ -98,6 +232,7 @@ module Statemachine
98
232
  end
99
233
  end
100
234
 
235
+ # Created by Statemachine.build as the root context for building the statemachine.
101
236
  class StatemachineBuilder < Builder
102
237
  include SuperstateBuilding
103
238
 
@@ -106,8 +241,17 @@ module Statemachine
106
241
  @subject = @statemachine.root
107
242
  end
108
243
 
244
+ # Used the set the context of the statemahine within the builder.
245
+ #
246
+ # sm = Statemachine.build do
247
+ # ...
248
+ # context MyContext.new
249
+ # end
250
+ #
251
+ # Statemachine.context may also be used.
109
252
  def context(a_context)
110
253
  @statemachine.context = a_context
254
+ a_context.statemachine = @statemachine if a_context.respond_to?(:statemachine=)
111
255
  end
112
256
  end
113
257
 
@@ -1,9 +1,9 @@
1
1
  module Statemachine
2
2
 
3
- class State
3
+ class State #:nodoc:
4
4
 
5
5
  attr_reader :id, :statemachine, :superstate
6
- attr_accessor :entry_action, :exit_action
6
+ attr_accessor :entry_action, :exit_action, :default_transition
7
7
 
8
8
  def initialize(id, superstate, state_machine)
9
9
  @id = id
@@ -20,13 +20,20 @@ module Statemachine
20
20
  return @superstate ? @transitions.merge(@superstate.transitions) : @transitions
21
21
  end
22
22
 
23
+ def transition_for(event)
24
+ transition = @transitions[event]
25
+ transition = @default_transition if not transition
26
+ transition = @superstate.transition_for(event) if @superstate and not transition
27
+ return transition
28
+ end
29
+
23
30
  def exit(args)
24
31
  @statemachine.trace("\texiting #{self}")
25
32
  @statemachine.invoke_action(@exit_action, args, "exit action for #{self}") if @exit_action
26
33
  @superstate.substate_exiting(self) if @superstate
27
34
  end
28
35
 
29
- def enter(args)
36
+ def enter(args=[])
30
37
  @statemachine.trace("\tentering #{self}")
31
38
  @statemachine.invoke_action(@entry_action, args, "entry action for #{self}") if @entry_action
32
39
  end
@@ -38,6 +45,9 @@ module Statemachine
38
45
  def is_concrete?
39
46
  return true
40
47
  end
48
+
49
+ def reset
50
+ end
41
51
 
42
52
  def to_s
43
53
  return "'#{id}' state"
@@ -3,34 +3,66 @@ module Statemachine
3
3
  class StatemachineException < Exception
4
4
  end
5
5
 
6
+ # Used at runtime to execute the behavior of the statemachine.
7
+ # Should be created by using the Statemachine.build method.
8
+ #
9
+ # sm = Statemachine.build do
10
+ # trans :locked, :coin, :unlocked
11
+ # trans :unlocked, :pass, :locked:
12
+ # end
13
+ #
14
+ # sm.coin
15
+ # sm.state
16
+ #
17
+ # This class will accept any method that corresponds to an event. If the
18
+ # current state respons to the event, the appropriate transtion will be invoked.
19
+ # Otherwise an exception will be raised.
6
20
  class Statemachine
7
-
8
21
  include ActionInvokation
9
22
 
10
- attr_accessor :tracer, :context
11
- attr_reader :root
23
+ # The tracer is an IO object. The statemachine will write run time execution
24
+ # information to the +tracer+. Can be helpful in debugging. Defaults to nil.
25
+ attr_accessor :tracer
26
+
27
+ # Provides access to the +context+ of the statemachine. The context is a object
28
+ # where all actions will be invoked. This provides a way to separate logic from
29
+ # behavior. The statemachine is responsible for all the logic and the context
30
+ # is responsible for all the behavior.
31
+ attr_accessor :context
32
+
33
+ attr_reader :root #:nodoc:
12
34
 
35
+ # Should not be called directly. Instances of Statemachine::Statemachine are created
36
+ # through the Statemachine.build method.
13
37
  def initialize(root = Superstate.new(:root, nil, self))
14
38
  @root = root
15
39
  @states = {}
16
40
  end
17
41
 
42
+ # Returns the id of the startstate of the statemachine.
18
43
  def startstate
19
44
  return @root.startstate_id
20
45
  end
21
46
 
47
+ # Resets the statemachine back to its starting state.
22
48
  def reset
23
49
  @state = get_state(@root.startstate_id)
24
50
  while @state and not @state.is_concrete?
25
51
  @state = get_state(@state.startstate_id)
26
52
  end
27
53
  raise StatemachineException.new("The state machine doesn't know where to start. Try setting the startstate.") if @state == nil
54
+ @state.enter
55
+
56
+ @states.values.each { |state| state.reset }
28
57
  end
29
58
 
59
+ # Return the id of the current state of the statemachine.
30
60
  def state
31
61
  return @state.id
32
62
  end
33
63
 
64
+ # You may change the state of the statemachine by using this method. The parameter should be
65
+ # the id of the desired state.
34
66
  def state= value
35
67
  if value.is_a? State
36
68
  @state = value
@@ -41,11 +73,16 @@ module Statemachine
41
73
  end
42
74
  end
43
75
 
76
+ # The key method to exercise the statemachine. Any extra arguments supplied will be passed into
77
+ # any actions associated with the transition.
78
+ #
79
+ # Alternatively to this method, you may invoke methods, names the same as the event, on the statemachine.
80
+ # The advantage of using +process_event+ is that errors messages are more informative.
44
81
  def process_event(event, *args)
45
82
  event = event.to_sym
46
83
  trace "Event: #{event}"
47
84
  if @state
48
- transition = @state.transitions[event]
85
+ transition = @state.transition_for(event)
49
86
  if transition
50
87
  transition.invoke(@state, self, args)
51
88
  else
@@ -56,11 +93,11 @@ module Statemachine
56
93
  end
57
94
  end
58
95
 
59
- def trace(message)
96
+ def trace(message) #:nodoc:
60
97
  @tracer.puts message if @tracer
61
98
  end
62
99
 
63
- def get_state(id)
100
+ def get_state(id) #:nodoc:
64
101
  if @states.has_key? id
65
102
  return @states[id]
66
103
  elsif(is_history_state_id?(id))
@@ -76,11 +113,11 @@ module Statemachine
76
113
  end
77
114
  end
78
115
 
79
- def add_state(state)
116
+ def add_state(state) #:nodoc:
80
117
  @states[state.id] = state
81
118
  end
82
119
 
83
- def has_state(id)
120
+ def has_state(id) #:nodoc:
84
121
  if(is_history_state_id?(id))
85
122
  return @states.has_key?(base_id(id))
86
123
  else
@@ -88,8 +125,8 @@ module Statemachine
88
125
  end
89
126
  end
90
127
 
91
- def method_missing(message, *args)
92
- if @state and @state.transitions[message]
128
+ def method_missing(message, *args) #:nodoc:
129
+ if @state and @state.transition_for(message)
93
130
  method = self.method(:process_event)
94
131
  params = [message.to_sym].concat(args)
95
132
  method.call(*params)
@@ -1,6 +1,6 @@
1
1
  module Statemachine
2
2
 
3
- class Superstate < State
3
+ class Superstate < State #:nodoc:
4
4
 
5
5
  attr_accessor :startstate_id
6
6
  attr_reader :history
@@ -22,6 +22,14 @@ module Statemachine
22
22
  def add_substates(*substate_ids)
23
23
  do_substate_adding(substate_ids)
24
24
  end
25
+
26
+ def default_history=(state)
27
+ @history = @default_history = state
28
+ end
29
+
30
+ def reset
31
+ @history = @default_history
32
+ end
25
33
 
26
34
  def to_s
27
35
  return "'#{id}' superstate"
@@ -1,6 +1,6 @@
1
1
  module Statemachine
2
2
 
3
- class Transition
3
+ class Transition #:nodoc:
4
4
 
5
5
  attr_reader :origin_id, :event, :action
6
6
  attr_accessor :destination_id
@@ -1,8 +1,8 @@
1
1
  module Statemachine
2
- module VERSION
2
+ module VERSION #:nodoc:
3
3
  unless defined? MAJOR
4
4
  MAJOR = 0
5
- MINOR = 2
5
+ MINOR = 3
6
6
  TINY = 0
7
7
 
8
8
  STRING = [MAJOR, MINOR, TINY].join('.')
data/spec/builder_spec.rb CHANGED
@@ -39,6 +39,7 @@ context "Builder" do
39
39
  end
40
40
 
41
41
  specify "Adding a superstate to the switch" do
42
+ the_context = self
42
43
  sm = Statemachine.build do
43
44
  superstate :operation do
44
45
  event :admin, :testing, lambda { @log << "testing" }
@@ -48,8 +49,8 @@ context "Builder" do
48
49
  end
49
50
  trans :testing, :resume, :operation, lambda { @log << "resuming" }
50
51
  startstate :off
52
+ context the_context
51
53
  end
52
- sm.context = self
53
54
 
54
55
  sm.state.should_be :off
55
56
  sm.toggle
@@ -61,6 +62,7 @@ context "Builder" do
61
62
  end
62
63
 
63
64
  specify "entry exit actions" do
65
+ the_context = self
64
66
  sm = Statemachine.build do
65
67
  state :off do
66
68
  on_entry Proc.new { @log << "enter off" }
@@ -68,18 +70,19 @@ context "Builder" do
68
70
  on_exit Proc.new { @log << "exit off" }
69
71
  end
70
72
  trans :on, :toggle, :off, lambda { @log << "toggle off" }
73
+ context the_context
71
74
  end
72
- sm.context = self
73
75
 
74
76
  sm.toggle
75
77
  sm.state.should_be :on
76
78
  sm.toggle
77
79
  sm.state.should_be :off
78
80
 
79
- @log.join(",").should_eql "exit off,toggle on,toggle off,enter off"
81
+ @log.join(",").should_eql "enter off,exit off,toggle on,toggle off,enter off"
80
82
  end
81
83
 
82
84
  specify "History state" do
85
+ the_context = self
83
86
  sm = Statemachine.build do
84
87
  superstate :operation do
85
88
  event :admin, :testing, lambda { @log << "testing" }
@@ -92,29 +95,30 @@ context "Builder" do
92
95
  end
93
96
  trans :testing, :resume, :operation_H, lambda { @log << "resuming" }
94
97
  startstate :off
98
+ context the_context
95
99
  end
96
- sm.context = self
97
100
 
98
101
  sm.admin
99
102
  sm.resume
100
103
  sm.state.should_be :off
101
104
 
102
- @log.join(",").should_eql "testing,resuming,enter off"
105
+ @log.join(",").should_eql "enter off,testing,resuming,enter off"
103
106
  end
104
107
 
105
108
  specify "entry and exit action created from superstate builder" do
109
+ the_context = self
106
110
  sm = Statemachine.build do
107
111
  trans :off, :toggle, :on, Proc.new { @log << "toggle on" }
108
112
  on_entry_of :off, Proc.new { @log << "entering off" }
109
113
  trans :on, :toggle, :off, Proc.new { @log << "toggle off" }
110
114
  on_exit_of :on, Proc.new { @log << "exiting on" }
115
+ context the_context
111
116
  end
112
- sm.context = self
113
117
 
114
118
  sm.toggle
115
119
  sm.toggle
116
120
 
117
- @log.join(",").should_eql "toggle on,exiting on,toggle off,entering off"
121
+ @log.join(",").should_eql "entering off,toggle on,exiting on,toggle off,entering off"
118
122
 
119
123
  end
120
124
 
@@ -172,10 +176,22 @@ context "Builder" do
172
176
  context widget
173
177
  end
174
178
 
175
- sm.context.should_be widget
179
+ sm.context.should be(widget)
176
180
  end
177
181
 
178
-
182
+ specify "statemachine will be set on context if possible" do
183
+ class Widget
184
+ attr_accessor :statemachine
185
+ end
186
+ widget = Widget.new
187
+
188
+ sm = Statemachine.build do
189
+ context widget
190
+ end
191
+
192
+ sm.context.should be(widget)
193
+ widget.statemachine.should be(sm)
194
+ end
179
195
 
180
196
  end
181
197
 
@@ -0,0 +1,65 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ context "Default Transition" do
4
+
5
+ setup do
6
+ @sm = Statemachine.build do
7
+ trans :default_state, :start, :test_state
8
+
9
+ state :test_state do
10
+ default :default_state
11
+ end
12
+ end
13
+ end
14
+
15
+ specify "the default transition is set" do
16
+ test_state = @sm.get_state(:test_state)
17
+ test_state.default_transition.should_not be(nil)
18
+ test_state.transition_for(:fake_event).should_not be(nil)
19
+ end
20
+
21
+ specify "Should go to the default_state with any event" do
22
+ @sm.start
23
+ @sm.fake_event
24
+
25
+ @sm.state.should eql(:default_state)
26
+ end
27
+
28
+ specify "default transition can have actions" do
29
+ me = self
30
+ @sm = Statemachine.build do
31
+ trans :default_state, :start, :test_state
32
+
33
+ state :test_state do
34
+ default :default_state, :hi
35
+ end
36
+ context me
37
+ end
38
+
39
+ @sm.start
40
+ @sm.blah
41
+
42
+ @sm.state.should eql(:default_state)
43
+ @hi.should eql(true)
44
+ end
45
+
46
+ def hi
47
+ @hi = true
48
+ end
49
+
50
+ specify "superstate supports the default" do
51
+ @sm = Statemachine.build do
52
+ superstate :test_superstate do
53
+ default :default_state
54
+
55
+ state :start_state
56
+ state :default_state
57
+ end
58
+
59
+ startstate :start_state
60
+ end
61
+
62
+ @sm.blah
63
+ @sm.state.should eql(:default_state)
64
+ end
65
+ end
@@ -0,0 +1,74 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ context "History States" do
4
+
5
+ setup do
6
+ @sm = Statemachine.build do
7
+ superstate :operate do
8
+ trans :on, :toggle, :off
9
+ trans :off, :toggle, :on
10
+ event :fiddle, :middle
11
+ end
12
+ trans :middle, :fiddle, :operate_H
13
+ trans :middle, :dream, :on_H
14
+ trans :middle, :faddle, :on
15
+ startstate :middle
16
+ end
17
+ end
18
+
19
+ specify "no history allowed for concrete states" do
20
+ lambda {
21
+ @sm.dream
22
+ }.should_raise(Statemachine::StatemachineException, "No history exists for 'on' state since it is not a super state.")
23
+ end
24
+
25
+ specify "error when trying to use history that doesn't exist yet" do
26
+ lambda {
27
+ @sm.fiddle
28
+ }.should_raise(Statemachine::StatemachineException, "'operate' superstate doesn't have any history yet.")
29
+ end
30
+
31
+ specify "reseting the statemachine resets history" do
32
+ @sm.faddle
33
+ @sm.fiddle
34
+ @sm.get_state(:operate).history.id.should eql(:on)
35
+
36
+ @sm.reset
37
+ @sm.get_state(:operate).history.should eql(nil)
38
+ end
39
+
40
+ end
41
+
42
+ context "History Default" do
43
+
44
+ setup do
45
+ @sm = Statemachine.build do
46
+ superstate :operate do
47
+ trans :on, :toggle, :off
48
+ trans :off, :toggle, :on
49
+ event :fiddle, :middle
50
+ default_history :on
51
+ end
52
+ trans :middle, :fiddle, :operate_H
53
+ startstate :middle
54
+ trans :middle, :faddle, :on
55
+ end
56
+ end
57
+
58
+ specify "default history" do
59
+ @sm.fiddle
60
+ @sm.state.should eql(:on)
61
+ end
62
+
63
+ specify "reseting the statemachine resets history" do
64
+ @sm.faddle
65
+ @sm.toggle
66
+ @sm.fiddle
67
+ @sm.get_state(:operate).history.id.should eql(:off)
68
+
69
+ @sm.reset
70
+ @sm.get_state(:operate).history.id.should eql(:on)
71
+ end
72
+
73
+ end
74
+
@@ -59,12 +59,12 @@ context "State Machine Entry and Exit Actions" do
59
59
  Statemachine.build(@sm) do
60
60
  superstate :off_super do
61
61
  on_exit Proc.new {@log << @sm.state}
62
- trans :off, :toggle, :on, Proc.new { @log << "on" }
62
+ state :off
63
63
  event :toggle, :on, Proc.new { @log << "super_on" }
64
64
  end
65
65
  superstate :on_super do
66
66
  on_entry Proc.new { @log << @sm.state }
67
- trans :on, :toggle, :off, Proc.new { @log << "off" }
67
+ state :on
68
68
  event :toggle, :off, Proc.new { @log << "super_off" }
69
69
  end
70
70
  startstate :off
@@ -83,6 +83,17 @@ context "State Machine Entry and Exit Actions" do
83
83
  @sm.state.should_be :off
84
84
  end
85
85
 
86
+ specify "startstate's entry action should be called when the statemachine starts" do
87
+ the_context = self
88
+ @sm = Statemachine.build do
89
+ trans :a, :b, :c
90
+ on_entry_of :a, Proc.new { @log << "entering a" }
91
+ context the_context
92
+ end
93
+
94
+ @log.join(",").should eql("entering a")
95
+ end
96
+
86
97
 
87
98
 
88
99
  end
@@ -28,40 +28,15 @@ context "State Machine Odds And Ends" do
28
28
  @sm.state.should_be :on
29
29
  end
30
30
 
31
- end
32
-
33
- context "Special States" do
34
-
35
- setup do
36
- @sm = Statemachine.build do |s|
37
- s.superstate :operate do |o|
38
- o.trans :on, :toggle, :off
39
- o.trans :off, :toggle, :on
40
- o.event :fiddle, :middle
41
- end
42
- s.trans :middle, :fiddle, :operate_H
43
- s.trans :middle, :push, :stuck
44
- s.trans :middle, :dream, :on_H
45
- s.startstate :middle
46
- end
47
- end
48
-
49
31
  specify "states without transitions are valid" do
32
+ @sm = Statemachine.build do
33
+ trans :middle, :push, :stuck
34
+ startstate :middle
35
+ end
36
+
50
37
  @sm.push
51
38
  @sm.state.should_be :stuck
52
39
  end
53
-
54
- specify "no history allowed for concrete states" do
55
- lambda {
56
- @sm.dream
57
- }.should_raise(Statemachine::StatemachineException, "No history exists for 'on' state since it is not a super state.")
58
- end
59
-
60
- specify "error when trying to use history that doesn't exist yet" do
61
- lambda {
62
- @sm.fiddle
63
- }.should_raise(Statemachine::StatemachineException, "'operate' superstate doesn't have any history yet.")
64
- end
65
40
 
66
41
  end
67
42
 
metadata CHANGED
@@ -1,11 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.8.11
2
+ rubygems_version: 0.9.1
3
3
  specification_version: 1
4
4
  name: statemachine
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.2.0
7
- date: 2006-11-14 00:00:00 -06:00
8
- summary: Statemachine-0.2.0 - Statemachine Library for Ruby http://statemachine.rubyforge.org/
6
+ version: 0.3.0
7
+ date: 2007-04-18 00:00:00 -05:00
8
+ summary: Statemachine-0.3.0 - Statemachine Library for Ruby http://statemachine.rubyforge.org/
9
9
  require_paths:
10
10
  - lib
11
11
  email: statemachine-devel@rubyforge.org
@@ -25,23 +25,27 @@ required_ruby_version: !ruby/object:Gem::Version::Requirement
25
25
  platform: ruby
26
26
  signing_key:
27
27
  cert_chain:
28
+ post_install_message:
28
29
  authors:
29
30
  - Micah Martin
30
31
  files:
31
32
  - CHANGES
32
33
  - LICENSE
33
34
  - Rakefile
35
+ - README
34
36
  - TODO
35
37
  - lib/statemachine.rb
36
38
  - lib/statemachine/action_invokation.rb
37
39
  - lib/statemachine/builder.rb
38
40
  - lib/statemachine/state.rb
39
- - lib/statemachine/state_machine.rb
40
- - lib/statemachine/super_state.rb
41
+ - lib/statemachine/statemachine.rb
42
+ - lib/statemachine/superstate.rb
41
43
  - lib/statemachine/transition.rb
42
44
  - lib/statemachine/version.rb
43
45
  - spec/action_invokation_spec.rb
44
46
  - spec/builder_spec.rb
47
+ - spec/default_transition_spec.rb
48
+ - spec/history_spec.rb
45
49
  - spec/sm_action_parameterization_spec.rb
46
50
  - spec/sm_entry_exit_actions_spec.rb
47
51
  - spec/sm_odds_n_ends_spec.rb
@@ -53,6 +57,8 @@ files:
53
57
  test_files:
54
58
  - spec/action_invokation_spec.rb
55
59
  - spec/builder_spec.rb
60
+ - spec/default_transition_spec.rb
61
+ - spec/history_spec.rb
56
62
  - spec/sm_action_parameterization_spec.rb
57
63
  - spec/sm_entry_exit_actions_spec.rb
58
64
  - spec/sm_odds_n_ends_spec.rb