mindi 0.5
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.
- checksums.yaml +7 -0
- data/COPYING +22 -0
- data/README.md +174 -0
- data/RELEASE-NOTES +26 -0
- data/Rakefile +64 -0
- data/examples/JW-app-cont-injected.rb +36 -0
- data/examples/JW-app-cont.rb +37 -0
- data/examples/coffee.rb +77 -0
- data/examples/color-namespace.rb +36 -0
- data/examples/duck.rb +66 -0
- data/examples/dynamic.rb +24 -0
- data/examples/game.rb +127 -0
- data/examples/inject.rb +141 -0
- data/examples/rake-alike.rb +13 -0
- data/examples/simple.rb +15 -0
- data/examples/test.rb +117 -0
- data/examples/transform.rb +54 -0
- data/examples/wip/drb-example.rb +14 -0
- data/examples/wip/inj-param.rb +12 -0
- data/examples/wip/mink.rb +58 -0
- data/lib/mindi.rb +383 -0
- data/test/test-injectable-container.rb +5 -0
- data/test/test-injectable.rb +48 -0
- metadata +78 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'mindi'
|
2
|
+
|
3
|
+
class Transformer
|
4
|
+
def transform string
|
5
|
+
string.gsub(pattern, &replacement)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class TransformerContainer
|
10
|
+
include MinDI::InjectableContainer
|
11
|
+
|
12
|
+
pattern { /foo/ }
|
13
|
+
replacement { proc {|match| match.upcase } }
|
14
|
+
transformer { Transformer.new }
|
15
|
+
transform { |str| transformer.transform(str) }
|
16
|
+
end
|
17
|
+
|
18
|
+
cont = TransformerContainer.new
|
19
|
+
s1 = cont.transform("fo foo fee")
|
20
|
+
s2 = cont.transform("fo foo fee")
|
21
|
+
p s1 # ==> "fo FOO fee"
|
22
|
+
p s1.equal?(s2) # ==> true
|
23
|
+
|
24
|
+
__END__
|
25
|
+
|
26
|
+
Here's what happens when the service is instantiated (i.e. the "{
|
27
|
+
Transformer.new }" block is called in response to the #transformer
|
28
|
+
method on the container):
|
29
|
+
|
30
|
+
The service object (the Transformer instance returned by the block) is
|
31
|
+
given an instance variable, @__injectable__object__, whose value is set
|
32
|
+
to the container.
|
33
|
+
|
34
|
+
Also, the service object is extended, but only with a fairly minimal
|
35
|
+
module called Injected:
|
36
|
+
|
37
|
+
module Injected
|
38
|
+
def method_missing(*args, &block)
|
39
|
+
@__injectable__object__ || super
|
40
|
+
@__injectable__object__.send(*args, &block)
|
41
|
+
rescue NoInjectedMethodError
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
The NoInjectedMethodError is raised by the container's own
|
47
|
+
method_missing. So the method search order for the service object is:
|
48
|
+
|
49
|
+
1. the service object itself (the Transformer instance)
|
50
|
+
|
51
|
+
2. other services in the container (this is how "replacement" and
|
52
|
+
"pattern" are resolved)
|
53
|
+
|
54
|
+
3. method_missing, as defined in the ancestry of the service object
|
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
# class ContainerWithDRbObject
|
3
|
+
# extend MinDI::Container
|
4
|
+
#
|
5
|
+
# server { #"druby://localhost:9000" }
|
6
|
+
# require 'drb'
|
7
|
+
## DRb.start_service(nil)
|
8
|
+
# DRbObject.new(nil, "druby://localhost:9000")
|
9
|
+
# }
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# cwdo = ContainerWithDRbObject.new
|
13
|
+
# p cwdo.server
|
14
|
+
# cwdo.server.foo
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# a make/rake type tool using MinDI
|
2
|
+
|
3
|
+
require 'mindi'
|
4
|
+
|
5
|
+
module Mink
|
6
|
+
include MinDI::Container
|
7
|
+
|
8
|
+
# def task(t, &bl)
|
9
|
+
# task_impl = "task_#{t}"
|
10
|
+
#
|
11
|
+
# case bl.arity
|
12
|
+
# when 0, -1
|
13
|
+
# define_method(task_impl, &bl)
|
14
|
+
# singleton(t) do
|
15
|
+
# ##ivname = Container.iv(name)
|
16
|
+
# ##if instance_variable_get(ivname)
|
17
|
+
# ## raise if value is :in_progress
|
18
|
+
# ## set value to :in_progress
|
19
|
+
# result = send(task_impl)
|
20
|
+
# result || true
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# when 1
|
24
|
+
# define_method(task_impl, &bl)
|
25
|
+
# multiton(t) do |arg|
|
26
|
+
# ##ivname = Container.iv(name)
|
27
|
+
# ##if instance_variable_get(ivname)
|
28
|
+
# ## raise if value is :in_progress
|
29
|
+
# ## set value to :in_progress
|
30
|
+
# result = send(task_impl, arg)
|
31
|
+
# result || true
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# else
|
35
|
+
# raise "arity == #{bl.arity}"
|
36
|
+
# end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class C
|
41
|
+
extend Mink
|
42
|
+
|
43
|
+
# print each string at most once
|
44
|
+
output do |s| puts s end
|
45
|
+
|
46
|
+
z do output "bar" end
|
47
|
+
a do z; output "bar" end
|
48
|
+
b do a end
|
49
|
+
c do a end
|
50
|
+
d do b; c; "d is done" end
|
51
|
+
|
52
|
+
xa { puts "xa" }
|
53
|
+
xb { xa; puts "xb" }
|
54
|
+
xc { xa; puts "xc" }
|
55
|
+
xd { xb; xc; puts "xd" }
|
56
|
+
end
|
57
|
+
|
58
|
+
p C.new.xd
|
data/lib/mindi.rb
ADDED
@@ -0,0 +1,383 @@
|
|
1
|
+
module MinDI # :nodoc:
|
2
|
+
# Extend a class (or module) with Container to make the class behave as a
|
3
|
+
# class of service containers. Containers encapsulate the way in which related
|
4
|
+
# services are instantiated and connected together. Services themselves are
|
5
|
+
# insulated from this process [except when they are injected with the
|
6
|
+
# container].
|
7
|
+
#
|
8
|
+
# Use MinDI by <b><tt>include</tt></b>ing the MinDI::InjectableContainer
|
9
|
+
# module in your container class. As of version 0.2, this does two things: it
|
10
|
+
# <b><tt>extend</tt></b>s your container class with the Container module,
|
11
|
+
# which provides class methods for expressing service definitions in a
|
12
|
+
# natural, rubylike way. It also injects the container into all services which
|
13
|
+
# gives them direct access to the other services in the container. If you
|
14
|
+
# prefer to have the container funtionality without injecting services (i.e.,
|
15
|
+
# just "contructor injection") You can just include MinDI::BasicContainer.
|
16
|
+
#
|
17
|
+
# The Container module also defines shortcuts for defining some service types
|
18
|
+
# using +method_missing+. These methods and shortcuts are described below.
|
19
|
+
#
|
20
|
+
# A service definition involves a service name and a block of code. It defines
|
21
|
+
# an instance method whose name is the service name. The block is used to
|
22
|
+
# instantiate the service. When and how that service is instantiated depends
|
23
|
+
# on the type of service.
|
24
|
+
#
|
25
|
+
# <i>Note:</i> MinDI uses some instance variables and auxiliary instance
|
26
|
+
# methods in the container. The instance variables have the same name as the
|
27
|
+
# corresponding services, possibly with added suffixes, as in the case of
|
28
|
+
# #deferred. The auxiliary methods are named so as to make conflicts unlikely.
|
29
|
+
# See the implemetations of #iv and #impl_method_name for details.
|
30
|
+
#
|
31
|
+
# Note that services can be defined dynamically by reopening the class scope
|
32
|
+
# or simply by calling the service with a block. See examples/dynamic.rb.
|
33
|
+
|
34
|
+
VERSION = '0.5'
|
35
|
+
|
36
|
+
module Container
|
37
|
+
|
38
|
+
# ------------------------
|
39
|
+
# :section: Basic services
|
40
|
+
#
|
41
|
+
# Methods which define a service in terms of simple rules involving
|
42
|
+
# existence, uniqeness, and parameters.
|
43
|
+
# ------------------------
|
44
|
+
|
45
|
+
# Define a generic service, which has no built-in rules for existence or
|
46
|
+
# uniqueness. There is no shortcut for generic service definition. Calling
|
47
|
+
# the service simply calls the associated block. This is also known as a
|
48
|
+
# _prototype_ service.
|
49
|
+
|
50
|
+
def generic(name, &impl) # :yields:
|
51
|
+
define_implementation(name, impl)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Define a singleton service:
|
55
|
+
#
|
56
|
+
# singleton(:service_name) { ... }
|
57
|
+
#
|
58
|
+
# The block will be called at most once to instantiate a unique value
|
59
|
+
# for the service. The shortcut for defining a singleton is:
|
60
|
+
#
|
61
|
+
# service_name { ... }
|
62
|
+
#
|
63
|
+
# The service is invoked as <tt>service_name</tt>.
|
64
|
+
|
65
|
+
def singleton(name, &impl) # :yields:
|
66
|
+
impl_name = Container.impl_method_name(name)
|
67
|
+
define_implementation(impl_name, impl)
|
68
|
+
|
69
|
+
ivname = Container.iv(name)
|
70
|
+
define_method(name) do
|
71
|
+
box = instance_variable_get(ivname)
|
72
|
+
box ||= instance_variable_set(ivname, [])
|
73
|
+
box << send(impl_name) if box.empty?
|
74
|
+
box.first
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Define a multiton service:
|
79
|
+
#
|
80
|
+
# multiton(:service_name) { |arg| ... }
|
81
|
+
#
|
82
|
+
# The block will be called once per distinct (in the sense of hash keys)
|
83
|
+
# argument to instantiate a unique value corresponding to the argument. The
|
84
|
+
# shortcut for defining a multiton is:
|
85
|
+
#
|
86
|
+
# service_name { |arg| ... }
|
87
|
+
#
|
88
|
+
# The service is invoked as <tt>service_name(arg)</tt>.
|
89
|
+
|
90
|
+
def multiton(name, &impl) # :yields: arg
|
91
|
+
impl_name = Container.impl_method_name(name)
|
92
|
+
define_implementation(impl_name, impl)
|
93
|
+
|
94
|
+
ivname = Container.iv(name)
|
95
|
+
define_method(name) do |key|
|
96
|
+
map = instance_variable_get(ivname)
|
97
|
+
map ||= instance_variable_set(ivname, {})
|
98
|
+
map.key?(key) ? map[key] : map[key] = send(impl_name, key)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Define a multiton service with multiple keys:
|
103
|
+
#
|
104
|
+
# multiton(:service_name) { |arg0, arg1, ...| ... }
|
105
|
+
#
|
106
|
+
# The block will be called once per distinct (in the sense of hash keys)
|
107
|
+
# argument list to instantiate a unique value corresponding to the argument
|
108
|
+
# list. The shortcut for defining a multikey_multiton with multiple keys is:
|
109
|
+
#
|
110
|
+
# service_name { |arg0, arg1, ...| ... }
|
111
|
+
#
|
112
|
+
# The service is invoked as <tt>service_name(arg0, arg1, ...)</tt>.
|
113
|
+
# Variable length argument lists, using the splat notation, are permitted.
|
114
|
+
|
115
|
+
def multikey_multiton(name, &impl) # :yields: arg0, arg1, ...
|
116
|
+
impl_name = Container.impl_method_name(name)
|
117
|
+
define_implementation(impl_name, impl)
|
118
|
+
|
119
|
+
ivname = Container.iv(name)
|
120
|
+
define_method(name) do |*key|
|
121
|
+
map = instance_variable_get(ivname)
|
122
|
+
map ||= instance_variable_set(ivname, {})
|
123
|
+
map.key?(key) ? map[key] : map[key] = send(impl_name, *key)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# ---------------------------
|
128
|
+
# :section: Advanced services
|
129
|
+
#
|
130
|
+
# Methods which define a service in terms of more complex modalities, such
|
131
|
+
# as per-thread uniqueness, and deferred, on-demand existence.
|
132
|
+
# ---------------------------
|
133
|
+
|
134
|
+
# Define a service with per-thread instantiation. For each thread, the
|
135
|
+
# service appears to be a singleton service. The block will be called at
|
136
|
+
# most once per thread. There is no shortcut. The block may take a single
|
137
|
+
# argument, in which case it will be passed the current thread.
|
138
|
+
#
|
139
|
+
# threaded(:service_name) { |thr| ... }
|
140
|
+
|
141
|
+
def threaded(name, &impl) # :yields: thr
|
142
|
+
impl_name = Container.impl_method_name(name)
|
143
|
+
define_implementation(impl_name, impl)
|
144
|
+
arity = impl.arity
|
145
|
+
|
146
|
+
ivname = Container.iv(name)
|
147
|
+
define_method(name) do
|
148
|
+
key = Thread.current
|
149
|
+
map = instance_variable_get(ivname)
|
150
|
+
map ||= instance_variable_set(ivname, {})
|
151
|
+
map[key] ||= (arity == 1 ? send(impl_name, key) : send(impl_name))
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
PROXY_METHODS = ["__send__", "__id__", "method_missing", "call"] # :nodoc:
|
156
|
+
|
157
|
+
# Define a singleton service with deferred instantiation. Syntax and
|
158
|
+
# semantics are the same as #singleton, except that the block is not called
|
159
|
+
# when the service is requested, but only when a method is called on the
|
160
|
+
# service.
|
161
|
+
|
162
|
+
def deferred(name, &impl) # :yields:
|
163
|
+
impl_name = Container.impl_method_name(name)
|
164
|
+
define_implementation(impl_name, impl)
|
165
|
+
|
166
|
+
proxy_name = Container.impl_method_name("#{name}_proxy")
|
167
|
+
|
168
|
+
ivname = Container.iv(name)
|
169
|
+
proxy_ivname = Container.iv("#{name}_proxy")
|
170
|
+
|
171
|
+
define_method(name) do
|
172
|
+
instance_variable_get(ivname) || send(proxy_name)
|
173
|
+
end
|
174
|
+
|
175
|
+
define_method(proxy_name) do
|
176
|
+
proxy = instance_variable_get(proxy_ivname)
|
177
|
+
|
178
|
+
unless proxy
|
179
|
+
proxy = proc {instance_variable_set(ivname, send(impl_name))}
|
180
|
+
def proxy.method_missing(*args, &block)
|
181
|
+
call.__send__(*args, &block)
|
182
|
+
end
|
183
|
+
instance_variable_set(proxy_ivname, proxy)
|
184
|
+
class << proxy; self; end.class_eval do
|
185
|
+
(proxy.methods - PROXY_METHODS).each do |m|
|
186
|
+
undef_method m
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
proxy
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# ---------------------------
|
196
|
+
# :section: Internal methods
|
197
|
+
# ---------------------------
|
198
|
+
|
199
|
+
# For declarative style container definitions ("shortcuts").
|
200
|
+
def method_missing(meth, *args, &bl) # :nodoc:
|
201
|
+
super unless bl
|
202
|
+
|
203
|
+
case bl.arity
|
204
|
+
when 0
|
205
|
+
singleton(meth, *args, &bl)
|
206
|
+
when 1
|
207
|
+
multiton(meth, *args, &bl)
|
208
|
+
else
|
209
|
+
# note that this includes the case of a block with _no_ args, i.e.
|
210
|
+
# { value }, which has arity -1, indistinguishabe from {|*args|}.
|
211
|
+
multikey_multiton(meth, *args, &bl)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
protected
|
216
|
+
def define_implementation impl_name, impl
|
217
|
+
if @__services_are_injected__
|
218
|
+
preinject_method = impl_name + "__preinject"
|
219
|
+
define_method(preinject_method, &impl)
|
220
|
+
define_method(impl_name) do |*args|
|
221
|
+
inject_into send(preinject_method, *args)
|
222
|
+
end
|
223
|
+
else
|
224
|
+
define_method(impl_name, &impl)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# The name of an instance variable that stores the state of the service
|
229
|
+
# named _name_.
|
230
|
+
def self.iv(name) # :doc:
|
231
|
+
"@___#{name}___value"
|
232
|
+
end
|
233
|
+
|
234
|
+
# The name of a method used internally to implement the service named
|
235
|
+
# _name_.
|
236
|
+
def self.impl_method_name(name) # :doc:
|
237
|
+
"___#{name}___implementation"
|
238
|
+
end
|
239
|
+
|
240
|
+
end
|
241
|
+
|
242
|
+
# Include this in a container class to allow use of #inject_into within
|
243
|
+
# services. See examples/inject.rb. Including this module also extends the
|
244
|
+
# class with the Container module, so a simple shortcut to making a fully
|
245
|
+
# functional injectable container is to simply include Injectable.
|
246
|
+
#
|
247
|
+
# This module can be used outside of the context of MinDI::Container: almost
|
248
|
+
# any object can be injected into any other. For example:
|
249
|
+
#
|
250
|
+
# x = [1,2,3]
|
251
|
+
# y = {}
|
252
|
+
# x.extend MinDI::Injectable
|
253
|
+
# x.inject_into y
|
254
|
+
# p y.reverse => [3, 2, 1]
|
255
|
+
#
|
256
|
+
# Note that injecting an Injectable object into another object never
|
257
|
+
# interferes with methods of the latter object.
|
258
|
+
#
|
259
|
+
# Note that injected methods operate on the injecting instance, not the
|
260
|
+
# injectee. So instance variables...
|
261
|
+
#
|
262
|
+
# Note similarity to Forwardable and Delegator in the stdlib. However,
|
263
|
+
# Injectable is more passive in that (as noted above) it only affects the
|
264
|
+
# handling of methods that are _missing_ in the target object. In that
|
265
|
+
# respect, Injectable is a little like inheritance (except as noted above
|
266
|
+
# about which instance is operated on).
|
267
|
+
module Injectable
|
268
|
+
# Raised and rescued internally to pass _method_missing_ back from container
|
269
|
+
# to service object. Client code should not have to handle this exception.
|
270
|
+
class NoInjectedMethodError < StandardError; end
|
271
|
+
|
272
|
+
# Raised on attempt to add object to a second container.
|
273
|
+
class NonUniqueContainerError < StandardError; end
|
274
|
+
|
275
|
+
# Internally used to extend service objects so that they can delegate sevice
|
276
|
+
# requests to their container.
|
277
|
+
#
|
278
|
+
# This module can be included explicitly in the class of the service
|
279
|
+
# objects. For most purposes there is no reason to do so. However, if an
|
280
|
+
# object is dumped and re-loaded with YAML, it will lose track of modules
|
281
|
+
# that it has been extended with. By including the module in the class, the
|
282
|
+
# loaded object will have the right ancestors. (Marshal does not have this
|
283
|
+
# limitation.)
|
284
|
+
#
|
285
|
+
# An object can be injected with at most one Injectable object at a time.
|
286
|
+
#
|
287
|
+
# The implementation of Injected is essentially <tt>extend</tt>-ing with a
|
288
|
+
# module that has a <tt>method_missing</tt>.
|
289
|
+
module Injected
|
290
|
+
# Delegates to the Injectable object any method which it must handle. If
|
291
|
+
# the Injectable object does not handle the method, or if there is no
|
292
|
+
# Injectable object assigned to self, then self's own _method_missing_ is
|
293
|
+
# called.
|
294
|
+
def method_missing(*args, &block)
|
295
|
+
@__injectable__object__ || super
|
296
|
+
@__injectable__object__.send(*args, &block)
|
297
|
+
rescue NoInjectedMethodError
|
298
|
+
super
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
# Inject the container's services into _obj_. The service methods can be
|
303
|
+
# called from within the object's methods, and they will return the same
|
304
|
+
# objects as if they were called from the container.
|
305
|
+
#
|
306
|
+
# Returns _obj_, so that the method can be called within service
|
307
|
+
# definitions.
|
308
|
+
#
|
309
|
+
def inject_into obj
|
310
|
+
begin
|
311
|
+
obj.extend Injected
|
312
|
+
rescue TypeError
|
313
|
+
warn "#{caller[2]}: warning: class #{obj.class} cannot be injected into"
|
314
|
+
return obj
|
315
|
+
end
|
316
|
+
|
317
|
+
cont = obj.instance_variable_get(:@__injectable__object__)
|
318
|
+
if cont and cont != self
|
319
|
+
raise NonUniqueContainerError,
|
320
|
+
"Object #{obj.inspect} already belongs to #{cont.inspect}." +
|
321
|
+
" Was attempting to inject #{self.inspect} into it."
|
322
|
+
end
|
323
|
+
|
324
|
+
obj.instance_variable_set(:@__injectable__object__, self)
|
325
|
+
obj
|
326
|
+
end
|
327
|
+
|
328
|
+
def method_missing(m, *rest) # :nodoc:
|
329
|
+
raise NoInjectedMethodError
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
# For convenience, so that you can use 'include MinDI::BasicContainer' rather
|
334
|
+
# than 'extend MinDI::Container'.
|
335
|
+
module BasicContainer
|
336
|
+
def self.included mod # :nodoc:
|
337
|
+
mod.extend Container
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
# An InjectableContainer "injects" itself into the services, so that they
|
342
|
+
# can all refer to each other.
|
343
|
+
#
|
344
|
+
# Including this module has the combined effect of making the class act as a
|
345
|
+
# container (by *extend*-ing it with Container) and making the class
|
346
|
+
# Injectable (by *include*-ing Injectable). Additionally, including this
|
347
|
+
# module defines two class methods #injected and #uninjected, which determine
|
348
|
+
# whether subsequently defined services have the container injected into them.
|
349
|
+
# (The default is #injected.)
|
350
|
+
#
|
351
|
+
# Also, the module defines an #inspect method, so that inspecting injected
|
352
|
+
# objects doesn't dump the entire container and all of its services.
|
353
|
+
#
|
354
|
+
module InjectableContainer
|
355
|
+
include Injectable
|
356
|
+
|
357
|
+
# These methods are available in the scope of your class definition, after
|
358
|
+
# you have included MinDI::InjectableContainer.
|
359
|
+
module ClassMethods
|
360
|
+
# Set state so subsequently defined services have the container injected
|
361
|
+
# into them when they are instantiated. (This is the default state.)
|
362
|
+
def injected
|
363
|
+
@__services_are_injected__ = true
|
364
|
+
end
|
365
|
+
|
366
|
+
# Set state so subsequently defined services do not have the container
|
367
|
+
# injected into them when they are instantiated.
|
368
|
+
def uninjected
|
369
|
+
@__services_are_injected__ = false
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
def self.included mod # :nodoc:
|
374
|
+
mod.extend Container
|
375
|
+
mod.extend InjectableContainer::ClassMethods
|
376
|
+
mod.injected
|
377
|
+
end
|
378
|
+
|
379
|
+
def inspect # :nodoc:
|
380
|
+
"<#{self.class}:0x%0x>" % object_id
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|