pure 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+
2
+ = Pure ChangeLog
3
+
4
+ == Version 0.1.0
5
+
6
+ * Initial release.
7
+
@@ -0,0 +1,30 @@
1
+ CHANGES.rdoc
2
+ MANIFEST
3
+ README.rdoc
4
+ Rakefile
5
+ devel/jumpstart.rb
6
+ devel/jumpstart/lazy_attribute.rb
7
+ devel/jumpstart/ruby.rb
8
+ devel/jumpstart/simple_installer.rb
9
+ install.rb
10
+ lib/pure.rb
11
+ lib/pure/pure_private/creator.rb
12
+ lib/pure/pure_private/driver.rb
13
+ lib/pure/pure_private/error.rb
14
+ lib/pure/pure_private/extractor.rb
15
+ lib/pure/pure_private/extractor_ripper.rb
16
+ lib/pure/pure_private/extractor_ruby_parser.rb
17
+ lib/pure/pure_private/function_database.rb
18
+ lib/pure/pure_private/singleton_features.rb
19
+ lib/pure/pure_private/util.rb
20
+ spec/basic_spec.rb
21
+ spec/combine_spec.rb
22
+ spec/common.rb
23
+ spec/error_spec.rb
24
+ spec/fun_spec.rb
25
+ spec/lazy_spec.rb
26
+ spec/parser_spec.rb
27
+ spec/readme_spec.rb
28
+ spec/splat_spec.rb
29
+ spec/subseqent_spec.rb
30
+ spec/timed_spec.rb
@@ -0,0 +1,59 @@
1
+
2
+ = Pure
3
+
4
+ == Summary
5
+
6
+ Language-level support for automatic parallelism and lazy evaluation.
7
+
8
+ == Synopsis
9
+
10
+ require 'pure'
11
+ include Pure
12
+
13
+ geometry = pure do
14
+ def area(width, height)
15
+ width*height
16
+ end
17
+
18
+ def width(border)
19
+ 7 + border
20
+ end
21
+
22
+ def height(border)
23
+ 5 + border
24
+ end
25
+
26
+ def border
27
+ 2
28
+ end
29
+ end
30
+
31
+ # compute the area using 3 parallel threads
32
+ puts geometry.compute(:area, :threads => 3)
33
+ # => 63
34
+
35
+ # We've done this computation.
36
+ puts((7 + 2)*(5 + 2))
37
+ # => 63
38
+
39
+ == Description
40
+
41
+ +Pure+ is an importation of the pure functional paradigm into Ruby.
42
+
43
+ Method names and argument names have lexicographical meaning within a
44
+ +pure+ block. Above, the +width+ argument to +area+ corresponds by
45
+ its literal name to the +width+ method, for example.
46
+
47
+ +pure+ returns a module which may be included into other +pure+ blocks.
48
+
49
+ Implementation details are placed inside Pure::PurePrivate, making
50
+ <tt>include Pure</tt> a hygienic operation.
51
+
52
+ Pure does not modify any of the standard classes.
53
+
54
+ == Implementation
55
+
56
+ The "ripper" parser is used if it exists, otherwise ruby_parser is
57
+ used.
58
+
59
+ Pure has been tested on MRI 1.8.6, 1.8.7, 1.9.1 and the latest jruby.
@@ -0,0 +1,10 @@
1
+ $LOAD_PATH.unshift "devel"
2
+
3
+ require "jumpstart"
4
+
5
+ Jumpstart.new "pure" do |s|
6
+ s.developer("James M. Lawrence", "quixoticsycophant@gmail.com")
7
+ s.dependency("comp_tree", ">= 0.7.6")
8
+ s.rubyforge_user = "quix"
9
+ s.rubyforge_name = "purefunctional"
10
+ end
@@ -0,0 +1,634 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
+ $LOAD_PATH.unshift File.dirname(__FILE__)
3
+
4
+ require 'rubygems'
5
+ require 'ostruct'
6
+ require 'rbconfig'
7
+
8
+ require 'rake/gempackagetask'
9
+ require 'rake/contrib/sshpublisher'
10
+ require 'rake/clean'
11
+
12
+ require 'rdoc/rdoc'
13
+
14
+ require 'jumpstart/ruby'
15
+ require 'jumpstart/lazy_attribute'
16
+ require 'jumpstart/simple_installer'
17
+
18
+ class Jumpstart
19
+ include LazyAttribute
20
+
21
+ def initialize(project_name)
22
+ attribute :name do
23
+ project_name
24
+ end
25
+
26
+ attribute :version do
27
+ const = "VERSION"
28
+ begin
29
+ require name
30
+ if mod = Object.const_get(to_camel_case(name))
31
+ candidates = mod.constants.grep(%r!#{const}!)
32
+ result = (
33
+ if candidates.size == 1
34
+ candidates.first
35
+ elsif candidates.include? const
36
+ const
37
+ else
38
+ raise
39
+ end
40
+ )
41
+ mod.const_get(result)
42
+ else
43
+ raise
44
+ end
45
+ rescue Exception
46
+ "0.0.0"
47
+ end
48
+ end
49
+
50
+ attribute :rubyforge_name do
51
+ name.gsub('_', '')
52
+ end
53
+
54
+ attribute :rubyforge_user do
55
+ email.first[%r!^.*(?=@)!]
56
+ end
57
+
58
+ attribute :readme_file do
59
+ "README.rdoc"
60
+ end
61
+
62
+ attribute :history_file do
63
+ "CHANGES.rdoc"
64
+ end
65
+
66
+ attribute :doc_dir do
67
+ "documentation"
68
+ end
69
+
70
+ attribute :spec_files do
71
+ Dir["spec/*_{spec,example}.rb"]
72
+ end
73
+
74
+ attribute :test_files do
75
+ Dir["test/test_*.rb"]
76
+ end
77
+
78
+ attribute :rcov_dir do
79
+ "coverage"
80
+ end
81
+
82
+ attribute :spec_output do
83
+ "spec.html"
84
+ end
85
+
86
+ %w[gem tgz].map { |ext|
87
+ attribute ext.to_sym do
88
+ "pkg/#{name}-#{version}.#{ext}"
89
+ end
90
+ }
91
+
92
+ attribute :rcov_options do
93
+ # workaround for the default rspec task
94
+ Dir["*"].select { |f| File.directory? f }.inject(Array.new) { |acc, dir|
95
+ if dir == "lib"
96
+ acc
97
+ else
98
+ acc + ["--exclude", dir + "/"]
99
+ end
100
+ } + ["--text-report"]
101
+ end
102
+
103
+ attribute :readme_file do
104
+ "README.rdoc"
105
+ end
106
+
107
+ attribute :manifest_file do
108
+ "MANIFEST"
109
+ end
110
+
111
+ attribute :files do
112
+ if File.exist?(manifest_file)
113
+ File.read(manifest_file).split("\n")
114
+ else
115
+ [manifest_file] + `git ls-files`.split("\n")
116
+ end
117
+ end
118
+
119
+ attribute :rdoc_files do
120
+ Dir["lib/**/*.rb"]
121
+ end
122
+
123
+ attribute :extra_rdoc_files do
124
+ if File.exist?(readme_file)
125
+ [readme_file]
126
+ else
127
+ []
128
+ end
129
+ end
130
+
131
+ attribute :rdoc_options do
132
+ if File.exist?(readme_file)
133
+ ["--main", readme_file]
134
+ else
135
+ []
136
+ end + [
137
+ "--title", "#{name}: #{summary}",
138
+ ] + (files - rdoc_files).inject(Array.new) { |acc, file|
139
+ acc + ["--exclude", file]
140
+ }
141
+ end
142
+
143
+ attribute :browser do
144
+ if Config::CONFIG["host"] =~ %r!darwin!
145
+ app = %w[Firefox Safari].map { |t|
146
+ "/Applications/#{t}.app"
147
+ }.select { |t|
148
+ File.exist? t
149
+ }.first
150
+ if app
151
+ ["open", app]
152
+ else
153
+ raise "need to set `browser'"
154
+ end
155
+ else
156
+ "firefox"
157
+ end
158
+ end
159
+
160
+ attribute :gemspec do
161
+ Gem::Specification.new { |g|
162
+ g.has_rdoc = true
163
+ %w[
164
+ name
165
+ authors
166
+ email
167
+ summary
168
+ version
169
+ description
170
+ files
171
+ extra_rdoc_files
172
+ rdoc_options
173
+ ].each { |param|
174
+ value = send(param) and (
175
+ g.send("#{param}=", value)
176
+ )
177
+ }
178
+
179
+ if rubyforge_name
180
+ g.rubyforge_project = rubyforge_name
181
+ end
182
+
183
+ if url
184
+ g.homepage = url
185
+ end
186
+
187
+ extra_deps.each { |dep|
188
+ g.add_dependency(*dep)
189
+ }
190
+
191
+ extra_dev_deps.each { |dep|
192
+ g.add_development_dependency(*dep)
193
+ }
194
+ }
195
+ end
196
+
197
+ attribute :readme_contents do
198
+ File.read(readme_file) rescue "FIXME: readme_file"
199
+ end
200
+
201
+ attribute :sections do
202
+ begin
203
+ pairs = Hash[*readme_contents.split(%r!^== (\w+).*?$!)[1..-1]].map {
204
+ |section, contents|
205
+ [section.downcase, contents.strip]
206
+ }
207
+ Hash[*pairs.flatten]
208
+ rescue
209
+ nil
210
+ end
211
+ end
212
+
213
+ attribute :description_section do
214
+ "description"
215
+ end
216
+
217
+ attribute :summary_section do
218
+ "summary"
219
+ end
220
+
221
+ attribute :description_sentences do
222
+ 1
223
+ end
224
+
225
+ attribute :summary_sentences do
226
+ 1
227
+ end
228
+
229
+ [:summary, :description].each { |section|
230
+ attribute section do
231
+ begin
232
+ sections[send("#{section}_section")].
233
+ gsub("\n", " ").
234
+ split(%r!\.\s*!m).
235
+ first(send("#{section}_sentences")).
236
+ join(". ") << "."
237
+ rescue
238
+ "FIXME: #{section}"
239
+ end
240
+ end
241
+ }
242
+
243
+ attribute :url do
244
+ begin
245
+ readme_contents.match(%r!^\*.*?(http://\S+)!)[1]
246
+ rescue
247
+ "http://#{rubyforge_name}.rubyforge.org"
248
+ end
249
+ end
250
+
251
+ attribute :extra_deps do
252
+ []
253
+ end
254
+
255
+ attribute :extra_dev_deps do
256
+ []
257
+ end
258
+
259
+ yield self
260
+
261
+ self.class.instance_methods(false).select { |t|
262
+ t.to_s =~ %r!\Adefine_!
263
+ }.each { |method_name|
264
+ send(method_name)
265
+ }
266
+ end
267
+
268
+ def developer(name, email)
269
+ authors << name
270
+ self.email << email
271
+ end
272
+
273
+ def authors
274
+ @authors ||= Array.new
275
+ end
276
+
277
+ def email
278
+ @email ||= Array.new
279
+ end
280
+
281
+ def dependency(name, version)
282
+ extra_deps << [name, version]
283
+ end
284
+
285
+ def define_clean
286
+ task :clean do
287
+ Rake::Task[:clobber].invoke
288
+ end
289
+ end
290
+
291
+ def define_package
292
+ task manifest_file do
293
+ create_manifest
294
+ end
295
+ CLEAN.include manifest_file
296
+ task :package => :clean
297
+ Rake::GemPackageTask.new(gemspec) { |t|
298
+ t.need_tar = true
299
+ }
300
+ end
301
+
302
+ def define_spec
303
+ unless spec_files.empty?
304
+ require 'spec/rake/spectask'
305
+
306
+ desc "run specs"
307
+ Spec::Rake::SpecTask.new('spec') do |t|
308
+ t.spec_files = spec_files
309
+ end
310
+
311
+ desc "run specs with text output"
312
+ Spec::Rake::SpecTask.new('text_spec') do |t|
313
+ t.spec_files = spec_files
314
+ t.spec_opts = ['-fs']
315
+ end
316
+
317
+ desc "run specs with html output"
318
+ Spec::Rake::SpecTask.new('full_spec') do |t|
319
+ t.spec_files = spec_files
320
+ t.rcov = true
321
+ t.rcov_opts = rcov_options
322
+ t.spec_opts = ["-fh:#{spec_output}"]
323
+ end
324
+
325
+ desc "run full_spec then open browser"
326
+ task :show_spec => :full_spec do
327
+ open_browser(spec_output, rcov_dir + "/index.html")
328
+ end
329
+
330
+ desc "run specs individually"
331
+ task :spec_deps do
332
+ run_ruby_on_each(*spec_files)
333
+ end
334
+
335
+ task :prerelease => [:spec, :spec_deps]
336
+ task :default => :spec
337
+
338
+ CLEAN.include spec_output
339
+ end
340
+ end
341
+
342
+ def define_test
343
+ unless test_files.empty?
344
+ desc "run tests"
345
+ task :test do
346
+ test_files.each { |file|
347
+ require file
348
+ }
349
+ end
350
+
351
+ desc "run tests with rcov"
352
+ task :full_test do
353
+ verbose(false) {
354
+ sh("rcov", "-o", rcov_dir, "--text-report",
355
+ *(test_files + rcov_options)
356
+ )
357
+ }
358
+ end
359
+
360
+ desc "run full_test then open browser"
361
+ task :show_test => :full_test do
362
+ open_browser(rcov_dir + "/index.html")
363
+ end
364
+
365
+ desc "run tests individually"
366
+ task :test_deps do
367
+ run_ruby_on_each(*test_files)
368
+ end
369
+
370
+ task :prerelease => [:test, :test_deps]
371
+ task :default => :test
372
+
373
+ CLEAN.include rcov_dir
374
+ end
375
+ end
376
+
377
+ def define_doc
378
+ desc "run rdoc"
379
+ task :doc => :clean_doc do
380
+ args = (
381
+ gemspec.rdoc_options +
382
+ gemspec.require_paths.clone +
383
+ gemspec.extra_rdoc_files +
384
+ ["-o", doc_dir]
385
+ ).flatten.map { |t| t.to_s }
386
+ RDoc::RDoc.new.document args
387
+ end
388
+
389
+ task :clean_doc do
390
+ # normally rm_rf, but mimic rake/clean output
391
+ rm_r(doc_dir) rescue nil
392
+ end
393
+
394
+ desc "run rdoc then open browser"
395
+ task :show_doc => :doc do
396
+ open_browser(doc_dir + "/index.html")
397
+ end
398
+
399
+ task :rdoc => :doc
400
+ task :clean => :clean_doc
401
+ end
402
+
403
+ def define_publish
404
+ desc "upload docs"
405
+ task :publish => [:clean_doc, :doc] do
406
+ Rake::SshDirPublisher.new(
407
+ "#{rubyforge_user}@rubyforge.org",
408
+ "/var/www/gforge-projects/#{rubyforge_name}",
409
+ doc_dir
410
+ ).upload
411
+ end
412
+ end
413
+
414
+ def define_install
415
+ desc "direct install (no gem)"
416
+ task :install do
417
+ SimpleInstaller.new.run([])
418
+ end
419
+
420
+ desc "direct uninstall (no gem)"
421
+ task :uninstall do
422
+ SimpleInstaller.new.run(["--uninstall"])
423
+ end
424
+ end
425
+
426
+ def define_debug
427
+ runner = Class.new do
428
+ def comment_src_dst(on)
429
+ on ? ["", "#"] : ["#", ""]
430
+ end
431
+
432
+ def comment_regions(on, contents, start)
433
+ src, dst = comment_src_dst(on)
434
+ contents.gsub(%r!^(\s+)#{src}#{start}.*?^\1#{src}(\}|end)!m) { |chunk|
435
+ indent = $1
436
+ chunk.gsub(%r!^#{indent}#{src}!, "#{indent}#{dst}")
437
+ }
438
+ end
439
+
440
+ def comment_lines(on, contents, start)
441
+ src, dst = comment_src_dst(on)
442
+ contents.gsub(%r!^(\s*)#{src}#{start}!) {
443
+ $1 + dst + start
444
+ }
445
+ end
446
+
447
+ def debug_info(enable)
448
+ Find.find("lib", "test") { |path|
449
+ if path =~ %r!\.rb\Z!
450
+ Jumpstart.replace_file(path) { |contents|
451
+ result = comment_regions(!enable, contents, "debug")
452
+ comment_lines(!enable, result, "trace")
453
+ }
454
+ end
455
+ }
456
+ end
457
+ end
458
+
459
+ desc "enable debug and trace calls"
460
+ task :debug_on do
461
+ runner.new.debug_info(true)
462
+ end
463
+
464
+ desc "disable debug and trace calls"
465
+ task :debug_off do
466
+ runner.new.debug_info(false)
467
+ end
468
+ end
469
+
470
+ def define_columns
471
+ desc "check for columns > 80"
472
+ task :check_columns do
473
+ Dir["**/*.rb"].each { |file|
474
+ File.read(file).scan(%r!^.{81}!) { |match|
475
+ unless match =~ %r!http://!
476
+ raise "#{file} greater than 80 columns: #{match}"
477
+ end
478
+ }
479
+ }
480
+ end
481
+ task :prerelease => :check_columns
482
+ end
483
+
484
+ def define_comments
485
+ task :comments do
486
+ file = "comments.txt"
487
+ write_file(file) {
488
+ Array.new.tap { |result|
489
+ (["Rakefile"] + Dir["**/*.{rb,rake}"]).each { |file|
490
+ File.read(file).scan(%r!\#[^\{].*$!) { |match|
491
+ result << match
492
+ }
493
+ }
494
+ }.join("\n")
495
+ }
496
+ CLEAN.include file
497
+ end
498
+ end
499
+
500
+ def define_check_directory
501
+ task :check_directory do
502
+ unless `git status` =~ %r!nothing to commit \(working directory clean\)!
503
+ raise "Directory not clean"
504
+ end
505
+ end
506
+ end
507
+
508
+ def define_ping
509
+ task :ping do
510
+ %w[github.com rubyforge.org].each { |server|
511
+ cmd = "ping " + (
512
+ if Config::CONFIG["host"] =~ %r!darwin!
513
+ "-c2 #{server}"
514
+ else
515
+ "#{server} 2 2"
516
+ end
517
+ )
518
+ unless `#{cmd}` =~ %r!0% packet loss!
519
+ raise "No ping for #{server}"
520
+ end
521
+ }
522
+ end
523
+ end
524
+
525
+ def define_update_jumpstart
526
+ url = ENV["RUBY_JUMPSTART"] || "git://github.com/quix/jumpstart.git"
527
+ task :update_jumpstart do
528
+ git "clone", url
529
+ rm_rf "devel/jumpstart"
530
+ Dir["jumpstart/**/*.rb"].each { |source|
531
+ dest = source.sub(%r!\Ajumpstart/!, "devel/")
532
+ dest_dir = File.dirname(dest)
533
+ mkdir_p(dest_dir) unless File.directory?(dest_dir)
534
+ cp source, dest
535
+ }
536
+ rm_r "jumpstart"
537
+ git "commit", "devel", "-m", "update jumpstart"
538
+ end
539
+ end
540
+
541
+ def git(*args)
542
+ sh("git", *args)
543
+ end
544
+
545
+ def create_manifest
546
+ write_file(manifest_file) {
547
+ files.sort.join("\n")
548
+ }
549
+ end
550
+
551
+ def rubyforge(command, file)
552
+ sh(
553
+ "rubyforge",
554
+ command,
555
+ rubyforge_name,
556
+ rubyforge_name,
557
+ version.to_s,
558
+ file
559
+ )
560
+ end
561
+
562
+ def define_release
563
+ task :prerelease => [:clean, :check_directory, :ping]
564
+
565
+ task :finish_release do
566
+ gem_md5, tgz_md5 = [gem, tgz].map { |file|
567
+ "#{file}.md5".tap { |md5|
568
+ sh("md5sum #{file} > #{md5}")
569
+ }
570
+ }
571
+
572
+ rubyforge("add_release", gem)
573
+ [gem_md5, tgz, tgz_md5].each { |file|
574
+ rubyforge("add_file", file)
575
+ }
576
+
577
+ git("tag", "#{name}-" + version.to_s)
578
+ git(*%w(push --tags origin master))
579
+ end
580
+
581
+ task :release => [:prerelease, :package, :publish, :finish_release]
582
+ end
583
+
584
+ def define_debug_gem
585
+ task :debug_gem do
586
+ puts gemspec.to_ruby
587
+ end
588
+ end
589
+
590
+ def open_browser(*files)
591
+ sh(*([browser].flatten + files))
592
+ end
593
+
594
+ def write_file(file)
595
+ yield.tap { |contents|
596
+ File.open(file, "wb") { |out|
597
+ out.print(contents)
598
+ }
599
+ }
600
+ end
601
+
602
+ def run_ruby_on_each(*files)
603
+ files.each { |file|
604
+ Ruby.run("-w", file)
605
+ }
606
+ end
607
+
608
+ def to_camel_case(str)
609
+ str.split('_').map { |t| t.capitalize }.join
610
+ end
611
+
612
+ class << self
613
+ def replace_file(file)
614
+ old_contents = File.read(file)
615
+ yield(old_contents).tap { |new_contents|
616
+ if old_contents != new_contents
617
+ File.open(file, "wb") { |output|
618
+ output.print(new_contents)
619
+ }
620
+ end
621
+ }
622
+ end
623
+ end
624
+ end
625
+
626
+ unless respond_to? :tap
627
+ class Object
628
+ def tap
629
+ yield self
630
+ self
631
+ end
632
+ end
633
+ end
634
+