live_ast 0.2.0

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