blockenspiel 0.4.5 → 0.5.0

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