barebone-fsm 0.0.1.1 → 0.0.1.2

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