blockenspiel 0.3.3-java → 0.4.0-java
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/Blockenspiel.rdoc +12 -4
- data/History.rdoc +8 -0
- data/README.rdoc +10 -6
- data/Rakefile +88 -92
- data/lib/blockenspiel/builder.rb +201 -0
- data/lib/blockenspiel/dsl_setup.rb +361 -0
- data/lib/blockenspiel/errors.rb +61 -0
- data/lib/blockenspiel/impl.rb +290 -604
- data/lib/blockenspiel/version.rb +2 -2
- data/lib/blockenspiel/versionomy.rb +1 -1
- data/lib/blockenspiel.rb +22 -4
- data/lib/blockenspiel_unmixer.jar +0 -0
- data/tests/files/file1.rb +2 -0
- data/tests/tc_basic.rb +30 -0
- data/tests/tc_mixins.rb +139 -2
- metadata +60 -51
data/lib/blockenspiel/impl.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Blockenspiel implementation
|
|
4
4
|
#
|
|
5
5
|
# -----------------------------------------------------------------------------
|
|
6
|
-
# Copyright 2008-
|
|
6
|
+
# Copyright 2008-2010 Daniel Azuma
|
|
7
7
|
#
|
|
8
8
|
# All rights reserved.
|
|
9
9
|
#
|
|
@@ -37,542 +37,13 @@
|
|
|
37
37
|
require 'thread'
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
# == Blockenspiel
|
|
41
|
-
#
|
|
42
|
-
# The Blockenspiel module provides a namespace for Blockenspiel, as well as
|
|
43
|
-
# the main entry point method "invoke".
|
|
44
|
-
|
|
45
40
|
module Blockenspiel
|
|
46
41
|
|
|
47
42
|
|
|
48
|
-
#
|
|
49
|
-
|
|
50
|
-
class BlockenspielError < ::RuntimeError
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
# This exception is rasied when attempting to use the <tt>:proxy</tt> or
|
|
55
|
-
# <tt>:mixin</tt> parameterless behavior with a target that does not have
|
|
56
|
-
# the DSL module included. It is an error made by the DSL implementor.
|
|
57
|
-
|
|
58
|
-
class DSLMissingError < ::Blockenspiel::BlockenspielError
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
# This exception is raised when the block provided does not take the
|
|
63
|
-
# expected number of parameters. It is an error made by the caller.
|
|
64
|
-
|
|
65
|
-
class BlockParameterError < ::Blockenspiel::BlockenspielError
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# === DSL setup methods
|
|
70
|
-
#
|
|
71
|
-
# These class methods are available after you have included the
|
|
72
|
-
# Blockenspiel::DSL module.
|
|
73
|
-
#
|
|
74
|
-
# By default, a class that has DSL capability will automatically make
|
|
75
|
-
# all public methods available to parameterless blocks, except for the
|
|
76
|
-
# +initialize+ method, any methods whose names begin with an underscore,
|
|
77
|
-
# and any methods whose names end with an equals sign.
|
|
78
|
-
#
|
|
79
|
-
# If you want to change this behavior, use the directives defined here to
|
|
80
|
-
# control exactly which methods are available to parameterless blocks.
|
|
81
|
-
|
|
82
|
-
module DSLSetupMethods
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
# :stopdoc:
|
|
86
|
-
|
|
87
|
-
# Called when DSLSetupMethods extends a class.
|
|
88
|
-
# This sets up the current class, and adds a hook that causes
|
|
89
|
-
# any subclass of the current class also to be set up.
|
|
90
|
-
|
|
91
|
-
def self.extended(klass_)
|
|
92
|
-
unless klass_.instance_variable_defined?(:@_blockenspiel_module)
|
|
93
|
-
_setup_class(klass_)
|
|
94
|
-
def klass_.inherited(subklass_)
|
|
95
|
-
::Blockenspiel::DSLSetupMethods._setup_class(subklass_)
|
|
96
|
-
super
|
|
97
|
-
end
|
|
98
|
-
class << klass_
|
|
99
|
-
unless private_method_defined?(:_blockenspiel_default_include)
|
|
100
|
-
alias_method :_blockenspiel_default_include, :include
|
|
101
|
-
alias_method :include, :_blockenspiel_custom_include
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# :startdoc:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
# Set up a class.
|
|
111
|
-
# Creates a DSL module for this class, optionally delegating to the superclass's module.
|
|
112
|
-
# Also initializes the class's methods hash and active flag.
|
|
113
|
-
|
|
114
|
-
def self._setup_class(klass_) # :nodoc:
|
|
115
|
-
superclass_ = klass_.superclass
|
|
116
|
-
superclass_ = nil unless superclass_.respond_to?(:_get_blockenspiel_module)
|
|
117
|
-
mod_ = ::Module.new
|
|
118
|
-
if superclass_
|
|
119
|
-
mod_.module_eval do
|
|
120
|
-
include superclass_._get_blockenspiel_module
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
klass_.instance_variable_set(:@_blockenspiel_superclass, superclass_)
|
|
124
|
-
klass_.instance_variable_set(:@_blockenspiel_module, mod_)
|
|
125
|
-
klass_.instance_variable_set(:@_blockenspiel_methods, ::Hash.new)
|
|
126
|
-
klass_.instance_variable_set(:@_blockenspiel_active, nil)
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
# Automatically make the given method a DSL method according to the current setting.
|
|
131
|
-
|
|
132
|
-
def _blockenspiel_auto_dsl_method(symbol_) # :nodoc:
|
|
133
|
-
if @_blockenspiel_active
|
|
134
|
-
dsl_method(symbol_)
|
|
135
|
-
elsif @_blockenspiel_active.nil?
|
|
136
|
-
if symbol_ != :initialize && symbol_.to_s !~ /^_/ && symbol_.to_s !~ /=$/
|
|
137
|
-
dsl_method(symbol_)
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
# Hook called when a method is added.
|
|
144
|
-
# This calls _blockenspiel_auto_dsl_method to auto-handle the method,
|
|
145
|
-
# possibly making it a DSL method according to the current setting.
|
|
146
|
-
|
|
147
|
-
def method_added(symbol_) # :nodoc:
|
|
148
|
-
_blockenspiel_auto_dsl_method(symbol_)
|
|
149
|
-
super
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
# Custom include method. Calls the main include implementation, but also
|
|
154
|
-
# goes through the public methods of the included module and calls
|
|
155
|
-
# _blockenspiel_auto_dsl_method on each to make them DSL methods
|
|
156
|
-
# (possibly) according to the current setting.
|
|
157
|
-
|
|
158
|
-
def _blockenspiel_custom_include(*modules_) # :nodoc:
|
|
159
|
-
_blockenspiel_default_include(*modules_)
|
|
160
|
-
modules_.reverse_each do |mod_|
|
|
161
|
-
mod_.public_instance_methods.each do |method_|
|
|
162
|
-
_blockenspiel_auto_dsl_method(method_)
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
# Get this class's corresponding DSL module
|
|
169
|
-
|
|
170
|
-
def _get_blockenspiel_module # :nodoc:
|
|
171
|
-
@_blockenspiel_module
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
# Get information on the given DSL method name.
|
|
176
|
-
# Possible values are the name of the delegate method, false for method disabled,
|
|
177
|
-
# or nil for method never defined.
|
|
178
|
-
|
|
179
|
-
def _get_blockenspiel_delegate(name_) # :nodoc:
|
|
180
|
-
delegate_ = @_blockenspiel_methods[name_]
|
|
181
|
-
if delegate_.nil? && @_blockenspiel_superclass
|
|
182
|
-
@_blockenspiel_superclass._get_blockenspiel_delegate(name_)
|
|
183
|
-
else
|
|
184
|
-
delegate_
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
# Make a particular method available to parameterless DSL blocks.
|
|
190
|
-
#
|
|
191
|
-
# To explicitly make a method available to parameterless blocks:
|
|
192
|
-
# dsl_method :my_method
|
|
193
|
-
#
|
|
194
|
-
# To explicitly exclude a method from parameterless blocks:
|
|
195
|
-
# dsl_method :my_method, false
|
|
196
|
-
#
|
|
197
|
-
# To explicitly make a method available to parameterless blocks, but
|
|
198
|
-
# point it to a method of a different name on the target class:
|
|
199
|
-
# dsl_method :my_method, :target_class_method
|
|
200
|
-
|
|
201
|
-
def dsl_method(name_, delegate_=nil)
|
|
202
|
-
name_ = name_.to_sym
|
|
203
|
-
if delegate_
|
|
204
|
-
delegate_ = delegate_.to_sym
|
|
205
|
-
elsif delegate_.nil?
|
|
206
|
-
delegate_ = name_
|
|
207
|
-
end
|
|
208
|
-
@_blockenspiel_methods[name_] = delegate_
|
|
209
|
-
unless @_blockenspiel_module.public_method_defined?(name_)
|
|
210
|
-
@_blockenspiel_module.module_eval("def #{name_}(*params_, &block_); val_ = ::Blockenspiel._target_dispatch(self, :#{name_}, params_, block_); ::Blockenspiel::NO_VALUE.equal?(val_) ? super(*params_, &block_) : val_; end\n")
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
# Control the behavior of methods with respect to parameterless blocks,
|
|
216
|
-
# or make a list of methods available to parameterless blocks in bulk.
|
|
217
|
-
#
|
|
218
|
-
# To enable automatic exporting of methods to parameterless blocks.
|
|
219
|
-
# After executing this command, all public methods defined in the class
|
|
220
|
-
# will be available on parameterless blocks, until
|
|
221
|
-
# <tt>dsl_methods false</tt> is called:
|
|
222
|
-
# dsl_methods true
|
|
223
|
-
#
|
|
224
|
-
# To disable automatic exporting of methods to parameterless blocks.
|
|
225
|
-
# After executing this command, methods defined in this class will be
|
|
226
|
-
# excluded from parameterless blocks, until <tt>dsl_methods true</tt>
|
|
227
|
-
# is called:
|
|
228
|
-
# dsl_methods false
|
|
229
|
-
#
|
|
230
|
-
# To make a list of methods available to parameterless blocks in bulk:
|
|
231
|
-
# dsl_methods :my_method1, :my_method2, ...
|
|
232
|
-
#
|
|
233
|
-
# You can also point dsl methods to a method of a different name on the
|
|
234
|
-
# target class, by using a hash syntax, as follows:
|
|
235
|
-
# dsl_methods :my_method1 => :target_class_method1,
|
|
236
|
-
# :my_method2 => :target_class_method2
|
|
237
|
-
#
|
|
238
|
-
# You can mix non-renamed and renamed method declarations as long as
|
|
239
|
-
# the renamed (hash) methods are at the end. e.g.:
|
|
240
|
-
# dsl_methods :my_method1, :my_method2 => :target_class_method2
|
|
241
|
-
|
|
242
|
-
def dsl_methods(*names_)
|
|
243
|
-
if names_.size == 0 || names_ == [true]
|
|
244
|
-
@_blockenspiel_active = true
|
|
245
|
-
elsif names_ == [false]
|
|
246
|
-
@_blockenspiel_active = false
|
|
247
|
-
else
|
|
248
|
-
if names_.last.kind_of?(::Hash)
|
|
249
|
-
names_.pop.each do |name_, delegate_|
|
|
250
|
-
dsl_method(name_, delegate_)
|
|
251
|
-
end
|
|
252
|
-
end
|
|
253
|
-
names_.each do |name_|
|
|
254
|
-
dsl_method(name_, name_)
|
|
255
|
-
end
|
|
256
|
-
end
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
# A DSL-friendly attr_accessor.
|
|
261
|
-
#
|
|
262
|
-
# This creates the usual "name" and "name=" methods in the current
|
|
263
|
-
# class that can be used in the usual way. However, its implementation
|
|
264
|
-
# of the "name" method (the getter) also takes an optional parameter
|
|
265
|
-
# that causes it to behave as a setter. This is done because the usual
|
|
266
|
-
# setter syntax cannot be used in a parameterless block, since it is
|
|
267
|
-
# syntactically indistinguishable from a local variable assignment.
|
|
268
|
-
# The "name" method is exposed as a dsl_method.
|
|
269
|
-
#
|
|
270
|
-
# For example:
|
|
271
|
-
#
|
|
272
|
-
# dsl_attr_accessor :foo
|
|
273
|
-
#
|
|
274
|
-
# enables the following:
|
|
275
|
-
#
|
|
276
|
-
# my_block do |param|
|
|
277
|
-
# param.foo = 1 # Usual setter syntax works
|
|
278
|
-
# param.foo 2 # Alternate setter syntax also works
|
|
279
|
-
# puts param.foo # Usual getter syntax still works
|
|
280
|
-
# end
|
|
281
|
-
#
|
|
282
|
-
# my_block do
|
|
283
|
-
# # foo = 1 # Usual setter syntax does NOT work since it
|
|
284
|
-
# # looks like a local variable assignment
|
|
285
|
-
# foo 2 # Alternate setter syntax does work
|
|
286
|
-
# puts foo # Usual getter syntax still works
|
|
287
|
-
# end
|
|
288
|
-
|
|
289
|
-
def dsl_attr_accessor(*names_)
|
|
290
|
-
names_.each do |name_|
|
|
291
|
-
unless name_.kind_of?(::String) || name_.kind_of?(::Symbol)
|
|
292
|
-
raise ::TypeError, "#{name_.inspect} is not a symbol"
|
|
293
|
-
end
|
|
294
|
-
unless name_.to_s =~ /^[_a-zA-Z]\w+$/
|
|
295
|
-
raise ::NameError, "invalid attribute name #{name_.inspect}"
|
|
296
|
-
end
|
|
297
|
-
module_eval("def #{name_}(value_=::Blockenspiel::NO_VALUE); ::Blockenspiel::NO_VALUE.equal?(value_) ? @#{name_} : @#{name_} = value_; end\n")
|
|
298
|
-
alias_method("#{name_}=", name_)
|
|
299
|
-
dsl_method(name_)
|
|
300
|
-
end
|
|
301
|
-
end
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
# A DSL-friendly attr_writer.
|
|
305
|
-
#
|
|
306
|
-
# This creates the usual "name=" method in the current class that can
|
|
307
|
-
# be used in the usual way. However, it also creates the method "name",
|
|
308
|
-
# which also functions as a setter (but not a getter). This is done
|
|
309
|
-
# because the usual setter syntax cannot be used in a parameterless
|
|
310
|
-
# block, since it is syntactically indistinguishable from a local
|
|
311
|
-
# variable assignment. The "name" method is exposed as a dsl_method.
|
|
312
|
-
#
|
|
313
|
-
# For example:
|
|
314
|
-
#
|
|
315
|
-
# dsl_attr_writer :foo
|
|
316
|
-
#
|
|
317
|
-
# is functionally equivalent to:
|
|
318
|
-
#
|
|
319
|
-
# attr_writer :foo
|
|
320
|
-
# alias_method :foo, :foo=
|
|
321
|
-
# dsl_method :foo
|
|
322
|
-
#
|
|
323
|
-
# which enables the following:
|
|
324
|
-
#
|
|
325
|
-
# my_block do |param|
|
|
326
|
-
# param.foo = 1 # Usual setter syntax works
|
|
327
|
-
# param.foo 2 # Alternate setter syntax also works
|
|
328
|
-
# end
|
|
329
|
-
# my_block do
|
|
330
|
-
# # foo = 1 # Usual setter syntax does NOT work since it
|
|
331
|
-
# # looks like a local variable assignment
|
|
332
|
-
# foo(2) # Alternate setter syntax does work
|
|
333
|
-
# end
|
|
334
|
-
|
|
335
|
-
def dsl_attr_writer(*names_)
|
|
336
|
-
names_.each do |name_|
|
|
337
|
-
attr_writer(name_)
|
|
338
|
-
alias_method(name_, "#{name_}=")
|
|
339
|
-
dsl_method(name_)
|
|
340
|
-
end
|
|
341
|
-
end
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
end
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
# === DSL activation module
|
|
348
|
-
#
|
|
349
|
-
# Include this module in a class to mark this class as a DSL class and
|
|
350
|
-
# make it possible for its methods to be called from a block that does not
|
|
351
|
-
# take a parameter.
|
|
352
|
-
#
|
|
353
|
-
# After you include this module, you can use the directives defined in
|
|
354
|
-
# DSLSetupMethods to control what methods are available to DSL blocks
|
|
355
|
-
# that do not take parameters.
|
|
356
|
-
|
|
357
|
-
module DSL
|
|
358
|
-
|
|
359
|
-
def self.included(klass_) # :nodoc:
|
|
360
|
-
unless klass_.kind_of?(::Class)
|
|
361
|
-
raise ::Blockenspiel::BlockenspielError, "You cannot include Blockenspiel::DSL in a module (yet)"
|
|
362
|
-
end
|
|
363
|
-
klass_.extend(::Blockenspiel::DSLSetupMethods)
|
|
364
|
-
end
|
|
365
|
-
|
|
366
|
-
end
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
# === DSL activation base class
|
|
370
|
-
#
|
|
371
|
-
# Subclasses of this base class are considered DSL classes.
|
|
372
|
-
# Methods of the class can be made available to be called from a block that
|
|
373
|
-
# doesn't take an explicit block parameter.
|
|
374
|
-
# You may use the directives defined in DSLSetupMethods to control how
|
|
375
|
-
# methods of the class are handled in such blocks.
|
|
376
|
-
#
|
|
377
|
-
# Subclassing this base class is functionally equivalent to simply
|
|
378
|
-
# including Blockenspiel::DSL in the class.
|
|
379
|
-
|
|
380
|
-
class Base
|
|
381
|
-
|
|
382
|
-
include ::Blockenspiel::DSL
|
|
383
|
-
|
|
384
|
-
end
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
# Class for proxy delegators.
|
|
388
|
-
# The proxy behavior creates one of these delegators, mixes in the dsl
|
|
389
|
-
# methods, and uses instance_eval to invoke the block. This class delegates
|
|
390
|
-
# non-handled methods to the context object.
|
|
391
|
-
|
|
392
|
-
class ProxyDelegator # :nodoc:
|
|
393
|
-
|
|
394
|
-
def method_missing(symbol_, *params_, &block_)
|
|
395
|
-
::Blockenspiel._proxy_dispatch(self, symbol_, params_, block_)
|
|
396
|
-
end
|
|
397
|
-
|
|
398
|
-
end
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
# === Dynamically construct a target
|
|
402
|
-
#
|
|
403
|
-
# These methods are available in a block passed to Blockenspiel#invoke and
|
|
404
|
-
# can be used to dynamically define what methods are available from a block.
|
|
405
|
-
# See Blockenspiel#invoke for more information.
|
|
406
|
-
|
|
407
|
-
class Builder
|
|
408
|
-
|
|
409
|
-
include ::Blockenspiel::DSL
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
# This is a base class for dynamically constructed targets.
|
|
413
|
-
# The actual target class is an anonymous subclass of this base class.
|
|
414
|
-
|
|
415
|
-
class Target # :nodoc:
|
|
416
|
-
|
|
417
|
-
include ::Blockenspiel::DSL
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
# Add a method specification to the subclass.
|
|
421
|
-
|
|
422
|
-
def self._add_methodinfo(name_, block_, yields_)
|
|
423
|
-
(@_blockenspiel_methodinfo ||= ::Hash.new)[name_] = [block_, yields_]
|
|
424
|
-
module_eval("def #{name_}(*params_, &block_); self.class._invoke_methodinfo(:#{name_}, params_, block_); end\n")
|
|
425
|
-
end
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
# Attempt to invoke the given method on the subclass.
|
|
429
|
-
|
|
430
|
-
def self._invoke_methodinfo(name_, params_, block_)
|
|
431
|
-
info_ = @_blockenspiel_methodinfo[name_]
|
|
432
|
-
case info_[1]
|
|
433
|
-
when :first
|
|
434
|
-
params_.unshift(block_)
|
|
435
|
-
when :last
|
|
436
|
-
params_.push(block_)
|
|
437
|
-
end
|
|
438
|
-
info_[0].call(*params_, &block_)
|
|
439
|
-
end
|
|
440
|
-
|
|
441
|
-
end
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
# Sets up the dynamic target class.
|
|
445
|
-
|
|
446
|
-
def initialize # :nodoc:
|
|
447
|
-
@target_class = ::Class.new(::Blockenspiel::Builder::Target)
|
|
448
|
-
@target_class.dsl_methods(false)
|
|
449
|
-
end
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
# Creates a new instance of the dynamic target class
|
|
453
|
-
|
|
454
|
-
def _create_target # :nodoc:
|
|
455
|
-
@target_class.new
|
|
456
|
-
end
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
# === Declare a DSL method.
|
|
460
|
-
#
|
|
461
|
-
# This call creates a method that can be called from the DSL block.
|
|
462
|
-
# Provide a name for the method, a block defining the method's
|
|
463
|
-
# implementation, and an optional hash of options.
|
|
464
|
-
#
|
|
465
|
-
# By default, a method of the same name is also made available to
|
|
466
|
-
# parameterless blocks. To change the name of the parameterless method,
|
|
467
|
-
# provide its name as the value of the <tt>:dsl_method</tt> option.
|
|
468
|
-
# To disable this method for parameterless blocks, set the
|
|
469
|
-
# <tt>:dsl_method</tt> option to +false+.
|
|
470
|
-
#
|
|
471
|
-
# The <tt>:mixin</tt> option is a deprecated alias for
|
|
472
|
-
# <tt>:dsl_method</tt>.
|
|
473
|
-
#
|
|
474
|
-
# === Warning about the +return+ keyword
|
|
475
|
-
#
|
|
476
|
-
# Because you are implementing your method using a block, remember the
|
|
477
|
-
# distinction between <tt>Proc.new</tt> and +lambda+. Invoking +return+
|
|
478
|
-
# from the former does not return from the block, but returns from the
|
|
479
|
-
# surrounding method scope. Since normal blocks passed to methods are
|
|
480
|
-
# of the former type, be very careful about using the +return+ keyword:
|
|
481
|
-
#
|
|
482
|
-
# add_method(:foo) do |param|
|
|
483
|
-
# puts "foo called with parameter "+param.inspect
|
|
484
|
-
# return "a return value" # DOESN'T WORK LIKE YOU EXPECT!
|
|
485
|
-
# end
|
|
486
|
-
#
|
|
487
|
-
# To return a value from the method you are creating, set the evaluation
|
|
488
|
-
# value at the end of the block:
|
|
489
|
-
#
|
|
490
|
-
# add_method(:foo) do |param|
|
|
491
|
-
# puts "foo called with parameter "+param.inspect
|
|
492
|
-
# "a return value" # Returns from method foo
|
|
493
|
-
# end
|
|
494
|
-
#
|
|
495
|
-
# If you must use the +return+ keyword, create your block as a lambda
|
|
496
|
-
# as in this example:
|
|
497
|
-
#
|
|
498
|
-
# code = lambda do |param|
|
|
499
|
-
# puts "foo called with parameter "+param.inspect
|
|
500
|
-
# return "a return value" # Returns from method foo
|
|
501
|
-
# end
|
|
502
|
-
# add_method(:foo, &code)
|
|
503
|
-
#
|
|
504
|
-
# === Accepting a block argument
|
|
505
|
-
#
|
|
506
|
-
# If you want your method to take a block, you have several options
|
|
507
|
-
# depending on your Ruby version. If you are running the standard Matz
|
|
508
|
-
# Ruby interpreter (MRI) version 1.8.7 or later (including 1.9.x), or a
|
|
509
|
-
# compatible interpreter such as JRuby 1.5 or later, you can use the
|
|
510
|
-
# standard "&" block argument notation to receive the block.
|
|
511
|
-
# Note that you must call the passed block using the +call+ method since
|
|
512
|
-
# Ruby doesn't support invoking such a block with +yield+.
|
|
513
|
-
# For example, to create a method named "foo" that takes one parameter
|
|
514
|
-
# and a block, do this:
|
|
515
|
-
#
|
|
516
|
-
# add_method(:foo) do |param, &block|
|
|
517
|
-
# puts "foo called with parameter "+param.inspect
|
|
518
|
-
# puts "the block returned "+block.call.inspect
|
|
519
|
-
# end
|
|
520
|
-
#
|
|
521
|
-
# In your DSL, you can then call:
|
|
522
|
-
#
|
|
523
|
-
# foo("hello"){ "a value" }
|
|
524
|
-
#
|
|
525
|
-
# If you are using MRI 1.8.6, or another Ruby interpreter that doesn't
|
|
526
|
-
# fully support this syntax (such as JRuby versions older than 1.5),
|
|
527
|
-
# Blockenspiel provides an alternative in the form of the <tt>:block</tt>
|
|
528
|
-
# option. This option causes blocks provided by the caller to be included
|
|
529
|
-
# in the normal parameter list to your method, instead of as a block
|
|
530
|
-
# parameter. It can be set to <tt>:first</tt> or <tt>:last</tt> to
|
|
531
|
-
# prepend or append, respectively, the block (as a +Proc+ object) to
|
|
532
|
-
# the parameter list. If the caller does not include a block when
|
|
533
|
-
# calling your DSL method, nil is prepended/appended. For example:
|
|
534
|
-
#
|
|
535
|
-
# add_method(:foo, :block => :last) do |param, block|
|
|
536
|
-
# puts "foo called with parameter "+param.inspect
|
|
537
|
-
# if block
|
|
538
|
-
# puts "the block returned "+block.call.inspect
|
|
539
|
-
# else
|
|
540
|
-
# puts "no block passed"
|
|
541
|
-
# end
|
|
542
|
-
# end
|
|
543
|
-
#
|
|
544
|
-
# The <tt>:receive_block</tt> option is a deprecated alternative.
|
|
545
|
-
# Setting <tt>:receive_block => true</tt> is currently equivalent to
|
|
546
|
-
# setting <tt>:block => :last</tt>.
|
|
547
|
-
|
|
548
|
-
def add_method(name_, opts_={}, &block_)
|
|
549
|
-
receive_block_ = opts_[:receive_block] ? :last : opts_[:block]
|
|
550
|
-
receive_block_ = :first if receive_block_ && receive_block_ != :last
|
|
551
|
-
@target_class._add_methodinfo(name_, block_, receive_block_)
|
|
552
|
-
dsl_method_name_ = opts_[:dsl_method] || opts_[:mixin]
|
|
553
|
-
if dsl_method_name_ != false
|
|
554
|
-
dsl_method_name_ = name_ if dsl_method_name_.nil? || dsl_method_name_ == true
|
|
555
|
-
@target_class.dsl_method(dsl_method_name_, name_)
|
|
556
|
-
end
|
|
557
|
-
end
|
|
558
|
-
|
|
559
|
-
end
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
# :stopdoc:
|
|
563
|
-
NO_VALUE = ::Object.new
|
|
564
|
-
# :startdoc:
|
|
565
|
-
|
|
566
|
-
@_target_stacks = ::Hash.new
|
|
567
|
-
@_mixin_counts = ::Hash.new
|
|
568
|
-
@_proxy_delegators = ::Hash.new
|
|
569
|
-
@_mutex = ::Mutex.new
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
# === Invoke a given block
|
|
43
|
+
# === Invoke a given DSL
|
|
573
44
|
#
|
|
574
|
-
# This is the
|
|
575
|
-
# provided by the user of your API.
|
|
45
|
+
# This is the entry point for Blockenspiel. Call this function to invoke
|
|
46
|
+
# a set of DSL code provided by the user of your API.
|
|
576
47
|
#
|
|
577
48
|
# For example, if you want users of your API to be able to do this:
|
|
578
49
|
#
|
|
@@ -592,21 +63,71 @@ module Blockenspiel
|
|
|
592
63
|
# In the above, <tt>create_block_implementation</tt> is a placeholder that
|
|
593
64
|
# returns an instance of your DSL methods class. This class includes the
|
|
594
65
|
# Blockenspiel::DSL module and defines the DSL methods +foo+ and +bar+.
|
|
66
|
+
# See Blockenspiel::DSLSetupMethods for a set of tools you can use in your
|
|
67
|
+
# DSL methods class for creating a DSL.
|
|
68
|
+
#
|
|
69
|
+
# === Usage patterns
|
|
70
|
+
#
|
|
71
|
+
# The invoke method has a number of forms, depending on whether the API
|
|
72
|
+
# user's DSL code is provided as a block or a string, and depending on
|
|
73
|
+
# whether the DSL methods are specified statically using a DSL class or
|
|
74
|
+
# dynamically using a block.
|
|
75
|
+
#
|
|
76
|
+
# [<tt>Blockenspiel.invoke(<i>user_block</i>, <i>my_dsl</i>, <i>opts</i>)</tt>]
|
|
77
|
+
# This form takes the user's code as a block, and the DSL itself as an
|
|
78
|
+
# object with DSL methods. The opts hash is optional and provides a
|
|
79
|
+
# set of arguments as described below under "Block DSL options".
|
|
80
|
+
#
|
|
81
|
+
# [<tt>Blockenspiel.invoke(<i>user_block</i>, <i>opts</i>) { ... }</tt>]
|
|
82
|
+
# This form takes the user's code as a block, while the DSL itself is
|
|
83
|
+
# specified in the given block, as described below under "Dynamic
|
|
84
|
+
# target generation". The opts hash is optional and provides a set of
|
|
85
|
+
# arguments as described below under "Block DSL options".
|
|
86
|
+
#
|
|
87
|
+
# [<tt>Blockenspiel.invoke(<i>user_string</i>, <i>my_dsl</i>, <i>opts</i>)</tt>]
|
|
88
|
+
# This form takes the user's code as a string, and the DSL itself as an
|
|
89
|
+
# object with DSL methods. The opts hash is optional and provides a
|
|
90
|
+
# set of arguments as described below under "String DSL options".
|
|
91
|
+
#
|
|
92
|
+
# [<tt>Blockenspiel.invoke(<i>user_string</i>, <i>opts</i>) { ... }</tt>]
|
|
93
|
+
# This form takes the user's code as a block, while the DSL itself is
|
|
94
|
+
# specified in the given block, as described below under "Dynamic
|
|
95
|
+
# target generation". The opts hash is optional and provides a set of
|
|
96
|
+
# arguments as described below under "String DSL options".
|
|
97
|
+
#
|
|
98
|
+
# [<tt>Blockenspiel.invoke(<i>my_dsl</i>, <i>opts</i>)</tt>]
|
|
99
|
+
# This form reads the user's code from a file, and takes the DSL itself
|
|
100
|
+
# as an object with DSL methods. The opts hash is required and provides
|
|
101
|
+
# a set of arguments as described below under "String DSL options". The
|
|
102
|
+
# <tt>:file</tt> option is required.
|
|
103
|
+
#
|
|
104
|
+
# [<tt>Blockenspiel.invoke(<i>opts</i>) { ... }</tt>]
|
|
105
|
+
# This form reads the user's code from a file, while the DSL itself is
|
|
106
|
+
# specified in the given block, as described below under "Dynamic
|
|
107
|
+
# target generation". The opts hash is required and provides a set of
|
|
108
|
+
# arguments as described below under "String DSL options". The
|
|
109
|
+
# <tt>:file</tt> option is required.
|
|
595
110
|
#
|
|
596
|
-
#
|
|
597
|
-
# takes a parameter. If so, it will pass the given target to the block.
|
|
598
|
-
# If the block takes no parameter, and the given target is an instance of
|
|
599
|
-
# a class with DSL capability, the DSL methods are made available on the
|
|
600
|
-
# caller's self object so they may be called without a block parameter.
|
|
111
|
+
# === Block DSL options
|
|
601
112
|
#
|
|
602
|
-
#
|
|
113
|
+
# When a user provides DSL code using a block, you simply pass that block
|
|
114
|
+
# as the first parameter to Blockenspiel.invoke. Normally, Blockenspiel
|
|
115
|
+
# will first check the block's arity to see whether it takes a parameter.
|
|
116
|
+
# If so, it will pass the given target to the block. If the block takes
|
|
117
|
+
# no parameter, and the given target is an instance of a class with DSL
|
|
118
|
+
# capability, the DSL methods are made available on the caller's self
|
|
119
|
+
# object so they may be called without a block parameter.
|
|
120
|
+
#
|
|
121
|
+
# Following are the options understood by Blockenspiel when providing
|
|
122
|
+
# code using a block:
|
|
603
123
|
#
|
|
604
124
|
# <tt>:parameterless</tt>::
|
|
605
125
|
# If set to false, disables parameterless blocks and always attempts to
|
|
606
126
|
# pass a parameter to the block. Otherwise, you may set it to one of
|
|
607
127
|
# three behaviors for parameterless blocks: <tt>:mixin</tt> (the
|
|
608
128
|
# default), <tt>:instance</tt>, and <tt>:proxy</tt>. See below for
|
|
609
|
-
# detailed descriptions of these behaviors.
|
|
129
|
+
# detailed descriptions of these behaviors. This option key is also
|
|
130
|
+
# available as <tt>:behavior</tt>.
|
|
610
131
|
# <tt>:parameter</tt>::
|
|
611
132
|
# If set to false, disables blocks with parameters, and always attempts
|
|
612
133
|
# to use parameterless blocks. Default is true, enabling parameter mode.
|
|
@@ -639,6 +160,51 @@ module Blockenspiel
|
|
|
639
160
|
# clobbered) in the block, and the transformations specified by
|
|
640
161
|
# <tt>dsl_method</tt> directives are honored.
|
|
641
162
|
#
|
|
163
|
+
# === String DSL options
|
|
164
|
+
#
|
|
165
|
+
# When a user provides DSL code using a string (either directly or via a
|
|
166
|
+
# file), Blockenspiel always treats it as a "parameterless" invocation,
|
|
167
|
+
# since there is no way to "pass a parameter" to a string. Thus, the two
|
|
168
|
+
# options recognized for block DSLs, <tt>:parameterless</tt>, and
|
|
169
|
+
# <tt>:parameter</tt>, are meaningless and ignored. However, the
|
|
170
|
+
# following new options are recognized:
|
|
171
|
+
#
|
|
172
|
+
# <tt>:file</tt>::
|
|
173
|
+
# The value of this option should be a string indicating the path to
|
|
174
|
+
# the file from which the user's DSL code is coming. It is passed
|
|
175
|
+
# as the "file" parameter to eval; that is, it is included in the stack
|
|
176
|
+
# trace should an exception be thrown out of the DSL. If no code string
|
|
177
|
+
# is provided directly, this option is required and must be set to the
|
|
178
|
+
# path of the file from which to load the code.
|
|
179
|
+
# <tt>:line</tt>::
|
|
180
|
+
# This option is passed as the "line" parameter to eval; that is, it
|
|
181
|
+
# indicates the starting line number for the code string, and is used
|
|
182
|
+
# to compute line numbers for the stack trace should an exception be
|
|
183
|
+
# thrown out of the DSL. This option is optional and defaults to 1.
|
|
184
|
+
# <tt>:behavior</tt>::
|
|
185
|
+
# Controls how the DSL is called. Recognized values are <tt>:proxy</tt>
|
|
186
|
+
# (the default) and <tt>:instance</tt>. See below for detailed
|
|
187
|
+
# descriptions of these behaviors. Note that <tt>:mixin</tt> is not
|
|
188
|
+
# allowed in this case because its behavior would be indistinguishable
|
|
189
|
+
# from the proxy behavior.
|
|
190
|
+
#
|
|
191
|
+
# The following values are recognized for the <tt>:behavior</tt> option:
|
|
192
|
+
#
|
|
193
|
+
# <tt>:proxy</tt>::
|
|
194
|
+
# This behavior changes +self+ to a proxy object created by applying the
|
|
195
|
+
# DSL methods to an empty object. Thus, the code in the DSL string does
|
|
196
|
+
# not have access to the target object's internal instance variables or
|
|
197
|
+
# private methods. Furthermore, the transformations specified by
|
|
198
|
+
# <tt>dsl_method</tt> directives are honored. This is the default
|
|
199
|
+
# behavior.
|
|
200
|
+
# <tt>:instance</tt>::
|
|
201
|
+
# This behavior actually changes +self+ to the target object using
|
|
202
|
+
# <tt>instance_eval</tt>. Thus, the code in the DSL string gains access
|
|
203
|
+
# to the target object's instance variables and private methods. Also,
|
|
204
|
+
# the target object's methods are not modified: this behavior does not
|
|
205
|
+
# apply any DSL method changes specified using <tt>dsl_method</tt>
|
|
206
|
+
# directives.
|
|
207
|
+
#
|
|
642
208
|
# === Dynamic target generation
|
|
643
209
|
#
|
|
644
210
|
# It is also possible to dynamically generate a target object by passing
|
|
@@ -684,32 +250,96 @@ module Blockenspiel
|
|
|
684
250
|
#
|
|
685
251
|
# See the Blockenspiel::Builder class for more details on add_method.
|
|
686
252
|
#
|
|
687
|
-
# When using dynamic target generation, you may pass the options hash as
|
|
688
|
-
# the second argument to invoke instead of the third, since you will not
|
|
689
|
-
# be passing a target object as the second argument.
|
|
690
|
-
#
|
|
691
253
|
# (And yes, you guessed it: this API is a DSL block, and is itself
|
|
692
254
|
# implemented using Blockenspiel.)
|
|
693
255
|
|
|
694
|
-
def self.invoke(
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
256
|
+
def self.invoke(*args_, &builder_block_)
|
|
257
|
+
# This method itself is responsible for parsing the args to invoke,
|
|
258
|
+
# and handling the dynamic target generation. It then passes control
|
|
259
|
+
# to one of the _invoke_with_* methods.
|
|
260
|
+
|
|
261
|
+
# The arguments.
|
|
262
|
+
block_ = nil
|
|
263
|
+
eval_str_ = nil
|
|
264
|
+
target_ = nil
|
|
265
|
+
opts_ = {}
|
|
266
|
+
|
|
267
|
+
# Get the code
|
|
268
|
+
case args_.first
|
|
269
|
+
when ::String
|
|
270
|
+
eval_str_ = args_.shift
|
|
271
|
+
when ::Proc
|
|
272
|
+
block_ = args_.shift
|
|
698
273
|
end
|
|
699
274
|
|
|
700
|
-
#
|
|
275
|
+
# Get the target, performing dynamic target generation if requested
|
|
701
276
|
if builder_block_
|
|
702
|
-
# Support passing the opts hash as the second argument since we
|
|
703
|
-
# aren't passing a target as an argument.
|
|
704
|
-
opts_ = target_ || opts_
|
|
705
277
|
builder_ = ::Blockenspiel::Builder.new
|
|
706
278
|
invoke(builder_block_, builder_)
|
|
707
279
|
target_ = builder_._create_target
|
|
280
|
+
args_.shift if args_.first.nil?
|
|
281
|
+
else
|
|
282
|
+
target_ = args_.shift
|
|
283
|
+
unless target_
|
|
284
|
+
raise ::ArgumentError, "No DSL target provided"
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Get the options hash
|
|
289
|
+
if args_.first.kind_of?(::Hash)
|
|
290
|
+
opts_ = args_.shift
|
|
291
|
+
end
|
|
292
|
+
if args_.size > 0
|
|
293
|
+
raise ::ArgumentError, "Unexpected arguments"
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# Invoke
|
|
297
|
+
if block_
|
|
298
|
+
_invoke_with_block(block_, target_, opts_)
|
|
299
|
+
else
|
|
300
|
+
_invoke_with_string(eval_str_, target_, opts_)
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
# Invoke when the DSL user provides code as a string or file.
|
|
306
|
+
# We open and read the file if need be, and then pass control
|
|
307
|
+
# to the _execute method.
|
|
308
|
+
|
|
309
|
+
def self._invoke_with_string(eval_str_, target_, opts_) # :nodoc:
|
|
310
|
+
# Read options
|
|
311
|
+
file_ = opts_[:file]
|
|
312
|
+
line_ = opts_[:line] || 1
|
|
313
|
+
|
|
314
|
+
# Read file if no string provided directly
|
|
315
|
+
unless eval_str_
|
|
316
|
+
if file_
|
|
317
|
+
eval_str_ = ::File.read(file_)
|
|
318
|
+
else
|
|
319
|
+
raise ::ArgumentError, "No code or file provided."
|
|
320
|
+
end
|
|
321
|
+
else
|
|
322
|
+
file_ ||= "(String passed to Blockenspiel)"
|
|
708
323
|
end
|
|
709
324
|
|
|
325
|
+
# Handle instance-eval behavior
|
|
326
|
+
if opts_[:behavior] == :instance
|
|
327
|
+
return target_.instance_eval(eval_str_, file_, line_)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# Execute the DSL using the proxy method.
|
|
331
|
+
_execute_dsl(true, nil, eval_str_, target_, file_, line_)
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
# Invoke when the DSL user provides code as a block. We read the given
|
|
336
|
+
# options hash, handle a few special cases, and then pass control to the
|
|
337
|
+
# _execute method.
|
|
338
|
+
|
|
339
|
+
def self._invoke_with_block(block_, target_, opts_) # :nodoc:
|
|
710
340
|
# Read options
|
|
711
341
|
parameter_ = opts_[:parameter]
|
|
712
|
-
parameterless_ = opts_[:parameterless]
|
|
342
|
+
parameterless_ = opts_.include?(:behavior) ? opts_[:behavior] : opts_[:parameterless]
|
|
713
343
|
|
|
714
344
|
# Handle no-target behavior
|
|
715
345
|
if parameter_ == false && parameterless_ == false
|
|
@@ -737,17 +367,49 @@ module Blockenspiel
|
|
|
737
367
|
return target_.instance_eval(&block_)
|
|
738
368
|
end
|
|
739
369
|
|
|
370
|
+
# Execute the DSL
|
|
371
|
+
_execute_dsl(parameterless_ == :proxy, block_, nil, target_, nil, nil)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
# Class for proxy delegators.
|
|
376
|
+
# The proxy behavior creates one of these delegators, mixes in the dsl
|
|
377
|
+
# methods, and uses instance_eval to invoke the block. This class delegates
|
|
378
|
+
# non-handled methods to the context object.
|
|
379
|
+
|
|
380
|
+
class ProxyDelegator # :nodoc:
|
|
381
|
+
|
|
382
|
+
def method_missing(symbol_, *params_, &block_)
|
|
383
|
+
::Blockenspiel._proxy_dispatch(self, symbol_, params_, block_)
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
# :stopdoc:
|
|
390
|
+
NO_VALUE = ::Object.new
|
|
391
|
+
# :startdoc:
|
|
392
|
+
|
|
393
|
+
@_target_stacks = {}
|
|
394
|
+
@_mixin_counts = {}
|
|
395
|
+
@_proxy_delegators = {}
|
|
396
|
+
@_mutex = ::Mutex.new
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
# This is the "meat" of Blockenspiel, implementing both the proxy and
|
|
400
|
+
# mixin methods.
|
|
401
|
+
|
|
402
|
+
def self._execute_dsl(use_proxy_method_, block_, eval_str_, target_, file_, line_) # :nodoc:
|
|
740
403
|
# Get the module of dsl methods
|
|
741
404
|
mod_ = target_.class._get_blockenspiel_module rescue nil
|
|
742
405
|
unless mod_
|
|
743
|
-
raise ::Blockenspiel::DSLMissingError
|
|
406
|
+
raise ::Blockenspiel::DSLMissingError, "Given DSL target does not include Blockenspiel::DSL"
|
|
744
407
|
end
|
|
745
408
|
|
|
746
409
|
# Get the block's calling context object
|
|
747
|
-
|
|
410
|
+
context_object_ = block_ ? ::Kernel.eval('self', block_.binding) : nil
|
|
748
411
|
|
|
749
|
-
|
|
750
|
-
if parameterless_ == :proxy
|
|
412
|
+
if use_proxy_method_
|
|
751
413
|
|
|
752
414
|
# Create proxy object
|
|
753
415
|
proxy_ = ::Blockenspiel::ProxyDelegator.new
|
|
@@ -755,73 +417,76 @@ module Blockenspiel
|
|
|
755
417
|
|
|
756
418
|
# Store the target and proxy object so dispatchers can get them
|
|
757
419
|
proxy_delegator_key_ = proxy_.object_id
|
|
758
|
-
target_stack_key_ = [
|
|
759
|
-
@_proxy_delegators[proxy_delegator_key_] =
|
|
420
|
+
target_stack_key_ = [_current_context_id, proxy_.object_id]
|
|
421
|
+
@_proxy_delegators[proxy_delegator_key_] = context_object_ if context_object_
|
|
760
422
|
@_target_stacks[target_stack_key_] = [target_]
|
|
761
423
|
|
|
762
424
|
begin
|
|
763
425
|
|
|
764
|
-
#
|
|
765
|
-
|
|
426
|
+
# Evaluate with the proxy as self
|
|
427
|
+
if block_
|
|
428
|
+
return proxy_.instance_eval(&block_)
|
|
429
|
+
else
|
|
430
|
+
return proxy_.instance_eval(eval_str_, file_, line_)
|
|
431
|
+
end
|
|
766
432
|
|
|
767
433
|
ensure
|
|
768
434
|
|
|
769
435
|
# Clean up the dispatcher information
|
|
770
|
-
@_proxy_delegators.delete(proxy_delegator_key_)
|
|
436
|
+
@_proxy_delegators.delete(proxy_delegator_key_) if context_object_
|
|
771
437
|
@_target_stacks.delete(target_stack_key_)
|
|
772
438
|
|
|
773
439
|
end
|
|
774
440
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
# Handle mixin behavior (default)
|
|
778
|
-
|
|
779
|
-
# Create hash keys
|
|
780
|
-
mixin_count_key_ = [object_.object_id, mod_.object_id]
|
|
781
|
-
target_stack_key_ = [::Thread.current.object_id, object_.object_id]
|
|
782
|
-
|
|
783
|
-
# Store the target for inheriting.
|
|
784
|
-
# We maintain a target call stack per thread.
|
|
785
|
-
target_stack_ = @_target_stacks[target_stack_key_] ||= ::Array.new
|
|
786
|
-
target_stack_.push(target_)
|
|
787
|
-
|
|
788
|
-
# Mix this module into the object, if required.
|
|
789
|
-
# This ensures that we keep track of the number of requests to
|
|
790
|
-
# mix this module in, from nested blocks and possibly multiple threads.
|
|
791
|
-
@_mutex.synchronize do
|
|
792
|
-
count_ = @_mixin_counts[mixin_count_key_]
|
|
793
|
-
if count_
|
|
794
|
-
@_mixin_counts[mixin_count_key_] = count_ + 1
|
|
795
|
-
else
|
|
796
|
-
@_mixin_counts[mixin_count_key_] = 1
|
|
797
|
-
object_.extend(mod_)
|
|
798
|
-
end
|
|
799
|
-
end
|
|
800
|
-
|
|
801
|
-
begin
|
|
802
|
-
|
|
803
|
-
# Now call the block
|
|
804
|
-
return block_.call
|
|
441
|
+
else
|
|
805
442
|
|
|
806
|
-
|
|
443
|
+
# Create hash keys
|
|
444
|
+
mixin_count_key_ = [context_object_.object_id, mod_.object_id]
|
|
445
|
+
target_stack_key_ = [_current_context_id, context_object_.object_id]
|
|
807
446
|
|
|
808
|
-
#
|
|
809
|
-
|
|
810
|
-
@_target_stacks
|
|
447
|
+
# Store the target for inheriting.
|
|
448
|
+
# We maintain a target call stack per thread.
|
|
449
|
+
target_stack_ = @_target_stacks[target_stack_key_] ||= []
|
|
450
|
+
target_stack_.push(target_)
|
|
811
451
|
|
|
812
|
-
#
|
|
452
|
+
# Mix this module into the object, if required.
|
|
453
|
+
# This ensures that we keep track of the number of requests to
|
|
454
|
+
# mix this module in, from nested blocks and possibly multiple threads.
|
|
813
455
|
@_mutex.synchronize do
|
|
814
456
|
count_ = @_mixin_counts[mixin_count_key_]
|
|
815
|
-
if count_
|
|
816
|
-
@_mixin_counts
|
|
817
|
-
::Blockenspiel::Unmixer.unmix(object_, mod_)
|
|
457
|
+
if count_
|
|
458
|
+
@_mixin_counts[mixin_count_key_] = count_ + 1
|
|
818
459
|
else
|
|
819
|
-
@_mixin_counts[mixin_count_key_] =
|
|
460
|
+
@_mixin_counts[mixin_count_key_] = 1
|
|
461
|
+
context_object_.extend(mod_)
|
|
820
462
|
end
|
|
821
463
|
end
|
|
822
464
|
|
|
465
|
+
begin
|
|
466
|
+
|
|
467
|
+
# Now call the block
|
|
468
|
+
return block_.call
|
|
469
|
+
|
|
470
|
+
ensure
|
|
471
|
+
|
|
472
|
+
# Clean up the target stack
|
|
473
|
+
target_stack_.pop
|
|
474
|
+
@_target_stacks.delete(target_stack_key_) if target_stack_.size == 0
|
|
475
|
+
|
|
476
|
+
# Remove the mixin from the object, if required.
|
|
477
|
+
@_mutex.synchronize do
|
|
478
|
+
count_ = @_mixin_counts[mixin_count_key_]
|
|
479
|
+
if count_ == 1
|
|
480
|
+
@_mixin_counts.delete(mixin_count_key_)
|
|
481
|
+
::Blockenspiel::Unmixer.unmix(context_object_, mod_)
|
|
482
|
+
else
|
|
483
|
+
@_mixin_counts[mixin_count_key_] = count_ - 1
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
end
|
|
488
|
+
|
|
823
489
|
end
|
|
824
|
-
|
|
825
490
|
end
|
|
826
491
|
|
|
827
492
|
|
|
@@ -831,7 +496,7 @@ module Blockenspiel
|
|
|
831
496
|
# If we can't find an appropriate method to call, return the special value NO_VALUE.
|
|
832
497
|
|
|
833
498
|
def self._target_dispatch(object_, name_, params_, block_) # :nodoc:
|
|
834
|
-
target_stack_ = @_target_stacks[[
|
|
499
|
+
target_stack_ = @_target_stacks[[_current_context_id, object_.object_id]]
|
|
835
500
|
return ::Blockenspiel::NO_VALUE unless target_stack_
|
|
836
501
|
target_stack_.reverse_each do |target_|
|
|
837
502
|
target_class_ = target_.class
|
|
@@ -848,7 +513,28 @@ module Blockenspiel
|
|
|
848
513
|
# We look up the context object, and call the given method on that object.
|
|
849
514
|
|
|
850
515
|
def self._proxy_dispatch(proxy_, name_, params_, block_) # :nodoc:
|
|
851
|
-
@_proxy_delegators[proxy_.object_id]
|
|
516
|
+
delegate_ = @_proxy_delegators[proxy_.object_id]
|
|
517
|
+
if delegate_
|
|
518
|
+
delegate_.send(name_, *params_, &block_)
|
|
519
|
+
else
|
|
520
|
+
raise ::NoMethodError, "undefined method `#{name_}' in DSL"
|
|
521
|
+
end
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
# This returns a current context ID. If fibers are available in the ruby
|
|
526
|
+
# implementation, this will be the current fiber's object_id. Otherwise,
|
|
527
|
+
# it is the current thread's object_id.
|
|
528
|
+
|
|
529
|
+
begin
|
|
530
|
+
require 'fiber'
|
|
531
|
+
def self._current_context_id # :nodoc:
|
|
532
|
+
::Fiber.current.object_id
|
|
533
|
+
end
|
|
534
|
+
rescue ::LoadError
|
|
535
|
+
def self._current_context_id # :nodoc:
|
|
536
|
+
::Thread.current.object_id
|
|
537
|
+
end
|
|
852
538
|
end
|
|
853
539
|
|
|
854
540
|
|