mindi 0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|