statemachine 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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