blockenspiel 0.2.2-java → 0.3.0-java

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -5,106 +5,29 @@ blocks. It is designed to be comprehensive and robust, supporting most common
5
5
  usage patterns, and working correctly in the presence of nested blocks and
6
6
  multithreading.
7
7
 
8
- === What's a DSL block?
8
+ === Summary
9
9
 
10
- A DSL block is an API pattern in which a method call takes a block that can
11
- provide further configuration for the call. A classic example is the
12
- {Rails}[http://www.rubyonrails.org/] route definition:
13
-
14
- ActionController::Routing::Routes.draw do |map|
15
- map.connect ':controller/:action/:id'
16
- map.connect ':controller/:action/:id.:format'
17
- end
18
-
19
- Some libraries go one step further and eliminate the need for a block
20
- parameter. {RSpec}[http://rspec.info/] is a well-known example:
21
-
22
- describe Stack do
23
- before(:each) do
24
- @stack = Stack.new
25
- end
26
- describe "(empty)" do
27
- it { @stack.should be_empty }
28
- it "should complain when sent #peek" do
29
- lambda { @stack.peek }.should raise_error(StackUnderflowError)
30
- end
31
- end
32
- end
33
-
34
- In both cases, the caller provides descriptive information in the block,
35
- using a domain-specific language. The second form, which eliminates the block
36
- parameter, often appears cleaner; however it is also sometimes less clear
37
- what is actually going on.
38
-
39
- === How does one implement such a beast?
40
-
41
- Implementing the first form is fairly straightforward. You would create a
42
- class defining the methods (such as +connect+ in our Rails routing example
43
- above) that should be available within the block. When, for example, the
44
- <tt>draw</tt> method is called with a block, you instantiate the class and
45
- yield it to the block.
46
-
47
- The second form is perhaps more mystifying. Somehow you would need to make
48
- the DSL methods available on the "self" object inside the block. There are
49
- several plausible ways to do this, such as using <tt>instance_eval</tt>.
50
- However, there are many subtle pitfalls in such techniques, and quite a bit
51
- of discussion has taken place in the Ruby community regarding how--or
52
- whether--to safely implement such a syntax.
53
-
54
- I have included a critical survey of the debate in the document
55
- {ImplementingDSLblocks.txt}[link:files/ImplementingDSLblocks\_txt.html] for
56
- the curious. Blockenspiel takes what I consider the best of the solutions and
57
- implements them in a comprehensive way, shielding you from the complexity of
58
- the Ruby metaprogramming while offering a simple way to implement both forms
59
- of DSL blocks.
60
-
61
- === So what _is_ Blockenspiel?
62
-
63
- Blockenspiel operates on the following observations:
64
-
65
- * Implementing a DSL block that takes a parameter is straightforward.
66
- * Safely implementing a DSL block that <em>doesn't</em> take a parameter is tricky.
67
-
68
- With that in mind, Blockenspiel provides a set of tools that allow you to
69
- take an implementation of the first form of a DSL block, one that takes a
70
- parameter, and turn it into an implementation of the second form, one that
71
- doesn't take a parameter.
72
-
73
- Suppose you wanted to write a simple DSL block that takes a parameter:
10
+ Blockenspiel is a helper library providing several different strategies for
11
+ implementing DSL blocks. It supports both DSLs that take a block parameter
12
+ and those that do not. For example:
74
13
 
14
+ # Call DSL block with parameter
75
15
  configure_me do |config|
76
16
  config.add_foo(1)
77
17
  config.add_bar(2)
78
18
  end
79
-
80
- You could write this as follows:
81
-
82
- class ConfigMethods
83
- def add_foo(value)
84
- # do something
85
- end
86
- def add_bar(value)
87
- # do something
88
- end
89
- end
90
19
 
91
- def configure_me
92
- yield ConfigMethods.new
93
- end
94
-
95
- That was easy. However, now suppose you wanted to support usage _without_
96
- the "config" parameter. e.g.
97
-
20
+ # Call DSL block without parameter
98
21
  configure_me do
99
- add_foo(1)
100
- add_bar(2)
22
+ add_foo(3)
23
+ add_bar(4)
101
24
  end
102
25
 
103
- With Blockenspiel, you can do this in two quick steps.
104
- First, tell Blockenspiel that your +ConfigMethods+ class is a DSL.
26
+ To support the above usage, you can do this:
105
27
 
28
+ # Implement DSL block methods
106
29
  class ConfigMethods
107
- include Blockenspiel::DSL # <--- Add this line
30
+ include Blockenspiel::DSL
108
31
  def add_foo(value)
109
32
  # do something
110
33
  end
@@ -112,179 +35,28 @@ First, tell Blockenspiel that your +ConfigMethods+ class is a DSL.
112
35
  # do something
113
36
  end
114
37
  end
115
-
116
- Next, write your <tt>configure_me</tt> method using Blockenspiel:
117
-
38
+
39
+ # Implement configure_me method
118
40
  def configure_me(&block)
119
41
  Blockenspiel.invoke(block, ConfigMethods.new)
120
42
  end
121
43
 
122
- Now, your <tt>configure_me</tt> method supports _both_ DSL block forms. A
123
- caller can opt to use the first form, with a parameter, simply by providing
124
- a block that takes a parameter. Or, if the caller provides a block that
125
- doesn't take a parameter, the second form without a parameter is used.
126
-
127
- === How does that help me? (Or, why not just use instance_eval?)
128
-
129
- As noted earlier, some libraries that provide parameter-less DSL blocks use
130
- <tt>instance_eval</tt>, and they could even support both the parameter and
131
- parameter-less mechanisms by checking the block arity:
132
-
133
- def configure_me(&block)
134
- if block.arity == 1
135
- yield ConfigMethods.new
136
- else
137
- ConfigMethods.new.instance_eval(&block)
138
- end
139
- end
140
-
141
- That seems like a simple and effective technique that doesn't require a
142
- separate library, so why use Blockenspiel? Because <tt>instance_eval</tt>
143
- introduces a number of surprising problems. I discuss these issues in detail
144
- in {ImplementingDSLblocks.txt}[link:files/ImplementingDSLblocks\_txt.html],
145
- but just to get your feet wet, suppose the caller wanted to call its own
146
- methods inside the block:
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
47
+ multithreaded access, and provides a variety of tools for handling the
48
+ typical issues you may encounter when writing DSLs.
147
49
 
148
- def callers_helper_method
149
- # ...
150
- end
151
-
152
- configure_me do
153
- add_foo(1)
154
- callers_helper_method # Error! self is now an instance of ConfigMethods
155
- # so this will fail with a NameError
156
- add_bar(2)
157
- end
158
-
159
- Blockenspiel by default does _not_ use the <tt>instance_eval</tt> technique.
160
- Instead, it implements a mechanism using mixin modules, a technique first
161
- {proposed}[http://hackety.org/2008/10/06/mixingOurWayOutOfInstanceEval.html]
162
- by Why. In this technique, the <tt>add_foo</tt> and <tt>add_bar</tt> methods
163
- are temporarily mixed into the caller's +self+ object. That is, +self+ does
164
- not change, as it would if we used <tt>instance_eval</tt>, so helper methods
165
- like <tt>callers_helper_method</tt> still remain available as expected. But,
166
- the <tt>add_foo</tt> and <tt>add_bar</tt> methods are also made available
167
- temporarily for the duration of the block. When called, they are intercepted
168
- and redirected to your +ConfigMethods+ instance just as if you had called
169
- them directly via a block parameter. Blockenspiel handles the object
170
- redirection behind the scenes so you do not have to think about it. With
171
- Blockenspiel, the caller retains access to its helper methods, and even its
172
- own instance variables, within the block, because +self+ has not been
173
- modified.
174
-
175
- === Is that it?
176
-
177
- Although the basic usage is very simple, Blockenspiel is designed to be
178
- _comprehensive_. It supports all the use cases that I've run into during my
179
- own implementation of DSL blocks. Notably:
180
-
181
- By default, Blockenspiel lets the caller choose to use a parametered block
182
- or a parameterless block, based on whether or not the block actually takes a
183
- parameter. You can also disable one or the other, to force the use of either
184
- a parametered or parameterless block.
185
-
186
- You can control wich methods of the class are available from parameterless
187
- blocks, and/or make some methods available under different names. Here are
188
- a few examples:
189
-
190
- class ConfigMethods
191
- include Blockenspiel::DSL
192
-
193
- def add_foo # automatically added to the dsl
194
- # do stuff...
195
- end
196
-
197
- def my_private_method
198
- # do stuff...
199
- end
200
- dsl_method :my_private_method, false # remove from the dsl
201
-
202
- dsl_methods false # stop automatically adding methods to the dsl
203
-
204
- def another_private_method # not added
205
- # do stuff...
206
- end
207
-
208
- dsl_methods true # resume automatically adding methods to the dsl
209
-
210
- def add_bar # this method is automatically added
211
- # do stuff...
212
- end
213
-
214
- def add_baz
215
- # do stuff
216
- end
217
- dsl_method :add_baz_in_dsl, :add_baz # Method named differently
218
- # in a parameterless block
219
- end
220
-
221
- This is also useful, for example, when you use <tt>attr_writer</tt>.
222
- Parameterless blocks do not support <tt>attr_writer</tt> (or, by corollary,
223
- <tt>attr_accessor</tt>) well because methods with names of the form
224
- "attribute=" are syntactically indistinguishable from variable assignments:
225
-
226
- configure_me do |config|
227
- config.foo = 1 # works fine when the block has a parameter
228
- end
229
-
230
- configure_me do
231
- # foo = 1 # <--- Doesn't work: looks like a variable assignment
232
- set_foo(1) # <--- Renamed to this instead
233
- end
234
-
235
- # This is implemented like this::
236
- class ConfigMethods
237
- include Blockenspiel::DSL
238
- attr_writer :foo
239
- dsl_method :set_foo, :foo= # Make "foo=" available as "set_foo"
240
- end
241
-
242
- In some cases, you might want to dynamically generate a DSL object rather
243
- than defining a static class. Blockenspiel provides a tool to do just that.
244
- Here's an example:
245
-
246
- Blockenspiel.invoke(block) do
247
- add_method(:set_foo) do |value|
248
- my_foo = value
249
- end
250
- add_method(:set_things_using_block, :receive_block => true) do |value, blk|
251
- my_foo = value
252
- my_bar = blk.call
253
- end
254
- end
255
-
256
- That API is in itself a DSL block, and yes, Blockenspiel uses itself to
257
- implement this feature.
258
-
259
- By default Blockenspiel uses mixins, which usually exhibit fairly safe and
260
- non-surprising behavior. However, there are a few cases when you might
261
- want the <tt>instance_eval</tt> behavior anyway. RSpec is a good example of
262
- such a case, since the DSL is being used to construct objects, so it makes
263
- sense for instance variables inside the block to belong to the object
264
- being constructed. Blockenspiel gives you the option of choosing
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.
268
-
269
- Blockenspiel also correctly handles nested blocks. e.g.
270
-
271
- configure_me do
272
- set_foo(1)
273
- configure_another do # A block within another block
274
- set_bar(2)
275
- configure_another do # A block within itself
276
- set_bar(3)
277
- end
278
- end
279
- end
50
+ For more detailed usage and examples, see
51
+ {Blockenspiel.rdoc}[link:Blockenspiel\_rdoc.html].
280
52
 
281
- Finally, it is completely thread safe, correctly handling, for example, the
282
- case of multiple threads trying to mix methods into the same object
283
- concurrently.
53
+ For an extended analysis of different ways to implement DSL blocks, see
54
+ {ImplementingDSLblocks.rdoc}[link:ImplementingDSLblocks\_rdoc.html].
284
55
 
285
56
  === Requirements
286
57
 
287
- * Ruby 1.8.6 or later (1.8.7 recommended), Ruby 1.9.1 or later, or JRuby 1.2 or later (1.4 recommended).
58
+ * Ruby 1.8.6 or later (1.8.7 recommended), Ruby 1.9.1 or later, or JRuby 1.2
59
+ or later (1.4 recommended).
288
60
 
289
61
  === Installation
290
62
 
@@ -317,6 +89,8 @@ copies or mirrors out there.
317
89
  The unmixer code is based on {Mixology}[http://rubyforge.org/projects/mixology],
318
90
  version 0.1 by Patrick Farley, anonymous z, Dan Manges, and Clint Bishop.
319
91
  The code has been stripped down and modified to support MRI 1.9 and JRuby 1.2.
92
+ I know Mixology 0.2 is available, but I'm keeping the unmixer bundled with
93
+ Blockenspiel for now, to reduce dependencies.
320
94
 
321
95
  === License
322
96
 
data/Rakefile CHANGED
@@ -43,7 +43,7 @@ require ::File.expand_path("#{::File.dirname(__FILE__)}/lib/blockenspiel/version
43
43
 
44
44
 
45
45
  # Configuration
46
- extra_rdoc_files_ = ['README.rdoc', 'History.rdoc', 'ImplementingDSLblocks.rdoc']
46
+ extra_rdoc_files_ = ['README.rdoc', 'Blockenspiel.rdoc', 'History.rdoc', 'ImplementingDSLblocks.rdoc']
47
47
 
48
48
 
49
49
  # Default task
@@ -138,6 +138,8 @@ task :compile_java do
138
138
  end
139
139
  end
140
140
 
141
+ task :package => :compile_java
142
+
141
143
 
142
144
  # Publish RDocs
143
145
  desc 'Publishes RDocs to RubyForge'
@@ -149,7 +151,7 @@ end
149
151
 
150
152
 
151
153
  # Publish gem
152
- task :release_gem_to_rubyforge do |t_|
154
+ task :release_gem_to_rubyforge => [:package] do |t_|
153
155
  v_ = ::ENV["VERSION"]
154
156
  abort "Must supply VERSION=x.y.z" unless v_
155
157
  if v_ != ::Blockenspiel::VERSION_STRING
@@ -202,7 +204,7 @@ task :release => [:release_gem_to_gemcutter, :release_gem_to_rubyforge, :publish
202
204
  # Custom task that takes the implementing dsl blocks paper
203
205
  # and converts it from RDoc format to Markdown
204
206
  task :idslb_markdown do
205
- ::File.open('ImplementingDSLblocks.txt') do |read_|
207
+ ::File.open('ImplementingDSLblocks.rdoc') do |read_|
206
208
  ::File.open('idslb_markdown.txt', 'w') do |write_|
207
209
  linenum_ = 0
208
210
  read_.each do |line_|