pure 0.1.0 → 0.2.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 (75) hide show
  1. data/CHANGES.rdoc +7 -0
  2. data/MANIFEST +44 -20
  3. data/README.rdoc +553 -16
  4. data/Rakefile +25 -2
  5. data/devel/jumpstart.rb +606 -253
  6. data/install.rb +1 -2
  7. data/lib/pure.rb +38 -16
  8. data/lib/pure/bundled_parsers.rb +4 -0
  9. data/lib/pure/bundled_plugin.rb +49 -0
  10. data/lib/pure/compiler/ruby_parser.rb +63 -0
  11. data/lib/pure/delegate.rb +16 -0
  12. data/lib/pure/driver.rb +33 -0
  13. data/lib/pure/dsl.rb +2 -0
  14. data/lib/pure/dsl_definition.rb +11 -0
  15. data/lib/pure/error.rb +89 -0
  16. data/lib/pure/extracted_functions.rb +11 -0
  17. data/lib/pure/extractor.rb +59 -0
  18. data/lib/pure/names.rb +9 -0
  19. data/lib/pure/native_worker.rb +27 -0
  20. data/lib/pure/parser/impl/base_parser.rb +21 -0
  21. data/lib/pure/parser/impl/internal.rb +31 -0
  22. data/lib/pure/parser/impl/ripper.rb +96 -0
  23. data/lib/pure/parser/impl/ruby_parser.rb +77 -0
  24. data/lib/pure/parser/internal.rb +4 -0
  25. data/lib/pure/parser/ripper.rb +2 -0
  26. data/lib/pure/parser/ruby_parser.rb +2 -0
  27. data/lib/pure/pure.rb +32 -0
  28. data/lib/pure/pure_module.rb +141 -0
  29. data/lib/pure/util.rb +15 -0
  30. data/lib/pure/version.rb +4 -0
  31. data/spec/compiler_ruby_parser_spec.rb +79 -0
  32. data/spec/compute_overrides_spec.rb +99 -0
  33. data/spec/compute_spec.rb +86 -0
  34. data/spec/compute_thread_spec.rb +29 -0
  35. data/spec/compute_timed_spec.rb +40 -0
  36. data/spec/delegate_spec.rb +141 -0
  37. data/spec/fstat_example.rb +26 -0
  38. data/spec/parser_sexp_spec.rb +100 -0
  39. data/spec/parser_spec.rb +18 -31
  40. data/spec/pure_combine_spec.rb +77 -0
  41. data/spec/pure_def_spec.rb +186 -0
  42. data/spec/pure_define_method_spec.rb +24 -0
  43. data/spec/pure_eval_spec.rb +18 -0
  44. data/spec/pure_fun_spec.rb +243 -0
  45. data/spec/pure_nested_spec.rb +35 -0
  46. data/spec/pure_parser_spec.rb +50 -0
  47. data/spec/pure_spec.rb +81 -0
  48. data/spec/pure_spec_base.rb +106 -0
  49. data/spec/pure_splat_spec.rb +18 -0
  50. data/spec/pure_two_defs_spec.rb +20 -0
  51. data/spec/pure_worker_spec.rb +33 -0
  52. data/spec/readme_spec.rb +36 -32
  53. data/spec/splat_spec.rb +12 -11
  54. data/spec/worker_spec.rb +89 -0
  55. metadata +157 -41
  56. data/devel/jumpstart/lazy_attribute.rb +0 -38
  57. data/devel/jumpstart/ruby.rb +0 -44
  58. data/devel/jumpstart/simple_installer.rb +0 -85
  59. data/lib/pure/pure_private/creator.rb +0 -27
  60. data/lib/pure/pure_private/driver.rb +0 -48
  61. data/lib/pure/pure_private/error.rb +0 -32
  62. data/lib/pure/pure_private/extractor.rb +0 -79
  63. data/lib/pure/pure_private/extractor_ripper.rb +0 -95
  64. data/lib/pure/pure_private/extractor_ruby_parser.rb +0 -47
  65. data/lib/pure/pure_private/function_database.rb +0 -10
  66. data/lib/pure/pure_private/singleton_features.rb +0 -67
  67. data/lib/pure/pure_private/util.rb +0 -23
  68. data/spec/basic_spec.rb +0 -38
  69. data/spec/combine_spec.rb +0 -62
  70. data/spec/common.rb +0 -44
  71. data/spec/error_spec.rb +0 -146
  72. data/spec/fun_spec.rb +0 -122
  73. data/spec/lazy_spec.rb +0 -22
  74. data/spec/subseqent_spec.rb +0 -42
  75. data/spec/timed_spec.rb +0 -30
data/Rakefile CHANGED
@@ -1,10 +1,33 @@
1
- $LOAD_PATH.unshift "devel"
2
1
 
2
+ $LOAD_PATH.unshift "devel"
3
3
  require "jumpstart"
4
4
 
5
5
  Jumpstart.new "pure" do |s|
6
6
  s.developer("James M. Lawrence", "quixoticsycophant@gmail.com")
7
- s.dependency("comp_tree", ">= 0.7.6")
8
7
  s.rubyforge_user = "quix"
9
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 = [
12
+ ["ruby2ruby", ">= 1.2.2"],
13
+ ["rspec", ">= 1.2.6"],
14
+ ]
15
+ s.rdoc_files = %w[
16
+ lib/pure/pure.rb
17
+ lib/pure/pure_module.rb
18
+ lib/pure/dsl.rb
19
+ lib/pure/dsl_definition.rb
20
+ ]
21
+ end
22
+
23
+ readme = "README.rdoc"
24
+ task readme do
25
+ re = %r!(The\s+default\s+worker\s+looks\s+like\s+this:).*?(?=^\S)!m
26
+ contents = File.read(readme).sub(re) { |sentence|
27
+ $1 + "\n" +
28
+ File.read("lib/pure/native_worker.rb").
29
+ gsub(%r!^!, " ").
30
+ sub(%r! \#:nodoc:!, "") + "\n"
31
+ }
32
+ File.open(readme, "w") { |f| f.print contents }
10
33
  end
@@ -1,281 +1,544 @@
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
1
 
18
2
  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
3
+ class SimpleInstaller
4
+ def initialize
5
+ require 'fileutils'
6
+ require 'rbconfig'
7
+ require 'find'
8
+ dest_root = Config::CONFIG["sitelibdir"]
9
+ sources = []
10
+ Find.find("./lib") { |source|
11
+ if install_file?(source)
12
+ sources << source
13
+ end
14
+ }
15
+ @spec = sources.inject(Array.new) { |acc, source|
16
+ if source == "./lib"
17
+ acc
18
+ else
19
+ dest = File.join(dest_root, source.sub(%r!\A\./lib!, ""))
20
+
21
+ install = lambda {
22
+ if File.directory?(source)
23
+ unless File.directory?(dest)
24
+ puts "mkdir #{dest}"
25
+ FileUtils.mkdir(dest)
26
+ end
37
27
  else
38
- raise
28
+ puts "install #{source} --> #{dest}"
29
+ FileUtils.install(source, dest)
39
30
  end
40
- )
41
- mod.const_get(result)
42
- else
43
- raise
31
+ }
32
+
33
+ uninstall = lambda {
34
+ if File.directory?(source)
35
+ if File.directory?(dest)
36
+ puts "rmdir #{dest}"
37
+ FileUtils.rmdir(dest)
38
+ end
39
+ else
40
+ if File.file?(dest)
41
+ puts "rm #{dest}"
42
+ FileUtils.rm(dest)
43
+ end
44
+ end
45
+ }
46
+
47
+ acc << {
48
+ :source => source,
49
+ :dest => dest,
50
+ :install => install,
51
+ :uninstall => uninstall,
52
+ }
44
53
  end
45
- rescue Exception
46
- "0.0.0"
47
- end
54
+ }
48
55
  end
49
-
50
- attribute :rubyforge_name do
51
- name.gsub('_', '')
56
+
57
+ def install_file?(source)
58
+ File.directory?(source) or
59
+ (File.file?(source) and File.extname(source) == ".rb")
52
60
  end
53
61
 
54
- attribute :rubyforge_user do
55
- email.first[%r!^.*(?=@)!]
62
+ def install
63
+ @spec.each { |entry|
64
+ entry[:install].call
65
+ }
56
66
  end
57
-
58
- attribute :readme_file do
59
- "README.rdoc"
67
+
68
+ def uninstall
69
+ @spec.reverse.each { |entry|
70
+ entry[:uninstall].call
71
+ }
60
72
  end
61
73
 
62
- attribute :history_file do
63
- "CHANGES.rdoc"
74
+ def run(args = ARGV)
75
+ if args.empty?
76
+ install
77
+ elsif args.size == 1 and args.first == "--uninstall"
78
+ uninstall
79
+ else
80
+ raise "unrecognized arguments: #{args.inspect}"
81
+ end
64
82
  end
83
+ end
65
84
 
66
- attribute :doc_dir do
67
- "documentation"
85
+ module AttrLazy
86
+ def attr_lazy(name, &block)
87
+ AttrLazy.define_reader(class << self ; self ; end, name, &block)
68
88
  end
69
89
 
70
- attribute :spec_files do
71
- Dir["spec/*_{spec,example}.rb"]
90
+ def attr_lazy_accessor(name, &block)
91
+ attr_lazy(name, &block)
92
+ AttrLazy.define_writer(class << self ; self ; end, name, &block)
72
93
  end
73
94
 
74
- attribute :test_files do
75
- Dir["test/test_*.rb"]
76
- end
95
+ class << self
96
+ def included(mod)
97
+ (class << mod ; self ; end).class_eval do
98
+ def attr_lazy(name, &block)
99
+ AttrLazy.define_reader(self, name, &block)
100
+ end
77
101
 
78
- attribute :rcov_dir do
79
- "coverage"
80
- end
102
+ def attr_lazy_accessor(name, &block)
103
+ attr_lazy(name, &block)
104
+ AttrLazy.define_writer(self, name, &block)
105
+ end
106
+ end
107
+ end
81
108
 
82
- attribute :spec_output do
83
- "spec.html"
84
- end
109
+ def define_evaluated_reader(instance, name, value)
110
+ (class << instance ; self ; end).class_eval do
111
+ remove_method name rescue nil
112
+ define_method name do
113
+ value
114
+ end
115
+ end
116
+ end
85
117
 
86
- %w[gem tgz].map { |ext|
87
- attribute ext.to_sym do
88
- "pkg/#{name}-#{version}.#{ext}"
118
+ def define_reader(klass, name, &block)
119
+ klass.class_eval do
120
+ remove_method name rescue nil
121
+ define_method name do
122
+ value = instance_eval(&block)
123
+ AttrLazy.define_evaluated_reader(self, name, value)
124
+ value
125
+ end
126
+ end
89
127
  end
90
- }
91
128
 
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 + "/"]
129
+ def define_writer(klass, name, &block)
130
+ klass.class_eval do
131
+ writer = "#{name}="
132
+ remove_method writer rescue nil
133
+ define_method writer do |value|
134
+ AttrLazy.define_evaluated_reader(self, name, value)
135
+ value
136
+ end
99
137
  end
100
- } + ["--text-report"]
138
+ end
101
139
  end
140
+ end
102
141
 
103
- attribute :readme_file do
104
- "README.rdoc"
105
- end
106
-
107
- attribute :manifest_file do
108
- "MANIFEST"
109
- end
142
+ module Ruby
143
+ module_function
144
+
145
+ def executable
146
+ require 'rbconfig'
110
147
 
111
- attribute :files do
112
- if File.exist?(manifest_file)
113
- File.read(manifest_file).split("\n")
148
+ name = File.join(
149
+ Config::CONFIG["bindir"],
150
+ Config::CONFIG["RUBY_INSTALL_NAME"]
151
+ )
152
+
153
+ if Config::CONFIG["host"] =~ %r!(mswin|cygwin|mingw)! and
154
+ File.basename(name) !~ %r!\.(exe|com|bat|cmd)\Z!i
155
+ name + Config::CONFIG["EXEEXT"]
114
156
  else
115
- [manifest_file] + `git ls-files`.split("\n")
157
+ name
158
+ end
159
+ end
160
+
161
+ def run(*args)
162
+ cmd = [executable, *args]
163
+ unless system(*cmd)
164
+ cmd_str = cmd.map { |t| "'#{t}'" }.join(", ")
165
+ raise "system(#{cmd_str}) failed with status #{$?.exitstatus}"
116
166
  end
117
167
  end
118
168
 
119
- attribute :rdoc_files do
120
- Dir["lib/**/*.rb"]
169
+ def run_code_and_capture(code)
170
+ IO.popen(%{"#{executable}"}, "r+") { |pipe|
171
+ pipe.print(code)
172
+ pipe.flush
173
+ pipe.close_write
174
+ pipe.read
175
+ }
121
176
  end
122
-
123
- attribute :extra_rdoc_files do
124
- if File.exist?(readme_file)
125
- [readme_file]
126
- else
127
- []
177
+
178
+ def run_file_and_capture(file)
179
+ unless File.file? file
180
+ raise "file does not exist: `#{file}'"
181
+ end
182
+ IO.popen(%{"#{executable}" "#{file}"}, "r") { |pipe|
183
+ pipe.read
184
+ }
185
+ end
186
+
187
+ def with_warnings(value = true)
188
+ previous = $VERBOSE
189
+ $VERBOSE = value
190
+ begin
191
+ yield
192
+ ensure
193
+ $VERBOSE = previous
128
194
  end
129
195
  end
196
+
197
+ def no_warnings(&block)
198
+ with_warnings(nil, &block)
199
+ end
200
+ end
130
201
 
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]
202
+ module Util
203
+ module_function
204
+
205
+ def run_ruby_on_each(*files)
206
+ files.each { |file|
207
+ Ruby.run("-w", file)
140
208
  }
141
209
  end
142
210
 
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
- )
211
+ def to_camel_case(str)
212
+ str.split('_').map { |t| t.capitalize }.join
213
+ end
214
+
215
+ def write_file(file)
216
+ contents = yield
217
+ File.open(file, "wb") { |out|
218
+ out.print(contents)
219
+ }
220
+ contents
221
+ end
222
+
223
+ def replace_file(file)
224
+ old_contents = File.read(file)
225
+ new_contents = yield(old_contents)
226
+ if old_contents != new_contents
227
+ File.open(file, "wb") { |output|
228
+ output.print(new_contents)
177
229
  }
230
+ end
231
+ new_contents
232
+ end
233
+ end
178
234
 
179
- if rubyforge_name
180
- g.rubyforge_project = rubyforge_name
181
- end
235
+ module InstanceEvalWithArgs
236
+ module_function
182
237
 
183
- if url
184
- g.homepage = url
238
+ def with_temp_method(instance, method_name, method_block)
239
+ (class << instance ; self ; end).class_eval do
240
+ define_method(method_name, &method_block)
241
+ begin
242
+ yield method_name
243
+ ensure
244
+ remove_method(method_name)
185
245
  end
246
+ end
247
+ end
186
248
 
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
- }
249
+ def call_temp_method(instance, method_name, *args, &method_block)
250
+ with_temp_method(instance, method_name, method_block) {
251
+ instance.send(method_name, *args)
194
252
  }
195
253
  end
196
254
 
197
- attribute :readme_contents do
198
- File.read(readme_file) rescue "FIXME: readme_file"
255
+ def instance_eval_with_args(instance, *args, &block)
256
+ call_temp_method(instance, :__temp_method, *args, &block)
199
257
  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
258
+ end
259
+
260
+ include AttrLazy
261
+ include Util
262
+
263
+ def initialize(project_name)
264
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
265
+ $LOAD_PATH.unshift File.dirname(__FILE__)
266
+
267
+ require 'rake/gempackagetask'
268
+ require 'rake/clean'
269
+
270
+ @project_name = project_name
271
+
272
+ yield self
273
+
274
+ self.class.instance_methods(false).select { |t|
275
+ t.to_s =~ %r!\Adefine_!
276
+ }.each { |method_name|
277
+ send(method_name)
278
+ }
279
+ end
280
+
281
+ class << self
282
+ alias_method :attribute, :attr_lazy_accessor
283
+ end
284
+
285
+ attribute :name do
286
+ @project_name
287
+ end
288
+
289
+ attribute :version_constant_name do
290
+ "VERSION"
291
+ end
292
+
293
+ attribute :version do
294
+ require name
295
+ mod_name = to_camel_case(name)
296
+ begin
297
+ mod = Kernel.const_get(mod_name)
298
+ if mod.constants.include?(version_constant_name)
299
+ mod.const_get(version_constant_name)
300
+ else
301
+ raise
210
302
  end
303
+ rescue
304
+ "0.0.0"
211
305
  end
306
+ end
307
+
308
+ attribute :rubyforge_name do
309
+ name.gsub('_', '')
310
+ end
311
+
312
+ attribute :rubyforge_user do
313
+ email.first[%r!^.*(?=@)!]
314
+ end
315
+
316
+ attribute :readme_file do
317
+ "README.rdoc"
318
+ end
319
+
320
+ attribute :history_file do
321
+ "CHANGES.rdoc"
322
+ end
323
+
324
+ attribute :doc_dir do
325
+ "documentation"
326
+ end
327
+
328
+ attribute :spec_files do
329
+ Dir["./spec/*_{spec,example}.rb"]
330
+ end
331
+
332
+ attribute :test_files do
333
+ (Dir["./test/test_*.rb"] + Dir["./test/*_test.rb"]).uniq
334
+ end
335
+
336
+ attribute :rcov_dir do
337
+ "coverage"
338
+ end
339
+
340
+ attribute :spec_output_dir do
341
+ "rspec_output"
342
+ end
343
+
344
+ attribute :spec_output_file do
345
+ "spec.html"
346
+ end
347
+
348
+ attr_lazy :spec_output do
349
+ "#{spec_output_dir}/#{spec_output_file}"
350
+ end
212
351
 
213
- attribute :description_section do
214
- "description"
352
+ [:gem, :tgz].each { |ext|
353
+ attribute ext do
354
+ "pkg/#{name}-#{version}.#{ext}"
215
355
  end
356
+ }
216
357
 
217
- attribute :summary_section do
218
- "summary"
358
+ attribute :rcov_options do
359
+ # workaround for the default rspec task
360
+ Dir["*"].select { |f| File.directory? f }.inject(Array.new) { |acc, dir|
361
+ if dir == "lib"
362
+ acc
363
+ else
364
+ acc + ["--exclude", dir + "/"]
365
+ end
366
+ } + ["--text-report"]
367
+ end
368
+
369
+ attribute :readme_file do
370
+ "README.rdoc"
371
+ end
372
+
373
+ attribute :manifest_file do
374
+ "MANIFEST"
375
+ end
376
+
377
+ attribute :generated_files do
378
+ []
379
+ end
380
+
381
+ attribute :files do
382
+ if File.exist?(manifest_file)
383
+ File.read(manifest_file).split("\n")
384
+ else
385
+ `git ls-files`.split("\n") + [manifest_file] + generated_files
219
386
  end
387
+ end
220
388
 
221
- attribute :description_sentences do
222
- 1
389
+ attribute :rdoc_files do
390
+ Dir["lib/**/*.rb"]
391
+ end
392
+
393
+ attribute :extra_rdoc_files do
394
+ if File.exist?(readme_file)
395
+ [readme_file]
396
+ else
397
+ []
223
398
  end
399
+ end
224
400
 
225
- attribute :summary_sentences do
226
- 1
401
+ attribute :rdoc_options do
402
+ if File.exist?(readme_file)
403
+ ["--main", readme_file]
404
+ else
405
+ []
406
+ end + [
407
+ "--title", "#{name}: #{summary}",
408
+ ] + (files - rdoc_files).inject(Array.new) { |acc, file|
409
+ acc + ["--exclude", file]
410
+ }
411
+ end
412
+
413
+ attribute :browser do
414
+ require 'rbconfig'
415
+ if Config::CONFIG["host"] =~ %r!darwin!
416
+ app = %w[Firefox Safari].map { |t|
417
+ "/Applications/#{t}.app"
418
+ }.select { |t|
419
+ File.exist? t
420
+ }.first
421
+ if app
422
+ ["open", app]
423
+ else
424
+ raise "need to set `browser'"
425
+ end
426
+ else
427
+ "firefox"
227
428
  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
429
+ end
430
+
431
+ attribute :gemspec do
432
+ Gem::Specification.new { |g|
433
+ g.has_rdoc = true
434
+ %w[
435
+ name
436
+ authors
437
+ email
438
+ summary
439
+ version
440
+ description
441
+ files
442
+ extra_rdoc_files
443
+ rdoc_options
444
+ ].each { |param|
445
+ value = send(param) and (
446
+ g.send("#{param}=", value)
447
+ )
448
+ }
449
+
450
+ if rubyforge_name
451
+ g.rubyforge_project = rubyforge_name
452
+ end
453
+
454
+ if url
455
+ g.homepage = url
240
456
  end
457
+
458
+ extra_deps.each { |dep|
459
+ g.add_dependency(*dep)
460
+ }
461
+
462
+ extra_dev_deps.each { |dep|
463
+ g.add_development_dependency(*dep)
464
+ }
241
465
  }
466
+ end
467
+
468
+ attribute :readme_contents do
469
+ File.read(readme_file) rescue "FIXME: readme_file"
470
+ end
471
+
472
+ attribute :sections do
473
+ require 'enumerator'
474
+ begin
475
+ data = readme_contents.split(%r!^==\s*(.*?)\s*$!)
476
+ pairs = data[1..-1].enum_slice(2).map { |section, contents|
477
+ [section.downcase, contents.strip]
478
+ }
479
+ Hash[*pairs.flatten]
480
+ rescue
481
+ nil
482
+ end
483
+ end
484
+
485
+ attribute :description_section do
486
+ "description"
487
+ end
488
+
489
+ attribute :summary_section do
490
+ "summary"
491
+ end
492
+
493
+ attribute :description_sentences do
494
+ 1
495
+ end
242
496
 
243
- attribute :url do
497
+ attribute :summary_sentences do
498
+ 1
499
+ end
500
+
501
+ [:summary, :description].each { |section|
502
+ attribute section do
244
503
  begin
245
- readme_contents.match(%r!^\*.*?(http://\S+)!)[1]
504
+ sections[send("#{section}_section")].
505
+ gsub("\n", " ").
506
+ split(%r!\.\s*!m).
507
+ first(send("#{section}_sentences")).
508
+ join(". ") << "."
246
509
  rescue
247
- "http://#{rubyforge_name}.rubyforge.org"
510
+ "FIXME: #{section}"
248
511
  end
249
512
  end
513
+ }
250
514
 
251
- attribute :extra_deps do
252
- []
253
- end
254
-
255
- attribute :extra_dev_deps do
256
- []
515
+ attribute :url do
516
+ begin
517
+ readme_contents.match(%r!^\*.*?(http://\S+)!)[1]
518
+ rescue
519
+ "http://#{rubyforge_name}.rubyforge.org"
257
520
  end
521
+ end
258
522
 
259
- yield self
523
+ attribute :extra_deps do
524
+ []
525
+ end
260
526
 
261
- self.class.instance_methods(false).select { |t|
262
- t.to_s =~ %r!\Adefine_!
263
- }.each { |method_name|
264
- send(method_name)
265
- }
527
+ attribute :extra_dev_deps do
528
+ []
266
529
  end
267
530
 
268
- def developer(name, email)
269
- authors << name
270
- self.email << email
531
+ attribute :authors do
532
+ Array.new
271
533
  end
272
534
 
273
- def authors
274
- @authors ||= Array.new
535
+ attribute :email do
536
+ Array.new
275
537
  end
276
538
 
277
- def email
278
- @email ||= Array.new
539
+ def developer(name, email)
540
+ authors << name
541
+ self.email << email
279
542
  end
280
543
 
281
544
  def dependency(name, version)
@@ -301,7 +564,9 @@ class Jumpstart
301
564
 
302
565
  def define_spec
303
566
  unless spec_files.empty?
304
- require 'spec/rake/spectask'
567
+ Ruby.no_warnings {
568
+ require 'spec/rake/spectask'
569
+ }
305
570
 
306
571
  desc "run specs"
307
572
  Spec::Rake::SpecTask.new('spec') do |t|
@@ -322,6 +587,8 @@ class Jumpstart
322
587
  t.spec_opts = ["-fh:#{spec_output}"]
323
588
  end
324
589
 
590
+ suppress_task_warnings :spec, :full_spec, :text_spec
591
+
325
592
  desc "run full_spec then open browser"
326
593
  task :show_spec => :full_spec do
327
594
  open_browser(spec_output, rcov_dir + "/index.html")
@@ -335,7 +602,7 @@ class Jumpstart
335
602
  task :prerelease => [:spec, :spec_deps]
336
603
  task :default => :spec
337
604
 
338
- CLEAN.include spec_output
605
+ CLEAN.include spec_output_dir
339
606
  end
340
607
  end
341
608
 
@@ -377,6 +644,7 @@ class Jumpstart
377
644
  def define_doc
378
645
  desc "run rdoc"
379
646
  task :doc => :clean_doc do
647
+ require 'rdoc/rdoc'
380
648
  args = (
381
649
  gemspec.rdoc_options +
382
650
  gemspec.require_paths.clone +
@@ -403,6 +671,7 @@ class Jumpstart
403
671
  def define_publish
404
672
  desc "upload docs"
405
673
  task :publish => [:clean_doc, :doc] do
674
+ require 'rake/contrib/sshpublisher'
406
675
  Rake::SshDirPublisher.new(
407
676
  "#{rubyforge_user}@rubyforge.org",
408
677
  "/var/www/gforge-projects/#{rubyforge_name}",
@@ -445,9 +714,10 @@ class Jumpstart
445
714
  end
446
715
 
447
716
  def debug_info(enable)
717
+ require 'find'
448
718
  Find.find("lib", "test") { |path|
449
719
  if path =~ %r!\.rb\Z!
450
- Jumpstart.replace_file(path) { |contents|
720
+ replace_file(path) { |contents|
451
721
  result = comment_regions(!enable, contents, "debug")
452
722
  comment_lines(!enable, result, "trace")
453
723
  }
@@ -485,13 +755,13 @@ class Jumpstart
485
755
  task :comments do
486
756
  file = "comments.txt"
487
757
  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
- }
758
+ result = Array.new
759
+ (["Rakefile"] + Dir["**/*.{rb,rake}"]).each { |f|
760
+ File.read(f).scan(%r!\#[^\{].*$!) { |match|
761
+ result << match
493
762
  }
494
- }.join("\n")
763
+ }
764
+ result.join("\n")
495
765
  }
496
766
  CLEAN.include file
497
767
  end
@@ -507,6 +777,7 @@ class Jumpstart
507
777
 
508
778
  def define_ping
509
779
  task :ping do
780
+ require 'rbconfig'
510
781
  %w[github.com rubyforge.org].each { |server|
511
782
  cmd = "ping " + (
512
783
  if Config::CONFIG["host"] =~ %r!darwin!
@@ -548,28 +819,29 @@ class Jumpstart
548
819
  }
549
820
  end
550
821
 
551
- def rubyforge(command, file)
552
- sh(
553
- "rubyforge",
554
- command,
822
+ def rubyforge(mode, file, *options)
823
+ command = ["rubyforge", mode] + options + [
555
824
  rubyforge_name,
556
825
  rubyforge_name,
557
826
  version.to_s,
558
- file
559
- )
827
+ file,
828
+ ]
829
+ sh(*command)
560
830
  end
561
831
 
562
832
  def define_release
563
- task :prerelease => [:clean, :check_directory, :ping]
833
+ task :prerelease => [:clean, :check_directory, :ping, history_file]
564
834
 
565
835
  task :finish_release do
566
836
  gem_md5, tgz_md5 = [gem, tgz].map { |file|
567
- "#{file}.md5".tap { |md5|
568
- sh("md5sum #{file} > #{md5}")
569
- }
837
+ md5 = "#{file}.md5"
838
+ sh("md5sum #{file} > #{md5}")
839
+ md5
570
840
  }
571
841
 
572
- rubyforge("add_release", gem)
842
+ rubyforge(
843
+ "add_release", gem, "--release_changes", history_file, "--preformatted"
844
+ )
573
845
  [gem_md5, tgz, tgz_md5].each { |file|
574
846
  rubyforge("add_file", file)
575
847
  }
@@ -591,44 +863,125 @@ class Jumpstart
591
863
  sh(*([browser].flatten + files))
592
864
  end
593
865
 
594
- def write_file(file)
595
- yield.tap { |contents|
596
- File.open(file, "wb") { |out|
597
- out.print(contents)
866
+ def suppress_task_warnings(*task_names)
867
+ task_names.each { |task_name|
868
+ Rake::Task[task_name].actions.map! { |action|
869
+ lambda { |*args|
870
+ Ruby.no_warnings {
871
+ action.call(*args)
872
+ }
873
+ }
598
874
  }
599
875
  }
600
876
  end
601
877
 
602
- def run_ruby_on_each(*files)
603
- files.each { |file|
604
- Ruby.run("-w", file)
605
- }
606
- end
878
+ class << self
879
+ include Util
880
+ include InstanceEvalWithArgs
607
881
 
608
- def to_camel_case(str)
609
- str.split('_').map { |t| t.capitalize }.join
610
- end
882
+ # From minitest, part of the Ruby source; by Ryan Davis.
883
+ def capture_io
884
+ require 'stringio'
611
885
 
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
- }
886
+ orig_stdout, orig_stderr = $stdout, $stderr
887
+ captured_stdout, captured_stderr = StringIO.new, StringIO.new
888
+ $stdout, $stderr = captured_stdout, captured_stderr
889
+
890
+ yield
891
+
892
+ return captured_stdout.string, captured_stderr.string
893
+ ensure
894
+ $stdout = orig_stdout
895
+ $stderr = orig_stderr
896
+ end
897
+
898
+ def run_doc_code(code, expected, index, instance, &block)
899
+ lib = File.expand_path(File.dirname(__FILE__) + "/../lib")
900
+ header = %{
901
+ $LOAD_PATH.unshift "#{lib}"
902
+ begin
903
+ }
904
+ footer = %{
905
+ rescue Exception => __jumpstart_exception
906
+ puts "raises \#{__jumpstart_exception.class}"
620
907
  end
621
908
  }
909
+ final_code = header + code + footer
910
+
911
+ # Sometimes code is required to be inside a file.
912
+ actual = nil
913
+ require 'tempfile'
914
+ Tempfile.open("run-rdoc-code") { |temp_file|
915
+ temp_file.print(final_code)
916
+ temp_file.close
917
+ actual = Ruby.run_file_and_capture(temp_file.path).chomp
918
+ }
919
+
920
+ instance_eval_with_args(instance, expected, actual, index, &block)
921
+ end
922
+
923
+ def run_doc_section(file, section, instance, &block)
924
+ contents = File.read(file)
925
+ re = %r!^=+[ \t]#{Regexp.quote(section)}.*?\n(.*?)^=!m
926
+ if section_contents = contents[re, 1]
927
+ index = 0
928
+ section_contents.scan(%r!^( \S.*?)(?=(^\S|\Z))!m) { |indented, unused|
929
+ code_sections = indented.split(%r!^ \#\#\#\# output:\s*$!)
930
+ code, expected = (
931
+ case code_sections.size
932
+ when 1
933
+ [indented, indented.scan(%r!\# => (.*?)\n!).flatten.join("\n")]
934
+ when 2
935
+ code_sections
936
+ else
937
+ raise "parse error"
938
+ end
939
+ )
940
+ run_doc_code(code, expected, index, instance, &block)
941
+ index += 1
942
+ }
943
+ else
944
+ raise "couldn't find section `#{section}' of `#{file}'"
945
+ end
946
+ end
947
+
948
+ def doc_to_spec(file, *sections, &block)
949
+ jump = self
950
+ describe file do
951
+ sections.each { |section|
952
+ describe "section `#{section}'" do
953
+ it "should run as claimed" do
954
+ if block
955
+ jump.run_doc_section(file, section, self, &block)
956
+ else
957
+ jump.run_doc_section(file, section, self) {
958
+ |expected, actual, index|
959
+ actual.should == expected
960
+ }
961
+ end
962
+ end
963
+ end
964
+ }
965
+ end
622
966
  end
623
- end
624
- end
625
967
 
626
- unless respond_to? :tap
627
- class Object
628
- def tap
629
- yield self
630
- self
968
+ def doc_to_test(file, *sections, &block)
969
+ jump = self
970
+ klass = Class.new Test::Unit::TestCase do
971
+ sections.each { |section|
972
+ define_method "test_#{file}_#{section}" do
973
+ if block
974
+ jump.run_doc_section(file, section, self, &block)
975
+ else
976
+ jump.run_doc_section(file, section, self) {
977
+ |expected, actual, index|
978
+ assert_equal expected, actual
979
+ }
980
+ end
981
+ end
982
+ }
983
+ end
984
+ Object.const_set("Test#{file}".gsub(".", ""), klass)
631
985
  end
632
986
  end
633
987
  end
634
-