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.
@@ -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,12 @@
1
+ require 'mindi'
2
+
3
+ class C
4
+ include MinDI::InjectableContainer
5
+
6
+ # uninjected
7
+ foo { |x,y| (x + y).to_s }
8
+ end
9
+
10
+ cont = C.new
11
+
12
+ p cont.foo(1,2).foo(2,3)
@@ -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