yasm 0.0.1

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.
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