pure 0.2.1 → 0.2.2

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