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