yasm 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
@@ -0,0 +1,228 @@
1
+ # YASM - Yet Another State Machine
2
+
3
+ Pronounced "yaz-um."
4
+
5
+
6
+ ## Install?
7
+
8
+ $ gem install yasm
9
+
10
+ ## Why?
11
+
12
+ In a state machine, there are states, contexts, and actions. Actions have side-effects, conditional logic, etc. States have various allowable actions.
13
+ Contexts can support various states. All beg to be defined in classes. The other ruby state machines out there are great. But they all have hashitis.
14
+ Classes and mixins are the cure.
15
+
16
+ ## How?
17
+
18
+ Let's create a state machine for a vending machine. What does a vending machine do? It lets you input money and make a selection. When you make a selection,
19
+ it vends the selection. Let's start off with a really simple model of this:
20
+
21
+ class VendingMachine
22
+ include Yasm::Context
23
+
24
+ start :waiting
25
+ end
26
+
27
+ class Waiting; include Yasm::State; end
28
+
29
+ class Vending; include Yasm::State; end
30
+
31
+ So far, we've created a context (a thing that has state), given it a start state, and then defined a couple of states (Waiting, Vending).
32
+
33
+ So, how do we use this vending machine? We'll need to create some actions first:
34
+
35
+ class InputMoney
36
+ include Yasm::Action
37
+ end
38
+
39
+ class MakeSelection
40
+ include Yasm::Action
41
+
42
+ triggers :vending
43
+ end
44
+
45
+ class RetrieveSelection
46
+ include Yasm::Action
47
+
48
+ triggers :waiting
49
+ end
50
+
51
+ And now we can run a simulation:
52
+
53
+ vending_machine = VendingMachine.new
54
+
55
+ vending_machine.state.value
56
+ #==> Waiting
57
+
58
+ vending_machine.do! InputMoney
59
+
60
+ vending_machine.state.value
61
+ #==> Waiting
62
+
63
+ vending_machine.do! MakeSelection
64
+
65
+ vending_machine.state.value
66
+ #==> Vending
67
+
68
+ vending_machine.do! RetrieveSelection
69
+
70
+ vending_machine.state.value
71
+ #==> Waiting
72
+
73
+ There's some problems, though. Our simple state machine is a little too simple; someone could make a selection without inputing any money.
74
+ We need a way to limit the actions that can be applied to our vending machine based on it's current state. How do we do that? Let's redefine
75
+ our states, using the actions macro:
76
+
77
+ class Waiting
78
+ include Yasm::State
79
+
80
+ actions :input_money, :make_selection
81
+ end
82
+
83
+ class Vending
84
+ include Yasm::State
85
+
86
+ actions :retrieve_selection
87
+ end
88
+
89
+ Now, when the vending machine is in the `Waiting` state, the only actions we can apply to it are `InputMoney` and `MakeSelection`. If we try to apply
90
+ invalid actions to the context, `Yasm` will raise an exception.
91
+
92
+ vending_machine.state.value
93
+ #==> Waiting
94
+
95
+ vending_machine.do! RetrieveSelection
96
+ #==> RuntimeError: We're sorry, but the action `RetrieveSelection`
97
+ is not possible given the current state `Waiting`.
98
+
99
+ vending_machine.do! InputMoney
100
+
101
+ vending_machine.state.value
102
+ #==> Waiting
103
+
104
+ ## Side Effects
105
+
106
+ How can we take our simulation farther? A real vending machine would verify that when you make a selection,
107
+ you actually have input enough money to pay for that selection. How can we model this?
108
+
109
+ For starters, we'll need to add a property to our `VendingMachine`
110
+ that lets us keep track of how much money was input. We'll also need to initialize our `InputMoney` actions with an amount.
111
+
112
+ class VendingMachine
113
+ include Yasm::Context
114
+ start :waiting
115
+
116
+ attr_accessor :amount_input
117
+
118
+ def initialize
119
+ @amount_input = 0
120
+ end
121
+ end
122
+
123
+ class InputMoney
124
+ include Yasm::Action
125
+
126
+ def initialize(amount_input)
127
+ @amount_input = amount_input
128
+ end
129
+
130
+ def execute
131
+ context.amount_input += @amount_input
132
+ end
133
+ end
134
+
135
+ Notice I defined the `execute` method on the action. This is the method that gets run whenever an action gets applied to a state container
136
+ (e.g., `vending_machine.do! InputMoney`). This is where you create side effects.
137
+
138
+ Now we can try out adding money into our vending machine:
139
+
140
+ vending_machine.amount_input
141
+ # ==> 0
142
+
143
+ vending_machine.do! InputMoney.new(10)
144
+
145
+ vending_machine.amount_input
146
+ # ==> 10
147
+
148
+ As for verifying that we have input enough money to pay for the selection we've chosen, we'll need to create an item, then add that to our `MakeSelection` class:
149
+
150
+ class SnickersBar
151
+ def self.price; 30; end
152
+ end
153
+
154
+ class MakeSelection
155
+ include Yasm::Action
156
+
157
+ def initialize(selection)
158
+ @selection = selection
159
+ end
160
+
161
+ def execute
162
+ if context.amount_input >= @selection.price
163
+ trigger Vending
164
+ else
165
+ raise "We're sorry, but you have not input enough money for a #{@selection}"
166
+ end
167
+ end
168
+ end
169
+
170
+ Notice that we called the `trigger` method inside the `execute` method instead of calling the `triggers` macro on the action. This way,
171
+ we can conditionally move to the next logical state only when our conditions have been met (in this case, that we've input enough money to
172
+ pay for our selection).
173
+
174
+ v = VendingMachine.new
175
+
176
+ v.amount_input
177
+ #==> 0
178
+
179
+ v.do! MakeSelection.new(SnickersBar)
180
+ #==> RuntimeError: We're sorry, but you have not input enough money for a SnickersBar
181
+
182
+ v.do! InputMoney.new(10)
183
+
184
+ v.do! MakeSelection.new(SnickersBar)
185
+ #==> RuntimeError: We're sorry, but you have not input enough money for a SnickersBar
186
+
187
+ v.do! InputMoney.new(20)
188
+
189
+ v.do! MakeSelection.new(SnickersBar)
190
+
191
+ v.state.value
192
+ #==> Vending
193
+
194
+ v.do! RetrieveSelection
195
+
196
+ v.state.value
197
+ #==> Waiting
198
+
199
+
200
+ ## End states
201
+
202
+ Sometimes, a state is final. Like, what if, out of frustration, you threw the vending machine off the top of a 10 story building? It's probably not going
203
+ to work again after that. You can use the `final!` macro on a state to denote that this is the end.
204
+
205
+ class TossOffBuilding
206
+ include Yasm::Action
207
+
208
+ triggers :obliterated
209
+ end
210
+
211
+ class Obliterated
212
+ include Yasm::State
213
+
214
+ final!
215
+ end
216
+
217
+ vending_machine = VendingMachine.new
218
+
219
+ vending_machine.do! TossOffBuilding
220
+
221
+ vending_machine.do! MakeSelection.new(SnickersBar)
222
+ #==> RuntimeError: We're sorry, but the current state `Obliterated` is final. It does not accept any actions
223
+
224
+
225
+
226
+ ## PUBLIC DOMAIN
227
+
228
+ This software is committed to the public domain. No license. No copyright. DO ANYTHING!
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/lib/yasm.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'active_support/core_ext/string'
2
+ require 'yasm/conversions'
3
+ require 'yasm/manager'
4
+ require 'yasm/version'
5
+ require 'yasm/state'
6
+ require 'yasm/action'
7
+ require 'yasm/context/anonymous_state_identifier'
8
+ require 'yasm/context/state_configuration'
9
+ require 'yasm/context/state_container'
10
+ require 'yasm/context'
@@ -0,0 +1,29 @@
1
+ module Yasm
2
+ module Action
3
+ attr_accessor :context, :state_container
4
+
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ module ClassMethods
10
+ def triggers(state)
11
+ @trigger_state = state
12
+ end
13
+
14
+ def trigger_state; @trigger_state; end
15
+ end
16
+
17
+ def triggers
18
+ self.class.trigger_state
19
+ end
20
+
21
+ def trigger(state)
22
+ Yasm::Manager.change_state :to => state, :on => state_container
23
+ end
24
+
25
+ def execute
26
+ true
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,58 @@
1
+ module Yasm
2
+ module Context
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ # for a simple, anonymous state
9
+ def start(state)
10
+ state_configurations[ANONYMOUS_STATE] = StateConfiguration.new
11
+ state_configurations[ANONYMOUS_STATE].start state
12
+ end
13
+
14
+ # for a named state
15
+ def state(name, &block)
16
+ raise ArgumentError, "The state name must respond to `to_sym`" unless name.respond_to?(:to_sym)
17
+ name = name.to_sym
18
+ state_configurations[name] = StateConfiguration.new
19
+ state_configurations[name].instance_eval &block
20
+
21
+ raise "You must provide a start state for #{name}" unless state_configurations[name].start_state
22
+
23
+ define_method(name) { state_container name }
24
+ end
25
+
26
+ # state configuration metadata
27
+ def state_configurations
28
+ @state_configurations ||= {}
29
+ end
30
+ end
31
+
32
+ def do!(*actions)
33
+ state.do! *actions
34
+ end
35
+
36
+ def state
37
+ raise "This class has no anonymous state" unless self.class.state_configurations[ANONYMOUS_STATE]
38
+ state_container ANONYMOUS_STATE
39
+ end
40
+
41
+ private
42
+ def state_containers
43
+ @state_containers ||= {}
44
+ end
45
+
46
+ def state_container(id)
47
+ unless state_containers[id]
48
+ state_containers[id] =
49
+ StateContainer.new(
50
+ :context => self,
51
+ :state => self.class.state_configurations[id].start_state.to_class.new
52
+ )
53
+ end
54
+
55
+ state_containers[id]
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,5 @@
1
+ module Yasm
2
+ module Context
3
+ ANONYMOUS_STATE = :yasm_anonymous_state
4
+ end
5
+ end
@@ -0,0 +1,15 @@
1
+ module Yasm
2
+ module Context
3
+ class StateConfiguration
4
+ attr_reader :start_state
5
+
6
+ def initialize(start_state = nil)
7
+ @start_state = start_state
8
+ end
9
+
10
+ def start(state)
11
+ @start_state = state
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,31 @@
1
+ module Yasm
2
+ module Context
3
+ class StateContainer
4
+ attr_accessor :context
5
+ attr_accessor :state #:nodoc:
6
+
7
+ def initialize(options)
8
+ @context = options[:context]
9
+ @state = options[:state]
10
+ end
11
+
12
+ def value; @state; end
13
+
14
+ def state=(s)
15
+ if s.class == Class
16
+ @state = s.new
17
+ else
18
+ @state = s
19
+ end
20
+ end
21
+
22
+ def do!(*actions)
23
+ Yasm::Manager.execute(
24
+ :context => context,
25
+ :state_container => self,
26
+ :actions => actions
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,2 @@
1
+ require 'yasm/conversions/class'
2
+ require 'yasm/conversions/symbol'
@@ -0,0 +1,17 @@
1
+ module Yasm
2
+ module Conversions
3
+ module Class
4
+ def to_sym
5
+ self.to_s.underscore.to_sym
6
+ end
7
+
8
+ def to_class
9
+ self
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ class Class
16
+ include Yasm::Conversions::Class
17
+ end
@@ -0,0 +1,13 @@
1
+ module Yasm
2
+ module Conversions
3
+ module Symbol
4
+ def to_class
5
+ self.to_s.camelize.constantize
6
+ end
7
+ end
8
+ end
9
+ end
10
+
11
+ class Symbol
12
+ include Yasm::Conversions::Symbol
13
+ end
@@ -0,0 +1,36 @@
1
+ module Yasm
2
+ module Manager
3
+ module_function
4
+
5
+ def change_state(options)
6
+ new_state = options[:to]
7
+ state_container = options[:on]
8
+ new_state = new_state.to_class if new_state.respond_to? :to_class
9
+
10
+ state_container.state = new_state.new
11
+ end
12
+
13
+ def execute(options)
14
+ context = options[:context]
15
+ actions = options[:actions]
16
+ state_container = options[:state_container]
17
+
18
+ actions.each do |action|
19
+ action = action.new if action.class == Class
20
+ action.context = context
21
+ action.state_container = state_container
22
+
23
+
24
+ # Verify that the action is possible given the current state
25
+ if state_container.state.class.final?
26
+ raise "We're sorry, but the current state `#{state_container.state}` is final. It does not accept any actions."
27
+ elsif !state_container.state.class.is_allowed?(action.class)
28
+ raise "We're sorry, but the action `#{action.class}` is not possible given the current state `#{state_container.state}`."
29
+ end
30
+
31
+ change_state :to => action.triggers.to_class, :on => state_container if action.triggers
32
+ action.execute
33
+ end
34
+ end
35
+ end
36
+ end
data/lib/yasm/state.rb ADDED
@@ -0,0 +1,34 @@
1
+ module Yasm
2
+ module State
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def actions(*allowed_actions)
9
+ @allowed_actions = allowed_actions
10
+ end
11
+
12
+ def allowed_actions
13
+ @allowed_actions
14
+ end
15
+
16
+ def is_allowed?(action)
17
+ return true if @allowed_actions.nil?
18
+ @allowed_actions.include? action.to_sym
19
+ end
20
+
21
+ def final!
22
+ @allowed_actions = []
23
+ end
24
+
25
+ def final?
26
+ @allowed_actions == []
27
+ end
28
+ end
29
+
30
+ def to_s
31
+ self.class.to_s
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ module Yasm
2
+ VERSION = File.read(File.expand_path("../../../VERSION", __FILE__)).strip
3
+ end
@@ -0,0 +1,100 @@
1
+ $LOAD_PATH.unshift './lib'
2
+ require 'yasm'
3
+
4
+ # class VendingMachine
5
+ # include Yasm::Context
6
+ #
7
+ # start Waiting
8
+ # end
9
+ #
10
+ # class Waiting
11
+ # include Yasm::State
12
+ # end
13
+ #
14
+ # class Vending
15
+ # end
16
+ #
17
+ # class InputMoney
18
+ # include Yasm::Action
19
+ #
20
+ # triggers Vending
21
+ # end
22
+ #
23
+ #
24
+ # class VendingMachine
25
+ # include Yasm::Context
26
+ #
27
+ # # start Waiting
28
+ # #
29
+ # # state :status do
30
+ # # start Waiting
31
+ # # end
32
+ # #
33
+ # attr_accessor :money
34
+ # def initialize
35
+ # @money = 0
36
+ # end
37
+ # end
38
+ #
39
+ # class Waiting
40
+ # include Yasm::State
41
+ #
42
+ # actions InputMoney
43
+ # end
44
+ #
45
+ # class Vending
46
+ # include Yasm::State
47
+ # end
48
+ #
49
+ # class NotAState
50
+ # end
51
+ #
52
+ # class InputMoney
53
+ # include Yasm::Action
54
+ # end
55
+ #
56
+ # class Vend
57
+ # include Yasm::Action
58
+ # triggers Vending
59
+ #
60
+ # def initialize(item)
61
+ # @item = item
62
+ # end
63
+ #
64
+ # def execute
65
+ # puts "Vending #{@item}"
66
+ # context.do! Refund if context.money > 0
67
+ # end
68
+ # end
69
+ #
70
+ # class Refund
71
+ # include Yasm::Action
72
+ #
73
+ # triggers Waiting
74
+ #
75
+ # def execute
76
+ # puts "Refunding #{context.money}"
77
+ # context.money = 0
78
+ # end
79
+ # end
80
+ #
81
+ # class MakeSelection
82
+ # include Yasm::Action
83
+ #
84
+ # def initialize(selection)
85
+ # @selection = selection
86
+ # end
87
+ #
88
+ # def execute
89
+ # if context.money >= selection.price
90
+ # trigger Vending
91
+ # context.money -= selection.price
92
+ # context.do! Vend.new(selection)
93
+ # else
94
+ # puts "You must enter more money."
95
+ # end
96
+ # end
97
+ # end
98
+ #
99
+ # class NotAnAction
100
+ # end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ describe Yasm::Context do
4
+ context "when included in a class" do
5
+ before do
6
+ class VendingMachine
7
+ include Yasm::Context
8
+ end
9
+
10
+ class Waiting
11
+ include Yasm::State
12
+ end
13
+
14
+ class InputMoney
15
+ include Yasm::Action
16
+ end
17
+ end
18
+
19
+ describe "##state" do
20
+ it "should require something that can be converted to a symbol" do
21
+ proc { VendingMachine.state(nil) }.should raise_exception(ArgumentError, "The state name must respond to `to_sym`")
22
+ end
23
+
24
+ it "should create a new state configuration" do
25
+ VendingMachine.state_configurations[:electricity].should be_nil
26
+ class On; include Yasm::State; end
27
+ VendingMachine.state(:electricity) { start :on }
28
+ VendingMachine.state_configurations[:electricity].should_not be_nil
29
+ VendingMachine.state_configurations[:electricity].start_state.should == :on
30
+ end
31
+
32
+ it "should instance_eval the block on the new state configuration" do
33
+ VendingMachine.state_configurations[:power].should be_nil
34
+ class On; include Yasm::State; end
35
+ VendingMachine.state(:power) { start :on }
36
+ VendingMachine.state_configurations[:power].should_not be_nil
37
+ VendingMachine.state_configurations[:power].start_state.should == :on
38
+ end
39
+
40
+ it "should create an instance method that returns the state" do
41
+ class On; include Yasm::State; end
42
+ VendingMachine.state(:light) { start :on }
43
+ VendingMachine.new.light.state.class.should == On
44
+ end
45
+ end
46
+
47
+ describe "##start" do
48
+ context "when called directly on the context" do
49
+ it "should store the state as the start state of the anonymous state configuration" do
50
+ VendingMachine.state_configurations[Yasm::Context::ANONYMOUS_STATE].should be_nil
51
+ VendingMachine.start :waiting
52
+ VendingMachine.state_configurations[Yasm::Context::ANONYMOUS_STATE].start_state.should == :waiting
53
+ end
54
+ end
55
+ end
56
+
57
+ describe "#do!" do
58
+ it "should pass all actions passed to it off to the anonymous state_container #do! method" do
59
+ v = VendingMachine.new
60
+ v.state.should_receive(:do!).with(InputMoney, InputMoney)
61
+ v.do! InputMoney, InputMoney
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe Yasm::Conversions::Symbol do
4
+ it "should create a :to_class instance method on Symbols" do
5
+ :sym.respond_to?(:to_class).should be_true
6
+ end
7
+ end
8
+
9
+ describe Symbol do
10
+ describe "#to_class" do
11
+ it "should convert the symbol to a class" do
12
+ class SymbolTest; end
13
+ :symbol_test.to_class.should == SymbolTest
14
+ end
15
+
16
+ it "should be the inverse of the Class #to_sym class method" do
17
+ class SymbolTest; end
18
+ :symbol_test.to_class.to_sym.should == :symbol_test
19
+ end
20
+ end
21
+ end
22
+
23
+
24
+ describe Yasm::Conversions::Class do
25
+ it "should create a :to_sym class method on classes" do
26
+ class SymbolTest; end
27
+ SymbolTest.respond_to?(:to_sym).should be_true
28
+ end
29
+ end
30
+
31
+ describe Symbol do
32
+ describe "#to_sym" do
33
+ it "should convert the class to a symbol" do
34
+ class SymbolTest; end
35
+ SymbolTest.to_sym.should == :symbol_test
36
+ end
37
+
38
+ it "should be the inverse of the Symbol #to_class instance method" do
39
+ class SymbolTest; end
40
+ SymbolTest.to_sym.to_class.should == SymbolTest
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+
3
+ describe Yasm::Manager do
4
+ describe "##execute" do
5
+ before do
6
+ class VendingMachine
7
+ include Yasm::Context
8
+
9
+ start :on
10
+ end
11
+
12
+ class Unplug
13
+ include Yasm::Action
14
+
15
+ triggers :off
16
+ end
17
+
18
+ class PlugIn
19
+ include Yasm::Action
20
+
21
+ triggers :on
22
+ end
23
+
24
+ class Destroy
25
+ include Yasm::Action
26
+
27
+ triggers :destroyed
28
+ end
29
+
30
+ class On
31
+ include Yasm::State
32
+
33
+ actions :unplug, :destroy
34
+ end
35
+
36
+ class Off
37
+ include Yasm::State
38
+
39
+ actions :plug_in, :destroy
40
+ end
41
+
42
+ class Destroyed
43
+ include Yasm::State
44
+
45
+ final!
46
+ end
47
+
48
+ @vending_machine = VendingMachine.new
49
+ end
50
+
51
+ it "should apply each action, sequentially, to the appropriate state_container within the context" do
52
+ @vending_machine.state.value.class.should == On
53
+
54
+ Yasm::Manager.execute :context => @vending_machine, :state_container => @vending_machine.state, :actions => [Unplug]
55
+ @vending_machine.state.value.class.should == Off
56
+
57
+ Yasm::Manager.execute :context => @vending_machine, :state_container => @vending_machine.state, :actions => [PlugIn]
58
+ @vending_machine.state.value.class.should == On
59
+ end
60
+
61
+ it "should verify that the action is possible given the current state" do
62
+ @vending_machine.state.value.class.should == On
63
+
64
+ proc {
65
+ Yasm::Manager.execute(
66
+ :context => @vending_machine,
67
+ :state_container => @vending_machine.state,
68
+ :actions => [PlugIn]
69
+ )
70
+ }.should raise_exception("We're sorry, but the action `PlugIn` is not possible given the current state `On`.")
71
+
72
+ proc {
73
+ Yasm::Manager.execute(
74
+ :context => @vending_machine,
75
+ :state_container => @vending_machine.state,
76
+ :actions => [Unplug]
77
+ )
78
+ }.should_not raise_exception
79
+
80
+ proc {
81
+ Yasm::Manager.execute(
82
+ :context => @vending_machine,
83
+ :state_container => @vending_machine.state,
84
+ :actions => [Destroy, PlugIn]
85
+ )
86
+ }.should raise_exception("We're sorry, but the current state `Destroyed` is final. It does not accept any actions.")
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe Yasm::Context::StateContainer do
4
+ before do
5
+ class VendingMachine
6
+ include Yasm::Context
7
+ end
8
+
9
+ class Waiting
10
+ include Yasm::State
11
+ end
12
+
13
+ class InputMoney
14
+ include Yasm::Action
15
+ end
16
+ end
17
+
18
+ describe "#do!" do
19
+ it "should pass the actions off to the Yasm::Manager.execute method" do
20
+ v = VendingMachine.new
21
+ Yasm::Manager.should_receive(:execute).with(:context => v, :state_container => v.state, :actions => [InputMoney, InputMoney])
22
+ v.state.do! InputMoney, InputMoney
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe Yasm::State do
4
+ before do
5
+ class TestState
6
+ include Yasm::State
7
+ end
8
+
9
+ class Action1; include Yasm::Action; end
10
+ class Action2; include Yasm::Action; end
11
+ end
12
+
13
+ describe "##actions" do
14
+ it "should set the @allowed_actions to the input to this method" do
15
+ TestState.actions Action1, Action2
16
+ TestState.allowed_actions.should == [Action1, Action2]
17
+ end
18
+ end
19
+
20
+ describe "##final!" do
21
+ it "should set the @allowed_actions to an empty array" do
22
+ TestState.final!
23
+ TestState.allowed_actions.should == []
24
+ end
25
+ end
26
+
27
+ describe "##final?" do
28
+ it "should return true if there are no allowed actions" do
29
+ TestState.final!
30
+ TestState.final?.should be_true
31
+ end
32
+ end
33
+ end
data/yasm.gemspec ADDED
@@ -0,0 +1,62 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{yasm}
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Matt Parker"]
12
+ s.date = %q{2011-02-12}
13
+ s.description = %q{Breaks up states, actions, and contexts into seperate classes.moonmaster9000@gmail.com}
14
+ s.email = %q{moonmaster9000@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README.markdown"
17
+ ]
18
+ s.files = [
19
+ "VERSION",
20
+ "lib/yasm.rb",
21
+ "lib/yasm/action.rb",
22
+ "lib/yasm/context.rb",
23
+ "lib/yasm/context/anonymous_state_identifier.rb",
24
+ "lib/yasm/context/state_configuration.rb",
25
+ "lib/yasm/context/state_container.rb",
26
+ "lib/yasm/conversions.rb",
27
+ "lib/yasm/conversions/class.rb",
28
+ "lib/yasm/conversions/symbol.rb",
29
+ "lib/yasm/manager.rb",
30
+ "lib/yasm/state.rb",
31
+ "lib/yasm/version.rb",
32
+ "yasm.gemspec"
33
+ ]
34
+ s.homepage = %q{http://github.com/moonmaster9000/yasm}
35
+ s.require_paths = ["lib"]
36
+ s.rubygems_version = %q{1.5.0}
37
+ s.summary = %q{Yet Another State Machine. Pronounced "yaz-um."}
38
+ s.test_files = [
39
+ "spec/spec_helper.rb",
40
+ "spec/yasm/context_spec.rb",
41
+ "spec/yasm/conversions_spec.rb",
42
+ "spec/yasm/manager_spec.rb",
43
+ "spec/yasm/state_container_spec.rb",
44
+ "spec/yasm/state_spec.rb"
45
+ ]
46
+
47
+ if s.respond_to? :specification_version then
48
+ s.specification_version = 3
49
+
50
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
51
+ s.add_runtime_dependency(%q<activesupport>, ["~> 3.0"])
52
+ s.add_runtime_dependency(%q<i18n>, ["~> 0.5.0"])
53
+ else
54
+ s.add_dependency(%q<activesupport>, ["~> 3.0"])
55
+ s.add_dependency(%q<i18n>, ["~> 0.5.0"])
56
+ end
57
+ else
58
+ s.add_dependency(%q<activesupport>, ["~> 3.0"])
59
+ s.add_dependency(%q<i18n>, ["~> 0.5.0"])
60
+ end
61
+ end
62
+
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yasm
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Matt Parker
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-02-12 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activesupport
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 7
30
+ segments:
31
+ - 3
32
+ - 0
33
+ version: "3.0"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: i18n
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 11
45
+ segments:
46
+ - 0
47
+ - 5
48
+ - 0
49
+ version: 0.5.0
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ description: Breaks up states, actions, and contexts into seperate classes.moonmaster9000@gmail.com
53
+ email: moonmaster9000@gmail.com
54
+ executables: []
55
+
56
+ extensions: []
57
+
58
+ extra_rdoc_files:
59
+ - README.markdown
60
+ files:
61
+ - VERSION
62
+ - lib/yasm.rb
63
+ - lib/yasm/action.rb
64
+ - lib/yasm/context.rb
65
+ - lib/yasm/context/anonymous_state_identifier.rb
66
+ - lib/yasm/context/state_configuration.rb
67
+ - lib/yasm/context/state_container.rb
68
+ - lib/yasm/conversions.rb
69
+ - lib/yasm/conversions/class.rb
70
+ - lib/yasm/conversions/symbol.rb
71
+ - lib/yasm/manager.rb
72
+ - lib/yasm/state.rb
73
+ - lib/yasm/version.rb
74
+ - yasm.gemspec
75
+ - README.markdown
76
+ - spec/spec_helper.rb
77
+ - spec/yasm/context_spec.rb
78
+ - spec/yasm/conversions_spec.rb
79
+ - spec/yasm/manager_spec.rb
80
+ - spec/yasm/state_container_spec.rb
81
+ - spec/yasm/state_spec.rb
82
+ has_rdoc: true
83
+ homepage: http://github.com/moonmaster9000/yasm
84
+ licenses: []
85
+
86
+ post_install_message:
87
+ rdoc_options: []
88
+
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ hash: 3
97
+ segments:
98
+ - 0
99
+ version: "0"
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ hash: 3
106
+ segments:
107
+ - 0
108
+ version: "0"
109
+ requirements: []
110
+
111
+ rubyforge_project:
112
+ rubygems_version: 1.5.0
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: Yet Another State Machine. Pronounced "yaz-um."
116
+ test_files:
117
+ - spec/spec_helper.rb
118
+ - spec/yasm/context_spec.rb
119
+ - spec/yasm/conversions_spec.rb
120
+ - spec/yasm/manager_spec.rb
121
+ - spec/yasm/state_container_spec.rb
122
+ - spec/yasm/state_spec.rb