mvz-live_ast 1.1.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 (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