stateology 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/LICENSE +14 -0
  2. data/README +113 -0
  3. data/lib/stateology.rb +118 -0
  4. data/sample.rb +80 -0
  5. 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
+