boc 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
data/devel/levitate.rb ADDED
@@ -0,0 +1,958 @@
1
+
2
+ class Levitate
3
+ class Installer
4
+ def initialize
5
+ require 'fileutils'
6
+ require 'rbconfig'
7
+ require 'find'
8
+
9
+ rb_root = RbConfig::CONFIG["sitelibdir"]
10
+ @spec = []
11
+
12
+ Find.find "lib" do |source|
13
+ next if source == "lib"
14
+ next unless File.directory?(source) || File.extname(source) == ".rb"
15
+ dest = File.join(rb_root, source.sub(%r!\Alib/!, ""))
16
+ @spec << { :source => source, :dest => dest }
17
+ end
18
+ end
19
+
20
+ def install
21
+ @spec.each do |entry|
22
+ source, dest = entry.values_at(:source, :dest)
23
+ if File.directory?(source)
24
+ unless File.directory?(dest)
25
+ puts "mkdir #{dest}"
26
+ FileUtils.mkdir(dest)
27
+ end
28
+ else
29
+ puts "install #{source} --> #{dest}"
30
+ FileUtils.install(source, dest)
31
+ end
32
+ end
33
+ end
34
+
35
+ def uninstall
36
+ @spec.reverse.each do |entry|
37
+ source, dest = entry.values_at(:source, :dest)
38
+ if File.directory?(source)
39
+ if File.directory?(dest)
40
+ puts "rmdir #{dest}"
41
+ FileUtils.rmdir(dest)
42
+ end
43
+ else
44
+ if File.file?(dest)
45
+ puts "rm #{dest}"
46
+ FileUtils.rm(dest)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ module AttrLazy
54
+ def attr_lazy(name, &block)
55
+ AttrLazy.define_reader(class << self ; self ; end, name, &block)
56
+ end
57
+
58
+ def attr_lazy_accessor(name, &block)
59
+ attr_lazy(name, &block)
60
+ AttrLazy.define_writer(class << self ; self ; end, name, &block)
61
+ end
62
+
63
+ class << self
64
+ def included(mod)
65
+ (class << mod ; self ; end).class_eval do
66
+ def attr_lazy(name, &block)
67
+ AttrLazy.define_reader(self, name, &block)
68
+ end
69
+
70
+ def attr_lazy_accessor(name, &block)
71
+ attr_lazy(name, &block)
72
+ AttrLazy.define_writer(self, name, &block)
73
+ end
74
+ end
75
+ end
76
+
77
+ def define_evaluated_reader(instance, name, value)
78
+ (class << instance ; self ; end).class_eval do
79
+ remove_method name rescue nil
80
+ define_method name do
81
+ value
82
+ end
83
+ end
84
+ end
85
+
86
+ def define_reader(klass, name, &block)
87
+ klass.class_eval do
88
+ remove_method name rescue nil
89
+ define_method name do
90
+ value = instance_eval(&block)
91
+ AttrLazy.define_evaluated_reader(self, name, value)
92
+ value
93
+ end
94
+ end
95
+ end
96
+
97
+ def define_writer(klass, name, &block)
98
+ klass.class_eval do
99
+ writer = "#{name}="
100
+ remove_method writer rescue nil
101
+ define_method writer do |value|
102
+ AttrLazy.define_evaluated_reader(self, name, value)
103
+ value
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ module Ruby
111
+ module_function
112
+
113
+ def executable
114
+ require 'rbconfig'
115
+
116
+ name = File.join(
117
+ RbConfig::CONFIG["bindir"],
118
+ RbConfig::CONFIG["RUBY_INSTALL_NAME"]
119
+ )
120
+
121
+ if RbConfig::CONFIG["host"] =~ %r!(mswin|cygwin|mingw)! and
122
+ File.basename(name) !~ %r!\.(exe|com|bat|cmd)\Z!i
123
+ name + RbConfig::CONFIG["EXEEXT"]
124
+ else
125
+ name
126
+ end
127
+ end
128
+
129
+ def run(*args)
130
+ cmd = [executable, *args]
131
+ unless system(*cmd)
132
+ cmd_str = cmd.map { |t| "'#{t}'" }.join(", ")
133
+ raise "system(#{cmd_str}) failed with status #{$?.exitstatus}"
134
+ end
135
+ end
136
+
137
+ def run_code_and_capture(code)
138
+ IO.popen(%{"#{executable}"}, "r+") { |pipe|
139
+ pipe.print(code)
140
+ pipe.flush
141
+ pipe.close_write
142
+ pipe.read
143
+ }
144
+ end
145
+
146
+ def run_file_and_capture(file)
147
+ unless File.file? file
148
+ raise "file does not exist: `#{file}'"
149
+ end
150
+ IO.popen(%{"#{executable}" "#{file}"}, "r") { |pipe|
151
+ pipe.read
152
+ }
153
+ end
154
+
155
+ def with_warnings(value = true)
156
+ previous = $VERBOSE
157
+ $VERBOSE = value
158
+ begin
159
+ yield
160
+ ensure
161
+ $VERBOSE = previous
162
+ end
163
+ end
164
+
165
+ def no_warnings(&block)
166
+ with_warnings(nil, &block)
167
+ end
168
+ end
169
+
170
+ module Util
171
+ module_function
172
+
173
+ def run_ruby_on_each(*files)
174
+ files.each { |file|
175
+ Ruby.run("-w", file)
176
+ }
177
+ end
178
+
179
+ def to_camel_case(str)
180
+ str.split('_').map { |t| t.capitalize }.join
181
+ end
182
+
183
+ def write_file(file)
184
+ contents = yield
185
+ File.open(file, "wb") { |out|
186
+ out.print(contents)
187
+ }
188
+ contents
189
+ end
190
+
191
+ def instance_exec2(obj, *args, &block)
192
+ method_name = ["_", obj.object_id, "_", Thread.current.object_id].join
193
+ (class << obj ; self ; end).class_eval do
194
+ define_method method_name, &block
195
+ begin
196
+ obj.send(method_name, *args)
197
+ ensure
198
+ remove_method method_name
199
+ end
200
+ end
201
+ end
202
+ end
203
+
204
+ include AttrLazy
205
+ include Util
206
+
207
+ def initialize(gem_name)
208
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
209
+
210
+ require 'rubygems/package_task'
211
+
212
+ @gem_name = gem_name
213
+
214
+ yield self
215
+
216
+ self.class.instance_methods(false).select { |t|
217
+ t.to_s =~ %r!\Adefine_!
218
+ }.sort.each { |method_name|
219
+ send(method_name)
220
+ }
221
+ end
222
+
223
+ class << self
224
+ alias_method :attribute, :attr_lazy_accessor
225
+ end
226
+
227
+ attr_reader :gem_name
228
+
229
+ attribute :version_constant_name do
230
+ "VERSION"
231
+ end
232
+
233
+ attribute :camel_name do
234
+ to_camel_case(gem_name)
235
+ end
236
+
237
+ attribute :version do
238
+ catch :bail do
239
+ if File.file?(version_file = "./lib/#{gem_name}/version.rb")
240
+ require version_file
241
+ elsif File.file?("./lib/#{gem_name}.rb")
242
+ require gem_name
243
+ else
244
+ throw :bail
245
+ end
246
+ mod = Kernel.const_get(camel_name)
247
+ constants = mod.constants.map { |t| t.to_sym }
248
+ unless constants.include?(version_constant_name.to_sym)
249
+ throw :bail
250
+ end
251
+ mod.const_get(version_constant_name)
252
+ end or "0.0.0"
253
+ end
254
+
255
+ attribute :required_ruby_version do
256
+ ">= 0"
257
+ end
258
+
259
+ attribute :readme_file do
260
+ "README.rdoc"
261
+ end
262
+
263
+ attribute :history_file do
264
+ "CHANGES.rdoc"
265
+ end
266
+
267
+ attribute :doc_dir do
268
+ "doc"
269
+ end
270
+
271
+ attribute :spec_files do
272
+ Dir["./spec/*_{spec,example}.rb"]
273
+ end
274
+
275
+ attribute :test_files do
276
+ (Dir["./test/test_*.rb"] + Dir["./test/*_test.rb"]).uniq
277
+ end
278
+
279
+ attribute :cov_dir do
280
+ "coverage"
281
+ end
282
+
283
+ attribute :spec_output_dir do
284
+ "rspec_output"
285
+ end
286
+
287
+ attribute :spec_output_file do
288
+ "spec.html"
289
+ end
290
+
291
+ attr_lazy :spec_output do
292
+ "#{spec_output_dir}/#{spec_output_file}"
293
+ end
294
+
295
+ [:gem, :tgz].each { |ext|
296
+ attribute ext do
297
+ "pkg/#{gem_name}-#{version}.#{ext}"
298
+ end
299
+ }
300
+
301
+ attribute :rcov_options do
302
+ # workaround for the default rspec task
303
+ Dir["*"].select { |f| File.directory? f }.inject(Array.new) { |acc, dir|
304
+ if dir == "lib"
305
+ acc
306
+ else
307
+ acc + ["--exclude", dir + "/"]
308
+ end
309
+ } + ["--text-report"]
310
+ end
311
+
312
+ attribute :readme_file do
313
+ "README.rdoc"
314
+ end
315
+
316
+ attribute :manifest_file do
317
+ "MANIFEST"
318
+ end
319
+
320
+ attribute :generated_files do
321
+ []
322
+ end
323
+
324
+ attribute :extra_gemspec do
325
+ lambda { |spec| }
326
+ end
327
+
328
+ attribute :files do
329
+ if File.file? manifest_file
330
+ File.read(manifest_file).split("\n")
331
+ elsif source_control?
332
+ IO.popen("git ls-files") { |pipe| pipe.read.split "\n" }
333
+ end.to_a + [manifest_file] + generated_files
334
+ end
335
+
336
+ def files_in_require_paths
337
+ require_paths.inject([]) { |acc, dir|
338
+ acc + Dir.glob("#{dir}/**/*.rb")
339
+ }
340
+ end
341
+
342
+ attribute :rdoc_files do
343
+ files_in_require_paths
344
+ end
345
+
346
+ attribute :rdoc_title do
347
+ "#{gem_name}: #{summary}".sub(/\.\Z/, "")
348
+ end
349
+
350
+ attribute :require_paths do
351
+ ["lib"]
352
+ end
353
+
354
+ attribute :rdoc_options do
355
+ if File.file?(readme_file)
356
+ ["--main", readme_file]
357
+ else
358
+ []
359
+ end + [
360
+ "--title", rdoc_title,
361
+ ] + (files_in_require_paths - rdoc_files).inject(Array.new) {
362
+ |acc, file|
363
+ acc + ["--exclude", file]
364
+ }
365
+ end
366
+
367
+ attribute :extra_rdoc_files do
368
+ [readme_file, history_file].select { |file| File.file?(file) }
369
+ end
370
+
371
+ attribute :browser do
372
+ require 'rbconfig'
373
+ if RbConfig::CONFIG["host"] =~ %r!darwin!
374
+ "open"
375
+ else
376
+ "firefox"
377
+ end
378
+ end
379
+
380
+ attribute :gemspec do
381
+ Gem::Specification.new do |g|
382
+ %w[
383
+ authors
384
+ email
385
+ summary
386
+ version
387
+ description
388
+ files
389
+ rdoc_options
390
+ extra_rdoc_files
391
+ require_paths
392
+ required_ruby_version
393
+ extensions
394
+ ].each do |param|
395
+ t = send(param) and g.send("#{param}=", t)
396
+ end
397
+ g.name = gem_name
398
+ g.has_rdoc = true
399
+ g.homepage = url if url
400
+ dependencies.each { |dep|
401
+ g.add_dependency(*dep)
402
+ }
403
+ development_dependencies.each { |dep|
404
+ g.add_development_dependency(*dep)
405
+ }
406
+ extra_gemspec.call(g)
407
+ end
408
+ end
409
+
410
+ attribute :readme_contents do
411
+ File.read(readme_file) rescue "FIXME: readme_file"
412
+ end
413
+
414
+ attribute :sections do
415
+ begin
416
+ data = readme_contents.split(%r!^==\s*(.*?)\s*$!)
417
+ pairs = data[1..-1].each_slice(2).map { |section, contents|
418
+ [section.downcase, contents.strip]
419
+ }
420
+ Hash[*pairs.flatten]
421
+ rescue
422
+ nil
423
+ end
424
+ end
425
+
426
+ attribute :description_section do
427
+ "description"
428
+ end
429
+
430
+ attribute :summary_section do
431
+ "summary"
432
+ end
433
+
434
+ attribute :description_sentences do
435
+ 1
436
+ end
437
+
438
+ attribute :summary_sentences do
439
+ 1
440
+ end
441
+
442
+ [:summary, :description].each { |section|
443
+ attribute section do
444
+ begin
445
+ sections[send("#{section}_section")].
446
+ gsub("\n", " ").
447
+ split(%r!\.\s+!m).
448
+ first(send("#{section}_sentences")).
449
+ join(". ").
450
+ concat(".").
451
+ sub(%r!\.+\Z!, ".")
452
+ rescue
453
+ "FIXME: #{section}"
454
+ end
455
+ end
456
+ }
457
+
458
+ attribute :url do
459
+ "http://#{username}.github.com/#{gem_name}"
460
+ end
461
+
462
+ attribute :username do
463
+ raise "username not set"
464
+ end
465
+
466
+ attribute :rubyforge_info do
467
+ nil
468
+ end
469
+
470
+ def authors
471
+ developers.map { |d| d[0] }
472
+ end
473
+
474
+ def email
475
+ developers.map { |d| d[1] }
476
+ end
477
+
478
+ attribute :dependencies do
479
+ []
480
+ end
481
+
482
+ attribute :development_dependencies do
483
+ []
484
+ end
485
+
486
+ attribute :developers do
487
+ []
488
+ end
489
+
490
+ attribute :extensions do
491
+ ["ext/#{gem_name}/extconf.rb"].select { |f| File.file? f }
492
+ end
493
+
494
+ attribute :so_file do
495
+ unless extensions.empty?
496
+ require 'rbconfig'
497
+ "lib/" + gem_name + "." + RbConfig::CONFIG["DLEXT"]
498
+ end
499
+ end
500
+
501
+ def define_clean
502
+ require 'rake/clean'
503
+ task :clean do
504
+ Rake::Task[:clobber].invoke
505
+ end
506
+ end
507
+
508
+ def define_package
509
+ if source_control?
510
+ task manifest_file do
511
+ create_manifest
512
+ end
513
+ CLEAN.add manifest_file
514
+ task :package => :clean
515
+ Gem::PackageTask.new(gemspec).define
516
+ end
517
+ end
518
+
519
+ def define_spec
520
+ unless spec_files.empty?
521
+ Ruby.no_warnings {
522
+ require 'spec/rake/spectask'
523
+ }
524
+
525
+ desc "run specs"
526
+ Spec::Rake::SpecTask.new('spec') do |t|
527
+ t.spec_files = spec_files
528
+ end
529
+
530
+ desc "run specs with text output"
531
+ Spec::Rake::SpecTask.new('text_spec') do |t|
532
+ t.spec_files = spec_files
533
+ t.spec_opts = ['-fs']
534
+ end
535
+
536
+ desc "run specs with html output"
537
+ Spec::Rake::SpecTask.new('full_spec') do |t|
538
+ t.spec_files = spec_files
539
+ t.rcov = true
540
+ t.rcov_opts = rcov_options
541
+ t.spec_opts = ["-fh:#{spec_output}"]
542
+ end
543
+
544
+ suppress_task_warnings :spec, :full_spec, :text_spec
545
+
546
+ desc "run full_spec then open browser"
547
+ task :show_spec => :full_spec do
548
+ open_browser(spec_output, cov_dir + "/index.html")
549
+ end
550
+
551
+ desc "run specs individually"
552
+ task :spec_deps do
553
+ run_ruby_on_each(*spec_files)
554
+ end
555
+
556
+ task :prerelease => [:spec, :spec_deps]
557
+ task :default => :spec
558
+
559
+ CLEAN.add spec_output_dir
560
+ end
561
+ end
562
+
563
+ def define_test
564
+ unless test_files.empty?
565
+ desc "run tests"
566
+ task :test do
567
+ test_files.each { |file| require file }
568
+
569
+ # if we use at_exit hook instead, it won't run before :release
570
+ MiniTest::Unit.new.run ARGV
571
+ end
572
+
573
+ desc "run tests with coverage"
574
+ if ruby_18?
575
+ task :full_test do
576
+ verbose(false) {
577
+ sh("rcov", "-o", cov_dir, "--text-report",
578
+ *(test_files + rcov_options)
579
+ )
580
+ }
581
+ end
582
+ else
583
+ task :full_test do
584
+ rm_rf cov_dir
585
+ require 'simplecov'
586
+ SimpleCov.start do
587
+ add_filter "test/"
588
+ add_filter "devel/"
589
+ end
590
+ Rake::Task[:test].invoke
591
+ end
592
+ end
593
+
594
+ desc "run full_test then open browser"
595
+ task :show_test => :full_test do
596
+ show = lambda { open_browser(cov_dir + "/index.html") }
597
+ if ruby_18?
598
+ show.call
599
+ else
600
+ SimpleCov.at_exit do
601
+ SimpleCov.result.format!
602
+ show.call
603
+ end
604
+ end
605
+ end
606
+
607
+ desc "run tests individually"
608
+ task :test_deps do
609
+ run_ruby_on_each(*test_files)
610
+ end
611
+
612
+ task :prerelease => [:test, :test_deps]
613
+ task :default => :test
614
+
615
+ CLEAN.add cov_dir
616
+ end
617
+ end
618
+
619
+ def define_doc
620
+ desc "run rdoc"
621
+ task :doc => :clean_doc do
622
+ Kernel.send :gem, 'rdoc' rescue nil
623
+ require 'rdoc/rdoc'
624
+ args = (
625
+ gemspec.rdoc_options +
626
+ gemspec.require_paths.clone +
627
+ gemspec.extra_rdoc_files +
628
+ ["-o", doc_dir]
629
+ ).flatten.map { |t| t.to_s }
630
+ RDoc::RDoc.new.document args
631
+ end
632
+
633
+ task :clean_doc do
634
+ # normally rm_rf, but mimic rake/clean output
635
+ rm_r(doc_dir) rescue nil
636
+ end
637
+
638
+ desc "run rdoc then open browser"
639
+ task :show_doc => :doc do
640
+ open_browser(doc_dir + "/index.html")
641
+ end
642
+
643
+ task :rdoc => :doc
644
+ task :clean => :clean_doc
645
+ end
646
+
647
+ def define_publish
648
+ if source_control?
649
+ desc "publish docs"
650
+ task :publish => [:clean, :check_directory, :doc] do
651
+ if rubyforge_info
652
+ user, project = rubyforge_info
653
+ Dir.chdir(doc_dir) do
654
+ sh "scp", "-r",
655
+ ".",
656
+ "#{user}@rubyforge.org:/var/www/gforge-projects/#{project}"
657
+ end
658
+ end
659
+ git "branch", "-D", "gh-pages"
660
+ git "checkout", "--orphan", "gh-pages"
661
+ FileUtils.rm ".git/index"
662
+ git "clean", "-fdx", "-e", "doc"
663
+ Dir["doc/*"].each { |path|
664
+ FileUtils.mv path, "."
665
+ }
666
+ FileUtils.rmdir "doc"
667
+ git "add", "."
668
+ git "commit", "-m", "generated by rdoc"
669
+ git "push", "-f", "origin", "gh-pages"
670
+ end
671
+ end
672
+ end
673
+
674
+ def define_install
675
+ desc "direct install (no gem)"
676
+ task :install do
677
+ Installer.new.install
678
+ end
679
+
680
+ desc "direct uninstall (no gem)"
681
+ task :uninstall do
682
+ Installer.new.uninstall
683
+ end
684
+
685
+ if so_file
686
+ dest = File.join(RbConfig::CONFIG["sitearchdir"], File.basename(so_file))
687
+
688
+ task :install => so_file do
689
+ puts "install #{so_file} --> #{dest}"
690
+ FileUtils.install(so_file, dest)
691
+ end
692
+
693
+ task :uninstall do
694
+ if File.file?(dest)
695
+ puts "rm #{dest}"
696
+ FileUtils.rm(dest)
697
+ end
698
+ end
699
+ end
700
+ end
701
+
702
+ def define_check_directory
703
+ task :check_directory do
704
+ unless `git status` =~ %r!nothing to commit \(working directory clean\)!
705
+ raise "directory not clean"
706
+ end
707
+ end
708
+ end
709
+
710
+ def define_ping
711
+ task :ping do
712
+ require 'rbconfig'
713
+ %w[github.com].each { |server|
714
+ cmd = "ping " + (
715
+ if RbConfig::CONFIG["host"] =~ %r!darwin!
716
+ "-c2 #{server}"
717
+ else
718
+ "#{server} 2 2"
719
+ end
720
+ )
721
+ unless `#{cmd}` =~ %r!0% packet loss!
722
+ raise "No ping for #{server}"
723
+ end
724
+ }
725
+ end
726
+ end
727
+
728
+ def define_update_levitate
729
+ url = ENV["LEVITATE"] ||
730
+ "https://github.com/quix/levitate/raw/master/levitate.rb"
731
+ task :update_levitate do
732
+ if system "curl", "-s", "-o", __FILE__, url
733
+ if `git diff #{__FILE__}` == ""
734
+ puts "Already up-to-date."
735
+ else
736
+ git "commit", __FILE__, "-m", "updated levitate"
737
+ puts "Updated levitate."
738
+ end
739
+ else
740
+ raise "levitate download failed"
741
+ end
742
+ end
743
+ end
744
+
745
+ def define_changes
746
+ task :changes do
747
+ if File.read(history_file).index version
748
+ raise "version not updated"
749
+ end
750
+
751
+ header = "\n\n== Version #{version}\n\n"
752
+
753
+ bullets = `git log --format=%s #{last_release}..HEAD`.lines.map { |line|
754
+ "* #{line}"
755
+ }.join.chomp
756
+
757
+ write_file(history_file) do
758
+ File.read(history_file).sub(/(?<=#{gem_name} Changes)/) {
759
+ header + bullets
760
+ }
761
+ end
762
+ end
763
+ end
764
+
765
+ def last_release
766
+ `git tag`.lines.select { |t| t.index(gem_name) == 0 }.last.chomp
767
+ end
768
+
769
+ def git(*args)
770
+ sh "git", *args
771
+ end
772
+
773
+ def create_manifest
774
+ write_file(manifest_file) {
775
+ files.sort.join("\n")
776
+ }
777
+ end
778
+
779
+ def define_release
780
+ task :prerelease => [:clean, :check_directory, :ping, history_file]
781
+
782
+ task :finish_release do
783
+ git "tag", "#{gem_name}-" + version.to_s
784
+ git "push", "--tags", "origin", "master"
785
+ sh "gem", "push", gem
786
+ end
787
+
788
+ task :release => [:prerelease, :package, :finish_release]
789
+ end
790
+
791
+ def define_debug_gem
792
+ task :debug_gem do
793
+ puts gemspec.to_ruby
794
+ end
795
+ end
796
+
797
+ def define_extension
798
+ if source_control? or (so_file and !File.file?(so_file))
799
+ require 'rbconfig'
800
+ require 'rake/extensiontask'
801
+
802
+ Rake::ExtensionTask.new gem_name, gemspec do |ext|
803
+ ext.cross_compile = true
804
+ ext.cross_platform = 'i386-mswin32'
805
+ ext.cross_compiling do |gemspec|
806
+ gemspec.post_install_message =
807
+ "U got dat binary versionation of this gemination!"
808
+ end
809
+ end
810
+
811
+ so = "lib/#{gem_name}.#{RbConfig::CONFIG["DLEXT"]}"
812
+ if Rake::Task[so].needed?
813
+ task :test => so
814
+ end
815
+
816
+ task :cross_native_gem do
817
+ Rake::Task[:gem].reenable
818
+ Rake.application.top_level_tasks.replace %w[cross native gem]
819
+ Rake.application.top_level
820
+ end
821
+
822
+ task :gem => :cross_native_gem
823
+ end
824
+ end
825
+
826
+ def open_browser(*files)
827
+ sh(*([browser].flatten + files))
828
+ end
829
+
830
+ def suppress_task_warnings(*task_names)
831
+ task_names.each { |task_name|
832
+ Rake::Task[task_name].actions.map! { |action|
833
+ lambda { |*args|
834
+ Ruby.no_warnings {
835
+ action.call(*args)
836
+ }
837
+ }
838
+ }
839
+ }
840
+ end
841
+
842
+ def ruby_18?
843
+ RUBY_VERSION =~ %r!\A1\.8!
844
+ end
845
+
846
+ def source_control?
847
+ File.directory? ".git"
848
+ end
849
+
850
+ class << self
851
+ include Util
852
+
853
+ # From minitest, part of the Ruby source; by Ryan Davis.
854
+ def capture_io
855
+ require 'stringio'
856
+
857
+ orig_stdout, orig_stderr = $stdout, $stderr
858
+ captured_stdout, captured_stderr = StringIO.new, StringIO.new
859
+ $stdout, $stderr = captured_stdout, captured_stderr
860
+
861
+ yield
862
+
863
+ return captured_stdout.string, captured_stderr.string
864
+ ensure
865
+ $stdout = orig_stdout
866
+ $stderr = orig_stderr
867
+ end
868
+
869
+ def run_doc_code(code, expected, index, instance, &block)
870
+ lib = File.expand_path(File.dirname(__FILE__) + "/../lib")
871
+ header = %{
872
+ $LOAD_PATH.unshift "#{lib}"
873
+ begin
874
+ }
875
+ footer = %{
876
+ rescue Exception => __levitate_exception
877
+ puts "raises \#{__levitate_exception.class}"
878
+ end
879
+ }
880
+ final_code = header + code + footer
881
+
882
+ # Sometimes code is required to be inside a file.
883
+ actual = nil
884
+ require 'tempfile'
885
+ Tempfile.open("run-rdoc-code") { |temp_file|
886
+ temp_file.print(final_code)
887
+ temp_file.close
888
+ actual = Ruby.run_file_and_capture(temp_file.path).chomp
889
+ }
890
+
891
+ instance_exec2(instance, expected, actual, index, &block)
892
+ end
893
+
894
+ def run_doc_section(file, section, instance, &block)
895
+ contents = File.read(file)
896
+ re = %r!^=+[ \t]#{Regexp.quote(section)}.*?\n(.*?)^=!m
897
+ if section_contents = contents[re, 1]
898
+ index = 0
899
+ section_contents.scan(%r!^( \S.*?)(?=(^\S|\Z))!m) { |indented, unused|
900
+ code_sections = indented.split(%r!^ \#\#\#\# output:\s*$!)
901
+ code, expected = (
902
+ case code_sections.size
903
+ when 1
904
+ [indented, indented.scan(%r!\# => (.*?)\n!).flatten.join("\n")]
905
+ when 2
906
+ code_sections
907
+ else
908
+ raise "parse error"
909
+ end
910
+ )
911
+ run_doc_code(code, expected, index, instance, &block)
912
+ index += 1
913
+ }
914
+ else
915
+ raise "couldn't find section `#{section}' of `#{file}'"
916
+ end
917
+ end
918
+
919
+ def doc_to_spec(file, *sections, &block)
920
+ levitate = self
921
+ describe file do
922
+ sections.each { |section|
923
+ describe "section `#{section}'" do
924
+ it "should run as claimed" do
925
+ if block
926
+ levitate.run_doc_section(file, section, self, &block)
927
+ else
928
+ levitate.run_doc_section(file, section, self) {
929
+ |expected, actual, index|
930
+ actual.should == expected
931
+ }
932
+ end
933
+ end
934
+ end
935
+ }
936
+ end
937
+ end
938
+
939
+ def doc_to_test(file, *sections, &block)
940
+ levitate = self
941
+ klass = Class.new MiniTest::Unit::TestCase do
942
+ sections.each { |section|
943
+ define_method "test_#{file}_#{section}" do
944
+ if block
945
+ levitate.run_doc_section(file, section, self, &block)
946
+ else
947
+ levitate.run_doc_section(file, section, self) {
948
+ |expected, actual, index|
949
+ assert_equal expected, actual
950
+ }
951
+ end
952
+ end
953
+ }
954
+ end
955
+ Object.const_set("Test#{file}".gsub(".", ""), klass)
956
+ end
957
+ end
958
+ end