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 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
@@ -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. There are better alternatives.
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
- As we have seen, the mixin idea seems like 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.
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
- * Does nothing to solve the helper method vs DSL method ambiguity
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, if a library is available to handle the details of the implementation.
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.2'
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._delegate(:#{name_}, params_, block_)
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
- # Mixin behavior
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 thread and self context
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
- # Mix this module into the object, if required.
471
- # This ensures that we keep track of the number of requests to
472
- # mix this module in, from nested blocks and possibly multiple threads.
473
- @_mutex.synchronize do
474
- count_ = @_mixin_counts[[object_id_, mod_]]
475
- if count_
476
- @_mixin_counts[[object_id_, mod_]] = count_ + 1
477
- else
478
- @_mixin_counts[[object_id_, mod_]] = 1
479
- object_.mixin(mod_)
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
- # Now call the block
486
- return block_.call
505
+ else
487
506
 
488
- ensure
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
- # Clean up the target stack
491
- target_stack_.pop
492
- @_target_stacks.delete(thread_id_) if target_stack_.size == 0
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
- # Remove the mixin from the object, if required.
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[[object_id_, mod_]]
497
- if count_ == 1
498
- @_mixin_counts.delete([object_id_, mod_])
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[[object_id_, mod_]] = count_ - 1
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 mixin behavior
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._delegate(name_, params_, block_) # :nodoc:
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 |elem_|
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.2
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-21 00:00:00 -07:00
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.0
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