blockenspiel 0.2.0.1-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,350 @@
1
+ == Blockenspiel
2
+
3
+ Blockenspiel is a helper library designed to make it easy to implement DSL
4
+ blocks. It is designed to be comprehensive and robust, supporting most common
5
+ usage patterns, and working correctly in the presence of nested blocks and
6
+ multithreading.
7
+
8
+ === What's a DSL block?
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:
74
+
75
+ configure_me do |config|
76
+ config.add_foo(1)
77
+ config.add_bar(2)
78
+ 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
+
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
+
98
+ configure_me do
99
+ add_foo(1)
100
+ add_bar(2)
101
+ end
102
+
103
+ With Blockenspiel, you can do this in two quick steps.
104
+ First, tell Blockenspiel that your +ConfigMethods+ class is a DSL.
105
+
106
+ class ConfigMethods
107
+ include Blockenspiel::DSL # <--- Add this line
108
+ def add_foo(value)
109
+ # do something
110
+ end
111
+ def add_bar(value)
112
+ # do something
113
+ end
114
+ end
115
+
116
+ Next, write your <tt>configure_me</tt> method using Blockenspiel:
117
+
118
+ def configure_me(&block)
119
+ Blockenspiel.invoke(block, ConfigMethods.new)
120
+ end
121
+
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:
147
+
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
280
+
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.
284
+
285
+ === Requirements
286
+
287
+ * Ruby 1.8.7 or later, or JRuby 1.2 or later. Ruby 1.9 compatible.
288
+ * Rubygems
289
+
290
+ === Installation
291
+
292
+ gem install blockenspiel
293
+
294
+ === Known issues and limitations
295
+
296
+ * Implementing wildcard DSL methods using <tt>method_missing</tt> doesn't
297
+ work. I haven't yet figured out the right semantics for this case.
298
+
299
+ === Development and support
300
+
301
+ Documentation is available at http://virtuoso.rubyforge.org/blockenspiel
302
+
303
+ Source code is hosted by Github at http://github.com/dazuma/blockenspiel/tree
304
+
305
+ Report bugs on RubyForge at http://rubyforge.org/projects/virtuoso
306
+
307
+ Contact the author at dazuma at gmail dot com.
308
+
309
+ === Author / Credits
310
+
311
+ Blockenspiel is written by Daniel Azuma (http://www.daniel-azuma.com/).
312
+
313
+ The mixin implementation is based on a concept by Why The Lucky Stiff.
314
+ See his 6 October 2008 blog posting,
315
+ <em>{Mixing Our Way Out Of Instance Eval?}[http://hackety.org/2008/10/06/mixingOurWayOutOfInstanceEval.html]</em>
316
+ for further discussion.
317
+
318
+ The unmixer code is based on {Mixology}[http://rubyforge.org/projects/mixology],
319
+ by Patrick Farley, anonymous z, Dan Manges, and Clint Bishop.
320
+ The code has been stripped down and modified to support MRI 1.9 and JRuby 1.2.
321
+
322
+ === License
323
+
324
+ Copyright 2008-2009 Daniel Azuma.
325
+
326
+ All rights reserved.
327
+
328
+ Redistribution and use in source and binary forms, with or without
329
+ modification, are permitted provided that the following conditions are met:
330
+
331
+ * Redistributions of source code must retain the above copyright notice,
332
+ this list of conditions and the following disclaimer.
333
+ * Redistributions in binary form must reproduce the above copyright notice,
334
+ this list of conditions and the following disclaimer in the documentation
335
+ and/or other materials provided with the distribution.
336
+ * Neither the name of the copyright holder, nor the names of any other
337
+ contributors to this software, may be used to endorse or promote products
338
+ derived from this software without specific prior written permission.
339
+
340
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
341
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
342
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
343
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
344
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
345
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
346
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
347
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
348
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
349
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
350
+ POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,218 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Blockenspiel Rakefile
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2008-2009 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+
35
+ require 'rake'
36
+ require 'rake/clean'
37
+ require 'rake/gempackagetask'
38
+ require 'rake/testtask'
39
+ require 'rake/rdoctask'
40
+ require 'rdoc/generator/darkfish'
41
+
42
+ require File.expand_path("#{File.dirname(__FILE__)}/lib/blockenspiel/version")
43
+
44
+
45
+ # Configuration
46
+ extra_rdoc_files_ = ['README.rdoc', 'History.rdoc', 'ImplementingDSLblocks.rdoc']
47
+
48
+
49
+ # Default task
50
+ task :default => [:clean, :compile, :rdoc, :test]
51
+
52
+
53
+ # Clean task
54
+ CLEAN.include(['ext/blockenspiel/Makefile', '**/unmixer.bundle', 'ext/blockenspiel/unmixer.o', '**/blockenspiel_unmixer.jar', 'ext/blockenspiel/BlockenspielUnmixerService.class', 'idslb_markdown.txt', 'doc', 'pkg'])
55
+
56
+
57
+ # Test task
58
+ Rake::TestTask.new('test') do |task_|
59
+ task_.pattern = 'tests/tc_*.rb'
60
+ end
61
+
62
+
63
+ # RDoc task
64
+ Rake::RDocTask.new do |task_|
65
+ task_.main = 'README.rdoc'
66
+ task_.rdoc_files.include(*extra_rdoc_files_)
67
+ task_.rdoc_files.include('lib/blockenspiel/*.rb')
68
+ task_.rdoc_dir = 'doc'
69
+ task_.title = "Blockenspiel #{Blockenspiel::VERSION_STRING} documentation"
70
+ task_.options << '-f' << 'darkfish'
71
+ end
72
+
73
+
74
+ # Gem task
75
+ gemspec_ = Gem::Specification.new do |s_|
76
+ s_.name = 'blockenspiel'
77
+ s_.summary = 'Blockenspiel is a helper library designed to make it easy to implement DSL blocks.'
78
+ s_.version = Blockenspiel::VERSION_STRING
79
+ s_.author = 'Daniel Azuma'
80
+ s_.email = 'dazuma@gmail.com'
81
+ s_.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.'
82
+ s_.homepage = 'http://virtuoso.rubyforge.org/blockenspiel'
83
+ s_.rubyforge_project = 'virtuoso'
84
+ s_.required_ruby_version = '>= 1.8.6'
85
+ s_.files = FileList['ext/**/*.{c,rb,java}', 'lib/**/*.{rb,jar}', 'tests/**/*.rb', '*.rdoc', 'Rakefile'].to_a
86
+ s_.extra_rdoc_files = extra_rdoc_files_
87
+ s_.has_rdoc = true
88
+ s_.test_files = FileList['tests/tc_*.rb']
89
+ if RUBY_PLATFORM =~ /java/
90
+ s_.platform = 'java'
91
+ s_.files += ['lib/blockenspiel_unmixer.jar']
92
+ else
93
+ s_.platform = Gem::Platform::RUBY
94
+ s_.extensions = ['ext/blockenspiel/extconf.rb']
95
+ end
96
+ end
97
+ task :package => [:compile]
98
+ Rake::GemPackageTask.new(gemspec_) do |task_|
99
+ task_.need_zip = false
100
+ task_.need_tar = RUBY_PLATFORM !~ /java/
101
+ end
102
+
103
+
104
+ # General build task
105
+ task :compile => RUBY_PLATFORM =~ /java/ ? [:compile_java] : [:compile_c]
106
+
107
+
108
+ # Build tasks for MRI
109
+ dlext_ = Config::CONFIG['DLEXT']
110
+
111
+ desc 'Builds the extension'
112
+ task :compile_c => ["lib/blockenspiel/unmixer.#{dlext_}"]
113
+
114
+ file 'ext/blockenspiel/Makefile' => ['ext/blockenspiel/extconf.rb'] do
115
+ Dir.chdir('ext/blockenspiel') do
116
+ ruby 'extconf.rb'
117
+ end
118
+ end
119
+
120
+ file "ext/blockenspiel/unmixer.#{dlext_}" => ['ext/blockenspiel/Makefile', 'ext/blockenspiel/unmixer.c'] do
121
+ Dir.chdir('ext/blockenspiel') do
122
+ sh 'make'
123
+ end
124
+ end
125
+
126
+ file "lib/blockenspiel/unmixer.#{dlext_}" => ["ext/blockenspiel/unmixer.#{dlext_}"] do
127
+ cp "ext/blockenspiel/unmixer.#{dlext_}", 'lib/blockenspiel'
128
+ end
129
+
130
+
131
+ # Build tasks for JRuby
132
+ desc "Compiles the JRuby extension"
133
+ task :compile_java do
134
+ Dir.chdir('ext/blockenspiel') do
135
+ sh 'javac -source 1.5 -target 1.5 -classpath $JRUBY_HOME/lib/jruby.jar BlockenspielUnmixerService.java'
136
+ sh 'jar cf blockenspiel_unmixer.jar BlockenspielUnmixerService.class'
137
+ cp 'blockenspiel_unmixer.jar', '../../lib/blockenspiel_unmixer.jar'
138
+ end
139
+ end
140
+
141
+
142
+ # Publish RDocs
143
+ desc 'Publishes RDocs to RubyForge'
144
+ task :publish_rdoc => [:rerdoc] do
145
+ config_ = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml")))
146
+ username_ = config_['username']
147
+ sh "rsync -av --delete doc/ #{username_}@rubyforge.org:/var/www/gforge-projects/virtuoso/blockenspiel"
148
+ end
149
+
150
+
151
+ # Publish gem
152
+ task :publish_gem do |t_|
153
+ v_ = ENV["VERSION"]
154
+ abort "Must supply VERSION=x.y.z" unless v_
155
+ if v_ != Blockenspiel::VERSION_STRING
156
+ abort "Versions don't match: #{v_} vs #{Blockenspiel::VERSION_STRING}"
157
+ end
158
+ mri_pkg_ = "pkg/blockenspiel-#{v_}.gem"
159
+ jruby_pkg_ = "pkg/blockenspiel-#{v_}-java.gem"
160
+ tgz_pkg_ = "pkg/blockenspiel-#{v_}.tgz"
161
+ if !File.file?(mri_pkg_) || !File.readable?(mri_pkg_)
162
+ abort "You haven't built #{mri_pkg_} yet. Try rake package"
163
+ end
164
+ if !File.file?(jruby_pkg_) || !File.readable?(jruby_pkg_)
165
+ abort "You haven't built #{jruby_pkg_} yet. Try jrake package"
166
+ end
167
+ if !File.file?(tgz_pkg_) || !File.readable?(tgz_pkg_)
168
+ abort "You haven't built #{tgz_pkg_} yet. Try rake package"
169
+ end
170
+ release_notes_ = File.read("README.rdoc").split(/^(==.*)/)[2].strip
171
+ release_changes_ = File.read("History.rdoc").split(/^(===.*)/)[1..2].join.strip
172
+
173
+ require 'rubyforge'
174
+ rf_ = RubyForge.new.configure
175
+ puts "Logging in to RubyForge"
176
+ rf_.login
177
+ config_ = rf_.userconfig
178
+ config_["release_notes"] = release_notes_
179
+ config_["release_changes"] = release_changes_
180
+ config_["preformatted"] = true
181
+ puts "Releasing blockenspiel #{v_}"
182
+ rf_.add_release('virtuoso', 'blockenspiel', v_, mri_pkg_, jruby_pkg_, tgz_pkg_)
183
+ end
184
+
185
+
186
+ # Custom task that takes the implementing dsl blocks paper
187
+ # and converts it from RDoc format to Markdown
188
+ task :idslb_markdown do
189
+ File.open('ImplementingDSLblocks.txt') do |read_|
190
+ File.open('idslb_markdown.txt', 'w') do |write_|
191
+ linenum_ = 0
192
+ read_.each do |line_|
193
+ linenum_ += 1
194
+ next if linenum_ < 4
195
+ line_.sub!(/^===\ /, '### ')
196
+ line_.sub!(/^\ \ /, ' ')
197
+ if line_[0..3] == '### '
198
+ line_.gsub!(/(\w)_(\w)/, '\1\_\2')
199
+ end
200
+ if line_[0..3] != ' '
201
+ line_.gsub!('"it_should_behave_like"', '"it\_should\_behave\_like"')
202
+ line_.gsub!('"time_zone"', '"time\_zone"')
203
+ line_.gsub!(/\+(\w+)\+/, '`\1`')
204
+ line_.gsub!(/\*(\w+)\*/, '**\1**')
205
+ line_.gsub!(/<\/?em>/, '*')
206
+ line_.gsub!(/<\/?tt>/, '`')
207
+ line_.gsub!(/<\/?b>/, '**')
208
+ line_.gsub!(/\{([^\}]+)\}\[([^\]]+)\]/) do |match_|
209
+ text_, url_ = $1, $2
210
+ "[#{text_.gsub('_', '\_')}](#{url_})"
211
+ end
212
+ line_.gsub!(/\ (http:\/\/[^\s]+)/, ' [\1](\1)')
213
+ end
214
+ write_.puts(line_)
215
+ end
216
+ end
217
+ end
218
+ end