pure 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
-