boc 0.4.2-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGES.rdoc ADDED
@@ -0,0 +1,21 @@
1
+
2
+ = boc Changes
3
+
4
+ == Version 0.4.2
5
+
6
+ * add jruby support
7
+ * add AlreadyEnabledError
8
+
9
+ == Version 0.3.2
10
+
11
+ * "rake test" now works without rake-compiler on Windows
12
+ * additions to readme
13
+
14
+ == Version 0.3.1
15
+
16
+ * add prototype for rb_funcall_passing block
17
+ (http://redmine.ruby-lang.org/issues/4504)
18
+
19
+ == Version 0.3.0
20
+
21
+ * birthday
data/README.rdoc ADDED
@@ -0,0 +1,119 @@
1
+
2
+ = boc
3
+
4
+ == Summary
5
+
6
+ Binding of caller.
7
+
8
+ == Synopsis
9
+
10
+ require 'boc'
11
+
12
+ class A
13
+ def f
14
+ p eval("x", Boc.value)
15
+ end
16
+ end
17
+
18
+ Boc.enable A, :f
19
+ x = 33
20
+ A.new.f # => 33
21
+
22
+ == Install
23
+
24
+ % gem install boc
25
+
26
+ Or from inside an unpacked .tgz download, <code>rake install</code> /
27
+ <code>rake uninstall</code>.
28
+
29
+ == Description
30
+
31
+ Binding of caller: obtain a caller's binding.
32
+
33
+ MRI 1.9.2 or JRuby 1.6+ is required. Support for other Ruby platforms
34
+ is a goal.
35
+
36
+ == <code>Binding.of_caller</code>
37
+
38
+ <code>require 'boc/binding_of_caller'</code> will define
39
+ <code>Binding.of_caller</code> (not present by default).
40
+
41
+ require 'boc/binding_of_caller'
42
+
43
+ class A
44
+ def f
45
+ Binding.of_caller do |bind|
46
+ p eval("x", bind)
47
+ end
48
+ end
49
+ end
50
+
51
+ Boc.enable A, :f
52
+ x = 33
53
+ A.new.f # => 33
54
+
55
+ This is not an actual compatibility layer since the call to +enable+
56
+ is still necessary. <code>Binding.of_caller</code> is merely a
57
+ convenience method for existing 1.8 code.
58
+
59
+ == Links
60
+
61
+ * Home: http://quix.github.com/boc
62
+ * Feature Requests, Bug Reports: http://github.com/quix/boc/issues
63
+ * Manual Download: http://github.com/quix/boc/archives/master
64
+ * Repository: http://github.com/quix/boc
65
+
66
+ == Implementation
67
+
68
+ When <code>Boc.enable(A, :f)</code> is called it first makes an alias,
69
+
70
+ class A
71
+ alias_method :f__impl, :f
72
+ end
73
+
74
+ Following that, +f+ is replaced by native method whose only tasks are
75
+ to set <code>Boc.value</code> and then call +f__impl+.
76
+
77
+ == Background
78
+
79
+ After adapting the old continuation-based
80
+ <code>Binding.of_caller</code> to 1.9.2
81
+ (http://quix.github.com/binding_of_caller), I found the result
82
+ unsatisfying. There were syntax restrictions surrounding the use of
83
+ it, and though workaroundable they raised practical problems.
84
+
85
+ While a caller's binding might be obtained by accessesing VM innards,
86
+ this approach would be subject to future breakage. The implementation
87
+ presented herein is a compromise. In exchange for restricting
88
+ functionality (the additional requirement of +enable+),
89
+ binding-of-caller may be implemented straightforwardly with only the
90
+ public C API, meaning that it should work in future MRI releases.
91
+
92
+ == Author
93
+
94
+ * James M. Lawrence < quixoticsycophant@gmail.com >
95
+
96
+ == License
97
+
98
+ Copyright (c) 2011 James M. Lawrence. All rights reserved.
99
+
100
+ Permission is hereby granted, free of charge, to any person
101
+ obtaining a copy of this software and associated documentation files
102
+ (the "Software"), to deal in the Software without restriction,
103
+ including without limitation the rights to use, copy, modify, merge,
104
+ publish, distribute, sublicense, and/or sell copies of the Software,
105
+ and to permit persons to whom the Software is furnished to do so,
106
+ subject to the following conditions:
107
+
108
+ The above copyright notice and this permission notice shall be
109
+ included in all copies or substantial portions of the Software.
110
+
111
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
112
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
113
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
114
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
115
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
116
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
117
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
118
+ SOFTWARE.
119
+
data/Rakefile ADDED
@@ -0,0 +1,139 @@
1
+ require_relative 'devel/levitate'
2
+ require 'rbconfig'
3
+
4
+ def config
5
+ @s.developers << ["James M. Lawrence", "quixoticsycophant@gmail.com"]
6
+ @s.username = "quix"
7
+ @s.required_ruby_version = ">= 1.9.2"
8
+ @s.development_dependencies << ["rake-compiler", "~> 0.7.6"]
9
+ @s.rdoc_files = %w[
10
+ lib/boc.rb
11
+ lib/boc/version.rb
12
+ ]
13
+ end
14
+
15
+ def mri_ext
16
+ require 'rake/extensiontask'
17
+
18
+ Rake::ExtensionTask.new @s.gem_name, @s.gemspec do |ext|
19
+ ext.cross_compile = true
20
+ ext.cross_platform = 'i386-mswin32'
21
+ ext.lib_dir = "lib/#{@s.gem_name}"
22
+ end
23
+
24
+ # compensate for strange rake-compiler invocation
25
+ task :cross_native_gem do
26
+ Rake::Task[:gem].reenable
27
+ Rake.application.top_level_tasks.replace %w[cross native gem]
28
+ Rake.application.top_level
29
+ end
30
+
31
+ task :gem => :cross_native_gem
32
+
33
+ task :test => so_file
34
+
35
+ @s.gemspec.extensions = ["Rakefile"]
36
+ end
37
+
38
+ def jruby_ext
39
+ require "rake/javaextensiontask"
40
+
41
+ Levitate.no_warnings do
42
+ Rake::JavaExtensionTask.new @s.gem_name, @s.gemspec do |ext|
43
+ ext.ext_dir = "jext"
44
+ ext.lib_dir = "lib/#{@s.gem_name}"
45
+ end
46
+ end
47
+
48
+ task :jar => jar_file
49
+
50
+ case RUBY_ENGINE
51
+ when "jruby"
52
+ task :test => jar_file
53
+ when "ruby"
54
+ # building/releasing from MRI
55
+
56
+ task :jruby_gem => jar_file do
57
+ # compensate for strange rake-compiler invocation
58
+ Rake::Task[:gem].reenable
59
+ Rake.application.top_level_tasks.replace %w[java gem]
60
+ Rake.application.top_level
61
+ end
62
+
63
+ task :gem => :jruby_gem
64
+
65
+ task :test_jruby do
66
+ run_jruby = ENV["RUN_JRUBY"] || "jruby"
67
+ cmd = run_jruby + " --1.9 -J-Djruby.astInspector.enabled=0 -S rake"
68
+ sh(cmd + " test")
69
+ sh(cmd + " test_deps")
70
+ end
71
+
72
+ task :prerelease => :test_jruby
73
+
74
+ CLEAN.add jar_file
75
+ end
76
+ end
77
+
78
+ def jar_file
79
+ # the only path jruby recognizes
80
+ File.join("lib", @s.gem_name, @s.gem_name + ".jar")
81
+ end
82
+
83
+ def so_file
84
+ File.join("lib", @s.gem_name, @s.gem_name + "." + RbConfig::CONFIG["DLEXT"])
85
+ end
86
+
87
+ def native_file
88
+ case RUBY_ENGINE
89
+ when "ruby"
90
+ so_file
91
+ when "jruby"
92
+ jar_file
93
+ end
94
+ end
95
+
96
+ def append_installer
97
+ fu = FileUtils::Verbose
98
+
99
+ source = native_file
100
+ dir = File.join RbConfig::CONFIG["sitearchdir"], @s.gem_name
101
+ dest = File.join dir, File.basename(source)
102
+
103
+ task :install => source do
104
+ fu.mkdir dir unless File.directory? dir
105
+ fu.install source, dest
106
+ end
107
+
108
+ task :uninstall do
109
+ fu.rm dest if File.file? dest
110
+ fu.rmdir dir if File.directory? dir
111
+ end
112
+ end
113
+
114
+ def config_extension
115
+ case RUBY_ENGINE
116
+ when "jruby"
117
+ jruby_ext
118
+ when "ruby"
119
+ mri_ext
120
+ else
121
+ raise "sorry, platform `#{RUBY_ENGINE}' not supported"
122
+ end
123
+ end
124
+
125
+ Levitate.new "boc" do |s|
126
+ @s = s
127
+ config
128
+ if File.directory? ".git"
129
+ mri_ext if RUBY_ENGINE == "ruby"
130
+ jruby_ext
131
+ elsif not File.file?(native_file)
132
+ config_extension
133
+ end
134
+ append_installer
135
+ task :finish_release do
136
+ sh "gem", "push", "pkg/#{@s.gem_name}-#{@s.version}-java.gem"
137
+ sh "gem", "push", "pkg/#{@s.gem_name}-#{@s.version}-x86-mswin32.gem"
138
+ end
139
+ end
data/devel/levitate.rb ADDED
@@ -0,0 +1,852 @@
1
+
2
+ class Levitate
3
+ def initialize(gem_name)
4
+ @gem_name = gem_name
5
+
6
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
7
+
8
+ yield self
9
+
10
+ self.class.instance_methods(false).each do |name|
11
+ if name.to_s =~ %r!\Adefine_!
12
+ send(name)
13
+ end
14
+ end
15
+ end
16
+
17
+ attr_reader :gem_name
18
+
19
+ def self.attribute(name, &block)
20
+ var = :"@#{name}"
21
+ define_method name do
22
+ if instance_variable_defined?(var)
23
+ instance_variable_get(var)
24
+ else
25
+ instance_variable_set(var, instance_eval(&block))
26
+ end
27
+ end
28
+ attr_writer name
29
+ end
30
+
31
+ attribute :version_constant_name do
32
+ "VERSION"
33
+ end
34
+
35
+ attribute :camel_name do
36
+ to_camel_case(gem_name)
37
+ end
38
+
39
+ attribute :version do
40
+ catch :bail do
41
+ if File.file?(version_file = "./lib/#{gem_name}/version.rb")
42
+ require version_file
43
+ elsif File.file?("./lib/#{gem_name}.rb")
44
+ require gem_name
45
+ else
46
+ throw :bail
47
+ end
48
+ mod = Kernel.const_get(camel_name)
49
+ constants = mod.constants.map { |t| t.to_sym }
50
+ unless constants.include?(version_constant_name.to_sym)
51
+ throw :bail
52
+ end
53
+ mod.const_get(version_constant_name)
54
+ end or "0.0.0"
55
+ end
56
+
57
+ attribute :required_ruby_version do
58
+ ">= 0"
59
+ end
60
+
61
+ attribute :readme_file do
62
+ "README.rdoc"
63
+ end
64
+
65
+ attribute :history_file do
66
+ "CHANGES.rdoc"
67
+ end
68
+
69
+ attribute :doc_dir do
70
+ "doc"
71
+ end
72
+
73
+ attribute :spec_files do
74
+ Dir["./spec/*_{spec,example}.rb"]
75
+ end
76
+
77
+ attribute :test_files do
78
+ (Dir["./test/test_*.rb"] + Dir["./test/*_test.rb"]).uniq
79
+ end
80
+
81
+ attribute :cov_dir do
82
+ "coverage"
83
+ end
84
+
85
+ attribute :spec_output_dir do
86
+ "rspec_output"
87
+ end
88
+
89
+ attribute :spec_output_file do
90
+ "spec.html"
91
+ end
92
+
93
+ attribute :spec_output do
94
+ "#{spec_output_dir}/#{spec_output_file}"
95
+ end
96
+
97
+ attribute :rcov_options do
98
+ # workaround for the default rspec task
99
+ Dir["*"].select { |f| File.directory? f }.inject(Array.new) { |acc, dir|
100
+ if dir == "lib"
101
+ acc
102
+ else
103
+ acc + ["--exclude", dir + "/"]
104
+ end
105
+ } + ["--text-report"]
106
+ end
107
+
108
+ attribute :generated_files do
109
+ []
110
+ end
111
+
112
+ attribute :extra_gemspec do
113
+ lambda { |spec| }
114
+ end
115
+
116
+ attribute :files do
117
+ if source_control?
118
+ IO.popen("git ls-files") { |pipe| pipe.read.split "\n" }
119
+ end.to_a + generated_files
120
+ end
121
+
122
+ attribute :rdoc_files do
123
+ files_in_require_paths
124
+ end
125
+
126
+ attribute :rdoc_title do
127
+ "#{gem_name}: #{summary}".sub(/\.\Z/, "")
128
+ end
129
+
130
+ attribute :require_paths do
131
+ ["lib"]
132
+ end
133
+
134
+ attribute :rdoc_options do
135
+ if File.file?(readme_file)
136
+ ["--main", readme_file]
137
+ else
138
+ []
139
+ end + [
140
+ "--title", rdoc_title,
141
+ ] + (files_in_require_paths - rdoc_files).inject(Array.new) {
142
+ |acc, file|
143
+ acc + ["--exclude", file]
144
+ }
145
+ end
146
+
147
+ attribute :extra_rdoc_files do
148
+ [readme_file, history_file].select { |file| File.file?(file) }
149
+ end
150
+
151
+ attribute :browser do
152
+ require 'rbconfig'
153
+ if RbConfig::CONFIG["host"] =~ %r!darwin!
154
+ "open"
155
+ else
156
+ "firefox"
157
+ end
158
+ end
159
+
160
+ attribute :gemspec do
161
+ Gem::Specification.new do |g|
162
+ %w[
163
+ summary
164
+ version
165
+ description
166
+ files
167
+ rdoc_options
168
+ extra_rdoc_files
169
+ require_paths
170
+ required_ruby_version
171
+ ].each do |param|
172
+ t = send(param) and g.send("#{param}=", t)
173
+ end
174
+ g.name = gem_name
175
+ g.has_rdoc = true
176
+ g.homepage = url if url
177
+ dependencies.each { |dep|
178
+ g.add_dependency(*dep)
179
+ }
180
+ development_dependencies.each { |dep|
181
+ g.add_development_dependency(*dep)
182
+ }
183
+ g.authors = developers.map { |d| d[0] }
184
+ g.email = developers.map { |d| d[1] }
185
+ extra_gemspec.call(g)
186
+ end
187
+ end
188
+
189
+ attribute :readme_contents do
190
+ File.read(readme_file) rescue "FIXME: readme_file"
191
+ end
192
+
193
+ attribute :sections do
194
+ begin
195
+ data = readme_contents.split(%r!^==\s*(.*?)\s*$!)
196
+ pairs = data[1..-1].each_slice(2).map { |section, contents|
197
+ [section.downcase, contents.strip]
198
+ }
199
+ Hash[*pairs.flatten]
200
+ rescue
201
+ nil
202
+ end
203
+ end
204
+
205
+ attribute :description_section do
206
+ "description"
207
+ end
208
+
209
+ attribute :summary_section do
210
+ "summary"
211
+ end
212
+
213
+ attribute :description_sentences do
214
+ 1
215
+ end
216
+
217
+ attribute :summary_sentences do
218
+ 1
219
+ end
220
+
221
+ [:summary, :description].each { |section|
222
+ attribute section do
223
+ begin
224
+ sections[send("#{section}_section")].
225
+ gsub("\n", " ").
226
+ split(%r!\.\s+!m).
227
+ first(send("#{section}_sentences")).
228
+ join(". ").
229
+ concat(".").
230
+ sub(%r!\.+\Z!, ".")
231
+ rescue
232
+ "FIXME: #{section}"
233
+ end
234
+ end
235
+ }
236
+
237
+ attribute :url do
238
+ "http://#{username}.github.com/#{gem_name}"
239
+ end
240
+
241
+ attribute :username do
242
+ raise "username not set"
243
+ end
244
+
245
+ attribute :rubyforge_info do
246
+ nil
247
+ end
248
+
249
+ attribute :dependencies do
250
+ []
251
+ end
252
+
253
+ attribute :development_dependencies do
254
+ []
255
+ end
256
+
257
+ attribute :developers do
258
+ []
259
+ end
260
+
261
+ attribute :remote_levitate do
262
+ url = ENV["LEVITATE"] ||
263
+ "https://github.com/quix/levitate/raw/master/levitate.rb"
264
+ IO.popen("curl -s #{url}") { |f| f.read }
265
+ end
266
+
267
+ attribute :local_levitate do
268
+ File.open(__FILE__, "rb") { |f| f.read }
269
+ end
270
+
271
+ #### tasks
272
+
273
+ def define_clean
274
+ require 'rake/clean'
275
+ task :clean do
276
+ Rake::Task[:clobber].invoke
277
+ end
278
+ end
279
+
280
+ def define_package
281
+ if source_control?
282
+ require 'rubygems/package_task'
283
+
284
+ task :package => :clean
285
+ Gem::PackageTask.new(gemspec).define
286
+ end
287
+ end
288
+
289
+ def define_spec
290
+ unless spec_files.empty?
291
+ no_warnings {
292
+ require 'spec/rake/spectask'
293
+ }
294
+
295
+ desc "run specs"
296
+ Spec::Rake::SpecTask.new('spec') do |t|
297
+ t.spec_files = spec_files
298
+ end
299
+
300
+ desc "run specs with text output"
301
+ Spec::Rake::SpecTask.new('text_spec') do |t|
302
+ t.spec_files = spec_files
303
+ t.spec_opts = ['-fs']
304
+ end
305
+
306
+ desc "run specs with html output"
307
+ Spec::Rake::SpecTask.new('full_spec') do |t|
308
+ t.spec_files = spec_files
309
+ t.rcov = true
310
+ t.rcov_opts = rcov_options
311
+ t.spec_opts = ["-fh:#{spec_output}"]
312
+ end
313
+
314
+ suppress_task_warnings :spec, :full_spec, :text_spec
315
+
316
+ desc "run full_spec then open browser"
317
+ task :show_spec => :full_spec do
318
+ open_browser(spec_output, cov_dir + "/index.html")
319
+ end
320
+
321
+ desc "run specs individually"
322
+ task :spec_deps do
323
+ run_each_file(*spec_files)
324
+ end
325
+
326
+ task :prerelease => [:spec, :spec_deps]
327
+ task :default => :spec
328
+
329
+ CLEAN.add spec_output_dir
330
+ end
331
+ end
332
+
333
+ def define_test
334
+ unless test_files.empty?
335
+ desc "run tests"
336
+ task :test do
337
+ test_files.each { |file| require file }
338
+
339
+ # if we use at_exit hook instead, it won't run before :release
340
+ MiniTest::Unit.new.run ARGV
341
+ end
342
+
343
+ desc "run tests with coverage"
344
+ if ruby_18?
345
+ task :full_test do
346
+ verbose(false) {
347
+ sh("rcov", "-o", cov_dir, "--text-report",
348
+ *(test_files + rcov_options)
349
+ )
350
+ }
351
+ end
352
+ else
353
+ task :full_test do
354
+ rm_rf cov_dir
355
+ require 'simplecov'
356
+ SimpleCov.start do
357
+ add_filter "test/"
358
+ add_filter "devel/"
359
+ end
360
+ Rake::Task[:test].invoke
361
+ end
362
+ end
363
+
364
+ desc "run full_test then open browser"
365
+ task :show_test => :full_test do
366
+ show = lambda { open_browser(cov_dir + "/index.html") }
367
+ if ruby_18?
368
+ show.call
369
+ else
370
+ SimpleCov.at_exit do
371
+ SimpleCov.result.format!
372
+ show.call
373
+ end
374
+ end
375
+ end
376
+
377
+ desc "run tests individually"
378
+ task :test_deps do
379
+ run_each_file(*test_files)
380
+ end
381
+
382
+ task :prerelease => [:test, :test_deps]
383
+ task :default => :test
384
+
385
+ CLEAN.add cov_dir
386
+ end
387
+ end
388
+
389
+ def define_doc
390
+ desc "run rdoc"
391
+ task :doc => :clean_doc do
392
+ gem 'rdoc' rescue nil
393
+ require 'rdoc/rdoc'
394
+ args = (
395
+ gemspec.rdoc_options +
396
+ gemspec.require_paths.clone +
397
+ gemspec.extra_rdoc_files +
398
+ ["-o", doc_dir]
399
+ ).flatten.map { |t| t.to_s }
400
+ RDoc::RDoc.new.document args
401
+ end
402
+
403
+ task :clean_doc do
404
+ # normally rm_rf, but mimic rake/clean output
405
+ rm_r(doc_dir) rescue nil
406
+ end
407
+
408
+ desc "run rdoc then open browser"
409
+ task :show_doc => :doc do
410
+ open_browser(doc_dir + "/index.html")
411
+ end
412
+
413
+ task :rdoc => :doc
414
+ task :clean => :clean_doc
415
+ end
416
+
417
+ def define_publish
418
+ if source_control?
419
+ desc "publish docs"
420
+ task :publish => [:clean, :check_directory, :doc] do
421
+ current_branch = `git branch`[/^\* (\S+)$/, 1] or raise "??? branch"
422
+ if rubyforge_info
423
+ user, project = rubyforge_info
424
+ Dir.chdir(doc_dir) do
425
+ sh "scp", "-r",
426
+ ".",
427
+ "#{user}@rubyforge.org:/var/www/gforge-projects/#{project}"
428
+ end
429
+ end
430
+ git "branch", "-D", "gh-pages"
431
+ git "checkout", "--orphan", "gh-pages"
432
+ FileUtils.rm ".git/index"
433
+ git "clean", "-fdx", "-e", "doc"
434
+ Dir["doc/*"].each { |path|
435
+ FileUtils.mv path, "."
436
+ }
437
+ FileUtils.rmdir "doc"
438
+ git "add", "."
439
+ git "commit", "-m", "generated by rdoc"
440
+ git "checkout", current_branch
441
+ git "push", "-f", "origin", "gh-pages"
442
+ end
443
+ end
444
+ end
445
+
446
+ def define_install
447
+ desc "direct install (no gem)"
448
+ task :install do
449
+ Installer.new.install
450
+ end
451
+
452
+ desc "direct uninstall (no gem)"
453
+ task :uninstall do
454
+ Installer.new.uninstall
455
+ end
456
+ end
457
+
458
+ def define_check_directory
459
+ task :check_directory do
460
+ unless `git status` =~ %r!nothing to commit \(working directory clean\)!
461
+ raise "directory not clean"
462
+ end
463
+ end
464
+ end
465
+
466
+ def define_ping
467
+ task :ping do
468
+ require 'rbconfig'
469
+ [
470
+ "github.com",
471
+ ("rubyforge.org" if rubyforge_info),
472
+ ].compact.each do |server|
473
+ cmd = "ping " + (
474
+ if RbConfig::CONFIG["host"] =~ %r!darwin!
475
+ "-c2 #{server}"
476
+ else
477
+ "#{server} 2 2"
478
+ end
479
+ )
480
+ unless `#{cmd}` =~ %r!0% packet loss!
481
+ raise "No ping for #{server}"
482
+ end
483
+ end
484
+ end
485
+ end
486
+
487
+ def define_check_levitate
488
+ task :check_levitate do
489
+ unless local_levitate == remote_levitate
490
+ raise "levitate is out of date"
491
+ end
492
+ end
493
+ end
494
+
495
+ def define_update_levitate
496
+ task :update_levitate do
497
+ if local_levitate == remote_levitate
498
+ puts "Already up-to-date."
499
+ else
500
+ File.open(__FILE__, "w") { |f| f.print(remote_levitate) }
501
+ git "commit", __FILE__, "-m", "update levitate"
502
+ puts "Updated levitate."
503
+ end
504
+ end
505
+ end
506
+
507
+ def define_changes
508
+ task :changes do
509
+ if File.read(history_file).index version
510
+ raise "version not updated"
511
+ end
512
+
513
+ header = "\n\n== Version #{version}\n\n"
514
+
515
+ bullets = `git log --format=%s #{last_release}..HEAD`.lines.map { |line|
516
+ "* #{line}"
517
+ }.join.chomp
518
+
519
+ write_file(history_file) do
520
+ File.read(history_file).sub(/(?<=#{gem_name} Changes)/) {
521
+ header + bullets
522
+ }
523
+ end
524
+ end
525
+ end
526
+
527
+ def define_release
528
+ task :prerelease => [
529
+ :clean,
530
+ :check_directory,
531
+ :check_levitate,
532
+ :ping,
533
+ history_file
534
+ ]
535
+
536
+ task :finish_release do
537
+ git "tag", "#{gem_name}-" + version.to_s
538
+ git "push", "--tags", "origin", "master"
539
+ sh "gem", "push", "pkg/#{gem_name}-#{version}.gem"
540
+ end
541
+
542
+ task :release => [:prerelease, :package, :finish_release, :publish]
543
+ end
544
+
545
+ def define_debug_gem
546
+ task :debug_gem do
547
+ puts gemspec.to_ruby
548
+ end
549
+ end
550
+
551
+ #### helpers
552
+
553
+ def files_in_require_paths
554
+ require_paths.inject([]) { |acc, dir|
555
+ acc + Dir.glob("#{dir}/**/*.rb")
556
+ }
557
+ end
558
+
559
+ def last_release
560
+ `git tag`.lines.select { |t| t.index(gem_name) == 0 }.last.chomp
561
+ end
562
+
563
+ def git(*args)
564
+ sh "git", *args
565
+ end
566
+
567
+ def open_browser(*files)
568
+ sh(*([browser].flatten + files))
569
+ end
570
+
571
+ def suppress_task_warnings(*task_names)
572
+ task_names.each { |task_name|
573
+ Rake::Task[task_name].actions.map! { |action|
574
+ lambda { |*args|
575
+ no_warnings {
576
+ action.call(*args)
577
+ }
578
+ }
579
+ }
580
+ }
581
+ end
582
+
583
+ def ruby_18?
584
+ RUBY_VERSION =~ %r!\A1\.8!
585
+ end
586
+
587
+ def source_control?
588
+ File.directory? ".git"
589
+ end
590
+
591
+ #### utility for instance and class
592
+
593
+ module Util
594
+ def ruby_bin
595
+ require 'rbconfig'
596
+
597
+ name = File.join(
598
+ RbConfig::CONFIG["bindir"],
599
+ RbConfig::CONFIG["RUBY_INSTALL_NAME"]
600
+ )
601
+
602
+ if RbConfig::CONFIG["host"] =~ %r!(mswin|cygwin|mingw)! and
603
+ File.basename(name) !~ %r!\.(exe|com|bat|cmd)\Z!i
604
+ name + RbConfig::CONFIG["EXEEXT"]
605
+ else
606
+ name
607
+ end
608
+ end
609
+
610
+ def ruby_command
611
+ [ruby_bin] + Levitate.ruby_opts.to_a
612
+ end
613
+
614
+ def ruby_command_string
615
+ ruby_command.join(" ")
616
+ end
617
+
618
+ def run(*args)
619
+ cmd = ruby_command + args
620
+ unless system(*cmd)
621
+ cmd_str = cmd.map { |t| "'#{t}'" }.join(", ")
622
+ raise "system(#{cmd_str}) failed with status #{$?.exitstatus}"
623
+ end
624
+ end
625
+
626
+ def run_each_file(*files)
627
+ files.each { |file|
628
+ run("-w", file)
629
+ }
630
+ end
631
+
632
+ def run_code_and_capture(code)
633
+ IO.popen(ruby_command_string, "r+") { |pipe|
634
+ pipe.print(code)
635
+ pipe.flush
636
+ pipe.close_write
637
+ pipe.read
638
+ }
639
+ end
640
+
641
+ def run_file_and_capture(file)
642
+ unless File.file? file
643
+ raise "file does not exist: `#{file}'"
644
+ end
645
+ IO.popen(ruby_command_string + " " + file, "r") { |pipe|
646
+ pipe.read
647
+ }
648
+ end
649
+
650
+ def with_warnings(value = true)
651
+ previous = $VERBOSE
652
+ $VERBOSE = value
653
+ begin
654
+ yield
655
+ ensure
656
+ $VERBOSE = previous
657
+ end
658
+ end
659
+
660
+ def no_warnings(&block)
661
+ with_warnings(nil, &block)
662
+ end
663
+
664
+ def to_camel_case(str)
665
+ str.split('_').map { |t| t.capitalize }.join
666
+ end
667
+
668
+ def write_file(file)
669
+ contents = yield
670
+ File.open(file, "wb") { |out|
671
+ out.print(contents)
672
+ }
673
+ contents
674
+ end
675
+
676
+ def instance_exec2(obj, *args, &block)
677
+ method_name = ["_", obj.object_id, "_", Thread.current.object_id].join
678
+ (class << obj ; self ; end).class_eval do
679
+ define_method method_name, &block
680
+ begin
681
+ obj.send(method_name, *args)
682
+ ensure
683
+ remove_method method_name
684
+ end
685
+ end
686
+ end
687
+ end
688
+ extend Util
689
+ include Util
690
+
691
+ #### public helpers for testing
692
+
693
+ class << self
694
+ # From 'minitest' by Ryan Davis.
695
+ def capture_io
696
+ require 'stringio'
697
+
698
+ orig_stdout, orig_stderr = $stdout, $stderr
699
+ captured_stdout, captured_stderr = StringIO.new, StringIO.new
700
+ $stdout, $stderr = captured_stdout, captured_stderr
701
+
702
+ yield
703
+
704
+ return captured_stdout.string, captured_stderr.string
705
+ ensure
706
+ $stdout = orig_stdout
707
+ $stderr = orig_stderr
708
+ end
709
+
710
+ def run_doc_code(code, expected, index, instance, &block)
711
+ lib = File.expand_path(File.dirname(__FILE__) + "/../lib")
712
+ header = %{
713
+ $LOAD_PATH.unshift "#{lib}"
714
+ begin
715
+ }
716
+ footer = %{
717
+ rescue Exception => __levitate_exception
718
+ puts "raises \#{__levitate_exception.class}"
719
+ end
720
+ }
721
+ final_code = header + code + footer
722
+
723
+ # Sometimes code is required to be inside a file.
724
+ actual = nil
725
+ require 'tempfile'
726
+ Tempfile.open("run-rdoc-code") { |temp_file|
727
+ temp_file.print(final_code)
728
+ temp_file.close
729
+ actual = run_file_and_capture(temp_file.path).chomp
730
+ }
731
+
732
+ instance_exec2(instance, expected, actual, index, &block)
733
+ end
734
+
735
+ def run_doc_section(file, section, instance, &block)
736
+ contents = File.read(file)
737
+ re = %r!^=+[ \t]#{Regexp.quote(section)}.*?\n(.*?)^=!m
738
+ if section_contents = contents[re, 1]
739
+ index = 0
740
+ section_contents.scan(%r!^( \S.*?)(?=(^\S|\Z))!m) { |indented, unused|
741
+ code_sections = indented.split(%r!^ \#\#\#\# output:\s*$!)
742
+ code, expected = (
743
+ case code_sections.size
744
+ when 1
745
+ [indented, indented.scan(%r!\# => (.*?)\n!).flatten.join("\n")]
746
+ when 2
747
+ code_sections
748
+ else
749
+ raise "parse error"
750
+ end
751
+ )
752
+ code.gsub!(/^\s*%.*$/, "") # ignore shell command examples
753
+ run_doc_code(code, expected, index, instance, &block)
754
+ index += 1
755
+ }
756
+ else
757
+ raise "couldn't find section `#{section}' of `#{file}'"
758
+ end
759
+ end
760
+
761
+ def doc_to_spec(file, *sections, &block)
762
+ levitate = self
763
+ describe file do
764
+ sections.each { |section|
765
+ describe "section `#{section}'" do
766
+ it "should run as claimed" do
767
+ if block
768
+ levitate.run_doc_section(file, section, self, &block)
769
+ else
770
+ levitate.run_doc_section(file, section, self) {
771
+ |expected, actual, index|
772
+ actual.should == expected
773
+ }
774
+ end
775
+ end
776
+ end
777
+ }
778
+ end
779
+ end
780
+
781
+ def doc_to_test(file, *sections, &block)
782
+ levitate = self
783
+ klass = Class.new MiniTest::Unit::TestCase do
784
+ sections.each { |section|
785
+ define_method "test_#{file}_#{section}" do
786
+ if block
787
+ levitate.run_doc_section(file, section, self, &block)
788
+ else
789
+ levitate.run_doc_section(file, section, self) {
790
+ |expected, actual, index|
791
+ assert_equal expected, actual
792
+ }
793
+ end
794
+ end
795
+ }
796
+ end
797
+ Object.const_set("Test#{file}".gsub(".", ""), klass)
798
+ end
799
+
800
+ def ruby_opts
801
+ @ruby_opts ||= []
802
+ end
803
+ attr_writer :ruby_opts
804
+ end
805
+
806
+ #### raw install, bypass gems
807
+
808
+ class Installer
809
+ def initialize
810
+ require 'fileutils'
811
+ require 'rbconfig'
812
+ require 'find'
813
+
814
+ @fu = FileUtils::Verbose
815
+ @spec = []
816
+
817
+ rb_root = RbConfig::CONFIG["sitelibdir"]
818
+
819
+ Find.find "lib" do |source|
820
+ next if source == "lib"
821
+ next unless File.directory?(source) || File.extname(source) == ".rb"
822
+ dest = File.join(rb_root, source.sub(%r!\Alib/!, ""))
823
+ @spec << [source, dest]
824
+ end
825
+ end
826
+
827
+ def install
828
+ @spec.each do |source, dest|
829
+ if File.directory?(source)
830
+ @fu.mkdir(dest) unless File.directory?(dest)
831
+ else
832
+ @fu.install(source, dest)
833
+ end
834
+ end
835
+ end
836
+
837
+ def uninstall
838
+ @spec.reverse.each do |source, dest|
839
+ if File.directory?(source)
840
+ @fu.rmdir(dest) if File.directory?(dest)
841
+ else
842
+ @fu.rm(dest) if File.file?(dest)
843
+ end
844
+ end
845
+ end
846
+ end
847
+ end
848
+
849
+ lambda do
850
+ config = File.join(File.dirname(__FILE__), "levitate_config.rb")
851
+ require config if File.file? config
852
+ end.call