blockenspiel 0.4.5 → 0.5.0

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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bad96a420e759925ce030818a785e845b0b7a426
4
+ data.tar.gz: 2671c4e900ce470cc51f244d4bc0faaf0c394cbe
5
+ SHA512:
6
+ metadata.gz: 3df2c3683828d0b4ab0749fe39b71dddb92945c43df25ab0f64bba65c51c78eb7f90b27a42ceb4d8dcf4bd64dde973522fdcd36d5f8061aad220ccfdd31c058a
7
+ data.tar.gz: 186032fa3c700bff84fc7ced3a2d1c4c472670ddfdcb09623a324611d3751567711253bc4bad28ac720f7049650ed9ea74b66dcded1884be0b2b0f023f7eda0a
@@ -90,7 +90,7 @@ You could write this as follows:
90
90
  # do something
91
91
  end
92
92
  end
93
-
93
+
94
94
  def configure_me
95
95
  yield ConfigMethods.new
96
96
  end
@@ -130,8 +130,8 @@ doesn't take a parameter, the second form without a parameter is used.
130
130
  === How does that help me? (Or, why not just use instance_eval?)
131
131
 
132
132
  As noted earlier, some libraries that provide parameter-less DSL blocks use
133
- <tt>instance_eval</tt>, and they could even support both the parameter and
134
- parameter-less mechanisms by checking the block arity:
133
+ a simple <tt>instance_eval</tt>, and they could even support both the parameter
134
+ and parameter-less mechanisms by checking the block arity:
135
135
 
136
136
  def configure_me(&block)
137
137
  if block.arity == 1
@@ -151,7 +151,7 @@ methods inside the block:
151
151
  def callers_helper_method
152
152
  # ...
153
153
  end
154
-
154
+
155
155
  configure_me do
156
156
  add_foo(1)
157
157
  callers_helper_method # Error! self is now an instance of ConfigMethods
@@ -159,21 +159,13 @@ methods inside the block:
159
159
  add_bar(2)
160
160
  end
161
161
 
162
- Blockenspiel by default does _not_ use the <tt>instance_eval</tt> technique.
163
- Instead, it implements a mechanism using mixin modules, a technique proposed
164
- by the late {Why}[http://en.wikipedia.org/wiki/Why_the_lucky_stiff]. In this
165
- technique, the <tt>add_foo</tt> and <tt>add_bar</tt> methods are temporarily
166
- mixed into the caller's +self+ object. That is, +self+ does not change, as
167
- it would if we used <tt>instance_eval</tt>, so helper methods like
168
- <tt>callers_helper_method</tt> still remain available as expected. But, the
169
- <tt>add_foo</tt> and <tt>add_bar</tt> methods are also made available
170
- temporarily for the duration of the block. When called, they are intercepted
171
- and redirected to your +ConfigMethods+ instance just as if you had called
172
- them directly via a block parameter. Blockenspiel handles the object
173
- redirection behind the scenes so you do not have to think about it. With
174
- Blockenspiel, the caller retains access to its helper methods, and even its
175
- own instance variables, within the block, because +self+ has not been
176
- modified.
162
+ Blockenspiel employs a number of techniques to mitigate the ill effects of
163
+ <tt>instance_eval</tt>. It delegates methods that are not part of the DSL,
164
+ back to the enclosing context object, so that the caller retains access to
165
+ helper methods. It also includes an optional experimental technique (not
166
+ available on all ruby platforms) that temporarily mixes the DSL methods
167
+ directly into the caller's +self+ object, so that instance variable access
168
+ is retained.
177
169
 
178
170
  === Is that it?
179
171
 
@@ -196,28 +188,28 @@ a few examples:
196
188
 
197
189
  class ConfigMethods
198
190
  include Blockenspiel::DSL
199
-
191
+
200
192
  def add_foo # automatically added to the dsl
201
193
  # do stuff...
202
194
  end
203
-
195
+
204
196
  def my_private_method
205
197
  # do stuff...
206
198
  end
207
199
  dsl_method :my_private_method, false # remove from the dsl
208
-
200
+
209
201
  dsl_methods false # stop automatically adding methods to the dsl
210
-
202
+
211
203
  def another_private_method # not added
212
204
  # do stuff...
213
205
  end
214
-
206
+
215
207
  dsl_methods true # resume automatically adding methods to the dsl
216
-
208
+
217
209
  def add_bar # this method is automatically added
218
210
  # do stuff...
219
211
  end
220
-
212
+
221
213
  def add_baz
222
214
  # do stuff
223
215
  end
@@ -233,12 +225,12 @@ Parameterless blocks do not support <tt>attr_writer</tt> (or, by corollary,
233
225
  configure_me do |config|
234
226
  config.foo = 1 # works fine when the block has a parameter
235
227
  end
236
-
228
+
237
229
  configure_me do
238
230
  # foo = 1 # <--- Doesn't work: looks like a variable assignment
239
231
  set_foo(1) # <--- Fix it by renaming to this instead
240
232
  end
241
-
233
+
242
234
  # This is implemented like this::
243
235
  class ConfigMethods
244
236
  include Blockenspiel::DSL
@@ -256,7 +248,7 @@ parameterless block:
256
248
  foo 1 # this syntax is now supported.
257
249
  puts "foo is #{foo}" # The getter still works.
258
250
  end
259
-
251
+
260
252
  # This is implemented like this::
261
253
  class ConfigMethods
262
254
  include Blockenspiel::DSL
@@ -302,12 +294,26 @@ Blockenspiel also correctly handles nested blocks. e.g.
302
294
  end
303
295
  end
304
296
 
305
- Finally, it is thread safe, correctly handling, for example, the case of
306
- multiple threads trying to mix methods into the same object concurrently.
297
+ Blockenspiel provides three strategies for doing parameterless DSL blocks.
298
+
299
+ The default strategy uses a proxy object that delegates unrecognized methods
300
+ out to the calling context. It should work well for most cases.
301
+
302
+ Second, some applications might want to use the simple <tt>instance_eval</tt>
303
+ behavior. RSpec is a good example of such a case, since the DSL is being used
304
+ to construct objects, so it makes sense for instance variables inside the block
305
+ to belong to the object being constructed.
306
+
307
+ Third, an experimental mixin strategy is provided, which adds the DSL methods
308
+ directly to the context's self object, and removes them afterward. This is
309
+ available on Rubinius and JRuby but not on MRI.
310
+
311
+ Finally, Blockenspiel is thread safe, correctly handling, for example, the case
312
+ of multiple threads trying to mix methods into the same object concurrently.
307
313
 
308
314
  === Requirements
309
315
 
310
- * Ruby 1.8.7, Ruby 1.9.1 or later, JRuby 1.5 or later, or Rubinius 1.0 or later.
316
+ * Ruby 1.9.3 or later, JRuby 1.5 or later, or Rubinius 1.0 or later.
311
317
 
312
318
  === Installation
313
319
 
@@ -320,9 +326,7 @@ multiple threads trying to mix methods into the same object concurrently.
320
326
  whether it is even a reasonable feature at all.
321
327
  * Including Blockenspiel::DSL in a module (rather than a class) is not yet
322
328
  supported, but this is planned for a future release.
323
- * Installing on Windows may be a challenge because blockenspiel includes a
324
- native extension. I'm considering evaluating Luis Lavena's rake-compiler
325
- to simplify this process.
329
+ * Find a way to implement mixin behavior reliably on MRI.
326
330
 
327
331
  === Development and support
328
332
 
@@ -349,19 +353,21 @@ copies or mirrors out there.
349
353
 
350
354
  The unmixer code is based on {Mixology}[http://rubyforge.org/projects/mixology],
351
355
  version by Patrick Farley, anonymous z, Dan Manges, and Clint Bishop.
352
- The MRI C extension and the JRuby code were adapted from Mixology 0.1, and
353
- have been stripped down and modified to support Ruby 1.9 and JRuby >= 1.2.
354
- The Rubinius code was adapted from unreleased code in the Mixology source
355
- tree and modified to support Rubinius 1.0. I know Mixology 0.2 is now
356
- available, but its Rubinius support is not active, and I'd rather keep the
357
- unmixer bundled with Blockenspiel for now to reduce dependencies.
356
+ The JRuby code is adapted from Mixology 0.1, and has been stripped down and
357
+ modified to support JRuby >= 1.2. The Rubinius code was adapted from unreleased
358
+ code in the Mixology source tree and modified to support Rubinius 1.0. I know
359
+ Mixology 0.2 is now available, but its Rubinius support is not active, and I'd
360
+ rather keep the unmixer bundled with Blockenspiel for now to reduce
361
+ dependencies. Earlier versions of Blockenspiel also included a C extension,
362
+ adapted from Mixology, to support mixins for MRI, but this code has been
363
+ disabled due to issues with newer versions of Ruby.
358
364
 
359
365
  The dsl_attr_writer and dsl_attr_accessor feature came from a suggestion by
360
366
  Luis Lavena.
361
367
 
362
368
  === License
363
369
 
364
- Copyright 2008-2011 Daniel Azuma.
370
+ Copyright 2008 Daniel Azuma.
365
371
 
366
372
  All rights reserved.
367
373
 
@@ -1,3 +1,17 @@
1
+ === 0.5.0 / 2016-01-07
2
+
3
+ * Fixed an issue with the proxy strategy, where if a block spawns blocks that live longer than it, the sub-blocks lost their context.
4
+ * Changed the default strategy to proxy due to semantic issues with mixin and difficulty supporting it.
5
+ * Dropped support for the mixin strategy on MRI because Ruby 2.3.0 broke it and I don't have the bandwidth to find a remedy.
6
+ * Updated the Rakefile, tests, and general infrastructure to play better with modern Rubies.
7
+ * Dropped support for Ruby 1.8, because who still uses 1.8???
8
+
9
+ === 0.4.6 / (never actually released)
10
+
11
+ * Compatibility with the signature change to reset_method_cache in recent builds of Rubinius 2.0.
12
+ * The gemspec no longer includes the timestamp in the version, so that bundler can pull from github. (Reported by corneverbruggen)
13
+ * The Rakefile is now compatible with Ruby 2.0 and RubyGems 2.0.
14
+
1
15
  === 0.4.5 / 2012-06-27
2
16
 
3
17
  * The 0.4.4 build was missing the JRuby unmixer. Fixed.
@@ -16,7 +16,7 @@ and those that do not. For example:
16
16
  config.add_foo(1)
17
17
  config.add_bar(2)
18
18
  end
19
-
19
+
20
20
  # Call DSL block without parameter
21
21
  configure_me do
22
22
  add_foo(3)
@@ -35,17 +35,18 @@ To support the above usage, you can do this:
35
35
  # do something
36
36
  end
37
37
  end
38
-
38
+
39
39
  # Implement configure_me method
40
40
  def configure_me(&block)
41
41
  Blockenspiel.invoke(block, ConfigMethods.new)
42
42
  end
43
43
 
44
- By default, Blockenspiel uses a mixin technique (proposed by the late Why
45
- The Lucky Stiff) to support parameterless blocks without the complications
46
- introduced by <tt>instance_eval</tt>. It supports nested blocks and
44
+ By default, Blockenspiel uses a "delegation" technique (to my knowledge first
45
+ proposed by Dan Manges) to support parameterless blocks while mitigating some
46
+ of the issues with <tt>instance_eval</tt>. It supports nested blocks and
47
47
  multithreaded access, and provides a variety of tools for handling the
48
- typical issues you may encounter when writing DSLs.
48
+ typical issues you may encounter when writing DSLs. On some ruby platforms,
49
+ Blockenspiel also supports a mixin technique (proposed by Why The Lucky Stiff).
49
50
 
50
51
  For more detailed usage and examples, see
51
52
  {Blockenspiel.rdoc}[link:Blockenspiel\_rdoc.html].
@@ -55,7 +56,7 @@ For an extended analysis of different ways to implement DSL blocks, see
55
56
 
56
57
  === Requirements
57
58
 
58
- * Ruby 1.8.7, Ruby 1.9.1 or later, JRuby 1.5 or later, or Rubinius 1.0 or later.
59
+ * Ruby 1.9.3 or later, JRuby 1.5 or later, or Rubinius 1.0 or later.
59
60
 
60
61
  === Installation
61
62
 
@@ -66,11 +67,9 @@ For an extended analysis of different ways to implement DSL blocks, see
66
67
  * Implementing wildcard DSL methods using <tt>method_missing</tt> doesn't
67
68
  work. I haven't yet decided on the right semantics for this case, or
68
69
  whether it is even a reasonable feature at all.
69
- * Including Blockenspiel::DSL in a module (rather than a class) is not yet
70
- supported, but this is planned for a future release.
71
- * Installing on Windows may be a challenge because blockenspiel includes a
72
- native extension. I'm considering evaluating Luis Lavena's rake-compiler
73
- to simplify this process.
70
+ * Including Blockenspiel::DSL in a module (rather than a class) is not
71
+ supported, but this could appear in a future release.
72
+ * Find a way to implement mixin behavior reliably on MRI.
74
73
 
75
74
  === Development and support
76
75
 
@@ -97,19 +96,21 @@ its author, but you may find copies or mirrors out there.
97
96
 
98
97
  The unmixer code is based on {Mixology}[http://rubyforge.org/projects/mixology],
99
98
  version by Patrick Farley, anonymous z, Dan Manges, and Clint Bishop.
100
- The MRI C extension and the JRuby code were adapted from Mixology 0.1, and
101
- have been stripped down and modified to support Ruby 1.9 and JRuby >= 1.2.
102
- The Rubinius code was adapted from unreleased code in the Mixology source
103
- tree and modified to support Rubinius 1.0. I know Mixology 0.2 is now
104
- available, but its Rubinius support is not active, and I'd rather keep the
105
- unmixer bundled with Blockenspiel for now to reduce dependencies.
99
+ The JRuby code is adapted from Mixology 0.1, and has been stripped down and
100
+ modified to support JRuby >= 1.2. The Rubinius code was adapted from unreleased
101
+ code in the Mixology source tree and modified to support Rubinius 1.0. I know
102
+ Mixology 0.2 is now available, but its Rubinius support is not active, and I'd
103
+ rather keep the unmixer bundled with Blockenspiel for now to reduce
104
+ dependencies. Earlier versions of Blockenspiel also included a C extension,
105
+ adapted from Mixology, to support mixins for MRI, but this code has been
106
+ disabled due to issues with newer versions of Ruby.
106
107
 
107
108
  The dsl_attr_writer and dsl_attr_accessor feature came from a suggestion by
108
109
  Luis Lavena.
109
110
 
110
111
  === License
111
112
 
112
- Copyright 2008-2012 Daniel Azuma.
113
+ Copyright 2008 Daniel Azuma.
113
114
 
114
115
  All rights reserved.
115
116
 
data/Version CHANGED
@@ -1 +1 @@
1
- 0.4.5
1
+ 0.5.0
@@ -3,7 +3,7 @@
3
3
  # Blockenspiel entry point
4
4
  #
5
5
  # -----------------------------------------------------------------------------
6
- # Copyright 2008-2011 Daniel Azuma
6
+ # Copyright 2008 Daniel Azuma
7
7
  #
8
8
  # All rights reserved.
9
9
  #
@@ -45,7 +45,7 @@ end
45
45
 
46
46
  case ::RUBY_DESCRIPTION
47
47
  when /^ruby\s/
48
- require 'blockenspiel/unmixer_mri'
48
+ require 'blockenspiel/unmixer_unimplemented'
49
49
  when /^jruby\s/
50
50
  require 'blockenspiel_unmixer_jruby'
51
51
  when /^rubinius\s/
@@ -3,7 +3,7 @@
3
3
  # Blockenspiel dynamic target construction
4
4
  #
5
5
  # -----------------------------------------------------------------------------
6
- # Copyright 2008-2011 Daniel Azuma
6
+ # Copyright 2008 Daniel Azuma
7
7
  #
8
8
  # All rights reserved.
9
9
  #
@@ -3,7 +3,7 @@
3
3
  # Blockenspiel DSL definition
4
4
  #
5
5
  # -----------------------------------------------------------------------------
6
- # Copyright 2008-2011 Daniel Azuma
6
+ # Copyright 2008 Daniel Azuma
7
7
  #
8
8
  # All rights reserved.
9
9
  #
@@ -3,7 +3,7 @@
3
3
  # Blockenspiel error classes
4
4
  #
5
5
  # -----------------------------------------------------------------------------
6
- # Copyright 2008-2011 Daniel Azuma
6
+ # Copyright 2008 Daniel Azuma
7
7
  #
8
8
  # All rights reserved.
9
9
  #
@@ -3,7 +3,7 @@
3
3
  # Blockenspiel implementation
4
4
  #
5
5
  # -----------------------------------------------------------------------------
6
- # Copyright 2008-2011 Daniel Azuma
6
+ # Copyright 2008 Daniel Azuma
7
7
  #
8
8
  # All rights reserved.
9
9
  #
@@ -40,6 +40,16 @@ require 'thread'
40
40
  module Blockenspiel
41
41
 
42
42
 
43
+ # === Determine whether the mixin strategy is available
44
+ #
45
+ # Returns true if the mixin strategy is available on the current ruby
46
+ # platform. This will be false for most platforms.
47
+
48
+ def self.mixin_available?
49
+ !::Blockenspiel::Unmixer.const_defined?(:UNIMPLEMENTED)
50
+ end
51
+
52
+
43
53
  # === Invoke a given DSL
44
54
  #
45
55
  # This is the entry point for Blockenspiel. Call this function to invoke
@@ -135,20 +145,8 @@ module Blockenspiel
135
145
  # The following values control the precise behavior of parameterless
136
146
  # blocks. These are values for the <tt>:parameterless</tt> option.
137
147
  #
138
- # [<tt>:mixin</tt>]
139
- # This is the default behavior. DSL methods from the target are
140
- # temporarily overlayed on the caller's +self+ object, but +self+ still
141
- # points to the same object, so the helper methods and instance
142
- # variables from the caller's closure remain available. The DSL methods
143
- # are removed when the block completes.
144
- # [<tt>:instance</tt>]
145
- # This behavior actually changes +self+ to the target object using
146
- # <tt>instance_eval</tt>. Thus, the caller loses access to its own
147
- # helper methods and instance variables, and instead gains access to the
148
- # target object's instance variables. The target object's methods are
149
- # not modified: this behavior does not apply any DSL method changes
150
- # specified using <tt>dsl_method</tt> directives.
151
148
  # [<tt>:proxy</tt>]
149
+ # This is the default behavior for parameterless blocks.
152
150
  # This behavior changes +self+ to a proxy object created by applying the
153
151
  # DSL methods to an empty object, whose <tt>method_missing</tt> points
154
152
  # back at the block's context. This behavior is a compromise between
@@ -159,6 +157,19 @@ module Blockenspiel
159
157
  # object's instance variables are not available (and thus cannot be
160
158
  # clobbered) in the block, and the transformations specified by
161
159
  # <tt>dsl_method</tt> directives are honored.
160
+ # [<tt>:instance</tt>]
161
+ # This behavior changes +self+ directly to the target object using
162
+ # <tt>instance_eval</tt>. Thus, the caller loses access to its own
163
+ # helper methods and instance variables, and instead gains access to the
164
+ # target object's instance variables. The target object's methods are
165
+ # not modified: this behavior does not apply any DSL method changes
166
+ # specified using <tt>dsl_method</tt> directives.
167
+ # [<tt>:mixin</tt>]
168
+ # This behavior is not available on all ruby platforms. DSL methods from
169
+ # the target are temporarily overlayed on the caller's +self+ object, but
170
+ # +self+ still points to the same object. Thus the helper methods and
171
+ # instance variables from the caller's closure remain available. The DSL
172
+ # methods are removed when the block completes.
162
173
  #
163
174
  # === String DSL options
164
175
  #
@@ -328,7 +339,7 @@ module Blockenspiel
328
339
  end
329
340
 
330
341
  # Execute the DSL using the proxy method.
331
- _execute_dsl(true, nil, eval_str_, target_, file_, line_)
342
+ _execute_dsl(false, nil, eval_str_, target_, file_, line_)
332
343
  end
333
344
 
334
345
 
@@ -368,7 +379,7 @@ module Blockenspiel
368
379
  end
369
380
 
370
381
  # Execute the DSL
371
- _execute_dsl(parameterless_ == :proxy, block_, nil, target_, nil, nil)
382
+ _execute_dsl(parameterless_ == :mixin, block_, nil, target_, nil, nil)
372
383
  end
373
384
 
374
385
 
@@ -379,6 +390,10 @@ module Blockenspiel
379
390
 
380
391
  class ProxyDelegator # :nodoc:
381
392
 
393
+ def initialize(delegate_)
394
+ @_blockenspiel_delegate = delegate_
395
+ end
396
+
382
397
  def method_missing(symbol_, *params_, &block_)
383
398
  ::Blockenspiel._proxy_dispatch(self, symbol_, params_, block_)
384
399
  end
@@ -399,7 +414,7 @@ module Blockenspiel
399
414
  # This is the "meat" of Blockenspiel, implementing both the proxy and
400
415
  # mixin methods.
401
416
 
402
- def self._execute_dsl(use_proxy_method_, block_, eval_str_, target_, file_, line_) # :nodoc:
417
+ def self._execute_dsl(use_mixin_method_, block_, eval_str_, target_, file_, line_) # :nodoc:
403
418
  # Get the module of dsl methods
404
419
  mod_ = target_.class._get_blockenspiel_module rescue nil
405
420
  unless mod_
@@ -409,36 +424,7 @@ module Blockenspiel
409
424
  # Get the block's calling context object
410
425
  context_object_ = block_ ? ::Kernel.eval('self', block_.binding) : nil
411
426
 
412
- if use_proxy_method_
413
-
414
- # Create proxy object
415
- proxy_ = ::Blockenspiel::ProxyDelegator.new
416
- proxy_.extend(mod_)
417
-
418
- # Store the target and proxy object so dispatchers can get them
419
- proxy_delegator_key_ = proxy_.object_id
420
- target_stack_key_ = _current_context_id(proxy_)
421
- @_proxy_delegators[proxy_delegator_key_] = context_object_ if context_object_
422
- @_target_stacks[target_stack_key_] = [target_]
423
-
424
- begin
425
-
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
432
-
433
- ensure
434
-
435
- # Clean up the dispatcher information
436
- @_proxy_delegators.delete(proxy_delegator_key_) if context_object_
437
- @_target_stacks.delete(target_stack_key_)
438
-
439
- end
440
-
441
- else
427
+ if use_mixin_method_
442
428
 
443
429
  # Create hash keys
444
430
  mixin_count_key_ = [context_object_.object_id, mod_.object_id]
@@ -486,6 +472,32 @@ module Blockenspiel
486
472
 
487
473
  end
488
474
 
475
+ else
476
+
477
+ # Create proxy object
478
+ proxy_ = ::Blockenspiel::ProxyDelegator.new(context_object_)
479
+ proxy_.extend(mod_)
480
+
481
+ # Store the target object so the dispatcher can get it
482
+ target_stack_key_ = _current_context_id(proxy_)
483
+ @_target_stacks[target_stack_key_] = [target_]
484
+
485
+ begin
486
+
487
+ # Evaluate with the proxy as self
488
+ if block_
489
+ return proxy_.instance_eval(&block_)
490
+ else
491
+ return proxy_.instance_eval(eval_str_, file_, line_)
492
+ end
493
+
494
+ ensure
495
+
496
+ # Clean up the dispatcher information
497
+ @_target_stacks.delete(target_stack_key_)
498
+
499
+ end
500
+
489
501
  end
490
502
  end
491
503
 
@@ -513,7 +525,7 @@ module Blockenspiel
513
525
  # We look up the context object, and call the given method on that object.
514
526
 
515
527
  def self._proxy_dispatch(proxy_, name_, params_, block_) # :nodoc:
516
- delegate_ = @_proxy_delegators[proxy_.object_id]
528
+ delegate_ = proxy_.instance_variable_get(:@_blockenspiel_delegate)
517
529
  if delegate_
518
530
  delegate_.send(name_, *params_, &block_)
519
531
  else