barebone-fsm 0.0.1.1 → 0.0.1.2

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.
Files changed (3) hide show
  1. data/README.rdoc +86 -0
  2. data/lib/barebone-fsm.rb +57 -69
  3. metadata +5 -3
data/README.rdoc ADDED
@@ -0,0 +1,86 @@
1
+ == Overview
2
+ This module implements a basic finite-state machine (FSM).
3
+ An FSM consists of a finite number of states,
4
+ with one of them being the current state of the FSM.
5
+ Transitions are defined between states, which are triggered on events.
6
+ For details on FSM, see this wiki page: {FSM}[http://en.wikipedia.org/wiki/Finite-state_machine].
7
+
8
+ The motivation behind the module was to implement a very basic barebone FSM in Ruby.
9
+ Features are kept at minimum as well as the code.
10
+ Only two classes for the FSM are defined as the following:
11
+ * FSM -> the finite state machine class
12
+ * FSMState -> the state class
13
+
14
+ Author:: Md. Imrul Hassan (mailto:mihassan@gmail.com)
15
+ Copyright:: Copyright: Md. Imrul Hassan, 2013
16
+
17
+ == Features
18
+ Apart from having support for states and events, this module offers the following features:
19
+ 1. Default state
20
+ 2. Default event for each state
21
+ 3. Entry and exit events for each state
22
+ 4. DSL like coding style
23
+ 5. Access to state variables (including @state and @event) inside event blocks.
24
+ Currently the state variables can only be shared among event blocks of the same state.
25
+
26
+ == Usage
27
+ The FSM can be setup and triggered Succinctly using Domain Specific Language(DSL) like coding style.
28
+ There are two methods to build and run which are defined for both FSM and FSMState classes.
29
+ These methods, #build and #run, are alias of each other and can be used interchangebly.
30
+ A basic fintite-state machine for a microwave is simulated in the following example:
31
+
32
+ fsm = FSM::FSM.new(:stopped) # :stopped is the default state to fall back
33
+
34
+ fsm.build do # the states and events can be configured within the build block
35
+ state :stopped do # the events can be setup within the state block for :stopped state
36
+ event :open do # the event block is run when the event :open is triggered
37
+ puts "[Stopped]: Door Opened"
38
+ :open # the return value of the event block, :open, is the next state
39
+ end
40
+ event :start do
41
+ puts "[Stopped]: Started"
42
+ :started
43
+ end
44
+ end
45
+ state :open do
46
+ event :close do
47
+ puts "[#{@state}]: Door #{@event}" # the event block has access to state variables such as @state and @event
48
+ :stopped
49
+ end
50
+ end
51
+ state :started do
52
+ event :open do
53
+ @open_time = Time::now # new state variables can be defined
54
+ puts "[Started]: Door Opened"
55
+ :open
56
+ end
57
+ event :stop do
58
+ puts "The door was opened at #{@open_time}" # state variables from other event blocks can be used
59
+ puts "[Started]: Door Stopped after #{elapsed_time}"
60
+ :stopped
61
+ end
62
+ end
63
+ end
64
+
65
+ fsm.run do # the events can be triggered from the run block
66
+ event :start
67
+ event :open
68
+ event :close
69
+ event :start
70
+ event :stop
71
+ end
72
+
73
+ puts fsm
74
+
75
+ == Status
76
+ The module works as it is, but further testing and documentation is needed.
77
+ The api is not stable yet, it may go trhough lots of changes before the first stable version is released.
78
+ I am open to any suggestion or request to support custom features.
79
+
80
+ == Changes
81
+ * Version: 0.0.1.2
82
+ * @state instance variable for FSM is dropped in favour of the #state instance method.
83
+ * #state method and index operator [] now accept nil arguement for state name, which returns the current state.
84
+ * Two methods #build and #run are added to FSM and FSMState classes to support DSL like features.
85
+ * Explicit parameter passing for the event blocks is no longer available.
86
+ Instead the block code for the events now have access to all instance variables of FSMState class such as @state and @event.
data/lib/barebone-fsm.rb CHANGED
@@ -1,6 +1,4 @@
1
1
  ##
2
- # == Overview
3
- # ---
4
2
  # This module implements a basic finite-state machine (FSM).
5
3
  # An FSM consists of a finite number of states,
6
4
  # with one of them being the current state of the FSM.
@@ -12,62 +10,12 @@
12
10
  # Only two classes for the FSM are defined as the following:
13
11
  # * FSM -> the finite state machine class
14
12
  # * FSMState -> the state class
13
+ #
14
+ # For further details see the README file.
15
15
  #
16
16
  # Author:: Md. Imrul Hassan (mailto:mihassan@gmail.com)
17
17
  # Copyright:: Copyright: Md. Imrul Hassan, 2013
18
18
  #
19
- # == Status
20
- # The module works, but further testing and documentation is needed.
21
- # The api is not stable yet, I may decide to change the way FSM is used.
22
- #
23
- # == Features
24
- # Apart from having support for states and events, this module offers the following features:
25
- # 1. Default state
26
- # 2. Default event for each state
27
- # 3. Entry and exit events for each state
28
- #
29
- # == Usage
30
- # ---
31
- # A simple example:
32
- #
33
- # fsm = FSM::FSM.new
34
- # state = fsm[:state_name]
35
- # state.event(:event_name) do |st, ev|
36
- # puts "#{ev} triggered on state #{st}"
37
- # :new_state}
38
- # end
39
- # fsm.event :event_name
40
- #
41
- # A complete example:
42
- #
43
- # fsm = FSM::FSM.new(:stopped)
44
- #
45
- # fsm[:stopped].event(:open) { puts "[Stopped]: Door Opened"; :open}
46
- # fsm[:stopped].event(:start) { puts "[Stopped]: Started"; :started}
47
- # fsm[:stopped].event(:enter) {|st, ev| puts "[Stopped]: Eneter into state #{st}"}
48
- # fsm[:stopped].event(:exit) {|st, ev| puts "[Stopped]: Exit from state #{st}"}
49
- #
50
- # fsm[:open].event(:close) { puts "[Open]: Door Closed"; :stopped}
51
- # fsm[:open].event(:default) {|st, ev| puts "[Open]: Event [#{ev}] not defined in state #{st}"; :open}
52
- # fsm[:open].event(:enter) {|st, ev| puts "[Open]: Eneter into state #{st}"}
53
- # fsm[:open].event(:exit) {|st, ev| puts "[Open]: Exit from state #{st}"}
54
- #
55
- # fsm[:started].event(:open) { puts "[Started]: Door Opened"; :open}
56
- # fsm[:started].event(:stop) { puts "[Started]: Stopped"; :stopped}
57
- # fsm[:started].event(:enter) {|st, ev| puts "[Started]: Eneter into state #{st}"}
58
- # fsm[:started].event(:exit) {|st, ev| puts "[Started]: Exit from state #{st}"}
59
- #
60
- # puts fsm
61
- #
62
- # fsm.event :open
63
- # fsm.event :start
64
- # fsm.event :close
65
- # fsm.event :start
66
- # fsm.event :stop
67
- # fsm.event :start
68
- #
69
- # puts sm
70
- #
71
19
  module FSM
72
20
 
73
21
  # == Overview
@@ -83,31 +31,45 @@ module FSM
83
31
  #
84
32
  class FSMState
85
33
 
86
- attr_reader :name
34
+ # The readonly state variable represents an unique state.
35
+ # Though it can have any data type, usage of symbol or string is preferable.
36
+ attr_reader :state
87
37
 
88
38
  def initialize(state_name)
89
- @name = state_name
39
+ @state = state_name
90
40
  @events = {}
91
41
  end
92
42
 
93
43
  def to_s()
94
- @name.to_s +
44
+ @state.to_s +
95
45
  ": [" +
96
46
  @events.keys.map(&:to_s).join(', ') +
97
47
  "]"
98
48
  end
99
49
 
50
+ # When the event_block is provided, it sets up a new event for this state.
51
+ # Otherwise, when the event_block is missing, the event_name is triggered.
52
+ # If the event is nil or not already setup, then the default event is triggered.
100
53
  def event(event_name, &event_block)
101
- if block_given? then
54
+ if event_name and block_given? then
102
55
  @events[event_name] = event_block
103
- else
104
- if @events.has_key? event_name then
105
- @events[event_name].call @name, event_name
106
- elsif @events.has_key? :default then
107
- @events[:default].call @name, event_name
108
- end
56
+ elsif event_name and @events.has_key? event_name then
57
+ @event = event_name
58
+ self.instance_eval &@events[@event]
59
+ elsif @events.has_key? :default then
60
+ @event = :default
61
+ self.instance_eval &@events[@event]
109
62
  end
110
63
  end
64
+
65
+ # The #build/#run method sets up the events as given in the build_block.
66
+ # Only event method is supported within the build_block with the name of the event and an optional block supplied.
67
+ # The operation for each such line is carried out by the #event method.
68
+ def build(&build_block)
69
+ self.instance_eval &build_block
70
+ end
71
+
72
+ alias_method :run, :build
111
73
 
112
74
  end
113
75
 
@@ -131,7 +93,6 @@ module FSM
131
93
  # end
132
94
  #
133
95
  class FSM
134
- attr_reader :state
135
96
 
136
97
  def initialize(default_state=nil)
137
98
  @states = {}
@@ -145,19 +106,36 @@ module FSM
145
106
  "FSM" +
146
107
  ": {" +
147
108
  @states.values.map{ |st|
148
- (st.name==@state ? ">" : "") + st.to_s
109
+ (st.state==@state ? ">" : "") + st.to_s
149
110
  }.join(', ') +
150
111
  "}"
151
112
  end
152
113
 
153
- def [](state_name)
154
- unless @states.has_key? state_name then
114
+ # It returns the FSMState object for state_name.
115
+ # If the state is missing, then it first sets up the state, and then returns the newly created state.
116
+ def [](state_name=nil)
117
+ state_name ||= @state
118
+ if state_name and not @states.has_key? state_name then
155
119
  @states[state_name] = FSMState.new(state_name)
156
120
  @state ||= state_name
157
121
  end
158
122
  @states[state_name]
159
123
  end
124
+
125
+ # When the state_block is provided, it sets up a new state.
126
+ # Otherwise, when the state_block is missing, the FSMState object for state_name is returned.
127
+ # If called without any parameter, then the current state is returned.
128
+ def state(state_name=nil, &state_block)
129
+ if block_given? then
130
+ self.[](state_name).build &state_block
131
+ else
132
+ self.[](state_name)
133
+ end
134
+ end
160
135
 
136
+ # It triggers the event_name event and changes the state of the FSM to its new state.
137
+ # The :entry and :exit events are called on the leaving state and the entering state.
138
+ # If the event does not mention the new state, then the state changes to the default state.
161
139
  def event(event_name)
162
140
  @states[@state].event :exit
163
141
  new_state = @states[@state].event event_name
@@ -168,5 +146,15 @@ module FSM
168
146
  @states[@state].event :enter
169
147
  end
170
148
 
149
+ # The #build/#run method sets up the states and events as given in the build_block.
150
+ # Only state and event methods are supported within the build_block with the name of the state/event and a block supplied.
151
+ # The operation for each such line is carried out by the #state/#event method.
152
+ def build(&build_block)
153
+ self.instance_eval &build_block
154
+ end
155
+
156
+ alias_method :run, :build
157
+
171
158
  end
172
- end
159
+
160
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: barebone-fsm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.1
4
+ version: 0.0.1.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,16 +9,18 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-18 00:00:00.000000000 Z
12
+ date: 2013-01-19 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: A barebone implementation of finite state machine keeping simplicity
15
15
  in mind.
16
16
  email: mihassan@gmail.com
17
17
  executables: []
18
18
  extensions: []
19
- extra_rdoc_files: []
19
+ extra_rdoc_files:
20
+ - README.rdoc
20
21
  files:
21
22
  - lib/barebone-fsm.rb
23
+ - README.rdoc
22
24
  homepage: http://rubygems.org/gems/barebone-fsm
23
25
  licenses: []
24
26
  post_install_message: