finite 0.0.1 → 1.0.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +6 -0
- data/README.md +88 -3
- data/Rakefile +5 -0
- data/finite.gemspec +3 -0
- data/lib/finite/class_methods.rb +13 -0
- data/lib/finite/event.rb +64 -1
- data/lib/finite/finite.rb +37 -0
- data/lib/finite/machine.rb +118 -2
- data/lib/finite/state.rb +36 -3
- data/lib/finite/transition.rb +20 -1
- data/lib/finite/version.rb +2 -1
- data/lib/finite.rb +17 -3
- data/spec/elevator_spec.rb +59 -0
- data/spec/event_spec.rb +64 -0
- data/spec/machine_spec.rb +67 -0
- data/spec/models/elevator.rb +94 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/state_spec.rb +36 -0
- data/spec/transition_spec.rb +20 -0
- metadata +63 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: efb775ca43d0ce5dddd4215fde0c54b5f764e097
|
4
|
+
data.tar.gz: f617e7113c8f30f590c353dfa664776e74cb6c32
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e6a27d3620e1ed8a3d80099d9ff9ae7ac693ac04a81ba570b6048565e9cc6d94cd4a7ff69e5944489d652aec55ace9cabc9b688ae60d64e6c6d37762352f3e0
|
7
|
+
data.tar.gz: ad0e60854b2334e045f78c35e0649e610e56abb9253ed0f22c6260972a8b469206e385052a1748dccc23ef84d7602bee120b44aafd2488aa6ab61f0583197d80
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
# Finite
|
1
|
+
# Finite [](https://travis-ci.org/kristenmills/finite)
|
2
|
+
|
2
3
|
|
3
4
|
A simple state machine implementation for ruby
|
4
5
|
|
@@ -17,8 +18,92 @@ Or install it yourself as:
|
|
17
18
|
$ gem install finite
|
18
19
|
|
19
20
|
## Usage
|
20
|
-
|
21
|
-
|
21
|
+
```ruby
|
22
|
+
|
23
|
+
class Elevator
|
24
|
+
include Finite
|
25
|
+
|
26
|
+
finite initial: :idle do
|
27
|
+
|
28
|
+
before do
|
29
|
+
"This is called before every state but has no purpose other than to show it's existence in this example."
|
30
|
+
end
|
31
|
+
|
32
|
+
before :doors_closing do
|
33
|
+
puts 'Doors Closing!'
|
34
|
+
end
|
35
|
+
|
36
|
+
before :doors_opening do
|
37
|
+
puts 'Doors Opening!'
|
38
|
+
end
|
39
|
+
|
40
|
+
event :prepare do
|
41
|
+
go from: :idle, to: :doors_closing
|
42
|
+
end
|
43
|
+
|
44
|
+
event :go_up do
|
45
|
+
go from: :doors_closing, to: :elevator_going_up
|
46
|
+
after do
|
47
|
+
puts 'Going Up!'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
event :go_down do
|
52
|
+
go from: :doors_closing, to: :elevator_going_down
|
53
|
+
after do
|
54
|
+
puts 'Going Down!'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
event :start do
|
59
|
+
go from: [:elevator_going_up, :elevator_going_down], to: :moving
|
60
|
+
end
|
61
|
+
|
62
|
+
event :approach do
|
63
|
+
go from: :moving, to: :stopping
|
64
|
+
end
|
65
|
+
|
66
|
+
event :stop do
|
67
|
+
go from: :stopping, to: :doors_opening
|
68
|
+
before do
|
69
|
+
announce_floor
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
event :open_doors do
|
74
|
+
go from: :doors_opening, to: :at_floor
|
75
|
+
end
|
76
|
+
|
77
|
+
event :finish do
|
78
|
+
go from: :at_floor, to: :checking_next_dest
|
79
|
+
end
|
80
|
+
|
81
|
+
event :make_request do
|
82
|
+
go from: :checking_next_dest, to: :doors_closing
|
83
|
+
end
|
84
|
+
|
85
|
+
event :make_no_request do
|
86
|
+
go from: :checking_next_dest, to: :idle
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def announce_floor
|
91
|
+
puts "Arriving on floor #{rand(10)}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
elevator = Elevator.new
|
98
|
+
elevator.current_state # => :idle
|
99
|
+
elevator.can_prepare? # => true
|
100
|
+
elevator.can_open_doors? # => false
|
101
|
+
elevator.open_doors # => RuntimeError 'Invalid Transition'
|
102
|
+
elevator.idle? # => true
|
103
|
+
elevator.prepare # => 'Doors Closing!'
|
104
|
+
elevator.current_state # => :doors_closing
|
105
|
+
elevator.possible_events # => [:go_up, :go_down]
|
106
|
+
```
|
22
107
|
|
23
108
|
## Contributing
|
24
109
|
|
data/Rakefile
CHANGED
data/finite.gemspec
CHANGED
@@ -20,4 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
22
|
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "simplecov"
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
spec.add_development_dependency "yard"
|
23
26
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Finite
|
2
|
+
# The class methods for any class that include the finite base
|
3
|
+
module ClassMethods
|
4
|
+
# The finite method for the dsl
|
5
|
+
#
|
6
|
+
# @param opts [Hash] any options including initial state
|
7
|
+
# @param block [Block] the block of code that creates the state machine
|
8
|
+
def finite(opts, &block)
|
9
|
+
StateMachine.machines ||= Hash.new
|
10
|
+
StateMachine.machines[self] = StateMachine.new(opts[:initial], self, &block)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/finite/event.rb
CHANGED
@@ -1,5 +1,68 @@
|
|
1
1
|
module Finite
|
2
|
+
|
3
|
+
# The event class. Represents an event in the state machine
|
2
4
|
class Event
|
3
|
-
|
5
|
+
|
6
|
+
attr_reader :name, :transitions, :callbacks
|
7
|
+
|
8
|
+
# Create an event object
|
9
|
+
#
|
10
|
+
# @param name [Symbol] the name of the event
|
11
|
+
# @param block [Block] the block of code in the event
|
12
|
+
def initialize(name, &block)
|
13
|
+
@name = name
|
14
|
+
@transitions = Hash.new
|
15
|
+
@callbacks = {before: Array.new, after: Array.new}
|
16
|
+
instance_eval &block
|
17
|
+
end
|
18
|
+
|
19
|
+
# Are two events equal
|
20
|
+
#
|
21
|
+
# @param event [Object] the object you are comparing it to
|
22
|
+
# @return true if they are equal false if not
|
23
|
+
def ==(event)
|
24
|
+
if event.is_a? Event
|
25
|
+
@name == event.name
|
26
|
+
elsif event.is_a? Symbol
|
27
|
+
@name == event
|
28
|
+
else
|
29
|
+
false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# overrriden for puts and print
|
34
|
+
def to_s
|
35
|
+
@name.to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
# Overridden for p
|
39
|
+
def inspect
|
40
|
+
@name
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
# The transition method for the dsl
|
45
|
+
#
|
46
|
+
# @param opts [Hash] the options for a transition
|
47
|
+
def go(opts)
|
48
|
+
options = []
|
49
|
+
if opts[:from].is_a? Array
|
50
|
+
opts[:from].each do |from|
|
51
|
+
options << {from: from, to: opts[:to]}
|
52
|
+
end
|
53
|
+
else
|
54
|
+
options << opts
|
55
|
+
end
|
56
|
+
options.each do |opt|
|
57
|
+
@transitions[opt[:from]] = Transition.new(opt)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Create the callback methods
|
62
|
+
[:after, :before].each do |callback|
|
63
|
+
define_method callback do |*args, &block|
|
64
|
+
@callbacks[callback] << block
|
65
|
+
end
|
66
|
+
end
|
4
67
|
end
|
5
68
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Finite
|
2
|
+
# Get's the current state
|
3
|
+
# @return the current state for an object
|
4
|
+
def current_state
|
5
|
+
machine = StateMachine.machines[self.class]
|
6
|
+
@current_state or machine.states[machine.initial]
|
7
|
+
end
|
8
|
+
|
9
|
+
# Get's (and sets) the array of states
|
10
|
+
# @return the array of states
|
11
|
+
def states
|
12
|
+
@states ||= StateMachine.machines[self.class].states
|
13
|
+
@states
|
14
|
+
end
|
15
|
+
|
16
|
+
# Get's (and sets) the array of events
|
17
|
+
# @return the array of events
|
18
|
+
def events
|
19
|
+
@events ||= StateMachine.machines[self.class].events
|
20
|
+
@events
|
21
|
+
end
|
22
|
+
|
23
|
+
# Get's (and sets) the array of callbacks
|
24
|
+
# @return the array of callbacks
|
25
|
+
def callbacks
|
26
|
+
@callbacks ||= StateMachine.machines[self.class].callbacks
|
27
|
+
@callbacks
|
28
|
+
end
|
29
|
+
|
30
|
+
# Get's all the possible events you can perform
|
31
|
+
# @return any event that you can perform given your state
|
32
|
+
def possible_events
|
33
|
+
pos = Array.new
|
34
|
+
events.each_value{|event| pos << event if event.transitions.key?(current_state.name)}
|
35
|
+
pos
|
36
|
+
end
|
37
|
+
end
|
data/lib/finite/machine.rb
CHANGED
@@ -1,5 +1,121 @@
|
|
1
1
|
module Finite
|
2
|
-
class
|
3
|
-
|
2
|
+
# The State Machine class. Represents the whole state machine
|
3
|
+
class StateMachine
|
4
|
+
|
5
|
+
class << self
|
6
|
+
attr_accessor :machines
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :states, :initial, :events, :callbacks
|
10
|
+
|
11
|
+
# Create a new state machine
|
12
|
+
#
|
13
|
+
# @param initial_state [Symbol] the initial state of this state machine
|
14
|
+
# @param klass [Class] the class of the state machine
|
15
|
+
# @param block [Block] the block of code that creates it
|
16
|
+
def initialize(initial_state, klass, &block)
|
17
|
+
@class = klass
|
18
|
+
@initial = initial_state
|
19
|
+
@states = Hash.new
|
20
|
+
@events = Hash.new
|
21
|
+
@callbacks = {before: Hash.new , after: Hash.new}
|
22
|
+
instance_eval &block
|
23
|
+
end
|
24
|
+
|
25
|
+
# Add an event to the state machine
|
26
|
+
#
|
27
|
+
# @param event_name [Symbol] the event you are trying to add
|
28
|
+
# @param block [Block] the block of code that creates an event
|
29
|
+
# @raise [Exception] if the event already exists
|
30
|
+
def add_event(event_name, &block)
|
31
|
+
# We don't want to randomly override things that we shouldn't
|
32
|
+
raise "Method already taken can_#{event_name}?" if @class.methods.include?(:"can_#{event_name}?")
|
33
|
+
raise "Method already taken #{event_name}" if @class.methods.include?(:"#{event_name}")
|
34
|
+
raise 'Event #{event_name} already exists. Rename or combine the events' if events.include? event_name
|
35
|
+
event = Event.new(event_name, &block)
|
36
|
+
@events[event_name] = event
|
37
|
+
event.transitions.each_value do |transition|
|
38
|
+
add_state transition.to
|
39
|
+
add_state transition.from
|
40
|
+
end
|
41
|
+
@class.send(:define_method, :"can_#{event_name}?") do
|
42
|
+
event.transitions.key? current_state.name
|
43
|
+
end
|
44
|
+
@class.send(:define_method, :"#{event_name}") do
|
45
|
+
if event.transitions.key? current_state.name
|
46
|
+
|
47
|
+
event.callbacks[:before].each do |callback|
|
48
|
+
self.instance_eval &callback
|
49
|
+
end
|
50
|
+
|
51
|
+
if callbacks[:before].key? :all
|
52
|
+
callbacks[:before][:all].each do |callback|
|
53
|
+
self.instance_eval &callback
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
new_state = states[event.transitions[current_state.name].to]
|
58
|
+
|
59
|
+
if callbacks[:before].key? new_state.name
|
60
|
+
callbacks[:before][new_state.name].each do |callback|
|
61
|
+
self.instance_eval &callback
|
62
|
+
end
|
63
|
+
end
|
64
|
+
@current_state = new_state
|
65
|
+
|
66
|
+
if callbacks[:after].key? :all
|
67
|
+
callbacks[:after][:all].each do |callback|
|
68
|
+
self.instance_eval &callback
|
69
|
+
end
|
70
|
+
end
|
71
|
+
if callbacks[:after].key? current_state.name
|
72
|
+
callbacks[:after][current_state.name].each do |callback|
|
73
|
+
self.instance_eval &callback
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
event.callbacks[:after].each do |callback|
|
78
|
+
self.instance_eval &callback
|
79
|
+
end
|
80
|
+
else
|
81
|
+
raise 'Invalid Transition'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Add a state to the the state machine if the state hasn't already been
|
87
|
+
# created
|
88
|
+
#
|
89
|
+
# @param state [Symbol] the state you are trying to add
|
90
|
+
def add_state(state)
|
91
|
+
if not @states.include? state
|
92
|
+
# Prevents arbitrarily overriding methods that you shouldn't be
|
93
|
+
raise "Method already taken #{state}?" if @class.methods.include?(:"#{state}?")
|
94
|
+
@states[state] = State.new(state)
|
95
|
+
@class.send(:define_method, :"#{state}?"){current_state == state}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
# The event method for the dsl
|
101
|
+
#
|
102
|
+
# @param name [Symbol] the name of the event
|
103
|
+
# @param block [Block] the block of code that creates events
|
104
|
+
def event(name, &block)
|
105
|
+
add_event name, &block
|
106
|
+
end
|
107
|
+
|
108
|
+
# Create the callback methods
|
109
|
+
[:after, :before].each do |callback|
|
110
|
+
define_method callback do |*args, &block|
|
111
|
+
if args.count > 0
|
112
|
+
callbacks[callback][args[0]] ||= Array.new
|
113
|
+
callbacks[callback][args[0]] << block
|
114
|
+
else
|
115
|
+
callbacks[callback][:all] ||= Array.new
|
116
|
+
callbacks[callback][:all] << block
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
4
120
|
end
|
5
121
|
end
|
data/lib/finite/state.rb
CHANGED
@@ -1,5 +1,38 @@
|
|
1
1
|
module Finite
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
|
3
|
+
# The State class. Represents a state in the state machine.
|
4
|
+
class State
|
5
|
+
attr_reader :name
|
6
|
+
|
7
|
+
# Create a new state
|
8
|
+
#
|
9
|
+
# @param name [Symbol] the name of the state
|
10
|
+
def initialize(name)
|
11
|
+
@name = name
|
12
|
+
end
|
13
|
+
|
14
|
+
# Overide the == method for state
|
15
|
+
#
|
16
|
+
# @param state [Object] the state your comparing to
|
17
|
+
# @return true if they are equal false if not
|
18
|
+
def ==(state)
|
19
|
+
if state.is_a? Symbol
|
20
|
+
@name == state
|
21
|
+
elsif state.is_a? State
|
22
|
+
@name == state.name
|
23
|
+
else
|
24
|
+
false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# overrriden for puts and print
|
29
|
+
def to_s
|
30
|
+
@name.to_s
|
31
|
+
end
|
32
|
+
|
33
|
+
# Overridden for p
|
34
|
+
def inspect
|
35
|
+
@name
|
36
|
+
end
|
37
|
+
end
|
5
38
|
end
|
data/lib/finite/transition.rb
CHANGED
@@ -1,5 +1,24 @@
|
|
1
1
|
module Finite
|
2
|
+
|
3
|
+
# The transition class. Represents a transition between two states
|
2
4
|
class Transition
|
3
|
-
|
5
|
+
|
6
|
+
attr_reader :to, :from
|
7
|
+
|
8
|
+
# Create a new transition object
|
9
|
+
#
|
10
|
+
# @param opts [Hash] the options for a transition. Include :to and :from
|
11
|
+
def initialize(opts)
|
12
|
+
@from = opts[:from]
|
13
|
+
@to = opts[:to]
|
14
|
+
end
|
15
|
+
|
16
|
+
# Does this transition equal another transition?
|
17
|
+
#
|
18
|
+
# @param other [Transition] another transition
|
19
|
+
# @return true if they are equal false if not
|
20
|
+
def ==(other)
|
21
|
+
from == other.from and to == other.to
|
22
|
+
end
|
4
23
|
end
|
5
24
|
end
|
data/lib/finite/version.rb
CHANGED
data/lib/finite.rb
CHANGED
@@ -1,5 +1,19 @@
|
|
1
|
-
|
1
|
+
[
|
2
|
+
'version',
|
3
|
+
'transition',
|
4
|
+
'event',
|
5
|
+
'state',
|
6
|
+
'machine',
|
7
|
+
'class_methods',
|
8
|
+
'finite'
|
9
|
+
].each { |file| require File.join(File.dirname(__FILE__), 'finite', file) }
|
2
10
|
|
11
|
+
# The Finite module. The module that contains all the classes and methods for
|
12
|
+
# the finite gem.
|
3
13
|
module Finite
|
4
|
-
#
|
5
|
-
|
14
|
+
# Override included method
|
15
|
+
def self.included(base)
|
16
|
+
base.extend(ClassMethods)
|
17
|
+
super
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Elevator do
|
4
|
+
before(:each) do
|
5
|
+
@elevator = Elevator.new
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'has a state machine' do
|
9
|
+
expect(Finite::StateMachine.machines[Elevator]).to_not be_nil
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'states' do
|
13
|
+
it 'has a current state' do
|
14
|
+
expect(@elevator.current_state).to_not be_nil
|
15
|
+
expect(@elevator.current_state).to eq(:idle)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'has methods to tell whether it is in a state or not' do
|
19
|
+
expect(@elevator.idle?).to be_true
|
20
|
+
expect(@elevator.moving?).to be_false
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'can access all states' do
|
24
|
+
expect(@elevator.states).to_not be_nil
|
25
|
+
expect(@elevator.states.count).to be(9)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'events' do
|
30
|
+
it 'can access all events' do
|
31
|
+
expect(@elevator.events).to_not be_nil
|
32
|
+
expect(@elevator.events.count).to be(10)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'can access possible events for a given state' do
|
36
|
+
expect(@elevator.possible_events).to eq([:prepare])
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'has methods to tell whether an event can be performed' do
|
40
|
+
expect(@elevator.can_prepare?).to be_true
|
41
|
+
expect(@elevator.can_go_up?).to be_false
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'can perform events' do
|
45
|
+
@elevator.prepare
|
46
|
+
expect(@elevator.current_state).to eq(:doors_closing)
|
47
|
+
expect(@elevator.doors_closing?).to be_true
|
48
|
+
expect(@elevator.idle?).to be_false
|
49
|
+
expect(@elevator.possible_events).to eq([:go_up,:go_down])
|
50
|
+
expect{@elevator.start}.to raise_error
|
51
|
+
@elevator.go_down
|
52
|
+
expect(@elevator.can_start?).to be_true
|
53
|
+
@elevator.start
|
54
|
+
@elevator.approach
|
55
|
+
@elevator.stop
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
data/spec/event_spec.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Finite::Event do
|
4
|
+
before(:each) do
|
5
|
+
@block = Proc.new do
|
6
|
+
go from: :state1, to: :state2
|
7
|
+
after do
|
8
|
+
'hello again'
|
9
|
+
end
|
10
|
+
before do
|
11
|
+
'hello for the first time'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'has a name' do
|
17
|
+
event = Finite::Event.new(:event1, &@block)
|
18
|
+
expect(event.name).to eq(:event1)
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'equality' do
|
22
|
+
it 'equals symbols that have the same name' do
|
23
|
+
event = Finite::Event.new(:event1, &@block)
|
24
|
+
expect(event).to eq(:event1)
|
25
|
+
expect(event).not_to eq(:event2)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'equals events with the same name' do
|
29
|
+
event1 = Finite::Event.new(:event1, &@block)
|
30
|
+
event2 = Finite::Event.new(:event2, &@block)
|
31
|
+
event3 = Finite::Event.new(:event1, &@block)
|
32
|
+
|
33
|
+
expect(event1).to eq(event3)
|
34
|
+
expect(event1).not_to eq(event2)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "doesn't equal things that aren't symbols or events" do
|
38
|
+
event = Finite::Event.new(:event1, &@block)
|
39
|
+
expect(event).not_to eq('string')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should create transitions' do
|
44
|
+
event = Finite::Event.new(:event1, &@block)
|
45
|
+
event.transitions.count.should be(1)
|
46
|
+
event.transitions[:state1].to.should eq(:state2)
|
47
|
+
event.transitions[:state1].from.should eq(:state1)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should create callbacks' do
|
51
|
+
event = Finite::Event.new(:event1, &@block)
|
52
|
+
|
53
|
+
expect(event.callbacks[:before]).not_to be_nil
|
54
|
+
expect(event.callbacks[:after]).not_to be_nil
|
55
|
+
expect(event.callbacks[:after][0].call).to eq('hello again')
|
56
|
+
expect(event.callbacks[:before][0].call).to eq('hello for the first time')
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'has to_s and inspect methods' do
|
60
|
+
event = Finite::Event.new(:event, &@block)
|
61
|
+
expect(event.to_s).to eq('event')
|
62
|
+
expect(event.inspect).to eq(:event)
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Finite::StateMachine do
|
4
|
+
before(:each) do
|
5
|
+
@elevator = Elevator.new
|
6
|
+
@machine = Finite::StateMachine.machines[Elevator]
|
7
|
+
end
|
8
|
+
|
9
|
+
context 'adding events' do
|
10
|
+
it 'has events' do
|
11
|
+
expect(@machine.events.count).to be(10)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'cannot have events with the same name' do
|
15
|
+
expect{@machine.add_event(:prepare){}}.to raise_error
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'cannot be stupid with your event naming' do
|
19
|
+
expect{@machine.add_event(:to_s)}.to raise_error
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'increases in size when an event is added' do
|
23
|
+
@machine.add_event(:random_event){}
|
24
|
+
expect(@machine.events.count).to be(11)
|
25
|
+
end
|
26
|
+
it 'adds the proper helper methods' do
|
27
|
+
expect(@elevator.methods).to include(:random_event)
|
28
|
+
expect(@elevator.methods).to include(:can_random_event?)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'adding states' do
|
33
|
+
it 'has states' do
|
34
|
+
expect(@machine.states.count).to be(9)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'cannot be stupid with your state naming' do
|
38
|
+
expect{@machine.add_state(:const_defined){}}.to raise_error
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'cannot have states with the same name' do
|
42
|
+
@machine.add_state(:doors_closing){}
|
43
|
+
expect(@machine.states.count).to be(9)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'increases in size when a state is added' do
|
47
|
+
@machine.add_state(:random_state){}
|
48
|
+
expect(@machine.states.count).to be(10)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'adds the proper helper method' do
|
52
|
+
expect(@elevator.methods).to include(:random_state?)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'callbacks' do
|
57
|
+
it 'has after and before keys' do
|
58
|
+
expect(@machine.callbacks).to include(:before)
|
59
|
+
expect(@machine.callbacks).to include(:after)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'adds callbacks' do
|
63
|
+
expect(@machine.callbacks[:before]).to include(:doors_closing)
|
64
|
+
expect(@machine.callbacks[:before]).to include(:doors_opening)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
|
2
|
+
class Elevator
|
3
|
+
include Finite
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@before_called = 0
|
7
|
+
@after_called = 0
|
8
|
+
end
|
9
|
+
|
10
|
+
finite initial: :idle do
|
11
|
+
|
12
|
+
before :doors_closing do
|
13
|
+
puts 'Doors Closing!'
|
14
|
+
end
|
15
|
+
|
16
|
+
before :doors_opening do
|
17
|
+
puts 'Doors Opening!'
|
18
|
+
end
|
19
|
+
|
20
|
+
after :doors_closing do
|
21
|
+
puts "I'm no longer idle"
|
22
|
+
end
|
23
|
+
|
24
|
+
before do
|
25
|
+
@before_called += 1
|
26
|
+
end
|
27
|
+
|
28
|
+
after do
|
29
|
+
@after_called += 1
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
event :prepare do
|
34
|
+
go from: :idle, to: :doors_closing
|
35
|
+
end
|
36
|
+
|
37
|
+
event :go_up do
|
38
|
+
go from: :doors_closing, to: :elevator_going_up
|
39
|
+
after do
|
40
|
+
puts 'Going Up!'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
event :go_down do
|
45
|
+
go from: :doors_closing, to: :elevator_going_down
|
46
|
+
after do
|
47
|
+
puts 'Going Down!'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
event :start do
|
52
|
+
go from: [:elevator_going_up, :elevator_going_down], to: :moving
|
53
|
+
end
|
54
|
+
|
55
|
+
event :approach do
|
56
|
+
go from: :moving, to: :stopping
|
57
|
+
end
|
58
|
+
|
59
|
+
event :stop do
|
60
|
+
go from: :stopping, to: :doors_opening
|
61
|
+
before do
|
62
|
+
announce_floor
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
event :open_doors do
|
67
|
+
go from: :doors_opening, to: :at_floor
|
68
|
+
end
|
69
|
+
|
70
|
+
event :finish do
|
71
|
+
go from: :at_floor, to: :checking_next_dest
|
72
|
+
end
|
73
|
+
|
74
|
+
event :make_request do
|
75
|
+
go from: :checking_next_dest, to: :doors_closing
|
76
|
+
end
|
77
|
+
|
78
|
+
event :make_no_request do
|
79
|
+
go from: :checking_next_dest, to: :idle
|
80
|
+
end
|
81
|
+
end
|
82
|
+
def announce_floor
|
83
|
+
puts "Arriving on floor #{rand(10)}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# elevator = Elevator.new
|
88
|
+
# elevator.prepare
|
89
|
+
# elevator.go_up
|
90
|
+
# elevator.start
|
91
|
+
# elevator.approach
|
92
|
+
# elevator.stop
|
93
|
+
# puts elevator.can_open_doors?
|
94
|
+
# puts elevator.can_finish?
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'simplecov'
|
3
|
+
|
4
|
+
SimpleCov.start
|
5
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'finite')
|
6
|
+
Dir[File.dirname(__FILE__) + "/models/*.rb"].sort.each { |f| require File.expand_path(f) }
|
7
|
+
|
8
|
+
RSpec.configure do |config|
|
9
|
+
original_stderr = $stderr
|
10
|
+
original_stdout = $stdout
|
11
|
+
config.before(:all) do
|
12
|
+
# Redirect stderr and stdout
|
13
|
+
$stderr = File.new(File.join(File.dirname(__FILE__), 'null.txt'), 'w')
|
14
|
+
$stdout = File.new(File.join(File.dirname(__FILE__), 'null.txt'), 'w')
|
15
|
+
end
|
16
|
+
config.after(:all) do
|
17
|
+
$stderr = original_stderr
|
18
|
+
$stdout = original_stdout
|
19
|
+
end
|
20
|
+
end
|
data/spec/state_spec.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Finite::State do
|
4
|
+
before(:each) do
|
5
|
+
@name = :state_name
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'has a name' do
|
9
|
+
Finite::State.new(@name).name.should eq(:state_name)
|
10
|
+
end
|
11
|
+
context 'equality' do
|
12
|
+
it 'equals a symbol of the same name' do
|
13
|
+
state = Finite::State.new(@name)
|
14
|
+
expect(state).to eq(:state_name)
|
15
|
+
expect(state).not_to eq(:different_name)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'equals a state with the same name' do
|
19
|
+
state1 = Finite::State.new(@name)
|
20
|
+
state2 = Finite::State.new(:different_name)
|
21
|
+
state3 = Finite::State.new(@name)
|
22
|
+
expect(state1).to eq(state3)
|
23
|
+
expect(state1).not_to eq(state2)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "doesn't equal objects that aren't states or symbols" do
|
27
|
+
state = Finite::State.new(@name)
|
28
|
+
expect(state).not_to eq('string')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
it 'has to_s and inspect methods' do
|
32
|
+
state = Finite::State.new(@name)
|
33
|
+
expect(state.to_s).to eq('state_name')
|
34
|
+
expect(state.inspect).to eq(:state_name)
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Finite::Transition do
|
4
|
+
it 'has a to and a from' do
|
5
|
+
transition = Finite::Transition.new({from: :state1, to: :state2})
|
6
|
+
expect(transition.from).to eq(:state1)
|
7
|
+
expect(transition.to).to eq(:state2)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'equals transitions with the same to and from' do
|
11
|
+
transition1 = Finite::Transition.new({from: :state1, to: :state2})
|
12
|
+
transition2 = Finite::Transition.new({from: :state1, to: :state3})
|
13
|
+
transition3 = Finite::Transition.new({from: :state4, to: :state2})
|
14
|
+
transition4 = Finite::Transition.new({from: :state1, to: :state2})
|
15
|
+
|
16
|
+
expect(transition1).not_to eq(transition2)
|
17
|
+
expect(transition1).not_to eq(transition3)
|
18
|
+
expect(transition1).to eq(transition4)
|
19
|
+
end
|
20
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: finite
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kristen Mills
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-07-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -38,6 +38,48 @@ dependencies:
|
|
38
38
|
- - '>='
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: simplecov
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: yard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
41
83
|
description: A simple state machine implementation for ruby
|
42
84
|
email:
|
43
85
|
- kristen@kristen-mills.com
|
@@ -46,17 +88,27 @@ extensions: []
|
|
46
88
|
extra_rdoc_files: []
|
47
89
|
files:
|
48
90
|
- .gitignore
|
91
|
+
- .travis.yml
|
49
92
|
- Gemfile
|
50
93
|
- LICENSE.txt
|
51
94
|
- README.md
|
52
95
|
- Rakefile
|
53
96
|
- finite.gemspec
|
54
97
|
- lib/finite.rb
|
98
|
+
- lib/finite/class_methods.rb
|
55
99
|
- lib/finite/event.rb
|
100
|
+
- lib/finite/finite.rb
|
56
101
|
- lib/finite/machine.rb
|
57
102
|
- lib/finite/state.rb
|
58
103
|
- lib/finite/transition.rb
|
59
104
|
- lib/finite/version.rb
|
105
|
+
- spec/elevator_spec.rb
|
106
|
+
- spec/event_spec.rb
|
107
|
+
- spec/machine_spec.rb
|
108
|
+
- spec/models/elevator.rb
|
109
|
+
- spec/spec_helper.rb
|
110
|
+
- spec/state_spec.rb
|
111
|
+
- spec/transition_spec.rb
|
60
112
|
homepage: http://github.com/kristenmills/finite
|
61
113
|
licenses:
|
62
114
|
- MIT
|
@@ -81,4 +133,12 @@ rubygems_version: 2.0.3
|
|
81
133
|
signing_key:
|
82
134
|
specification_version: 4
|
83
135
|
summary: A simple state machine implementation for ruby
|
84
|
-
test_files:
|
136
|
+
test_files:
|
137
|
+
- spec/elevator_spec.rb
|
138
|
+
- spec/event_spec.rb
|
139
|
+
- spec/machine_spec.rb
|
140
|
+
- spec/models/elevator.rb
|
141
|
+
- spec/spec_helper.rb
|
142
|
+
- spec/state_spec.rb
|
143
|
+
- spec/transition_spec.rb
|
144
|
+
has_rdoc:
|