mvz-live_ast 1.1.0

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