finite 0.0.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://travis-ci.org/kristenmills/finite.png)](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:
|