pure 0.2.1 → 0.2.2

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