blockenspiel 0.4.3 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,15 +1,15 @@
1
1
  # -----------------------------------------------------------------------------
2
- #
2
+ #
3
3
  # Blockenspiel error classes
4
- #
4
+ #
5
5
  # -----------------------------------------------------------------------------
6
6
  # Copyright 2008-2011 Daniel Azuma
7
- #
7
+ #
8
8
  # All rights reserved.
9
- #
9
+ #
10
10
  # Redistribution and use in source and binary forms, with or without
11
11
  # modification, are permitted provided that the following conditions are met:
12
- #
12
+ #
13
13
  # * Redistributions of source code must retain the above copyright notice,
14
14
  # this list of conditions and the following disclaimer.
15
15
  # * Redistributions in binary form must reproduce the above copyright notice,
@@ -18,7 +18,7 @@
18
18
  # * Neither the name of the copyright holder, nor the names of any other
19
19
  # contributors to this software, may be used to endorse or promote products
20
20
  # derived from this software without specific prior written permission.
21
- #
21
+ #
22
22
  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
23
  # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
24
  # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
@@ -35,27 +35,27 @@
35
35
 
36
36
 
37
37
  module Blockenspiel
38
-
39
-
40
- # Base exception for all exceptions raised by Blockenspiel
41
-
38
+
39
+
40
+ # Base exception for all exceptions raised by Blockenspiel
41
+
42
42
  class BlockenspielError < ::RuntimeError
43
43
  end
44
-
45
-
44
+
45
+
46
46
  # This exception is rasied when attempting to use the <tt>:proxy</tt> or
47
47
  # <tt>:mixin</tt> parameterless behavior with a target that does not have
48
48
  # the DSL module included. It is an error made by the DSL implementor.
49
-
49
+
50
50
  class DSLMissingError < ::Blockenspiel::BlockenspielError
51
51
  end
52
-
53
-
52
+
53
+
54
54
  # This exception is raised when the block provided does not take the
55
55
  # expected number of parameters. It is an error made by the caller.
56
-
56
+
57
57
  class BlockParameterError < ::Blockenspiel::BlockenspielError
58
58
  end
59
-
60
-
59
+
60
+
61
61
  end
@@ -1,15 +1,15 @@
1
1
  # -----------------------------------------------------------------------------
2
- #
2
+ #
3
3
  # Blockenspiel implementation
4
- #
4
+ #
5
5
  # -----------------------------------------------------------------------------
6
6
  # Copyright 2008-2011 Daniel Azuma
7
- #
7
+ #
8
8
  # All rights reserved.
9
- #
9
+ #
10
10
  # Redistribution and use in source and binary forms, with or without
11
11
  # modification, are permitted provided that the following conditions are met:
12
- #
12
+ #
13
13
  # * Redistributions of source code must retain the above copyright notice,
14
14
  # this list of conditions and the following disclaimer.
15
15
  # * Redistributions in binary form must reproduce the above copyright notice,
@@ -18,7 +18,7 @@
18
18
  # * Neither the name of the copyright holder, nor the names of any other
19
19
  # contributors to this software, may be used to endorse or promote products
20
20
  # derived from this software without specific prior written permission.
21
- #
21
+ #
22
22
  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
23
  # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
24
  # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
@@ -38,78 +38,78 @@ require 'thread'
38
38
 
39
39
 
40
40
  module Blockenspiel
41
-
42
-
41
+
42
+
43
43
  # === Invoke a given DSL
44
- #
44
+ #
45
45
  # This is the entry point for Blockenspiel. Call this function to invoke
46
46
  # a set of DSL code provided by the user of your API.
47
- #
47
+ #
48
48
  # For example, if you want users of your API to be able to do this:
49
- #
49
+ #
50
50
  # call_dsl do
51
51
  # foo(1)
52
52
  # bar(2)
53
53
  # end
54
- #
54
+ #
55
55
  # Then you should implement <tt>call_dsl</tt> like this:
56
- #
56
+ #
57
57
  # def call_dsl(&block)
58
58
  # my_dsl = create_block_implementation
59
59
  # Blockenspiel.invoke(block, my_dsl)
60
60
  # do_something_with(my_dsl)
61
61
  # end
62
- #
62
+ #
63
63
  # In the above, <tt>create_block_implementation</tt> is a placeholder that
64
64
  # returns an instance of your DSL methods class. This class includes the
65
65
  # Blockenspiel::DSL module and defines the DSL methods +foo+ and +bar+.
66
66
  # See Blockenspiel::DSLSetupMethods for a set of tools you can use in your
67
67
  # DSL methods class for creating a DSL.
68
- #
68
+ #
69
69
  # === Usage patterns
70
- #
70
+ #
71
71
  # The invoke method has a number of forms, depending on whether the API
72
72
  # user's DSL code is provided as a block or a string, and depending on
73
73
  # whether the DSL methods are specified statically using a DSL class or
74
74
  # dynamically using a block.
75
- #
75
+ #
76
76
  # [<tt>Blockenspiel.invoke(<i>user_block</i>, <i>my_dsl</i>, <i>opts</i>)</tt>]
77
77
  # This form takes the user's code as a block, and the DSL itself as an
78
78
  # object with DSL methods. The opts hash is optional and provides a
79
79
  # set of arguments as described below under "Block DSL options".
80
- #
80
+ #
81
81
  # [<tt>Blockenspiel.invoke(<i>user_block</i>, <i>opts</i>) { ... }</tt>]
82
82
  # This form takes the user's code as a block, while the DSL itself is
83
83
  # specified in the given block, as described below under "Dynamic
84
84
  # target generation". The opts hash is optional and provides a set of
85
85
  # arguments as described below under "Block DSL options".
86
- #
86
+ #
87
87
  # [<tt>Blockenspiel.invoke(<i>user_string</i>, <i>my_dsl</i>, <i>opts</i>)</tt>]
88
88
  # This form takes the user's code as a string, and the DSL itself as an
89
89
  # object with DSL methods. The opts hash is optional and provides a
90
90
  # set of arguments as described below under "String DSL options".
91
- #
91
+ #
92
92
  # [<tt>Blockenspiel.invoke(<i>user_string</i>, <i>opts</i>) { ... }</tt>]
93
93
  # This form takes the user's code as a block, while the DSL itself is
94
94
  # specified in the given block, as described below under "Dynamic
95
95
  # target generation". The opts hash is optional and provides a set of
96
96
  # arguments as described below under "String DSL options".
97
- #
97
+ #
98
98
  # [<tt>Blockenspiel.invoke(<i>my_dsl</i>, <i>opts</i>)</tt>]
99
99
  # This form reads the user's code from a file, and takes the DSL itself
100
100
  # as an object with DSL methods. The opts hash is required and provides
101
101
  # a set of arguments as described below under "String DSL options". The
102
102
  # <tt>:file</tt> option is required.
103
- #
103
+ #
104
104
  # [<tt>Blockenspiel.invoke(<i>opts</i>) { ... }</tt>]
105
105
  # This form reads the user's code from a file, while the DSL itself is
106
106
  # specified in the given block, as described below under "Dynamic
107
107
  # target generation". The opts hash is required and provides a set of
108
108
  # arguments as described below under "String DSL options". The
109
109
  # <tt>:file</tt> option is required.
110
- #
110
+ #
111
111
  # === Block DSL options
112
- #
112
+ #
113
113
  # When a user provides DSL code using a block, you simply pass that block
114
114
  # as the first parameter to Blockenspiel.invoke. Normally, Blockenspiel
115
115
  # will first check the block's arity to see whether it takes a parameter.
@@ -117,10 +117,10 @@ module Blockenspiel
117
117
  # no parameter, and the given target is an instance of a class with DSL
118
118
  # capability, the DSL methods are made available on the caller's self
119
119
  # object so they may be called without a block parameter.
120
- #
120
+ #
121
121
  # Following are the options understood by Blockenspiel when providing
122
122
  # code using a block:
123
- #
123
+ #
124
124
  # [<tt>:parameterless</tt>]
125
125
  # If set to false, disables parameterless blocks and always attempts to
126
126
  # pass a parameter to the block. Otherwise, you may set it to one of
@@ -131,10 +131,10 @@ module Blockenspiel
131
131
  # [<tt>:parameter</tt>]
132
132
  # If set to false, disables blocks with parameters, and always attempts
133
133
  # to use parameterless blocks. Default is true, enabling parameter mode.
134
- #
134
+ #
135
135
  # The following values control the precise behavior of parameterless
136
136
  # blocks. These are values for the <tt>:parameterless</tt> option.
137
- #
137
+ #
138
138
  # [<tt>:mixin</tt>]
139
139
  # This is the default behavior. DSL methods from the target are
140
140
  # temporarily overlayed on the caller's +self+ object, but +self+ still
@@ -159,16 +159,16 @@ module Blockenspiel
159
159
  # object's instance variables are not available (and thus cannot be
160
160
  # clobbered) in the block, and the transformations specified by
161
161
  # <tt>dsl_method</tt> directives are honored.
162
- #
162
+ #
163
163
  # === String DSL options
164
- #
164
+ #
165
165
  # When a user provides DSL code using a string (either directly or via a
166
166
  # file), Blockenspiel always treats it as a "parameterless" invocation,
167
167
  # since there is no way to "pass a parameter" to a string. Thus, the two
168
168
  # options recognized for block DSLs, <tt>:parameterless</tt>, and
169
169
  # <tt>:parameter</tt>, are meaningless and ignored. However, the
170
170
  # following new options are recognized:
171
- #
171
+ #
172
172
  # [<tt>:file</tt>]
173
173
  # The value of this option should be a string indicating the path to
174
174
  # the file from which the user's DSL code is coming. It is passed
@@ -187,9 +187,9 @@ module Blockenspiel
187
187
  # descriptions of these behaviors. Note that <tt>:mixin</tt> is not
188
188
  # allowed in this case because its behavior would be indistinguishable
189
189
  # from the proxy behavior.
190
- #
190
+ #
191
191
  # The following values are recognized for the <tt>:behavior</tt> option:
192
- #
192
+ #
193
193
  # [<tt>:proxy</tt>]
194
194
  # This behavior changes +self+ to a proxy object created by applying the
195
195
  # DSL methods to an empty object. Thus, the code in the DSL string does
@@ -204,12 +204,12 @@ module Blockenspiel
204
204
  # the target object's methods are not modified: this behavior does not
205
205
  # apply any DSL method changes specified using <tt>dsl_method</tt>
206
206
  # directives.
207
- #
207
+ #
208
208
  # === Dynamic target generation
209
- #
209
+ #
210
210
  # It is also possible to dynamically generate a target object by passing
211
211
  # a block to this method. This is probably best illustrated by example:
212
- #
212
+ #
213
213
  # Blockenspiel.invoke(block) do
214
214
  # add_method(:set_foo) do |value|
215
215
  # my_foo = value
@@ -219,10 +219,10 @@ module Blockenspiel
219
219
  # my_bar = blk.call
220
220
  # end
221
221
  # end
222
- #
222
+ #
223
223
  # The above is roughly equivalent to invoking Blockenspiel with an
224
224
  # instance of this target class:
225
- #
225
+ #
226
226
  # class MyFooTarget
227
227
  # include Blockenspiel::DSL
228
228
  # def set_foo(value)
@@ -233,37 +233,37 @@ module Blockenspiel
233
233
  # set_my_bar_from(yield)
234
234
  # end
235
235
  # end
236
- #
236
+ #
237
237
  # Blockenspiel.invoke(block, MyFooTarget.new)
238
- #
238
+ #
239
239
  # The obvious advantage of using dynamic object generation is that you are
240
240
  # creating methods using closures, which provides the opportunity to, for
241
241
  # example, modify closure local variables such as my_foo. This is more
242
242
  # difficult to do when you create a target class since its methods do not
243
243
  # have access to outside data. Hence, in the above example, we hand-waved,
244
244
  # assuming the existence of some method called "set_my_foo_from".
245
- #
245
+ #
246
246
  # The disadvantage is performance. If you dynamically generate a target
247
247
  # object, it involves parsing and creating a new class whenever it is
248
248
  # invoked. Thus, it is recommended that you use this technique for calls
249
249
  # that are not used repeatedly, such as one-time configuration.
250
- #
250
+ #
251
251
  # See the Blockenspiel::Builder class for more details on add_method.
252
- #
252
+ #
253
253
  # (And yes, you guessed it: this API is a DSL block, and is itself
254
254
  # implemented using Blockenspiel.)
255
-
255
+
256
256
  def self.invoke(*args_, &builder_block_)
257
257
  # This method itself is responsible for parsing the args to invoke,
258
258
  # and handling the dynamic target generation. It then passes control
259
259
  # to one of the _invoke_with_* methods.
260
-
260
+
261
261
  # The arguments.
262
262
  block_ = nil
263
263
  eval_str_ = nil
264
264
  target_ = nil
265
265
  opts_ = {}
266
-
266
+
267
267
  # Get the code
268
268
  case args_.first
269
269
  when ::String
@@ -271,7 +271,7 @@ module Blockenspiel
271
271
  when ::Proc
272
272
  block_ = args_.shift
273
273
  end
274
-
274
+
275
275
  # Get the target, performing dynamic target generation if requested
276
276
  if builder_block_
277
277
  builder_ = ::Blockenspiel::Builder.new
@@ -284,7 +284,7 @@ module Blockenspiel
284
284
  raise ::ArgumentError, "No DSL target provided"
285
285
  end
286
286
  end
287
-
287
+
288
288
  # Get the options hash
289
289
  if args_.first.kind_of?(::Hash)
290
290
  opts_ = args_.shift
@@ -292,7 +292,7 @@ module Blockenspiel
292
292
  if args_.size > 0
293
293
  raise ::ArgumentError, "Unexpected arguments"
294
294
  end
295
-
295
+
296
296
  # Invoke
297
297
  if block_
298
298
  _invoke_with_block(block_, target_, opts_)
@@ -300,17 +300,17 @@ module Blockenspiel
300
300
  _invoke_with_string(eval_str_, target_, opts_)
301
301
  end
302
302
  end
303
-
304
-
303
+
304
+
305
305
  # Invoke when the DSL user provides code as a string or file.
306
306
  # We open and read the file if need be, and then pass control
307
307
  # to the _execute method.
308
-
308
+
309
309
  def self._invoke_with_string(eval_str_, target_, opts_) # :nodoc:
310
310
  # Read options
311
311
  file_ = opts_[:file]
312
312
  line_ = opts_[:line] || 1
313
-
313
+
314
314
  # Read file if no string provided directly
315
315
  unless eval_str_
316
316
  if file_
@@ -321,26 +321,26 @@ module Blockenspiel
321
321
  else
322
322
  file_ ||= "(String passed to Blockenspiel)"
323
323
  end
324
-
324
+
325
325
  # Handle instance-eval behavior
326
326
  if opts_[:behavior] == :instance
327
327
  return target_.instance_eval(eval_str_, file_, line_)
328
328
  end
329
-
329
+
330
330
  # Execute the DSL using the proxy method.
331
331
  _execute_dsl(true, nil, eval_str_, target_, file_, line_)
332
332
  end
333
-
334
-
333
+
334
+
335
335
  # Invoke when the DSL user provides code as a block. We read the given
336
336
  # options hash, handle a few special cases, and then pass control to the
337
337
  # _execute method.
338
-
338
+
339
339
  def self._invoke_with_block(block_, target_, opts_) # :nodoc:
340
340
  # Read options
341
341
  parameter_ = opts_[:parameter]
342
342
  parameterless_ = opts_.include?(:behavior) ? opts_[:behavior] : opts_[:parameterless]
343
-
343
+
344
344
  # Handle no-target behavior
345
345
  if parameter_ == false && parameterless_ == false
346
346
  if block_.arity != 0 && block_.arity != -1
@@ -348,7 +348,7 @@ module Blockenspiel
348
348
  end
349
349
  return block_.call
350
350
  end
351
-
351
+
352
352
  # Handle parametered block case
353
353
  if parameter_ != false && block_.arity == 1 || parameterless_ == false
354
354
  if block_.arity != 1
@@ -356,99 +356,99 @@ module Blockenspiel
356
356
  end
357
357
  return block_.call(target_)
358
358
  end
359
-
359
+
360
360
  # Check arity for parameterless case
361
361
  if block_.arity != 0 && block_.arity != -1
362
362
  raise ::Blockenspiel::BlockParameterError, "Block should not take parameters"
363
363
  end
364
-
364
+
365
365
  # Handle instance-eval behavior
366
366
  if parameterless_ == :instance
367
367
  return target_.instance_eval(&block_)
368
368
  end
369
-
369
+
370
370
  # Execute the DSL
371
371
  _execute_dsl(parameterless_ == :proxy, block_, nil, target_, nil, nil)
372
372
  end
373
-
374
-
373
+
374
+
375
375
  # Class for proxy delegators.
376
376
  # The proxy behavior creates one of these delegators, mixes in the dsl
377
377
  # methods, and uses instance_eval to invoke the block. This class delegates
378
378
  # non-handled methods to the context object.
379
-
379
+
380
380
  class ProxyDelegator # :nodoc:
381
-
381
+
382
382
  def method_missing(symbol_, *params_, &block_)
383
383
  ::Blockenspiel._proxy_dispatch(self, symbol_, params_, block_)
384
384
  end
385
-
385
+
386
386
  end
387
-
388
-
387
+
388
+
389
389
  # :stopdoc:
390
390
  NO_VALUE = ::Object.new
391
391
  # :startdoc:
392
-
392
+
393
393
  @_target_stacks = {}
394
394
  @_mixin_counts = {}
395
395
  @_proxy_delegators = {}
396
396
  @_mutex = ::Mutex.new
397
-
398
-
397
+
398
+
399
399
  # This is the "meat" of Blockenspiel, implementing both the proxy and
400
400
  # mixin methods.
401
-
401
+
402
402
  def self._execute_dsl(use_proxy_method_, block_, eval_str_, target_, file_, line_) # :nodoc:
403
403
  # Get the module of dsl methods
404
404
  mod_ = target_.class._get_blockenspiel_module rescue nil
405
405
  unless mod_
406
406
  raise ::Blockenspiel::DSLMissingError, "Given DSL target does not include Blockenspiel::DSL"
407
407
  end
408
-
408
+
409
409
  # Get the block's calling context object
410
410
  context_object_ = block_ ? ::Kernel.eval('self', block_.binding) : nil
411
-
411
+
412
412
  if use_proxy_method_
413
-
413
+
414
414
  # Create proxy object
415
415
  proxy_ = ::Blockenspiel::ProxyDelegator.new
416
416
  proxy_.extend(mod_)
417
-
417
+
418
418
  # Store the target and proxy object so dispatchers can get them
419
419
  proxy_delegator_key_ = proxy_.object_id
420
- target_stack_key_ = [_current_context_id, proxy_.object_id]
420
+ target_stack_key_ = _current_context_id(proxy_)
421
421
  @_proxy_delegators[proxy_delegator_key_] = context_object_ if context_object_
422
422
  @_target_stacks[target_stack_key_] = [target_]
423
-
423
+
424
424
  begin
425
-
425
+
426
426
  # Evaluate with the proxy as self
427
427
  if block_
428
428
  return proxy_.instance_eval(&block_)
429
429
  else
430
430
  return proxy_.instance_eval(eval_str_, file_, line_)
431
431
  end
432
-
432
+
433
433
  ensure
434
-
434
+
435
435
  # Clean up the dispatcher information
436
436
  @_proxy_delegators.delete(proxy_delegator_key_) if context_object_
437
437
  @_target_stacks.delete(target_stack_key_)
438
-
438
+
439
439
  end
440
-
440
+
441
441
  else
442
-
442
+
443
443
  # Create hash keys
444
444
  mixin_count_key_ = [context_object_.object_id, mod_.object_id]
445
- target_stack_key_ = [_current_context_id, context_object_.object_id]
446
-
445
+ target_stack_key_ = _current_context_id(context_object_)
446
+
447
447
  # Store the target for inheriting.
448
448
  # We maintain a target call stack per thread.
449
449
  target_stack_ = @_target_stacks[target_stack_key_] ||= []
450
450
  target_stack_.push(target_)
451
-
451
+
452
452
  # Mix this module into the object, if required.
453
453
  # This ensures that we keep track of the number of requests to
454
454
  # mix this module in, from nested blocks and possibly multiple threads.
@@ -461,18 +461,18 @@ module Blockenspiel
461
461
  context_object_.extend(mod_)
462
462
  end
463
463
  end
464
-
464
+
465
465
  begin
466
-
466
+
467
467
  # Now call the block
468
468
  return block_.call
469
-
469
+
470
470
  ensure
471
-
471
+
472
472
  # Clean up the target stack
473
473
  target_stack_.pop
474
474
  @_target_stacks.delete(target_stack_key_) if target_stack_.size == 0
475
-
475
+
476
476
  # Remove the mixin from the object, if required.
477
477
  @_mutex.synchronize do
478
478
  count_ = @_mixin_counts[mixin_count_key_]
@@ -483,20 +483,20 @@ module Blockenspiel
483
483
  @_mixin_counts[mixin_count_key_] = count_ - 1
484
484
  end
485
485
  end
486
-
486
+
487
487
  end
488
-
488
+
489
489
  end
490
490
  end
491
-
492
-
491
+
492
+
493
493
  # This implements the mapping between DSL module methods and target object methods.
494
494
  # We look up the current target object based on the current thread.
495
495
  # Then we attempt to call the given method on that object.
496
496
  # If we can't find an appropriate method to call, return the special value NO_VALUE.
497
-
497
+
498
498
  def self._target_dispatch(object_, name_, params_, block_) # :nodoc:
499
- target_stack_ = @_target_stacks[[_current_context_id, object_.object_id]]
499
+ target_stack_ = @_target_stacks[_current_context_id(object_)]
500
500
  return ::Blockenspiel::NO_VALUE unless target_stack_
501
501
  target_stack_.reverse_each do |target_|
502
502
  target_class_ = target_.class
@@ -507,11 +507,11 @@ module Blockenspiel
507
507
  end
508
508
  return ::Blockenspiel::NO_VALUE
509
509
  end
510
-
511
-
510
+
511
+
512
512
  # This implements the proxy fall-back behavior.
513
513
  # We look up the context object, and call the given method on that object.
514
-
514
+
515
515
  def self._proxy_dispatch(proxy_, name_, params_, block_) # :nodoc:
516
516
  delegate_ = @_proxy_delegators[proxy_.object_id]
517
517
  if delegate_
@@ -520,28 +520,28 @@ module Blockenspiel
520
520
  raise ::NoMethodError, "undefined method `#{name_}' in DSL"
521
521
  end
522
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
-
523
+
524
+
525
+ # This returns a current context ID, which includes both the curren thread
526
+ # object_id and the current fiber object_id (if available).
527
+
529
528
  begin
530
529
  require 'fiber'
531
530
  raise ::LoadError unless defined?(::Fiber)
532
- def self._current_context_id # :nodoc:
531
+ def self._current_context_id(object_) # :nodoc:
532
+ thid_ = ::Thread.current.object_id
533
533
  begin
534
- ::Fiber.current.object_id
534
+ [thid_, ::Fiber.current.object_id, object_.object_id]
535
535
  rescue ::Exception
536
536
  # JRuby hack (see JRUBY-5842)
537
- ::Thread.current.object_id
537
+ [thid_, 0, object_.object_id]
538
538
  end
539
539
  end
540
540
  rescue ::LoadError
541
- def self._current_context_id # :nodoc:
542
- ::Thread.current.object_id
541
+ def self._current_context_id(object_) # :nodoc:
542
+ [::Thread.current.object_id, object_.object_id]
543
543
  end
544
544
  end
545
-
546
-
545
+
546
+
547
547
  end