pure 0.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.
@@ -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
+