stateology 0.1.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.
- data/LICENSE +14 -0
- data/README +113 -0
- data/lib/stateology.rb +118 -0
- data/sample.rb +80 -0
- metadata +65 -0
data/LICENSE
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Copyright (c) 2008 John Mair
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the
|
4
|
+
"Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
5
|
+
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following
|
6
|
+
conditions:
|
7
|
+
|
8
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
9
|
+
|
10
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
11
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
12
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
13
|
+
OR OTHER DEALINGS IN THE SOFTWARE.
|
14
|
+
|
data/README
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
Clean and fast Object state transitions in Ruby using the Mixology C extension.
|
2
|
+
|
3
|
+
Supports:
|
4
|
+
* Dynamic switching between states (mixing and unmixing modules)
|
5
|
+
* Clean DSL-style syntax
|
6
|
+
* Optional state_entry() and state_exit() hooks for each state (automatically called upon state entry and exit)
|
7
|
+
|
8
|
+
Use as in the following:
|
9
|
+
|
10
|
+
class Sample
|
11
|
+
include Stateology
|
12
|
+
|
13
|
+
state(:Happy) {
|
14
|
+
def state_entry
|
15
|
+
puts "entering Happy state"
|
16
|
+
end
|
17
|
+
|
18
|
+
def do_something
|
19
|
+
puts "Pets a puppy"
|
20
|
+
end
|
21
|
+
|
22
|
+
def state_exit
|
23
|
+
puts "exiting Happy state"
|
24
|
+
end
|
25
|
+
}
|
26
|
+
|
27
|
+
state(:Angry) {
|
28
|
+
def state_entry
|
29
|
+
puts "entering Angry state"
|
30
|
+
end
|
31
|
+
|
32
|
+
def do_something
|
33
|
+
puts "Kicks a puppy"
|
34
|
+
end
|
35
|
+
|
36
|
+
def state_exit
|
37
|
+
puts "exiting Angry state"
|
38
|
+
end
|
39
|
+
}
|
40
|
+
|
41
|
+
# methods declared outside a 'state' are not part of any state
|
42
|
+
|
43
|
+
def state_entry
|
44
|
+
puts "entering Default state"
|
45
|
+
end
|
46
|
+
|
47
|
+
def do_something
|
48
|
+
puts "stares at the ceiling"
|
49
|
+
end
|
50
|
+
|
51
|
+
def state_exit
|
52
|
+
puts "exiting Default state"
|
53
|
+
end
|
54
|
+
|
55
|
+
# if we want the state_entry to run on instantiation
|
56
|
+
# we must call it from the initialize method
|
57
|
+
def initialize
|
58
|
+
state_entry
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
s = Sample.new
|
64
|
+
|
65
|
+
# in no state
|
66
|
+
s.do_something #=> "stares at the ceiling"
|
67
|
+
|
68
|
+
# now switch to Happy state
|
69
|
+
s.state :Happy
|
70
|
+
s.do_something #=> "Pets a puppy"
|
71
|
+
|
72
|
+
# now switch to Angry state
|
73
|
+
s.state :Angry
|
74
|
+
s.do_something #=> "Kicks a puppy"
|
75
|
+
|
76
|
+
# now switch back to no state
|
77
|
+
s.state nil
|
78
|
+
s.do_something #=> "stares at the ceiling"
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
---=A FEW THINGS TO NOTE=---
|
83
|
+
|
84
|
+
* When an object is instantiated it begins life in no state and only ordinary instance methods are accessible (The ordinary instance methods are those defined outside of any state() {} block)
|
85
|
+
|
86
|
+
* The ordinary instance methods are available to any state so long as they are not overridden by the state.
|
87
|
+
|
88
|
+
* To change from any given state to 'no state' pass nil as a parameter to the state method
|
89
|
+
e.g s.state nil
|
90
|
+
|
91
|
+
* 'no state', while not a state, may nonetheless have state_entry() and state_exit() methods; and these methods will be invoked on 'entry' and exit from 'no state'
|
92
|
+
|
93
|
+
* The state_entry method for 'no state' is not automatically called on object instantiation. If you wish state_entry to run when the object is instantiated invoke it in the initialize() method.
|
94
|
+
|
95
|
+
* The state_entry method can also accept parameters:
|
96
|
+
e.g s.state :Happy, "hello"
|
97
|
+
In the above the string "hello" is passed as a parameter to the state_entry() method of the Happy state.
|
98
|
+
|
99
|
+
* The #state method can accept either a Symbol (e.g :Happy) or a Module (e.g Happy or Sample::Happy). The following are equivalent:
|
100
|
+
s.state :Happy #=> change state to Happy
|
101
|
+
|
102
|
+
s.state Sample::Happy #=> equivalent to above (note the fully qualified name; as Happy is a module defined under the Sample class)
|
103
|
+
|
104
|
+
* alternatively; if the #state method is invoked internally by another instance method of the Sample class then a fully qualified module name is not required:
|
105
|
+
state Happy #=> Fully qualified module name not required when #state invoked in an instance method
|
106
|
+
|
107
|
+
* The #state method can also act as a 'getter' method when invoked with no parameters. It will return the current state name in Symbol form (e.g :Happy)
|
108
|
+
|
109
|
+
* The #state_mod method works similarly to the #state 'getter' except it returns the Module representing the current state (e.g Sample::Happy)
|
110
|
+
|
111
|
+
* The #state?(state_name) returns boolean true if the current state is equal to state_name, and false if not. state_name can be either a Module or a Symbol
|
112
|
+
|
113
|
+
* One last note: state(:Name) {} is just DSL-style syntactic sugar for module Name...end
|
data/lib/stateology.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
begin
|
2
|
+
require 'rubygems'
|
3
|
+
rescue LoadError
|
4
|
+
# do nothing
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'mixology'
|
8
|
+
|
9
|
+
|
10
|
+
module Stateology
|
11
|
+
|
12
|
+
# alternative to 'nil'
|
13
|
+
Default = nil
|
14
|
+
|
15
|
+
# bring in class methods on include
|
16
|
+
def self.included(c)
|
17
|
+
c.extend(SM_Class_Methods)
|
18
|
+
end
|
19
|
+
|
20
|
+
# class methods
|
21
|
+
module SM_Class_Methods
|
22
|
+
def state(name, &block)
|
23
|
+
|
24
|
+
const_set(name, Module.new(&block))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# instance methods
|
29
|
+
|
30
|
+
def __state_epilogue(old_state)
|
31
|
+
|
32
|
+
# ensure that the constant is a module
|
33
|
+
raise NameError if(!(Module === old_state) && old_state != nil)
|
34
|
+
|
35
|
+
begin
|
36
|
+
state_exit()
|
37
|
+
rescue NoMethodError
|
38
|
+
# do nothing
|
39
|
+
end
|
40
|
+
|
41
|
+
if old_state then unmix(old_state) end
|
42
|
+
end
|
43
|
+
|
44
|
+
def __state_prologue(new_state, state_args)
|
45
|
+
|
46
|
+
# ensure that the constant is a module
|
47
|
+
raise NameError if(!(Module === new_state) && new_state != nil)
|
48
|
+
|
49
|
+
# only mixin if
|
50
|
+
if new_state then mixin(new_state) end
|
51
|
+
|
52
|
+
begin
|
53
|
+
state_entry(*state_args)
|
54
|
+
rescue NoMethodError
|
55
|
+
# do nothing
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
def state(*state_args)
|
61
|
+
|
62
|
+
# behave as getter
|
63
|
+
if(state_args.empty?) then
|
64
|
+
return @__SM_cur_state ? "#{@__SM_cur_state}".split(/::/).last.intern : nil
|
65
|
+
end
|
66
|
+
|
67
|
+
# behave as setter (only care about first argument)
|
68
|
+
state_name = state_args.shift
|
69
|
+
|
70
|
+
# if we receive a Symbol convert it to a constant
|
71
|
+
if(Symbol === state_name) then
|
72
|
+
state_name = self.class.const_get(state_name)
|
73
|
+
end
|
74
|
+
|
75
|
+
# prevent unnecessary state transitions
|
76
|
+
return if(@__SM_cur_state == state_name)
|
77
|
+
|
78
|
+
# exit old state
|
79
|
+
__state_epilogue(@__SM_cur_state)
|
80
|
+
|
81
|
+
|
82
|
+
# enter new state
|
83
|
+
__state_prologue(state_name, state_args)
|
84
|
+
|
85
|
+
# update the current state variable
|
86
|
+
@__SM_cur_state = state_name
|
87
|
+
|
88
|
+
rescue NameError
|
89
|
+
raise NameError, "#{state_name} not a valid state"
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
# is the current state equal to state_name?
|
94
|
+
def state?(state_name)
|
95
|
+
|
96
|
+
# if we receive a Symbol convert it to a constant
|
97
|
+
if(Symbol === state_name) then
|
98
|
+
state_name = self.class.const_get(state_name)
|
99
|
+
end
|
100
|
+
|
101
|
+
raise NameError if(!(Module === state_name) && state_name != nil)
|
102
|
+
|
103
|
+
state_name == @__SM_cur_state
|
104
|
+
|
105
|
+
rescue NameError
|
106
|
+
raise NameError, "#{state_name} not a valid state"
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
# return the current state as a module
|
111
|
+
def state_mod
|
112
|
+
@__SM_cur_state
|
113
|
+
end
|
114
|
+
|
115
|
+
private :__state_prologue, :__state_epilogue
|
116
|
+
end
|
117
|
+
|
118
|
+
|
data/sample.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'lib/stateology'
|
2
|
+
|
3
|
+
class Sample
|
4
|
+
include Stateology
|
5
|
+
|
6
|
+
state(:Happy) {
|
7
|
+
def state_entry
|
8
|
+
puts "entering Happy state"
|
9
|
+
end
|
10
|
+
|
11
|
+
def do_something
|
12
|
+
puts "Pets a puppy"
|
13
|
+
end
|
14
|
+
|
15
|
+
def state_exit
|
16
|
+
puts "exiting Happy state"
|
17
|
+
end
|
18
|
+
}
|
19
|
+
|
20
|
+
state(:Angry) {
|
21
|
+
def state_entry
|
22
|
+
puts "entering Angry state"
|
23
|
+
end
|
24
|
+
|
25
|
+
def do_something
|
26
|
+
puts "Kicks a puppy"
|
27
|
+
end
|
28
|
+
|
29
|
+
def state_exit
|
30
|
+
puts "exiting Angry state"
|
31
|
+
end
|
32
|
+
}
|
33
|
+
|
34
|
+
# methods declared outside a 'state' are part of the Default state
|
35
|
+
|
36
|
+
def state_entry
|
37
|
+
puts "entering Default state"
|
38
|
+
end
|
39
|
+
|
40
|
+
def do_something
|
41
|
+
puts "stares at the ceiling"
|
42
|
+
end
|
43
|
+
|
44
|
+
def state_exit
|
45
|
+
puts "exiting Default state"
|
46
|
+
end
|
47
|
+
|
48
|
+
# if we want the Default state_entry to run on instantiation
|
49
|
+
# we must call it from the initialize method
|
50
|
+
def initialize
|
51
|
+
state_entry
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
s = Sample.new
|
57
|
+
|
58
|
+
# in Default state
|
59
|
+
s.do_something #=> "stares at the ceiling"
|
60
|
+
|
61
|
+
# now switch to Happy state
|
62
|
+
s.state :Happy
|
63
|
+
s.do_something #=> "Pets a puppy"
|
64
|
+
|
65
|
+
# now switch to Angry state
|
66
|
+
s.state Sample::Angry
|
67
|
+
s.do_something #=> "Kicks a puppy"
|
68
|
+
|
69
|
+
# now switch back to Default state
|
70
|
+
s.state nil
|
71
|
+
s.do_something #=> "stares at the ceiling"
|
72
|
+
|
73
|
+
s.state :Angry
|
74
|
+
|
75
|
+
# what state are we in?
|
76
|
+
puts s.state
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stateology
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Mair
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-11-14 00:00:00 +13:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: mixology
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.1.0
|
24
|
+
version:
|
25
|
+
description: Clean and fast Object state transitions in Ruby using the Mixology C extension
|
26
|
+
email: jrmair@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- README
|
35
|
+
- LICENSE
|
36
|
+
- lib/stateology.rb
|
37
|
+
- sample.rb
|
38
|
+
has_rdoc: false
|
39
|
+
homepage: http://banisterfiend.wordpress.com
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 1.2.0
|
61
|
+
signing_key:
|
62
|
+
specification_version: 2
|
63
|
+
summary: Clean and fast Object state transitions in Ruby using the Mixology C extension.
|
64
|
+
test_files: []
|
65
|
+
|