live_ast 0.2.0

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.
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