blockenspiel 0.1.0 → 0.1.1
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 +41 -42
- data/Manifest.txt +1 -0
- data/Rakefile +36 -0
- data/lib/blockenspiel.rb +63 -19
- data/tests/tc_basic.rb +2 -1
- data/tests/tc_behaviors.rb +1 -0
- data/tests/tc_dsl_methods.rb +40 -0
- data/tests/tc_dynamic.rb +174 -0
- data/tests/tc_mixins.rb +1 -0
- metadata +4 -2
data/History.txt
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
=== 0.1.1 / 2008-11-06
|
2
|
+
|
3
|
+
* Added ability to pass the block as the first parameter in
|
4
|
+
the dynamic DSL builder API; cleaned up the API a little
|
5
|
+
* Minor fixes to Implementing DSL Blocks paper
|
6
|
+
* Some updates to rdocs
|
7
|
+
* More test coverage
|
8
|
+
|
1
9
|
=== 0.1.0 / 2008-10-29
|
2
10
|
|
3
11
|
* Alpha release, opened for public feedback
|
data/ImplementingDSLblocks.txt
CHANGED
@@ -83,7 +83,7 @@ Perhaps you were introduced to Ruby via the {Rails}[http://www.rubyonrails.org/]
|
|
83
83
|
|
84
84
|
Blocks are central to Ruby as a language, and it feels natural to Ruby programmers to use them to delimit specialized code. When designing an API for a Ruby library, blocks like these are, in many cases, a natural and effective pattern.
|
85
85
|
|
86
|
-
===
|
86
|
+
=== Defining DSL blocks
|
87
87
|
|
88
88
|
Blocks in Ruby are used for a variety of purposes. In many cases, they are used to provide _callbacks_, specifying functionality to inject into an operation. If you come from a functional programming background, you might see them as lambda expressions; in object-oriented-speak, they implement the Visitor pattern. A simple example is the +each+ method, which iterates over a collection, using the given block as a callback that allows the caller to specify processing to perform on each element.
|
89
89
|
|
@@ -97,7 +97,7 @@ Note that in both this case and the Routing case, the information contained in t
|
|
97
97
|
|
98
98
|
The RSpec example illustrates a more sophisticated case with many keywords and multiple levels of blocks, but it shares common features with the Rails examples. Again, a language is being defined to describe things that could conceivably have been passed in as parameters, but are being specified in a block for clarity and readability.
|
99
99
|
|
100
|
-
|
100
|
+
Based on this discussion, we can see that DSL blocks have the following properties:
|
101
101
|
|
102
102
|
* An API requires a caller to communicate complex descriptive information.
|
103
103
|
* The API defines a domain-specific language designed to express this information.
|
@@ -336,9 +336,8 @@ Using the above method, it becomes easy to generate URL strings:
|
|
336
336
|
|
337
337
|
Our <tt>routes.rb</tt> file, utilizing our "improvement" to the routing DSL, might now like this:
|
338
338
|
|
339
|
-
URL_PREFIX = 'mywebsite/:controller/:action/'
|
340
339
|
def makeurl(*params)
|
341
|
-
|
340
|
+
'mywebsite/:controller/:action/' + params.map{ |e| e.inspect }.join('/')
|
342
341
|
end
|
343
342
|
|
344
343
|
ActionController::Routing::Routes.draw do
|
@@ -522,7 +521,7 @@ Here is a sample implementation of these two techniques for handling instance va
|
|
522
521
|
map.instance_eval(&block)
|
523
522
|
ensure # Ensure the hashes are cleaned up and instance
|
524
523
|
map.cleanup # variables are pushed back to original_self,
|
525
|
-
end # even if the block threw
|
524
|
+
end # even if the block threw an exception
|
526
525
|
named_routes.install
|
527
526
|
end
|
528
527
|
|
@@ -709,45 +708,45 @@ One more issue with the mixin strategy is that, like all implementations that dr
|
|
709
708
|
|
710
709
|
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:
|
711
710
|
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
711
|
+
# Suppose we have a DSL block available, via "call_my_dsl",
|
712
|
+
# that implements the methods "foo" and "bar"...
|
713
|
+
|
714
|
+
# First, let's implement a simple class
|
715
|
+
class MyClass
|
716
|
+
|
717
|
+
# A test method
|
718
|
+
def foo
|
719
|
+
puts "in foo"
|
720
|
+
end
|
721
|
+
|
722
|
+
end
|
723
|
+
|
724
|
+
# Create an instance of MyClass
|
725
|
+
obj = MyClass.new
|
726
|
+
|
727
|
+
# Now, add a new method "bar" to the object.
|
728
|
+
def obj.bar
|
729
|
+
puts "in bar"
|
730
|
+
end
|
731
|
+
|
732
|
+
# Finally, add a method "run" that runs a DSL block
|
733
|
+
def obj.run
|
734
|
+
call_my_dsl do
|
735
|
+
foo # DSL "foo" method takes precedence over MyClass#foo
|
736
|
+
bar # The object's "bar" method takes precedence over DSL "bar"
|
737
|
+
end
|
738
|
+
end
|
739
|
+
|
740
|
+
# At this point, obj has methods "foo", "bar", and "run"
|
741
|
+
# Run the DSL block to test the behavior
|
742
|
+
obj.run
|
744
743
|
|
745
744
|
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?
|
746
745
|
|
747
746
|
The reason points 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:
|
748
747
|
|
749
|
-
|
750
|
-
|
748
|
+
singleton methods of obj -> mixin module from the DSL -> methods of MyClass
|
749
|
+
(e.g. bar, run) (e.g. foo, bar) (e.g. foo)
|
751
750
|
|
752
751
|
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.
|
753
752
|
|
@@ -827,14 +826,14 @@ Atop this basic usage, blockenspiel provides two types of customization. First,
|
|
827
826
|
dsl_method :set_title, :title= # blocks under these alternate names.
|
828
827
|
end
|
829
828
|
|
830
|
-
Now, when we use block parameters, we use the methods of the original ConfigMethods class:
|
829
|
+
Now, when we use block parameters, we use the methods of the original +ConfigMethods+ class:
|
831
830
|
|
832
831
|
create_paper do |config|
|
833
832
|
config.author = "Daniel Azuma"
|
834
833
|
config.title = "Implementing DSL Blocks"
|
835
834
|
end
|
836
835
|
|
837
|
-
And
|
836
|
+
And, when we omit the parameter, the alternate method names are mixed in:
|
838
837
|
|
839
838
|
create_paper do
|
840
839
|
set_author "Daniel Azuma"
|
@@ -932,4 +931,4 @@ The Blockenspiel library provides a concrete and robust implementation of DSL bl
|
|
932
931
|
|
933
932
|
=== About the author
|
934
933
|
|
935
|
-
Daniel Azuma is Chief Software Architect at Zoodango. He has been working with Ruby for about three years, and finds the language generally pleasant to work with, though he thinks the scoping rules could use some improvement. His home page is at http://www.daniel-azuma.com
|
934
|
+
Daniel Azuma is Chief Software Architect at Zoodango. He has been working with Ruby for about three years, and finds the language generally pleasant to work with, though he thinks the scoping rules could use some improvement. His home page is at http://www.daniel-azuma.com/
|
data/Manifest.txt
CHANGED
data/Rakefile
CHANGED
@@ -46,4 +46,40 @@ Hoe.new('blockenspiel', Blockenspiel::VERSION_STRING) do |p_|
|
|
46
46
|
p_.extra_deps = [['mixology', '>= 0.1.0']]
|
47
47
|
p_.description_sections = ['blockenspiel']
|
48
48
|
p_.url = 'http://virtuoso.rubyforge.org/blockenspiel'
|
49
|
+
p_.clean_globs << 'idslb_markdown.txt'
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
# Custom task that takes the implementing dsl blocks paper
|
54
|
+
# and converts it from RDoc format to Markdown
|
55
|
+
task :idslb_markdown do
|
56
|
+
File.open('ImplementingDSLblocks.txt') do |read_|
|
57
|
+
File.open('idslb_markdown.txt', 'w') do |write_|
|
58
|
+
linenum_ = 0
|
59
|
+
read_.each do |line_|
|
60
|
+
linenum_ += 1
|
61
|
+
next if linenum_ < 4
|
62
|
+
line_.sub!(/^===\ /, '### ')
|
63
|
+
line_.sub!(/^\ \ /, ' ')
|
64
|
+
if line_[0..3] == '### '
|
65
|
+
line_.gsub!(/(\w)_(\w)/, '\1\_\2')
|
66
|
+
end
|
67
|
+
if line_[0..3] != ' '
|
68
|
+
line_.gsub!('"it_should_behave_like"', '"it\_should\_behave\_like"')
|
69
|
+
line_.gsub!('"time_zone"', '"time\_zone"')
|
70
|
+
line_.gsub!(/\+(\w+)\+/, '`\1`')
|
71
|
+
line_.gsub!(/\*(\w+)\*/, '**\1**')
|
72
|
+
line_.gsub!(/<\/?em>/, '*')
|
73
|
+
line_.gsub!(/<\/?tt>/, '`')
|
74
|
+
line_.gsub!(/<\/?b>/, '**')
|
75
|
+
line_.gsub!(/\{([^\}]+)\}\[([^\]]+)\]/) do |match_|
|
76
|
+
text_, url_ = $1, $2
|
77
|
+
"[#{text_.gsub('_', '\_')}](#{url_})"
|
78
|
+
end
|
79
|
+
line_.gsub!(/\ (http:\/\/[^\s]+)/, ' [\1](\1)')
|
80
|
+
end
|
81
|
+
write_.puts(line_)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
49
85
|
end
|
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.1.
|
49
|
+
VERSION_STRING = '0.1.1'.freeze
|
50
50
|
|
51
51
|
|
52
52
|
# Base exception for all exceptions raised by Blockenspiel
|
@@ -193,17 +193,26 @@ module Blockenspiel
|
|
193
193
|
# To enable automatic exporting of methods to parameterless blocks.
|
194
194
|
# After executing this command, all public methods defined in the class
|
195
195
|
# will be available on parameterless blocks, until
|
196
|
-
# <tt>dsl_methods false</tt> is called
|
196
|
+
# <tt>dsl_methods false</tt> is called:
|
197
197
|
# dsl_methods true
|
198
198
|
#
|
199
199
|
# To disable automatic exporting of methods to parameterless blocks.
|
200
200
|
# After executing this command, methods defined in this class will be
|
201
201
|
# excluded from parameterless blocks, until <tt>dsl_methods true</tt>
|
202
|
-
# is called
|
202
|
+
# is called:
|
203
203
|
# dsl_methods false
|
204
204
|
#
|
205
205
|
# To make a list of methods available to parameterless blocks in bulk:
|
206
206
|
# dsl_methods :my_method1, :my_method2, ...
|
207
|
+
#
|
208
|
+
# You can also point dsl methods to a method of a different name on the
|
209
|
+
# target class, by using a hash syntax, as follows:
|
210
|
+
# dsl_methods :my_method1 => :target_class_method1,
|
211
|
+
# :my_method2 => :target_class_method2
|
212
|
+
#
|
213
|
+
# You can mix non-renamed and renamed method declarations as long as
|
214
|
+
# the renamed (hash) methods are at the end. e.g.:
|
215
|
+
# dsl_methods :my_method1, :my_method2 => :target_class_method2
|
207
216
|
|
208
217
|
def dsl_methods(*names_)
|
209
218
|
if names_.size == 0 || names_ == [true]
|
@@ -311,12 +320,13 @@ module Blockenspiel
|
|
311
320
|
|
312
321
|
def self._invoke_methodinfo(name_, params_, block_)
|
313
322
|
info_ = @_blockenspiel_methodinfo[name_]
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
323
|
+
case info_[1]
|
324
|
+
when :first
|
325
|
+
params_.unshift(block_)
|
326
|
+
when :last
|
327
|
+
params_.push(block_)
|
319
328
|
end
|
329
|
+
info_[0].call(*params_)
|
320
330
|
end
|
321
331
|
|
322
332
|
end
|
@@ -337,22 +347,56 @@ module Blockenspiel
|
|
337
347
|
end
|
338
348
|
|
339
349
|
|
340
|
-
#
|
350
|
+
# === Declare a DSL method.
|
341
351
|
#
|
352
|
+
# This call creates a method that can be called from the DSL block.
|
342
353
|
# Provide a name for the method, and a block defining the method's
|
343
|
-
# implementation.
|
354
|
+
# implementation. You may also provided a list of options as follows:
|
355
|
+
#
|
356
|
+
# The <tt>:block</tt> option controls how the generated method reports
|
357
|
+
# any block provided by the caller. If set to +false+ or not given, any
|
358
|
+
# caller-provided block is ignored. If set to <tt>:first</tt>, the
|
359
|
+
# block is _prepended_ (as a +Proc+ object) to the parameters passed to
|
360
|
+
# the method's implementing block. If set to <tt>:last</tt>, the block
|
361
|
+
# is _appended_. In either case, if the caller does not provide a block,
|
362
|
+
# a value of +nil+ is pre- or appended to the parameter list. A value of
|
363
|
+
# +true+ is equivalent to <tt>:first</tt>.
|
364
|
+
# (This is a workaround for the fact that blocks cannot themselves take
|
365
|
+
# block parameters in Ruby 1.8.)
|
366
|
+
#
|
367
|
+
# For example, to create a method named "foo" that takes one parameter
|
368
|
+
# and a block, do this:
|
369
|
+
#
|
370
|
+
# add_method(:foo, :block => :first) do |block, param|
|
371
|
+
# puts "foo called with parameter "+param.inspect
|
372
|
+
# puts "the block returned "+block.call.inspect
|
373
|
+
# end
|
374
|
+
#
|
375
|
+
# In your DSL, you can then call:
|
376
|
+
#
|
377
|
+
# foo("hello"){ "a value" }
|
378
|
+
#
|
379
|
+
# By default, a method of the same name is also made available to
|
380
|
+
# parameterless blocks. To change the name of the parameterless method,
|
381
|
+
# provide its name as the value of the <tt>:dsl_method</tt> option.
|
382
|
+
# To disable this method for parameterless blocks, set the
|
383
|
+
# <tt>:dsl_method</tt> option to +false+.
|
384
|
+
#
|
385
|
+
# For historical reasons, the <tt>:mixin</tt> option is an alias for
|
386
|
+
# <tt>:dsl_method</tt>. However, its use is deprecated.
|
344
387
|
#
|
345
|
-
#
|
346
|
-
#
|
347
|
-
#
|
348
|
-
# mixin method, set the <tt>:mixin</tt> parameter to +false+.
|
388
|
+
# The <tt>:receive_block</tt> option is also supported for historical
|
389
|
+
# reasons, but its use is deprecated. Setting <tt>:receive_block</tt> to
|
390
|
+
# +true+ is equivalent to setting <tt>:block</tt> to <tt>:last</tt>.
|
349
391
|
|
350
392
|
def add_method(name_, opts_={}, &block_)
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
393
|
+
receive_block_ = opts_[:receive_block] ? :last : opts_[:block]
|
394
|
+
receive_block_ = :first if receive_block_ && receive_block_ != :last
|
395
|
+
@target_class._add_methodinfo(name_, block_, receive_block_)
|
396
|
+
dsl_method_name_ = opts_[:dsl_method] || opts_[:mixin]
|
397
|
+
if dsl_method_name_ != false
|
398
|
+
dsl_method_name_ = name_ if dsl_method_name_.nil? || dsl_method_name_ == true
|
399
|
+
@target_class.dsl_method(dsl_method_name_, name_)
|
356
400
|
end
|
357
401
|
end
|
358
402
|
|
data/tests/tc_basic.rb
CHANGED
@@ -33,6 +33,7 @@
|
|
33
33
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
34
34
|
# POSSIBILITY OF SUCH DAMAGE.
|
35
35
|
# -----------------------------------------------------------------------------
|
36
|
+
;
|
36
37
|
|
37
38
|
|
38
39
|
require File.expand_path("#{File.dirname(__FILE__)}/../lib/blockenspiel.rb")
|
@@ -120,7 +121,7 @@ module Blockenspiel
|
|
120
121
|
add_method(:set_value) do |key_, value_|
|
121
122
|
hash_[key_] = value_
|
122
123
|
end
|
123
|
-
add_method(:set_value_by_block, :
|
124
|
+
add_method(:set_value_by_block, :block => true) do |bl_, key_|
|
124
125
|
hash_[key_] = bl_.call
|
125
126
|
end
|
126
127
|
end
|
data/tests/tc_behaviors.rb
CHANGED
@@ -33,6 +33,7 @@
|
|
33
33
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
34
34
|
# POSSIBILITY OF SUCH DAMAGE.
|
35
35
|
# -----------------------------------------------------------------------------
|
36
|
+
;
|
36
37
|
|
37
38
|
|
38
39
|
require File.expand_path("#{File.dirname(__FILE__)}/../lib/blockenspiel.rb")
|
data/tests/tc_dsl_methods.rb
CHANGED
@@ -33,6 +33,7 @@
|
|
33
33
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
34
34
|
# POSSIBILITY OF SUCH DAMAGE.
|
35
35
|
# -----------------------------------------------------------------------------
|
36
|
+
;
|
36
37
|
|
37
38
|
|
38
39
|
require File.expand_path("#{File.dirname(__FILE__)}/../lib/blockenspiel.rb")
|
@@ -174,6 +175,27 @@ module Blockenspiel
|
|
174
175
|
end
|
175
176
|
|
176
177
|
|
178
|
+
class Target6 < Blockenspiel::Base
|
179
|
+
|
180
|
+
def initialize(hash_)
|
181
|
+
@hash = hash_
|
182
|
+
end
|
183
|
+
|
184
|
+
dsl_methods false
|
185
|
+
|
186
|
+
def set_value1(key_, value_)
|
187
|
+
@hash["#{key_}1"] = value_
|
188
|
+
end
|
189
|
+
|
190
|
+
def set_value2(key_)
|
191
|
+
@hash["#{key_}2"] = yield
|
192
|
+
end
|
193
|
+
|
194
|
+
dsl_methods :set_value1, :renamed_set_value2 => :set_value2
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
|
177
199
|
# Test default dsl method setting.
|
178
200
|
#
|
179
201
|
# * Asserts the right dsl methods are added for the default setting.
|
@@ -277,6 +299,24 @@ module Blockenspiel
|
|
277
299
|
end
|
278
300
|
|
279
301
|
|
302
|
+
# Test dsl_methods with multiple parameters.
|
303
|
+
#
|
304
|
+
# * Asserts that hash syntax for dsl_methods works.
|
305
|
+
# * Asserts that combined array and hash parameters works.
|
306
|
+
|
307
|
+
def test_multiple_dsl_methods
|
308
|
+
hash_ = Hash.new
|
309
|
+
block_ = proc do
|
310
|
+
set_value1('a', 1)
|
311
|
+
renamed_set_value2('b'){ 2 }
|
312
|
+
assert_raise(NoMethodError){ set_value2('c', 3) }
|
313
|
+
end
|
314
|
+
Blockenspiel.invoke(block_, Target6.new(hash_))
|
315
|
+
assert_equal(1, hash_['a1'])
|
316
|
+
assert_equal(2, hash_['b2'])
|
317
|
+
end
|
318
|
+
|
319
|
+
|
280
320
|
end
|
281
321
|
|
282
322
|
end
|
data/tests/tc_dynamic.rb
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
#
|
3
|
+
# Blockenspiel dynamic tests
|
4
|
+
#
|
5
|
+
# This file contains tests for dynamic DSL generation.
|
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
|
+
|
39
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../lib/blockenspiel.rb")
|
40
|
+
|
41
|
+
|
42
|
+
module Blockenspiel
|
43
|
+
module Tests # :nodoc:
|
44
|
+
|
45
|
+
class TestDynamic < Test::Unit::TestCase # :nodoc:
|
46
|
+
|
47
|
+
|
48
|
+
# Test the simple case.
|
49
|
+
#
|
50
|
+
# * Asserts that the simplest case works.
|
51
|
+
|
52
|
+
def test_simple
|
53
|
+
block_ = proc do
|
54
|
+
set_value(:a, 1)
|
55
|
+
end
|
56
|
+
hash_ = Hash.new
|
57
|
+
Blockenspiel.invoke(block_) do
|
58
|
+
add_method(:set_value) do |key_, value_|
|
59
|
+
hash_[key_] = value_
|
60
|
+
end
|
61
|
+
end
|
62
|
+
assert_equal(1, hash_[:a])
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
# Test renaming.
|
67
|
+
#
|
68
|
+
# * Asserts that the method appears renamed in a parameterless block.
|
69
|
+
# * Asserts that the method appears in its original name in a parametered block.
|
70
|
+
|
71
|
+
def test_renaming
|
72
|
+
hash_ = Hash.new
|
73
|
+
dsl_definition_ = proc do
|
74
|
+
add_method(:set_value, :dsl_method => :renamed_set_value) do |key_, value_|
|
75
|
+
hash_[key_] = value_
|
76
|
+
end
|
77
|
+
end
|
78
|
+
block1_ = proc do
|
79
|
+
renamed_set_value(:a, 1)
|
80
|
+
assert_raise(NoMethodError){ set_value(:b, 2) }
|
81
|
+
end
|
82
|
+
Blockenspiel.invoke(block1_, &dsl_definition_)
|
83
|
+
block2_ = proc do |dsl_|
|
84
|
+
dsl_.set_value(:c, 3)
|
85
|
+
assert_raise(NoMethodError){ renamed_set_value(:d, 4) }
|
86
|
+
end
|
87
|
+
Blockenspiel.invoke(block2_, &dsl_definition_)
|
88
|
+
assert_equal(1, hash_[:a])
|
89
|
+
assert_nil(hash_[:b])
|
90
|
+
assert_equal(3, hash_[:c])
|
91
|
+
assert_nil(hash_[:d])
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
# Test calls with blocks.
|
96
|
+
#
|
97
|
+
# * Asserts that a block passed "first" works.
|
98
|
+
# * Asserts that a block passed "last" works.
|
99
|
+
# * Asserts that a block passed "last" works.
|
100
|
+
|
101
|
+
def test_blocks_first_and_last
|
102
|
+
hash_ = Hash.new
|
103
|
+
dsl_definition_ = proc do
|
104
|
+
add_method(:set_value1, :block => :first) do |bl_, key_|
|
105
|
+
hash_[key_] = bl_.call
|
106
|
+
end
|
107
|
+
add_method(:set_value2, :block => :last) do |key_, bl_|
|
108
|
+
hash_[key_] = bl_.call
|
109
|
+
end
|
110
|
+
add_method(:set_value3, :block => true) do |bl_, key_|
|
111
|
+
hash_[key_] = bl_.call
|
112
|
+
end
|
113
|
+
end
|
114
|
+
block_ = proc do
|
115
|
+
set_value1(:a){ 1 }
|
116
|
+
set_value2(:b){ 2 }
|
117
|
+
set_value2(:c){ 3 }
|
118
|
+
end
|
119
|
+
Blockenspiel.invoke(block_, &dsl_definition_)
|
120
|
+
assert_equal(1, hash_[:a])
|
121
|
+
assert_equal(2, hash_[:b])
|
122
|
+
assert_equal(3, hash_[:c])
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
# Test calls with blocks not passed.
|
127
|
+
#
|
128
|
+
# * Asserts that if a block isn't given, it is set to nil.
|
129
|
+
|
130
|
+
def test_blocks_nil
|
131
|
+
hash_ = Hash.new
|
132
|
+
dsl_definition_ = proc do
|
133
|
+
add_method(:set_value1, :block => :first) do |bl_, key_|
|
134
|
+
assert_nil(bl_)
|
135
|
+
end
|
136
|
+
add_method(:set_value2, :block => :last) do |key_, bl_|
|
137
|
+
assert_nil(bl_)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
block_ = proc do
|
141
|
+
set_value1(:a)
|
142
|
+
set_value2(:b)
|
143
|
+
end
|
144
|
+
Blockenspiel.invoke(block_, &dsl_definition_)
|
145
|
+
assert_nil(hash_[:a])
|
146
|
+
assert_nil(hash_[:b])
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
# Test calls with blocks (legacy api)
|
151
|
+
#
|
152
|
+
# * Asserts that a block passed "first" works.
|
153
|
+
# * Asserts that a block passed "last" works.
|
154
|
+
# * Asserts that a block passed "last" works.
|
155
|
+
|
156
|
+
def test_blocks_legacy
|
157
|
+
hash_ = Hash.new
|
158
|
+
dsl_definition_ = proc do
|
159
|
+
add_method(:set_value, :receive_block => true) do |key_, bl_|
|
160
|
+
hash_[key_] = bl_.call
|
161
|
+
end
|
162
|
+
end
|
163
|
+
block_ = proc do
|
164
|
+
set_value(:a){ 1 }
|
165
|
+
end
|
166
|
+
Blockenspiel.invoke(block_, &dsl_definition_)
|
167
|
+
assert_equal(1, hash_[:a])
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
data/tests/tc_mixins.rb
CHANGED
@@ -34,6 +34,7 @@
|
|
34
34
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
35
35
|
# POSSIBILITY OF SUCH DAMAGE.
|
36
36
|
# -----------------------------------------------------------------------------
|
37
|
+
;
|
37
38
|
|
38
39
|
|
39
40
|
require File.expand_path("#{File.dirname(__FILE__)}/../lib/blockenspiel.rb")
|
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.1.
|
4
|
+
version: 0.1.1
|
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-
|
12
|
+
date: 2008-11-06 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -54,6 +54,7 @@ files:
|
|
54
54
|
- tests/tc_basic.rb
|
55
55
|
- tests/tc_behaviors.rb
|
56
56
|
- tests/tc_dsl_methods.rb
|
57
|
+
- tests/tc_dynamic.rb
|
57
58
|
- tests/tc_mixins.rb
|
58
59
|
has_rdoc: true
|
59
60
|
homepage: http://virtuoso.rubyforge.org/blockenspiel
|
@@ -86,4 +87,5 @@ test_files:
|
|
86
87
|
- tests/tc_basic.rb
|
87
88
|
- tests/tc_behaviors.rb
|
88
89
|
- tests/tc_dsl_methods.rb
|
90
|
+
- tests/tc_dynamic.rb
|
89
91
|
- tests/tc_mixins.rb
|