blockenspiel 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +8 -0
- data/ImplementingDSLblocks.txt +55 -5
- data/README.txt +3 -1
- data/lib/blockenspiel.rb +111 -54
- data/tests/tc_behaviors.rb +137 -0
- metadata +4 -3
data/History.txt
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
=== 0.0.3 / 2008-10-23
|
2
|
+
|
3
|
+
* Added :proxy behavior for parameterless blocks
|
4
|
+
* Removed option to turn off inheriting, since the semantics are somewhat
|
5
|
+
ill-defined and inconsistent. All parameterless blocks now exhibit the
|
6
|
+
inheriting behavior.
|
7
|
+
* Added tests for the different behavior settings.
|
8
|
+
|
1
9
|
=== 0.0.2 / 2008-10-21
|
2
10
|
|
3
11
|
* Cleaned up some of the documentation
|
data/ImplementingDSLblocks.txt
CHANGED
@@ -335,7 +335,7 @@ So does this mean we're stuck with block parameters for better or worse? Not qui
|
|
335
335
|
* Breaks encapuslation of the proxy class.
|
336
336
|
* Possibility of a helper method vs DSL method ambiguity.
|
337
337
|
|
338
|
-
*Verdict*: Use it if you are writing a DSL that constructs classes or modifies class internals. Otherwise avoid it.
|
338
|
+
*Verdict*: Use it if you are writing a DSL that constructs classes or modifies class internals. Otherwise try to avoid it.
|
339
339
|
|
340
340
|
=== Implementation strategy 3: delegation
|
341
341
|
|
@@ -448,7 +448,7 @@ Let's wrap up our discussion of delegation and then delve into some different, a
|
|
448
448
|
* Does nothing to solve the helper method vs DSL method ambiguity.
|
449
449
|
* Harder to implement than a simple <tt>instance_eval</tt>.
|
450
450
|
|
451
|
-
*Verdict*: Use it for cases where <tt>instance_eval</tt> is appropriate (i.e. if you are writing a DSL that constructs classes or modifies class internals) and are worried about helper methods being available. Otherwise avoid it.
|
451
|
+
*Verdict*: Use it for cases where <tt>instance_eval</tt> is appropriate (i.e. if you are writing a DSL that constructs classes or modifies class internals) and are worried about helper methods being available. Otherwise try to avoid it.
|
452
452
|
|
453
453
|
=== Implementation strategy 4: arity detection
|
454
454
|
|
@@ -588,7 +588,57 @@ A third approach involves dynamically generating a singleton module, "hard codin
|
|
588
588
|
|
589
589
|
This probably can be made to work, and it also has the benefit of solving the nesting and multithreading issue neatly since each mixin is done exactly once. However, it seems to be a fairly heavyweight solution: creating a new module for every DSL block invocation may have performance implications. It is also not clear how to support constructs that are not available to <tt>define_method</tt>, such as blocks and parameter default values. However, such an approach may still be useful in certain cases when you need a dynamically generated DSL depending on the context.
|
590
590
|
|
591
|
-
|
591
|
+
One more issue with the mixin strategy is that, like all implementations that drop the block parameter, there is an ambiguity regarding whether methods should be directed to the DSL or to the surrounding context. In the implementations we've discussed previously, based on <tt>instance_eval</tt>, the actual behavior is fairly straightforward to reason about. A simple <tt>instance_eval</tt> disables method calls to the block's context altogether: you can call _only_ the DSL methods. An <tt>instance_eval</tt> with delegation re-enables method calls to the block's context but gives the DSL priority. If both the DSL and the surrounding block define the same method name, the DSL's method will be called.
|
592
|
+
|
593
|
+
Mixin's behavior is less straightforward, because of a subtlety in Ruby's method lookup behavior. Under most cases, it behaves similarly to an <tt>instance_eval</tt> with delegation: the DSL's methods take priority. However, if methods have been added directly to the object, they will take precedence over the DSL's methods. Following is an example of this case:
|
594
|
+
|
595
|
+
# Suppose we have a DSL block available, via "call_my_dsl",
|
596
|
+
# that implements the methods "foo" and "bar"...
|
597
|
+
|
598
|
+
# First, let's implement a simple class
|
599
|
+
class MyClass
|
600
|
+
|
601
|
+
# A test method
|
602
|
+
def foo
|
603
|
+
puts "in foo"
|
604
|
+
end
|
605
|
+
|
606
|
+
end
|
607
|
+
|
608
|
+
# Create an instance of MyClass
|
609
|
+
obj = MyClass.new
|
610
|
+
|
611
|
+
# Now, add a new method "bar" to the object.
|
612
|
+
def obj.bar
|
613
|
+
puts "in bar"
|
614
|
+
end
|
615
|
+
|
616
|
+
# Finally, add a method "run" that runs a DSL block
|
617
|
+
def obj.run
|
618
|
+
call_my_dsl do
|
619
|
+
foo # DSL "foo" method takes precedence over MyClass#foo
|
620
|
+
bar # The object's "bar" method takes precedence over DSL "bar"
|
621
|
+
end
|
622
|
+
end
|
623
|
+
|
624
|
+
# At this point, obj has methods "foo", "bar", and "run"
|
625
|
+
# Run the DSL block to test the behavior
|
626
|
+
obj.run
|
627
|
+
|
628
|
+
In the above example, suppose both +foo+ and +bar+ are methods of the DSL. They are also both defined as methods of +obj+. (+foo+ is available because it is a method of +MyClass+, while +bar+ is available because it is explicitly added to +obj+.) However, if you run the code, it calls the DSL's +foo+ but +obj+'s +bar+. Why?
|
629
|
+
|
630
|
+
The reason is due to a subtlety in how Ruby does method lookup. When you define a method in the way +foo+ is defined, it is just added to the class. However, when you define a method in the way +bar+ is defined, it is defined as a "singleton method", and added to the "singleton class", which is an anonymous class that holds methods defined directly on a particular object. It turns out that the singleton class is always given the highest priority in method lookup. So, for example, the lookup order for methods of +obj+ within the block would look like this:
|
631
|
+
|
632
|
+
singleton methods of obj -> mixin module from the DSL -> methods of MyClass
|
633
|
+
(e.g. bar, run) (e.g. foo, bar) (e.g. foo)
|
634
|
+
|
635
|
+
So when the +foo+ method is called, it is not found in the singleton class, but it is found in the mixin, so the mixin's version is invoked. However, when +bar+ is called, it is found in the singleton class, so that version is invoked in favor of the mixin's version.
|
636
|
+
|
637
|
+
Does this esoteric-sounding case actually happen in practice? In fact it does, quite frequently: class methods are singleton methods of the class object, so you should beware of this issue when designing a DSL block that will be called from a class method.
|
638
|
+
|
639
|
+
Well, that was confusing. It is on account of such behavior that we need to take the method lookup ambiguity seriously when dealing with mixins. In fact, I would go so far as to suggest that the mixin implementation should always go hand-in-hand with a way to mitigate that ambiguity, such as Gray's arity check.
|
640
|
+
|
641
|
+
As we have seen, the mixin idea seems like it may be a compelling solution, particularly in conjunction with Gray's arity check, but the implementation details present some challenges. It may be a winner if a library can be written to hide the implementation complexity. Let's summarize this approach, and then proceed to examine such a library, one that uses some of the best of what we've discussed to make implementing DSL blocks simple.
|
592
642
|
|
593
643
|
*Implementation*:
|
594
644
|
|
@@ -606,9 +656,9 @@ As we have seen, the mixin idea seems like a compelling solution, particularly i
|
|
606
656
|
|
607
657
|
* Requires an extension to Ruby to implement mixin removal.
|
608
658
|
* Implementation is complicated and error-prone.
|
609
|
-
*
|
659
|
+
* The helper method vs DSL method ambiguity remains, exhibiting surprising behavior in the presence of singleton methods.
|
610
660
|
|
611
|
-
*Verdict*: Use it for cases where parameterless blocks are desired,
|
661
|
+
*Verdict*: Use it for cases where parameterless blocks are desired and the method lookup ambiguity can be mitigated, as long as a library is available to handle the details of the implementation.
|
612
662
|
|
613
663
|
=== Blockenspiel: a comprehensive implementation
|
614
664
|
|
data/README.txt
CHANGED
@@ -262,7 +262,9 @@ want the <tt>instance_eval</tt> behavior anyway. RSpec is a good example of
|
|
262
262
|
such a case, since the DSL is being used to construct objects, so it makes
|
263
263
|
sense for instance variables inside the block to belong to the object
|
264
264
|
being constructed. Blockenspiel gives you the option of choosing
|
265
|
-
<tt>instance_eval</tt> in case you need it.
|
265
|
+
<tt>instance_eval</tt> in case you need it. Blockenspiel also provides a
|
266
|
+
compromise behavior that uses a proxy to dispatch methods to the DSL object
|
267
|
+
or the block's context.
|
266
268
|
|
267
269
|
Blockenspiel also correctly handles nested blocks. e.g.
|
268
270
|
|
data/lib/blockenspiel.rb
CHANGED
@@ -46,7 +46,7 @@ require 'mixology'
|
|
46
46
|
module Blockenspiel
|
47
47
|
|
48
48
|
# Current gem version
|
49
|
-
VERSION_STRING = '0.0.
|
49
|
+
VERSION_STRING = '0.0.3'
|
50
50
|
|
51
51
|
|
52
52
|
# === DSL setup methods
|
@@ -158,7 +158,7 @@ module Blockenspiel
|
|
158
158
|
unless @_blockenspiel_module.public_method_defined?(name_)
|
159
159
|
@_blockenspiel_module.module_eval("
|
160
160
|
def #{name_}(*params_, &block_)
|
161
|
-
val_ = Blockenspiel.
|
161
|
+
val_ = Blockenspiel._target_dispatch(self, :#{name_}, params_, block_)
|
162
162
|
val_ == Blockenspiel::TARGET_MISMATCH ? super(*params_, &block_) : val_
|
163
163
|
end
|
164
164
|
")
|
@@ -241,6 +241,20 @@ module Blockenspiel
|
|
241
241
|
end
|
242
242
|
|
243
243
|
|
244
|
+
# Class for proxy delegators.
|
245
|
+
# The proxy behavior creates one of these delegators, mixes in the dsl
|
246
|
+
# methods, and uses instance_eval to invoke the block. This class delegates
|
247
|
+
# non-handled methods to the context object.
|
248
|
+
|
249
|
+
class ProxyDelegator # :nodoc:
|
250
|
+
|
251
|
+
def method_missing(symbol_, *params_, &block_)
|
252
|
+
Blockenspiel._proxy_dispatch(self, symbol_, params_, block_)
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
256
|
+
|
257
|
+
|
244
258
|
# === Dynamically construct a target
|
245
259
|
#
|
246
260
|
# These methods are available in a block passed to Blockenspiel#invoke and
|
@@ -330,6 +344,7 @@ module Blockenspiel
|
|
330
344
|
|
331
345
|
@_target_stacks = Hash.new
|
332
346
|
@_mixin_counts = Hash.new
|
347
|
+
@_proxy_delegators = Hash.new
|
333
348
|
@_mutex = Mutex.new
|
334
349
|
|
335
350
|
|
@@ -365,19 +380,23 @@ module Blockenspiel
|
|
365
380
|
# not modified, so the helper methods and instance variables from the
|
366
381
|
# caller's closure remain available. The DSL methods are removed when
|
367
382
|
# the block completes.
|
368
|
-
# <tt>:mixin_inheriting</tt>::
|
369
|
-
# This behavior is the same as mixin, with an additional feature when
|
370
|
-
# DSL blocks are nested. Under normal mixin, only the current block's
|
371
|
-
# DSL methods are available; any outer blocks have their methods
|
372
|
-
# disabled. If you use mixin_inheriting, and a method is not implemented
|
373
|
-
# in the current block, then the next outer block is given a chance to
|
374
|
-
# handle it-- that is, this block "inherits" methods from any block it
|
375
|
-
# is nested within.
|
376
383
|
# <tt>:instance</tt>::
|
377
384
|
# This behavior actually changes +self+ to the target object using
|
378
385
|
# <tt>instance_eval</tt>. Thus, the caller loses access to its own
|
379
386
|
# helper methods and instance variables, and instead gains access to the
|
380
|
-
# target object's instance variables.
|
387
|
+
# target object's instance variables. Any DSL method changes applied
|
388
|
+
# using <tt>dsl_method</tt> directives are ignored.
|
389
|
+
# <tt>:proxy</tt>::
|
390
|
+
# This behavior changes +self+ to a proxy object created by applying the
|
391
|
+
# DSL methods to an empty object, whose <tt>method_missing</tt> points
|
392
|
+
# back at the block's context. This behavior is a compromise between
|
393
|
+
# instance and mixin. As with instance, +self+ is changed, so the caller
|
394
|
+
# loses access to its own instance variables. However, the caller's own
|
395
|
+
# methods should still be available since any methods not handled by the
|
396
|
+
# DSL are delegated back to the caller. Also, as with mixin, the target
|
397
|
+
# object's instance variables are not available (and thus cannot be
|
398
|
+
# clobbered) in the block, and the transformations specified by
|
399
|
+
# <tt>dsl_method</tt> directives are honored.
|
381
400
|
#
|
382
401
|
# === Dynamic target generation
|
383
402
|
#
|
@@ -446,69 +465,101 @@ module Blockenspiel
|
|
446
465
|
if parameterless_ == :instance
|
447
466
|
|
448
467
|
# Instance-eval behavior.
|
449
|
-
# Note: this does not honor DSL method renaming, etc.
|
450
|
-
# Not sure how best to handle those cases, since we cannot
|
451
|
-
# overlay the module on its own target.
|
452
468
|
return target_.instance_eval(&block_)
|
453
469
|
|
454
470
|
else
|
455
471
|
|
456
|
-
#
|
472
|
+
# Remaining behaviors use the module of dsl methods
|
457
473
|
mod_ = target_.class._get_blockenspiel_module rescue nil
|
458
474
|
if mod_
|
459
475
|
|
460
|
-
# Get the
|
461
|
-
thread_id_ = Thread.current.object_id
|
476
|
+
# Get the block's calling context object
|
462
477
|
object_ = Kernel.eval('self', block_.binding)
|
463
|
-
object_id_ = object_.object_id
|
464
|
-
|
465
|
-
# Store the target for inheriting.
|
466
|
-
# We maintain a target call stack per thread.
|
467
|
-
target_stack_ = @_target_stacks[thread_id_] ||= Array.new
|
468
|
-
target_stack_.push([target_, parameterless_ == :mixin_inheriting])
|
469
478
|
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
479
|
+
if parameterless_ == :proxy
|
480
|
+
|
481
|
+
# Proxy behavior:
|
482
|
+
# Create proxy object
|
483
|
+
proxy_ = ProxyDelegator.new
|
484
|
+
proxy_.extend(mod_)
|
485
|
+
|
486
|
+
# Store the target and proxy object so dispatchers can get them
|
487
|
+
proxy_delegator_key_ = proxy_.object_id
|
488
|
+
target_stack_key_ = [Thread.current.object_id, proxy_.object_id]
|
489
|
+
@_proxy_delegators[proxy_delegator_key_] = object_
|
490
|
+
@_target_stacks[target_stack_key_] = [target_]
|
491
|
+
|
492
|
+
begin
|
493
|
+
|
494
|
+
# Call the block with the proxy as self
|
495
|
+
return proxy_.instance_eval(&block_)
|
496
|
+
|
497
|
+
ensure
|
498
|
+
|
499
|
+
# Clean up the dispatcher information
|
500
|
+
@_proxy_delegators.delete(proxy_delegator_key_)
|
501
|
+
@_target_stacks.delete(target_stack_key_)
|
502
|
+
|
480
503
|
end
|
481
|
-
end
|
482
|
-
|
483
|
-
begin
|
484
504
|
|
485
|
-
|
486
|
-
return block_.call
|
505
|
+
else
|
487
506
|
|
488
|
-
|
507
|
+
# Mixin behavior:
|
508
|
+
# Create hash keys
|
509
|
+
mixin_count_key_ = [object_.object_id, mod_.object_id]
|
510
|
+
target_stack_key_ = [Thread.current.object_id, object_.object_id]
|
489
511
|
|
490
|
-
#
|
491
|
-
|
492
|
-
@_target_stacks
|
512
|
+
# Store the target for inheriting.
|
513
|
+
# We maintain a target call stack per thread.
|
514
|
+
target_stack_ = @_target_stacks[target_stack_key_] ||= Array.new
|
515
|
+
target_stack_.push(target_)
|
493
516
|
|
494
|
-
#
|
517
|
+
# Mix this module into the object, if required.
|
518
|
+
# This ensures that we keep track of the number of requests to
|
519
|
+
# mix this module in, from nested blocks and possibly multiple threads.
|
495
520
|
@_mutex.synchronize do
|
496
|
-
count_ = @_mixin_counts[
|
497
|
-
if count_
|
498
|
-
@_mixin_counts
|
499
|
-
object_.unmix(mod_)
|
521
|
+
count_ = @_mixin_counts[mixin_count_key_]
|
522
|
+
if count_
|
523
|
+
@_mixin_counts[mixin_count_key_] = count_ + 1
|
500
524
|
else
|
501
|
-
@_mixin_counts[
|
525
|
+
@_mixin_counts[mixin_count_key_] = 1
|
526
|
+
object_.mixin(mod_)
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
begin
|
531
|
+
|
532
|
+
# Now call the block
|
533
|
+
return block_.call
|
534
|
+
|
535
|
+
ensure
|
536
|
+
|
537
|
+
# Clean up the target stack
|
538
|
+
target_stack_.pop
|
539
|
+
@_target_stacks.delete(target_stack_key_) if target_stack_.size == 0
|
540
|
+
|
541
|
+
# Remove the mixin from the object, if required.
|
542
|
+
@_mutex.synchronize do
|
543
|
+
count_ = @_mixin_counts[mixin_count_key_]
|
544
|
+
if count_ == 1
|
545
|
+
@_mixin_counts.delete(mixin_count_key_)
|
546
|
+
object_.unmix(mod_)
|
547
|
+
else
|
548
|
+
@_mixin_counts[mixin_count_key_] = count_ - 1
|
549
|
+
end
|
502
550
|
end
|
551
|
+
|
503
552
|
end
|
553
|
+
# End mixin behavior
|
504
554
|
|
505
555
|
end
|
506
556
|
|
507
557
|
end
|
508
|
-
# End
|
558
|
+
# End use of dsl methods module
|
509
559
|
|
510
560
|
end
|
511
561
|
end
|
562
|
+
# End attempt of parameterless block
|
512
563
|
|
513
564
|
# Attempt parametered block
|
514
565
|
if opts_[:parameter] != false && block_.arity != 0
|
@@ -526,20 +577,26 @@ module Blockenspiel
|
|
526
577
|
# Then we attempt to call the given method on that object.
|
527
578
|
# If we can't find an appropriate method to call, return the special value TARGET_MISMATCH.
|
528
579
|
|
529
|
-
def self.
|
530
|
-
target_stack_ = @_target_stacks[Thread.current.object_id]
|
580
|
+
def self._target_dispatch(object_, name_, params_, block_) # :nodoc:
|
581
|
+
target_stack_ = @_target_stacks[[Thread.current.object_id, object_.object_id]]
|
531
582
|
return TARGET_MISMATCH unless target_stack_
|
532
|
-
target_stack_.reverse_each do |
|
533
|
-
target_ = elem_[0]
|
583
|
+
target_stack_.reverse_each do |target_|
|
534
584
|
target_class_ = target_.class
|
535
585
|
delegate_ = target_class_._get_blockenspiel_delegate(name_)
|
536
586
|
if delegate_ && target_class_.public_method_defined?(delegate_)
|
537
587
|
return target_.send(delegate_, *params_, &block_)
|
538
588
|
end
|
539
|
-
return TARGET_MISMATCH unless elem_[1]
|
540
589
|
end
|
541
590
|
return TARGET_MISMATCH
|
542
591
|
end
|
543
592
|
|
544
593
|
|
594
|
+
# This implements the proxy fall-back behavior.
|
595
|
+
# We look up the context object, and call the given method on that object.
|
596
|
+
|
597
|
+
def self._proxy_dispatch(proxy_, name_, params_, block_) # :nodoc:
|
598
|
+
@_proxy_delegators[proxy_.object_id].send(name_, *params_, &block_)
|
599
|
+
end
|
600
|
+
|
601
|
+
|
545
602
|
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
#
|
3
|
+
# Blockenspiel behavior tests
|
4
|
+
#
|
5
|
+
# This file contains tests for behavior settings.
|
6
|
+
#
|
7
|
+
# -----------------------------------------------------------------------------
|
8
|
+
# Copyright 2008 Daniel Azuma
|
9
|
+
#
|
10
|
+
# All rights reserved.
|
11
|
+
#
|
12
|
+
# Redistribution and use in source and binary forms, with or without
|
13
|
+
# modification, are permitted provided that the following conditions are met:
|
14
|
+
#
|
15
|
+
# * Redistributions of source code must retain the above copyright notice,
|
16
|
+
# this list of conditions and the following disclaimer.
|
17
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
18
|
+
# this list of conditions and the following disclaimer in the documentation
|
19
|
+
# and/or other materials provided with the distribution.
|
20
|
+
# * Neither the name of the copyright holder, nor the names of any other
|
21
|
+
# contributors to this software, may be used to endorse or promote products
|
22
|
+
# derived from this software without specific prior written permission.
|
23
|
+
#
|
24
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
25
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
26
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
27
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
28
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
29
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
30
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
31
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
32
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
33
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
34
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
35
|
+
# -----------------------------------------------------------------------------
|
36
|
+
|
37
|
+
|
38
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../lib/blockenspiel.rb")
|
39
|
+
|
40
|
+
|
41
|
+
module Blockenspiel
|
42
|
+
module Tests # :nodoc:
|
43
|
+
|
44
|
+
class TextBehaviors < Test::Unit::TestCase # :nodoc:
|
45
|
+
|
46
|
+
|
47
|
+
class Target1 < Blockenspiel::Base
|
48
|
+
|
49
|
+
dsl_methods false
|
50
|
+
|
51
|
+
def initialize(hash_)
|
52
|
+
@hash = hash_
|
53
|
+
end
|
54
|
+
|
55
|
+
def set_value1(key_, value_)
|
56
|
+
@hash["#{key_}1"] = value_
|
57
|
+
end
|
58
|
+
dsl_method :set_value1
|
59
|
+
|
60
|
+
def set_value2(key_)
|
61
|
+
@hash["#{key_}2"] = yield
|
62
|
+
end
|
63
|
+
dsl_method :set_value2
|
64
|
+
|
65
|
+
def set_value3(key_, value_)
|
66
|
+
@hash["#{key_}3"] = value_
|
67
|
+
end
|
68
|
+
dsl_method :set_value3_dslversion, :set_value3
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
def helper_method
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
# Test instance_eval behavior.
|
79
|
+
#
|
80
|
+
# * Asserts that self points at the target.
|
81
|
+
# * Asserts that the target methods are available.
|
82
|
+
# * Asserts that the target methods are not renamed by dsl_method directives.
|
83
|
+
# * Asserts that the caller's instance variables are not available.
|
84
|
+
# * Asserts that the caller's helper methods are not available.
|
85
|
+
|
86
|
+
def test_instance_eval_behavior
|
87
|
+
hash_ = Hash.new
|
88
|
+
context_self_ = self
|
89
|
+
@my_instance_variable_test = :hello
|
90
|
+
block_ = proc do
|
91
|
+
set_value1('a', 1)
|
92
|
+
set_value2('b'){ 2 }
|
93
|
+
set_value3('c', 3)
|
94
|
+
context_self_.assert_raise(NoMethodError){ set_value3_dslversion('d', 4) }
|
95
|
+
context_self_.assert_raise(NoMethodError){ helper_method() }
|
96
|
+
context_self_.assert(!instance_variable_defined?(:@my_instance_variable_test))
|
97
|
+
context_self_.assert_instance_of(Target1, self)
|
98
|
+
end
|
99
|
+
Blockenspiel.invoke(block_, Target1.new(hash_), :parameterless => :instance)
|
100
|
+
assert_equal(1, hash_['a1'])
|
101
|
+
assert_equal(2, hash_['b2'])
|
102
|
+
assert_equal(3, hash_['c3'])
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
# Test proxy behavior.
|
107
|
+
#
|
108
|
+
# * Asserts that self doesn't point at the Target nor the original context.
|
109
|
+
# * Asserts that the target methods are available in their dsl renamed forms.
|
110
|
+
# * Asserts that the caller's instance variables are not available.
|
111
|
+
# * Asserts that the caller's helper methods *are* available.
|
112
|
+
|
113
|
+
def test_proxy_behavior
|
114
|
+
hash_ = Hash.new
|
115
|
+
context_self_ = self
|
116
|
+
@my_instance_variable_test = :hello
|
117
|
+
block_ = proc do
|
118
|
+
set_value1('a', 1)
|
119
|
+
set_value2('b'){ 2 }
|
120
|
+
set_value3_dslversion('c', 3)
|
121
|
+
context_self_.assert_raise(NoMethodError){ set_value3('d', 4) }
|
122
|
+
context_self_.assert(helper_method())
|
123
|
+
context_self_.assert(!instance_variable_defined?(:@my_instance_variable_test))
|
124
|
+
context_self_.assert(!self.kind_of?(Target1))
|
125
|
+
context_self_.assert_not_equal(context_self_, self)
|
126
|
+
end
|
127
|
+
Blockenspiel.invoke(block_, Target1.new(hash_), :parameterless => :proxy)
|
128
|
+
assert_equal(1, hash_['a1'])
|
129
|
+
assert_equal(2, hash_['b2'])
|
130
|
+
assert_equal(3, hash_['c3'])
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: blockenspiel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Azuma
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-10-
|
12
|
+
date: 2008-10-23 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -30,7 +30,7 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 1.8.
|
33
|
+
version: 1.8.1
|
34
34
|
version:
|
35
35
|
description: Blockenspiel is a helper library designed to make it easy to implement DSL blocks. It is designed to be comprehensive and robust, supporting most common usage patterns, and working correctly in the presence of nested blocks and multithreading.
|
36
36
|
email:
|
@@ -83,5 +83,6 @@ specification_version: 2
|
|
83
83
|
summary: Blockenspiel is a helper library designed to make it easy to implement DSL blocks
|
84
84
|
test_files:
|
85
85
|
- tests/tc_basic.rb
|
86
|
+
- tests/tc_behaviors.rb
|
86
87
|
- tests/tc_dsl_methods.rb
|
87
88
|
- tests/tc_mixins.rb
|