fsm 0.0.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/gemspec.rb +23 -0
- data/install.rb +206 -0
- data/instance_exec.rb +26 -0
- data/lib/fsm-0.0.0.rb +78 -0
- data/lib/fsm-0.0.0/dsl.rb +71 -0
- data/lib/fsm-0.0.0/event.rb +68 -0
- data/lib/fsm-0.0.0/fsm.rb +211 -0
- data/lib/fsm-0.0.0/fsm.rb.bak +366 -0
- data/lib/fsm-0.0.0/graph/base_extensions.rb +70 -0
- data/lib/fsm-0.0.0/graph/directed_graph.rb +481 -0
- data/lib/fsm-0.0.0/graph/graphviz_dot.rb +188 -0
- data/lib/fsm-0.0.0/observer.rb +194 -0
- data/lib/fsm-0.0.0/system.rb +65 -0
- data/lib/fsm-0.0.0/util.rb +172 -0
- data/lib/fsm.rb +78 -0
- data/sample/a.rb +81 -0
- metadata +58 -0
@@ -0,0 +1,188 @@
|
|
1
|
+
class DotGraphPrinter
|
2
|
+
attr_accessor :orientation, :size, :color
|
3
|
+
|
4
|
+
# The following can be set to blocks of code that gives a default
|
5
|
+
# value for the node shapes, node labels and link labels, respectively.
|
6
|
+
attr_accessor :node_shaper, :node_labeler, :link_labeler
|
7
|
+
|
8
|
+
# A node shaper maps each node to a string describing its shape.
|
9
|
+
# Valid shapes are:
|
10
|
+
# "ellipse" (default)
|
11
|
+
# "box"
|
12
|
+
# "circle"
|
13
|
+
# "plaintext" (no outline)
|
14
|
+
# "doublecircle"
|
15
|
+
# "diamond"
|
16
|
+
# Not yet supported or untested once are:
|
17
|
+
# "polygon", "record", "epsf"
|
18
|
+
@@default_node_shaper = proc{|n| "box"}
|
19
|
+
|
20
|
+
@@default_node_labeler = proc{|n|
|
21
|
+
if Symbol===n
|
22
|
+
n.id2name
|
23
|
+
elsif String===n
|
24
|
+
n
|
25
|
+
else
|
26
|
+
n.inspect
|
27
|
+
end
|
28
|
+
}
|
29
|
+
|
30
|
+
@@default_link_labeler = proc{|info| info ? info.inspect : nil}
|
31
|
+
|
32
|
+
# links is either array of
|
33
|
+
# arrays [fromNode, toNode [, infoOnLink]], or
|
34
|
+
# objects with attributes :from, :to, :info
|
35
|
+
# nodes is array of node objects
|
36
|
+
# All nodes used in the links are used as nodes even if they are not
|
37
|
+
# in the "nodes" parameters.
|
38
|
+
def initialize(links = [], nodes = [])
|
39
|
+
@links, @nodes = links, add_nodes_in_links(links, nodes)
|
40
|
+
@node_attributes, @edge_attributes = Hash.new, Hash.new
|
41
|
+
set_default_values
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_default_values
|
45
|
+
@color = "black"
|
46
|
+
@size = "9,11"
|
47
|
+
@orientation = "portrait"
|
48
|
+
@node_shaper = @@default_node_shaper
|
49
|
+
@node_labeler = @@default_node_labeler
|
50
|
+
@link_labeler = @@default_link_labeler
|
51
|
+
end
|
52
|
+
|
53
|
+
def write_to_file(filename, fileType = "ps")
|
54
|
+
dotfile = temp_filename(filename)
|
55
|
+
File.open(dotfile, "w") {|f| f.write to_dot_specification}
|
56
|
+
system "dot -T#{fileType} -o #{filename} #{dotfile}"
|
57
|
+
File.delete(dotfile)
|
58
|
+
end
|
59
|
+
|
60
|
+
def set_edge_attributes(anEdge, aHash)
|
61
|
+
# TODO check if attributes are valid dot edge attributes
|
62
|
+
edge = find_edge(anEdge)
|
63
|
+
set_attributes(edge, @edge_attributes, true, aHash)
|
64
|
+
end
|
65
|
+
|
66
|
+
def set_node_attributes(aNode, aHash)
|
67
|
+
# TODO check if attributes are valid dot node attributes
|
68
|
+
set_attributes(aNode, @node_attributes, true, aHash)
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_dot_specification
|
72
|
+
set_edge_labels(@links)
|
73
|
+
set_node_labels_and_shape(@nodes)
|
74
|
+
"digraph G {\n" +
|
75
|
+
graph_parameters_to_dot_specification +
|
76
|
+
@nodes.uniq.map {|n| format_node(n)}.join(";\n") + ";\n" +
|
77
|
+
@links.uniq.map {|l| format_link(l)}.join(";\n") + ";\n" +
|
78
|
+
"}"
|
79
|
+
end
|
80
|
+
|
81
|
+
protected
|
82
|
+
|
83
|
+
def find_edge(anEdge)
|
84
|
+
@links.each do |link|
|
85
|
+
return link if source_and_dest(link) == source_and_dest(anEdge)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def set_attributes(key, hash, override, newAttributeHash)
|
90
|
+
h = hash[key] || Hash.new
|
91
|
+
newAttributeHash = all_keys_to_s(newAttributeHash)
|
92
|
+
newAttributeHash.each do |k, value|
|
93
|
+
h[k] = value unless h[k] and !override
|
94
|
+
end
|
95
|
+
hash[key] = h
|
96
|
+
end
|
97
|
+
|
98
|
+
def graph_parameters_to_dot_specification
|
99
|
+
"graph [\n" +
|
100
|
+
(self.size ? " size = #{@size.inspect},\n" : "") +
|
101
|
+
(self.orientation ? " orientation = #{@orientation},\n" : "") +
|
102
|
+
(self.color ? " color = #{@color}\n" : "") +
|
103
|
+
"]\n"
|
104
|
+
|
105
|
+
<<-dot_spec
|
106
|
+
graph[
|
107
|
+
rankdir = LR
|
108
|
+
]
|
109
|
+
dot_spec
|
110
|
+
end
|
111
|
+
|
112
|
+
def each_node_in_links(links)
|
113
|
+
links.each do |l|
|
114
|
+
src, dest = source_and_dest(l)
|
115
|
+
yield src
|
116
|
+
yield dest
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def add_nodes_in_links(links, nodes)
|
121
|
+
new_nodes = []
|
122
|
+
each_node_in_links(links) {|node| new_nodes.push node}
|
123
|
+
(nodes + new_nodes).uniq
|
124
|
+
end
|
125
|
+
|
126
|
+
def all_keys_to_s(aHash)
|
127
|
+
# MAYBE reuse existing hash?
|
128
|
+
Hash[*(aHash.map{|p| p[0] = p[0].to_s; p}.flatten)]
|
129
|
+
end
|
130
|
+
|
131
|
+
def set_edge_labels(edges)
|
132
|
+
edges.each do |edge|
|
133
|
+
src, dest, info = get_link_data(edge)
|
134
|
+
if info
|
135
|
+
label = @link_labeler.call(info)
|
136
|
+
set_attributes(edge, @edge_attributes, false, :label =>label) if label
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def set_node_labels_and_shape(nodes)
|
142
|
+
nodes.each do |node|
|
143
|
+
set_attributes(node, @node_attributes, false,
|
144
|
+
:label => @node_labeler.call(node).inspect,
|
145
|
+
:shape => @node_shaper.call(node).inspect)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def get_link_data(link)
|
150
|
+
begin
|
151
|
+
return link.from, link.to, link.info
|
152
|
+
rescue Exception
|
153
|
+
return link[0], link[1], link[2]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def source_and_dest(link)
|
158
|
+
get_link_data(link)[0,2]
|
159
|
+
end
|
160
|
+
|
161
|
+
def format_attributes(attributes)
|
162
|
+
return "" unless attributes
|
163
|
+
strings = attributes.map {|a, v| "#{a}=#{v}"}
|
164
|
+
strings.length > 0 ? (" [" + strings.join(", ") + "]") : ("")
|
165
|
+
end
|
166
|
+
|
167
|
+
def mangle_node_name(node)
|
168
|
+
"n" + node.hash.abs.inspect
|
169
|
+
end
|
170
|
+
|
171
|
+
def format_link(link)
|
172
|
+
from, to, info = get_link_data(link)
|
173
|
+
mangle_node_name(from) + " -> " + mangle_node_name(to) +
|
174
|
+
format_attributes(@edge_attributes[link])
|
175
|
+
end
|
176
|
+
|
177
|
+
def format_node(node)
|
178
|
+
mangle_node_name(node) + format_attributes(@node_attributes[node])
|
179
|
+
end
|
180
|
+
|
181
|
+
def temp_filename(base = "tmp")
|
182
|
+
tmpfile = base + rand(100000).inspect
|
183
|
+
while test(?f, tmpfile)
|
184
|
+
tmpfile = base + rand(100000).inspect
|
185
|
+
end
|
186
|
+
tmpfile
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
unless defined? $__fsm_observer__
|
2
|
+
$__fsm_observer__ = __FILE__
|
3
|
+
|
4
|
+
module FSM
|
5
|
+
FSM::LIBDIR =
|
6
|
+
File::dirname(File::expand_path(__FILE__)) + File::SEPARATOR unless
|
7
|
+
defined? FSM::LIBDIR
|
8
|
+
|
9
|
+
FSM::INCDIR =
|
10
|
+
File::dirname(FSM::LIBDIR) + File::SEPARATOR unless
|
11
|
+
defined? FSM::INCDIR
|
12
|
+
|
13
|
+
require INCDIR + 'fsm'
|
14
|
+
|
15
|
+
class Observer
|
16
|
+
include Util
|
17
|
+
|
18
|
+
ANY = const 'ANY'
|
19
|
+
|
20
|
+
tattrs %w[
|
21
|
+
fsm
|
22
|
+
hooks
|
23
|
+
q
|
24
|
+
thread
|
25
|
+
dsl
|
26
|
+
]
|
27
|
+
|
28
|
+
def initialize fsm, &b
|
29
|
+
sync_initialize
|
30
|
+
|
31
|
+
@fsm = fsm
|
32
|
+
@fsm.add_observer self
|
33
|
+
|
34
|
+
@hooks = Hash.new{|etype_h, etype|
|
35
|
+
etype_h[etype] = Hash.new{|fstate_h, fstate|
|
36
|
+
fstate_h[fstate] = Array.new
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
@q = Queue.new
|
41
|
+
|
42
|
+
@thread = Thread.new{
|
43
|
+
Thread.current.abort_on_exception = true
|
44
|
+
loop{
|
45
|
+
b, *a = @q.pop
|
46
|
+
break unless b
|
47
|
+
bcall b, *a
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
@dsl = DSL.new self
|
52
|
+
configure &b if b
|
53
|
+
end
|
54
|
+
|
55
|
+
def configure &b
|
56
|
+
ex{
|
57
|
+
@dsl.configure &b
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def defer hook, *a
|
62
|
+
ex{
|
63
|
+
@q.push [hook, *a]
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
def idefer hook, *a
|
68
|
+
ihook = lambda{|*a| instance_exec *a, &hook }
|
69
|
+
defer ihook, *a
|
70
|
+
end
|
71
|
+
|
72
|
+
def on etype = Event::Any, fstate = ANY, &hook
|
73
|
+
ex{
|
74
|
+
idx = (@hooks[etype][fstate] << hook).size - 1
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
def on_entry *a, &b
|
79
|
+
on Event::Entry, *a, &b
|
80
|
+
end
|
81
|
+
|
82
|
+
def on_transition *a, &b
|
83
|
+
on Event::Transition, *a, &b
|
84
|
+
end
|
85
|
+
|
86
|
+
def on_exit *a, &b
|
87
|
+
on Event::Exit, *a, &b
|
88
|
+
end
|
89
|
+
|
90
|
+
def on_input *a, &b
|
91
|
+
on Event::Input, *a, &b
|
92
|
+
end
|
93
|
+
|
94
|
+
module Once; end
|
95
|
+
|
96
|
+
def once_on etype = Event::Any, fstate = ANY, &hook
|
97
|
+
on etype, fstate, &hook.extend(Once)
|
98
|
+
end
|
99
|
+
|
100
|
+
def once_on_entry *a, &b
|
101
|
+
once_on Event::Entry, *a, &b
|
102
|
+
end
|
103
|
+
|
104
|
+
def once_on_transition *a, &b
|
105
|
+
once_on Event::Transition, *a, &b
|
106
|
+
end
|
107
|
+
|
108
|
+
def once_on_exit *a, &b
|
109
|
+
once_on Event::Exit, *a, &b
|
110
|
+
end
|
111
|
+
|
112
|
+
def once_on_input *a, &b
|
113
|
+
once_on Event::Input, *a, &b
|
114
|
+
end
|
115
|
+
|
116
|
+
def wait_for_entry fstate, &hook
|
117
|
+
argv = [fstate]
|
118
|
+
unless fsm.state == fstate
|
119
|
+
q = Queue.new
|
120
|
+
once_on(Event::Entry, fstate){|*a| q.push a }
|
121
|
+
argv.replace q.pop
|
122
|
+
end
|
123
|
+
icall hook, *argv if hook
|
124
|
+
end
|
125
|
+
alias_method 'wait_for', 'wait_for_entry'
|
126
|
+
|
127
|
+
def wait_for_transition fstate, &hook
|
128
|
+
argv = [fstate]
|
129
|
+
unless fsm.state == fstate
|
130
|
+
q = Queue.new
|
131
|
+
once_on(Event::Transition, fstate){|*a| q.push a }
|
132
|
+
argv.replace q.pop
|
133
|
+
end
|
134
|
+
icall hook, *argv if hook
|
135
|
+
end
|
136
|
+
|
137
|
+
def wait_for_exit fstate, &hook
|
138
|
+
argv = [fstate]
|
139
|
+
unless fsm.state == fstate
|
140
|
+
q = Queue.new
|
141
|
+
once_on(Event::Exit, fstate){|*a| q.push a }
|
142
|
+
argv.replace q.pop
|
143
|
+
end
|
144
|
+
icall hook, *argv if hook
|
145
|
+
end
|
146
|
+
|
147
|
+
def wait_for_input fstate, &hook
|
148
|
+
argv = [fstate]
|
149
|
+
unless fsm.state == fstate
|
150
|
+
q = Queue.new
|
151
|
+
once_on(Event::Input, fstate){|*a| q.push a }
|
152
|
+
argv.replace q.pop
|
153
|
+
end
|
154
|
+
icall hook, *argv if hook
|
155
|
+
end
|
156
|
+
|
157
|
+
def fsm_event event
|
158
|
+
ex{
|
159
|
+
[event.type, Event::Any].each do |etype|
|
160
|
+
[event.state, ANY].each do |estate|
|
161
|
+
#@hooks[etype][estate].each{|hook| idefer hook, event}
|
162
|
+
hooks = []
|
163
|
+
while((hook = @hooks[etype][estate].shift))
|
164
|
+
idefer hook, event
|
165
|
+
hooks.push hook unless hook.is_a? Once
|
166
|
+
end
|
167
|
+
@hooks[etype][estate].replace hooks
|
168
|
+
end
|
169
|
+
end
|
170
|
+
}
|
171
|
+
end
|
172
|
+
|
173
|
+
def join
|
174
|
+
@thread.join
|
175
|
+
end
|
176
|
+
|
177
|
+
def kill
|
178
|
+
@thread.kill
|
179
|
+
end
|
180
|
+
|
181
|
+
alias_method 'stop', 'kill'
|
182
|
+
|
183
|
+
delegate %w[
|
184
|
+
start
|
185
|
+
transition
|
186
|
+
input
|
187
|
+
display
|
188
|
+
plot
|
189
|
+
state
|
190
|
+
] => '@fsm'
|
191
|
+
|
192
|
+
end # class Observer
|
193
|
+
end # module FSM
|
194
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
unless defined? $__fsm_system__
|
2
|
+
$__fsm_system__ = __FILE__
|
3
|
+
|
4
|
+
module FSM
|
5
|
+
FSM::LIBDIR =
|
6
|
+
File::dirname(File::expand_path(__FILE__)) + File::SEPARATOR unless
|
7
|
+
defined? FSM::LIBDIR
|
8
|
+
|
9
|
+
FSM::INCDIR =
|
10
|
+
File::dirname(FSM::LIBDIR) + File::SEPARATOR unless
|
11
|
+
defined? FSM::INCDIR
|
12
|
+
|
13
|
+
require INCDIR + 'fsm'
|
14
|
+
|
15
|
+
class System
|
16
|
+
include Util
|
17
|
+
|
18
|
+
%w[
|
19
|
+
fsm
|
20
|
+
observer
|
21
|
+
].each{|a| attr a}
|
22
|
+
|
23
|
+
def initialize fsm = FSM.new, &b
|
24
|
+
@fsm = fsm
|
25
|
+
@observer = Observer.new @fsm
|
26
|
+
@dsl = DSL.new self
|
27
|
+
configure &b if b
|
28
|
+
end
|
29
|
+
|
30
|
+
def configure &b
|
31
|
+
@dsl.configure &b
|
32
|
+
end
|
33
|
+
|
34
|
+
delegate %w[
|
35
|
+
graph
|
36
|
+
state_attributes
|
37
|
+
state
|
38
|
+
subscribers
|
39
|
+
subscribe
|
40
|
+
inspect
|
41
|
+
start
|
42
|
+
add_observer
|
43
|
+
transition
|
44
|
+
traverse
|
45
|
+
input
|
46
|
+
add_state
|
47
|
+
add_transition
|
48
|
+
plot
|
49
|
+
to_dot
|
50
|
+
display
|
51
|
+
] => '@fsm'
|
52
|
+
|
53
|
+
delegate %w[
|
54
|
+
on
|
55
|
+
on_entry
|
56
|
+
on_transition
|
57
|
+
on_exit
|
58
|
+
on_input
|
59
|
+
join
|
60
|
+
kill
|
61
|
+
stop
|
62
|
+
] => '@observer'
|
63
|
+
end # class System
|
64
|
+
end # module FSM
|
65
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
unless defined? $__fsm_util__
|
2
|
+
$__fsm_util__ = __FILE__
|
3
|
+
|
4
|
+
module FSM
|
5
|
+
FSM::LIBDIR =
|
6
|
+
File::dirname(File::expand_path(__FILE__)) + File::SEPARATOR unless
|
7
|
+
defined? FSM::LIBDIR
|
8
|
+
|
9
|
+
FSM::INCDIR =
|
10
|
+
File::dirname(FSM::LIBDIR) + File::SEPARATOR unless
|
11
|
+
defined? FSM::INCDIR
|
12
|
+
|
13
|
+
require INCDIR + 'fsm'
|
14
|
+
|
15
|
+
module Util
|
16
|
+
class Const
|
17
|
+
def self.new s
|
18
|
+
s = s.to_s
|
19
|
+
(@instances ||= {})[s] ||= super(s)
|
20
|
+
end
|
21
|
+
|
22
|
+
attr 'to_s'
|
23
|
+
alias_method 'to_str', 'to_s'
|
24
|
+
alias_method 'inspect', 'to_s'
|
25
|
+
|
26
|
+
def initialize s
|
27
|
+
@to_s = s.to_s
|
28
|
+
freeze
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module Methods
|
33
|
+
include Sync_m
|
34
|
+
|
35
|
+
def const(*a)
|
36
|
+
Const.new(*a)
|
37
|
+
end
|
38
|
+
|
39
|
+
def string_list *list
|
40
|
+
list.flatten.map{|elem| elem.to_s}
|
41
|
+
end
|
42
|
+
|
43
|
+
def string *list
|
44
|
+
string_list(*list).first
|
45
|
+
end
|
46
|
+
|
47
|
+
def klass
|
48
|
+
self.class
|
49
|
+
end
|
50
|
+
|
51
|
+
def bcall b, *a
|
52
|
+
arity = b.arity
|
53
|
+
if arity > 0
|
54
|
+
a = a[0...arity]
|
55
|
+
elsif arity < 0
|
56
|
+
arity = arity.abs
|
57
|
+
a = a[0...arity] + (a[arity..-1] || [])
|
58
|
+
else
|
59
|
+
a.clear
|
60
|
+
end
|
61
|
+
b.call *a
|
62
|
+
end
|
63
|
+
|
64
|
+
def mcall obj, msg, *a
|
65
|
+
m = obj.method(msg).to_proc
|
66
|
+
bcall m, *a
|
67
|
+
end
|
68
|
+
|
69
|
+
def icall b, *a
|
70
|
+
arity = b.arity
|
71
|
+
if arity > 0
|
72
|
+
a = a[0...arity]
|
73
|
+
elsif arity < 0
|
74
|
+
arity = arity.abs
|
75
|
+
a = a[0...arity] + (a[arity..-1] || [])
|
76
|
+
else
|
77
|
+
a.clear
|
78
|
+
end
|
79
|
+
instance_exec *a, &b
|
80
|
+
end
|
81
|
+
|
82
|
+
def system *a, &b
|
83
|
+
verbose = $VERBOSE
|
84
|
+
$VERBOSE = nil
|
85
|
+
::Kernel.system *a, &b
|
86
|
+
ensure
|
87
|
+
$VERBOSE = verbose
|
88
|
+
end
|
89
|
+
|
90
|
+
unless instance_methods.include? 'instance_exec'
|
91
|
+
def instance_exec *a, &b
|
92
|
+
m, n = nil, -1
|
93
|
+
loop{
|
94
|
+
m = "__instance_exec_#{ Thread.current.object_id.abs }_#{ n += 1 }__"
|
95
|
+
break unless respond_to? m
|
96
|
+
}
|
97
|
+
singleton_class{ define_method m, &b }
|
98
|
+
send m, *a
|
99
|
+
ensure
|
100
|
+
singleton_class{ undef_method m }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
unless instance_methods.include? 'singleton_class'
|
105
|
+
def singleton_class &b
|
106
|
+
sc =
|
107
|
+
class << self; self; end
|
108
|
+
sc.module_eval &b if b
|
109
|
+
sc
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def sh(&b) synchronize(:SH, &b) end
|
114
|
+
def ex(&b) synchronize(:EX, &b) end
|
115
|
+
|
116
|
+
|
117
|
+
def initialize(*a, &b)
|
118
|
+
sync_initialize
|
119
|
+
end
|
120
|
+
alias_method 'sync_init', 'sync_initialize'
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
module ClassMethods
|
125
|
+
include Methods
|
126
|
+
|
127
|
+
def tattrs *ms
|
128
|
+
ms.flatten.each do |m|
|
129
|
+
module_eval <<-code
|
130
|
+
def #{ m }(*a, &b)
|
131
|
+
if a.empty?
|
132
|
+
sh{ @#{ m } }
|
133
|
+
else
|
134
|
+
self.#{ m }= a.shift
|
135
|
+
end
|
136
|
+
end
|
137
|
+
alias_method '#{ m }?', '#{ m }'
|
138
|
+
def #{ m }= val
|
139
|
+
ex{ @#{ m } = val }
|
140
|
+
end
|
141
|
+
code
|
142
|
+
end
|
143
|
+
end
|
144
|
+
alias_method 'tattr', 'tattrs'
|
145
|
+
|
146
|
+
def delegate hash = {}
|
147
|
+
methods, to = hash.to_a.first
|
148
|
+
methods.each do |m|
|
149
|
+
module_eval <<-code
|
150
|
+
def #{ m } *a, &b
|
151
|
+
#{ to }.#{ m } *a, &b
|
152
|
+
end
|
153
|
+
code
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
module InstanceMethods
|
159
|
+
include Methods
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.included other
|
163
|
+
other.extend ClassMethods
|
164
|
+
other.module_eval{
|
165
|
+
include InstanceMethods
|
166
|
+
include Sync_m
|
167
|
+
}
|
168
|
+
super
|
169
|
+
end
|
170
|
+
end # module Util
|
171
|
+
end # module FSM
|
172
|
+
end
|