cond 0.2.1 → 0.3.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.
- data/CHANGES.rdoc +12 -0
- data/MANIFEST +39 -0
- data/{README → README.rdoc} +23 -28
- data/Rakefile +23 -180
- data/devel/jumpstart.rb +970 -0
- data/install.rb +2 -3
- data/lib/cond.rb +38 -453
- data/lib/cond/code_section.rb +51 -0
- data/lib/cond/cond.rb +159 -0
- data/lib/cond/defaults.rb +73 -0
- data/lib/cond/dsl.rb +2 -0
- data/lib/cond/dsl_definition.rb +74 -0
- data/lib/cond/error.rb +17 -0
- data/lib/cond/handler.rb +10 -0
- data/lib/cond/handling_section.rb +12 -0
- data/lib/cond/kernel_raise.rb +59 -0
- data/lib/cond/message_proc.rb +15 -0
- data/lib/cond/restart.rb +10 -0
- data/lib/cond/restartable_section.rb +12 -0
- data/lib/cond/symbol_generator.rb +41 -0
- data/lib/cond/thread_local.rb +71 -0
- data/lib/cond/wrapping.rb +45 -0
- data/readmes/restarts.rb +1 -2
- data/readmes/seibel_pcl.rb +1 -2
- data/{examples/bad_example.rb → spec/bad_spec.rb} +2 -2
- data/spec/basic_spec.rb +2 -2
- data/{examples/calc_example.rb → spec/calc_spec.rb} +2 -2
- data/spec/{common.rb → cond_spec_base.rb} +3 -20
- data/spec/error_spec.rb +2 -2
- data/spec/leave_again_spec.rb +10 -10
- data/spec/matching_spec.rb +1 -1
- data/spec/raise_spec.rb +1 -1
- data/spec/readme_spec.rb +10 -0
- data/spec/reraise_spec.rb +2 -2
- data/{examples/restarts_example.rb → spec/restarts_spec.rb} +7 -4
- data/{examples/seibel_example.rb → spec/seibel_spec.rb} +4 -6
- data/spec/symbols_spec.rb +2 -2
- data/spec/thread_local_spec.rb +8 -8
- data/spec/wrapping_spec.rb +2 -2
- metadata +110 -42
- data/cond.gemspec +0 -37
- data/examples/readme_example.rb +0 -27
- data/lib/cond/cond_private/defaults.rb +0 -78
- data/lib/cond/cond_private/symbol_generator.rb +0 -45
- data/lib/cond/cond_private/thread_local.rb +0 -77
- data/spec/specs_spec.rb +0 -16
- data/support/quix/ruby.rb +0 -51
- data/support/quix/simple_installer.rb +0 -88
data/CHANGES.rdoc
ADDED
data/MANIFEST
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
CHANGES.rdoc
|
2
|
+
MANIFEST
|
3
|
+
README.rdoc
|
4
|
+
Rakefile
|
5
|
+
devel/jumpstart.rb
|
6
|
+
install.rb
|
7
|
+
lib/cond.rb
|
8
|
+
lib/cond/code_section.rb
|
9
|
+
lib/cond/cond.rb
|
10
|
+
lib/cond/defaults.rb
|
11
|
+
lib/cond/dsl.rb
|
12
|
+
lib/cond/dsl_definition.rb
|
13
|
+
lib/cond/error.rb
|
14
|
+
lib/cond/handler.rb
|
15
|
+
lib/cond/handling_section.rb
|
16
|
+
lib/cond/kernel_raise.rb
|
17
|
+
lib/cond/message_proc.rb
|
18
|
+
lib/cond/restart.rb
|
19
|
+
lib/cond/restartable_section.rb
|
20
|
+
lib/cond/symbol_generator.rb
|
21
|
+
lib/cond/thread_local.rb
|
22
|
+
lib/cond/wrapping.rb
|
23
|
+
readmes/restarts.rb
|
24
|
+
readmes/seibel_pcl.rb
|
25
|
+
spec/bad_spec.rb
|
26
|
+
spec/basic_spec.rb
|
27
|
+
spec/calc_spec.rb
|
28
|
+
spec/cond_spec_base.rb
|
29
|
+
spec/error_spec.rb
|
30
|
+
spec/leave_again_spec.rb
|
31
|
+
spec/matching_spec.rb
|
32
|
+
spec/raise_spec.rb
|
33
|
+
spec/readme_spec.rb
|
34
|
+
spec/reraise_spec.rb
|
35
|
+
spec/restarts_spec.rb
|
36
|
+
spec/seibel_spec.rb
|
37
|
+
spec/symbols_spec.rb
|
38
|
+
spec/thread_local_spec.rb
|
39
|
+
spec/wrapping_spec.rb
|
data/{README → README.rdoc}
RENAMED
@@ -1,14 +1,13 @@
|
|
1
1
|
|
2
2
|
= Cond
|
3
3
|
|
4
|
-
==
|
4
|
+
== Summary
|
5
5
|
|
6
6
|
Resolve errors without unwinding the stack.
|
7
7
|
|
8
8
|
== Synopsis
|
9
9
|
|
10
|
-
require 'cond'
|
11
|
-
include Cond
|
10
|
+
require 'cond/dsl'
|
12
11
|
|
13
12
|
def divide(x, y)
|
14
13
|
restartable do
|
@@ -36,7 +35,7 @@ Resolve errors without unwinding the stack.
|
|
36
35
|
or
|
37
36
|
% ruby install.rb [--uninstall]
|
38
37
|
|
39
|
-
==
|
38
|
+
== Description
|
40
39
|
|
41
40
|
Cond allows errors to be handled near the place where they occur,
|
42
41
|
before the stack unwinds. It offers several advantages over
|
@@ -84,9 +83,8 @@ propagate in the usual unwinding fashion, as if the handler was never
|
|
84
83
|
called.
|
85
84
|
|
86
85
|
Cond is 100% compatible with the built-in exception-handling system.
|
87
|
-
We may imagine that Ruby had this handler
|
88
|
-
|
89
|
-
since no restarts were available, no handlers were written.
|
86
|
+
We may imagine that Ruby had always had this handler+restart
|
87
|
+
functionality but nobody remembered to use it.
|
90
88
|
|
91
89
|
== Background
|
92
90
|
|
@@ -105,6 +103,8 @@ http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restart
|
|
105
103
|
See readmes/seibel_pcl.rb for a Ruby translation.
|
106
104
|
|
107
105
|
== Synopsis 2.0
|
106
|
+
|
107
|
+
require 'cond/dsl'
|
108
108
|
|
109
109
|
x, y = 7, 0
|
110
110
|
|
@@ -134,14 +134,10 @@ arbitrarily.
|
|
134
134
|
for repetitive catch blocks and prevent symbol collisions for nested
|
135
135
|
catch labels.
|
136
136
|
|
137
|
-
A default handler is provided which runs a simple choose-a-restart
|
138
|
-
input loop when +raise+ is called, as the next example demonstrates.
|
139
|
-
|
140
137
|
== Restart Example
|
141
138
|
|
142
139
|
require 'pp'
|
143
|
-
require 'cond'
|
144
|
-
include Cond
|
140
|
+
require 'cond/dsl'
|
145
141
|
|
146
142
|
class RestartableFetchError < RuntimeError
|
147
143
|
end
|
@@ -228,25 +224,24 @@ Cond has been tested on MRI 1.8.6, 1.8.7, 1.9, and the latest jruby.
|
|
228
224
|
Each thread keeps its own list of handlers, restarts, and other data.
|
229
225
|
All operations are fully thread-safe.
|
230
226
|
|
231
|
-
It is not required to <tt>include Cond</tt>. The includable methods
|
232
|
-
of +Cond+ are <tt>module_function</tt>s and are thus callable via e.g.
|
233
|
-
<tt>Cond.handling</tt>.
|
234
|
-
|
235
|
-
+Cond+ nests private modules and classes inside Cond::CondPrivate in
|
236
|
-
order to improve the hygiene of <tt>include Cond</tt> and encourage
|
237
|
-
its use.
|
238
|
-
|
239
227
|
Except for the redefinition +raise+, Cond does not silently modify any
|
240
|
-
of the standard classes
|
228
|
+
of the standard classes.
|
241
229
|
|
242
230
|
The essential implementation is small and simple: it consists of two
|
243
231
|
per-thread stacks of hashes (handlers and restarts) with merge-push
|
244
|
-
and pop operations.
|
245
|
-
|
246
|
-
|
247
|
-
|
232
|
+
and pop operations.
|
233
|
+
|
234
|
+
== DSL Form and Raw Form
|
235
|
+
|
236
|
+
The optional <tt>require 'cond/dsl'</tt> defines some pseudo-keywords
|
237
|
+
in the global scope which comprise a DSL for the system. These
|
238
|
+
methods are also available with <tt>require 'cond'</tt> through the
|
239
|
+
Cond singleton (e.g. Cond.handling) or by including Cond::DSL into the
|
240
|
+
class or module which uses them.
|
248
241
|
|
249
|
-
|
242
|
+
The DSL shown in the above examples is a thin layer concealing the
|
243
|
+
underlying hashes. It is equivalent to the following raw form. You
|
244
|
+
are free to use either form according to preference or circumstance.
|
250
245
|
|
251
246
|
require 'cond'
|
252
247
|
|
@@ -276,10 +271,10 @@ preference or circumstance.
|
|
276
271
|
puts divide(7, 0) # => 42
|
277
272
|
end
|
278
273
|
|
279
|
-
|
274
|
+
== Limitations
|
280
275
|
|
281
276
|
There must be a call to +raise+ inside Ruby code (as opposed to C
|
282
|
-
code) for a handler to be invoked.
|
277
|
+
code) in order for a handler to be invoked.
|
283
278
|
|
284
279
|
The above synopsis gives an example: Why is there a check for division
|
285
280
|
by zero when +ZeroDivisionError+ would be raised anyway? Because
|
data/Rakefile
CHANGED
@@ -1,59 +1,27 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require '
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
# default
|
22
|
-
|
23
|
-
task :default => :spec
|
24
|
-
|
25
|
-
######################################################################
|
26
|
-
# spec
|
27
|
-
|
28
|
-
Spec::Rake::SpecTask.new('spec') do |t|
|
29
|
-
t.spec_files = SPEC_FILES
|
30
|
-
end
|
31
|
-
|
32
|
-
Spec::Rake::SpecTask.new('text_spec') do |t|
|
33
|
-
t.spec_files = SPEC_FILES
|
34
|
-
t.spec_opts = ['-fs']
|
35
|
-
end
|
36
|
-
|
37
|
-
Spec::Rake::SpecTask.new('full_spec') do |t|
|
38
|
-
t.spec_files = SPEC_FILES
|
39
|
-
t.rcov = true
|
40
|
-
exclude_dirs = %w[readmes support examples spec]
|
41
|
-
t.rcov_opts = exclude_dirs.inject(Array.new) { |acc, dir|
|
42
|
-
acc + ["--exclude", dir]
|
43
|
-
}
|
44
|
-
t.spec_opts = ["-fh:#{SPEC_OUTPUT}"]
|
45
|
-
end
|
46
|
-
|
47
|
-
task :show_full_spec => :full_spec do
|
48
|
-
args = SPEC_OUTPUT, "coverage/index.html"
|
49
|
-
open_browser(*args)
|
1
|
+
$LOAD_PATH.unshift "devel"
|
2
|
+
|
3
|
+
require 'jumpstart'
|
4
|
+
|
5
|
+
readme_file = nil
|
6
|
+
|
7
|
+
Jumpstart.new('cond') do |s|
|
8
|
+
s.developer('James M. Lawrence', 'quixoticsycophant@gmail.com')
|
9
|
+
s.rubyforge_user = "quix"
|
10
|
+
s.description_sentences = 2
|
11
|
+
s.rdoc_files = %w[
|
12
|
+
lib/cond/cond.rb
|
13
|
+
lib/cond/dsl_definition.rb
|
14
|
+
lib/cond/error.rb
|
15
|
+
lib/cond/handler.rb
|
16
|
+
lib/cond/message_proc.rb
|
17
|
+
lib/cond/restart.rb
|
18
|
+
lib/cond/wrapping.rb
|
19
|
+
]
|
20
|
+
readme_file = s.readme_file
|
50
21
|
end
|
51
22
|
|
52
|
-
######################################################################
|
53
|
-
# readme
|
54
|
-
|
55
23
|
task :readme do
|
56
|
-
readme = File.read(
|
24
|
+
readme = File.read(readme_file)
|
57
25
|
restarts = File.read("readmes/restarts.rb")
|
58
26
|
run_re = %r!\A\# !
|
59
27
|
update = readme.sub(%r!(= Restart Example\n)(.*?)(?=^Run)!m) {
|
@@ -64,132 +32,7 @@ task :readme do
|
|
64
32
|
$1 + "\n" +
|
65
33
|
restarts.lines.grep(run_re).map { |t| t.sub(run_re, " ") }.join + "\n"
|
66
34
|
}
|
67
|
-
File.open(
|
68
|
-
end
|
69
|
-
|
70
|
-
######################################################################
|
71
|
-
# clean
|
72
|
-
|
73
|
-
task :clean => [:clobber, :clean_doc] do
|
74
|
-
end
|
75
|
-
|
76
|
-
task :clean_doc do
|
77
|
-
rm_rf(DOC_DIR)
|
78
|
-
rm_f(SPEC_OUTPUT)
|
79
|
-
end
|
80
|
-
|
81
|
-
######################################################################
|
82
|
-
# package
|
83
|
-
|
84
|
-
task :package => :clean
|
85
|
-
|
86
|
-
Rake::GemPackageTask.new(GEMSPEC) { |t|
|
87
|
-
t.need_tar = true
|
88
|
-
}
|
89
|
-
|
90
|
-
######################################################################
|
91
|
-
# doc
|
92
|
-
|
93
|
-
task :doc => :clean_doc do
|
94
|
-
files = %W[#{README} lib/cond.rb]
|
95
|
-
|
96
|
-
options = [
|
97
|
-
"-o", DOC_DIR,
|
98
|
-
"--title", "#{GEMSPEC.name}: #{GEMSPEC.summary}",
|
99
|
-
"--main", README
|
100
|
-
]
|
101
|
-
|
102
|
-
RDoc::RDoc.new.document(files + options)
|
103
|
-
end
|
104
|
-
|
105
|
-
task :rdoc => :doc
|
106
|
-
|
107
|
-
task :show_doc => :doc do
|
108
|
-
open_browser("#{DOC_DIR}/index.html")
|
109
|
-
end
|
110
|
-
|
111
|
-
######################################################################
|
112
|
-
# misc
|
113
|
-
|
114
|
-
def open_browser(*files)
|
115
|
-
if Config::CONFIG["host"] =~ %r!darwin!
|
116
|
-
sh("open", "/Applications/Firefox.app", *files)
|
117
|
-
else
|
118
|
-
sh("firefox", *files)
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
######################################################################
|
123
|
-
# git
|
124
|
-
|
125
|
-
def git(*args)
|
126
|
-
sh("git", *args)
|
127
|
-
end
|
128
|
-
|
129
|
-
######################################################################
|
130
|
-
# publisher
|
131
|
-
|
132
|
-
task :publish => :doc do
|
133
|
-
Rake::RubyForgePublisher.new(GEMSPEC.name, 'quix').upload
|
134
|
-
end
|
135
|
-
|
136
|
-
######################################################################
|
137
|
-
# release
|
138
|
-
|
139
|
-
unless respond_to? :tap
|
140
|
-
module Kernel
|
141
|
-
def tap
|
142
|
-
yield self
|
143
|
-
self
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
task :prerelease => :clean do
|
149
|
-
rm_rf(DOC_DIR)
|
150
|
-
rm_rf("pkg")
|
151
|
-
unless `git status` =~ %r!nothing to commit \(working directory clean\)!
|
152
|
-
raise "Directory not clean"
|
153
|
-
end
|
154
|
-
unless `ping github.com 2 2` =~ %r!0% packet loss!i
|
155
|
-
raise "No ping for github.com"
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
def rubyforge(command, file)
|
160
|
-
sh(
|
161
|
-
"rubyforge",
|
162
|
-
command,
|
163
|
-
GEMSPEC.rubyforge_project,
|
164
|
-
GEMSPEC.rubyforge_project,
|
165
|
-
GEMSPEC.version.to_s,
|
166
|
-
file
|
167
|
-
)
|
168
|
-
end
|
169
|
-
|
170
|
-
task :finish_release do
|
171
|
-
gem, tgz = %w(gem tgz).map { |ext|
|
172
|
-
"pkg/#{GEMSPEC.name}-#{GEMSPEC.version}.#{ext}"
|
173
|
-
}
|
174
|
-
gem_md5, tgz_md5 = [gem, tgz].map { |file|
|
175
|
-
"#{file}.md5".tap { |md5|
|
176
|
-
sh("md5sum #{file} > #{md5}")
|
177
|
-
}
|
178
|
-
}
|
179
|
-
|
180
|
-
rubyforge("add_release", gem)
|
181
|
-
rubyforge("add_file", gem_md5)
|
182
|
-
rubyforge("add_file", tgz)
|
183
|
-
rubyforge("add_file", tgz_md5)
|
184
|
-
|
185
|
-
git("tag", "#{GEMSPEC.name}-" + GEMSPEC.version.to_s)
|
186
|
-
git(*%w(push --tags origin master))
|
35
|
+
File.open(readme_file, "w") { |f| f.print update }
|
187
36
|
end
|
188
37
|
|
189
|
-
task :
|
190
|
-
[
|
191
|
-
:prerelease,
|
192
|
-
:package,
|
193
|
-
:publish,
|
194
|
-
:finish_release,
|
195
|
-
]
|
38
|
+
task :prerelease => :readme
|
data/devel/jumpstart.rb
ADDED
@@ -0,0 +1,970 @@
|
|
1
|
+
|
2
|
+
class Jumpstart
|
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
|
27
|
+
else
|
28
|
+
puts "install #{source} --> #{dest}"
|
29
|
+
FileUtils.install(source, dest)
|
30
|
+
end
|
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
|
+
}
|
53
|
+
end
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
def install_file?(source)
|
58
|
+
File.directory?(source) or
|
59
|
+
(File.file?(source) and File.extname(source) == ".rb")
|
60
|
+
end
|
61
|
+
|
62
|
+
def install
|
63
|
+
@spec.each { |entry|
|
64
|
+
entry[:install].call
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
def uninstall
|
69
|
+
@spec.reverse.each { |entry|
|
70
|
+
entry[:uninstall].call
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
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
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
module AttrLazy
|
86
|
+
def attr_lazy(name, &block)
|
87
|
+
AttrLazy.define_reader(class << self ; self ; end, name, &block)
|
88
|
+
end
|
89
|
+
|
90
|
+
def attr_lazy_accessor(name, &block)
|
91
|
+
attr_lazy(name, &block)
|
92
|
+
AttrLazy.define_writer(class << self ; self ; end, name, &block)
|
93
|
+
end
|
94
|
+
|
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
|
101
|
+
|
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
|
108
|
+
|
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
|
117
|
+
|
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
|
127
|
+
end
|
128
|
+
|
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
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
module Ruby
|
143
|
+
module_function
|
144
|
+
|
145
|
+
def executable
|
146
|
+
require 'rbconfig'
|
147
|
+
|
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"]
|
156
|
+
else
|
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}"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
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
|
+
}
|
176
|
+
end
|
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
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def no_warnings(&block)
|
198
|
+
with_warnings(nil, &block)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
module Util
|
203
|
+
module_function
|
204
|
+
|
205
|
+
def run_ruby_on_each(*files)
|
206
|
+
files.each { |file|
|
207
|
+
Ruby.run("-w", file)
|
208
|
+
}
|
209
|
+
end
|
210
|
+
|
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)
|
229
|
+
}
|
230
|
+
end
|
231
|
+
new_contents
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
module InstanceEvalWithArgs
|
236
|
+
module_function
|
237
|
+
|
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)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
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)
|
252
|
+
}
|
253
|
+
end
|
254
|
+
|
255
|
+
def instance_eval_with_args(instance, *args, &block)
|
256
|
+
call_temp_method(instance, :__temp_method, *args, &block)
|
257
|
+
end
|
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
|
302
|
+
end
|
303
|
+
rescue
|
304
|
+
"0.0.0"
|
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
|
351
|
+
|
352
|
+
[:gem, :tgz].each { |ext|
|
353
|
+
attribute ext do
|
354
|
+
"pkg/#{name}-#{version}.#{ext}"
|
355
|
+
end
|
356
|
+
}
|
357
|
+
|
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
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
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
|
+
[]
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
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"
|
428
|
+
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
|
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
|
+
}
|
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
|
+
begin
|
474
|
+
pairs = Hash[*readme_contents.split(%r!^== (\w+).*?$!)[1..-1]].map {
|
475
|
+
|section, contents|
|
476
|
+
[section.downcase, contents.strip]
|
477
|
+
}
|
478
|
+
Hash[*pairs.flatten]
|
479
|
+
rescue
|
480
|
+
nil
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
attribute :description_section do
|
485
|
+
"description"
|
486
|
+
end
|
487
|
+
|
488
|
+
attribute :summary_section do
|
489
|
+
"summary"
|
490
|
+
end
|
491
|
+
|
492
|
+
attribute :description_sentences do
|
493
|
+
1
|
494
|
+
end
|
495
|
+
|
496
|
+
attribute :summary_sentences do
|
497
|
+
1
|
498
|
+
end
|
499
|
+
|
500
|
+
[:summary, :description].each { |section|
|
501
|
+
attribute section do
|
502
|
+
begin
|
503
|
+
sections[send("#{section}_section")].
|
504
|
+
gsub("\n", " ").
|
505
|
+
split(%r!\.\s*!m).
|
506
|
+
first(send("#{section}_sentences")).
|
507
|
+
join(". ") << "."
|
508
|
+
rescue
|
509
|
+
"FIXME: #{section}"
|
510
|
+
end
|
511
|
+
end
|
512
|
+
}
|
513
|
+
|
514
|
+
attribute :url do
|
515
|
+
begin
|
516
|
+
readme_contents.match(%r!^\*.*?(http://\S+)!)[1]
|
517
|
+
rescue
|
518
|
+
"http://#{rubyforge_name}.rubyforge.org"
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
attribute :extra_deps do
|
523
|
+
[]
|
524
|
+
end
|
525
|
+
|
526
|
+
attribute :extra_dev_deps do
|
527
|
+
[]
|
528
|
+
end
|
529
|
+
|
530
|
+
attribute :authors do
|
531
|
+
Array.new
|
532
|
+
end
|
533
|
+
|
534
|
+
attribute :email do
|
535
|
+
Array.new
|
536
|
+
end
|
537
|
+
|
538
|
+
def developer(name, email)
|
539
|
+
authors << name
|
540
|
+
self.email << email
|
541
|
+
end
|
542
|
+
|
543
|
+
def dependency(name, version)
|
544
|
+
extra_deps << [name, version]
|
545
|
+
end
|
546
|
+
|
547
|
+
def define_clean
|
548
|
+
task :clean do
|
549
|
+
Rake::Task[:clobber].invoke
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
def define_package
|
554
|
+
task manifest_file do
|
555
|
+
create_manifest
|
556
|
+
end
|
557
|
+
CLEAN.include manifest_file
|
558
|
+
task :package => :clean
|
559
|
+
Rake::GemPackageTask.new(gemspec) { |t|
|
560
|
+
t.need_tar = true
|
561
|
+
}
|
562
|
+
end
|
563
|
+
|
564
|
+
def define_spec
|
565
|
+
unless spec_files.empty?
|
566
|
+
require 'spec/rake/spectask'
|
567
|
+
|
568
|
+
desc "run specs"
|
569
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
570
|
+
t.spec_files = spec_files
|
571
|
+
end
|
572
|
+
|
573
|
+
desc "run specs with text output"
|
574
|
+
Spec::Rake::SpecTask.new('text_spec') do |t|
|
575
|
+
t.spec_files = spec_files
|
576
|
+
t.spec_opts = ['-fs']
|
577
|
+
end
|
578
|
+
|
579
|
+
desc "run specs with html output"
|
580
|
+
Spec::Rake::SpecTask.new('full_spec') do |t|
|
581
|
+
t.spec_files = spec_files
|
582
|
+
t.rcov = true
|
583
|
+
t.rcov_opts = rcov_options
|
584
|
+
t.spec_opts = ["-fh:#{spec_output}"]
|
585
|
+
end
|
586
|
+
|
587
|
+
desc "run full_spec then open browser"
|
588
|
+
task :show_spec => :full_spec do
|
589
|
+
open_browser(spec_output, rcov_dir + "/index.html")
|
590
|
+
end
|
591
|
+
|
592
|
+
desc "run specs individually"
|
593
|
+
task :spec_deps do
|
594
|
+
run_ruby_on_each(*spec_files)
|
595
|
+
end
|
596
|
+
|
597
|
+
task :prerelease => [:spec, :spec_deps]
|
598
|
+
task :default => :spec
|
599
|
+
|
600
|
+
CLEAN.include spec_output_dir
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
def define_test
|
605
|
+
unless test_files.empty?
|
606
|
+
desc "run tests"
|
607
|
+
task :test do
|
608
|
+
test_files.each { |file|
|
609
|
+
require file
|
610
|
+
}
|
611
|
+
end
|
612
|
+
|
613
|
+
desc "run tests with rcov"
|
614
|
+
task :full_test do
|
615
|
+
verbose(false) {
|
616
|
+
sh("rcov", "-o", rcov_dir, "--text-report",
|
617
|
+
*(test_files + rcov_options)
|
618
|
+
)
|
619
|
+
}
|
620
|
+
end
|
621
|
+
|
622
|
+
desc "run full_test then open browser"
|
623
|
+
task :show_test => :full_test do
|
624
|
+
open_browser(rcov_dir + "/index.html")
|
625
|
+
end
|
626
|
+
|
627
|
+
desc "run tests individually"
|
628
|
+
task :test_deps do
|
629
|
+
run_ruby_on_each(*test_files)
|
630
|
+
end
|
631
|
+
|
632
|
+
task :prerelease => [:test, :test_deps]
|
633
|
+
task :default => :test
|
634
|
+
|
635
|
+
CLEAN.include rcov_dir
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
def define_doc
|
640
|
+
desc "run rdoc"
|
641
|
+
task :doc => :clean_doc do
|
642
|
+
require 'rdoc/rdoc'
|
643
|
+
args = (
|
644
|
+
gemspec.rdoc_options +
|
645
|
+
gemspec.require_paths.clone +
|
646
|
+
gemspec.extra_rdoc_files +
|
647
|
+
["-o", doc_dir]
|
648
|
+
).flatten.map { |t| t.to_s }
|
649
|
+
RDoc::RDoc.new.document args
|
650
|
+
end
|
651
|
+
|
652
|
+
task :clean_doc do
|
653
|
+
# normally rm_rf, but mimic rake/clean output
|
654
|
+
rm_r(doc_dir) rescue nil
|
655
|
+
end
|
656
|
+
|
657
|
+
desc "run rdoc then open browser"
|
658
|
+
task :show_doc => :doc do
|
659
|
+
open_browser(doc_dir + "/index.html")
|
660
|
+
end
|
661
|
+
|
662
|
+
task :rdoc => :doc
|
663
|
+
task :clean => :clean_doc
|
664
|
+
end
|
665
|
+
|
666
|
+
def define_publish
|
667
|
+
desc "upload docs"
|
668
|
+
task :publish => [:clean_doc, :doc] do
|
669
|
+
require 'rake/contrib/sshpublisher'
|
670
|
+
Rake::SshDirPublisher.new(
|
671
|
+
"#{rubyforge_user}@rubyforge.org",
|
672
|
+
"/var/www/gforge-projects/#{rubyforge_name}",
|
673
|
+
doc_dir
|
674
|
+
).upload
|
675
|
+
end
|
676
|
+
end
|
677
|
+
|
678
|
+
def define_install
|
679
|
+
desc "direct install (no gem)"
|
680
|
+
task :install do
|
681
|
+
SimpleInstaller.new.run([])
|
682
|
+
end
|
683
|
+
|
684
|
+
desc "direct uninstall (no gem)"
|
685
|
+
task :uninstall do
|
686
|
+
SimpleInstaller.new.run(["--uninstall"])
|
687
|
+
end
|
688
|
+
end
|
689
|
+
|
690
|
+
def define_debug
|
691
|
+
runner = Class.new do
|
692
|
+
def comment_src_dst(on)
|
693
|
+
on ? ["", "#"] : ["#", ""]
|
694
|
+
end
|
695
|
+
|
696
|
+
def comment_regions(on, contents, start)
|
697
|
+
src, dst = comment_src_dst(on)
|
698
|
+
contents.gsub(%r!^(\s+)#{src}#{start}.*?^\1#{src}(\}|end)!m) { |chunk|
|
699
|
+
indent = $1
|
700
|
+
chunk.gsub(%r!^#{indent}#{src}!, "#{indent}#{dst}")
|
701
|
+
}
|
702
|
+
end
|
703
|
+
|
704
|
+
def comment_lines(on, contents, start)
|
705
|
+
src, dst = comment_src_dst(on)
|
706
|
+
contents.gsub(%r!^(\s*)#{src}#{start}!) {
|
707
|
+
$1 + dst + start
|
708
|
+
}
|
709
|
+
end
|
710
|
+
|
711
|
+
def debug_info(enable)
|
712
|
+
require 'find'
|
713
|
+
Find.find("lib", "test") { |path|
|
714
|
+
if path =~ %r!\.rb\Z!
|
715
|
+
replace_file(path) { |contents|
|
716
|
+
result = comment_regions(!enable, contents, "debug")
|
717
|
+
comment_lines(!enable, result, "trace")
|
718
|
+
}
|
719
|
+
end
|
720
|
+
}
|
721
|
+
end
|
722
|
+
end
|
723
|
+
|
724
|
+
desc "enable debug and trace calls"
|
725
|
+
task :debug_on do
|
726
|
+
runner.new.debug_info(true)
|
727
|
+
end
|
728
|
+
|
729
|
+
desc "disable debug and trace calls"
|
730
|
+
task :debug_off do
|
731
|
+
runner.new.debug_info(false)
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
735
|
+
def define_columns
|
736
|
+
desc "check for columns > 80"
|
737
|
+
task :check_columns do
|
738
|
+
Dir["**/*.rb"].each { |file|
|
739
|
+
File.read(file).scan(%r!^.{81}!) { |match|
|
740
|
+
unless match =~ %r!http://!
|
741
|
+
raise "#{file} greater than 80 columns: #{match}"
|
742
|
+
end
|
743
|
+
}
|
744
|
+
}
|
745
|
+
end
|
746
|
+
task :prerelease => :check_columns
|
747
|
+
end
|
748
|
+
|
749
|
+
def define_comments
|
750
|
+
task :comments do
|
751
|
+
file = "comments.txt"
|
752
|
+
write_file(file) {
|
753
|
+
result = Array.new
|
754
|
+
(["Rakefile"] + Dir["**/*.{rb,rake}"]).each { |f|
|
755
|
+
File.read(f).scan(%r!\#[^\{].*$!) { |match|
|
756
|
+
result << match
|
757
|
+
}
|
758
|
+
}
|
759
|
+
result.join("\n")
|
760
|
+
}
|
761
|
+
CLEAN.include file
|
762
|
+
end
|
763
|
+
end
|
764
|
+
|
765
|
+
def define_check_directory
|
766
|
+
task :check_directory do
|
767
|
+
unless `git status` =~ %r!nothing to commit \(working directory clean\)!
|
768
|
+
raise "Directory not clean"
|
769
|
+
end
|
770
|
+
end
|
771
|
+
end
|
772
|
+
|
773
|
+
def define_ping
|
774
|
+
task :ping do
|
775
|
+
require 'rbconfig'
|
776
|
+
%w[github.com rubyforge.org].each { |server|
|
777
|
+
cmd = "ping " + (
|
778
|
+
if Config::CONFIG["host"] =~ %r!darwin!
|
779
|
+
"-c2 #{server}"
|
780
|
+
else
|
781
|
+
"#{server} 2 2"
|
782
|
+
end
|
783
|
+
)
|
784
|
+
unless `#{cmd}` =~ %r!0% packet loss!
|
785
|
+
raise "No ping for #{server}"
|
786
|
+
end
|
787
|
+
}
|
788
|
+
end
|
789
|
+
end
|
790
|
+
|
791
|
+
def define_update_jumpstart
|
792
|
+
url = ENV["RUBY_JUMPSTART"] || "git://github.com/quix/jumpstart.git"
|
793
|
+
task :update_jumpstart do
|
794
|
+
git "clone", url
|
795
|
+
rm_rf "devel/jumpstart"
|
796
|
+
Dir["jumpstart/**/*.rb"].each { |source|
|
797
|
+
dest = source.sub(%r!\Ajumpstart/!, "devel/")
|
798
|
+
dest_dir = File.dirname(dest)
|
799
|
+
mkdir_p(dest_dir) unless File.directory?(dest_dir)
|
800
|
+
cp source, dest
|
801
|
+
}
|
802
|
+
rm_r "jumpstart"
|
803
|
+
git "commit", "devel", "-m", "update jumpstart"
|
804
|
+
end
|
805
|
+
end
|
806
|
+
|
807
|
+
def git(*args)
|
808
|
+
sh("git", *args)
|
809
|
+
end
|
810
|
+
|
811
|
+
def create_manifest
|
812
|
+
write_file(manifest_file) {
|
813
|
+
files.sort.join("\n")
|
814
|
+
}
|
815
|
+
end
|
816
|
+
|
817
|
+
def rubyforge(mode, file, *options)
|
818
|
+
command = ["rubyforge", mode] + options + [
|
819
|
+
rubyforge_name,
|
820
|
+
rubyforge_name,
|
821
|
+
version.to_s,
|
822
|
+
file,
|
823
|
+
]
|
824
|
+
sh(*command)
|
825
|
+
end
|
826
|
+
|
827
|
+
def define_release
|
828
|
+
task :prerelease => [:clean, :check_directory, :ping, history_file]
|
829
|
+
|
830
|
+
task :finish_release do
|
831
|
+
gem_md5, tgz_md5 = [gem, tgz].map { |file|
|
832
|
+
md5 = "#{file}.md5"
|
833
|
+
sh("md5sum #{file} > #{md5}")
|
834
|
+
md5
|
835
|
+
}
|
836
|
+
|
837
|
+
rubyforge(
|
838
|
+
"add_release", gem, "--release_changes", history_file, "--preformatted"
|
839
|
+
)
|
840
|
+
[gem_md5, tgz, tgz_md5].each { |file|
|
841
|
+
rubyforge("add_file", file)
|
842
|
+
}
|
843
|
+
|
844
|
+
git("tag", "#{name}-" + version.to_s)
|
845
|
+
git(*%w(push --tags origin master))
|
846
|
+
end
|
847
|
+
|
848
|
+
task :release => [:prerelease, :package, :publish, :finish_release]
|
849
|
+
end
|
850
|
+
|
851
|
+
def define_debug_gem
|
852
|
+
task :debug_gem do
|
853
|
+
puts gemspec.to_ruby
|
854
|
+
end
|
855
|
+
end
|
856
|
+
|
857
|
+
def open_browser(*files)
|
858
|
+
sh(*([browser].flatten + files))
|
859
|
+
end
|
860
|
+
|
861
|
+
class << self
|
862
|
+
include Util
|
863
|
+
include InstanceEvalWithArgs
|
864
|
+
|
865
|
+
# From minitest, part of the Ruby source; by Ryan Davis.
|
866
|
+
def capture_io
|
867
|
+
require 'stringio'
|
868
|
+
|
869
|
+
orig_stdout, orig_stderr = $stdout, $stderr
|
870
|
+
captured_stdout, captured_stderr = StringIO.new, StringIO.new
|
871
|
+
$stdout, $stderr = captured_stdout, captured_stderr
|
872
|
+
|
873
|
+
yield
|
874
|
+
|
875
|
+
return captured_stdout.string, captured_stderr.string
|
876
|
+
ensure
|
877
|
+
$stdout = orig_stdout
|
878
|
+
$stderr = orig_stderr
|
879
|
+
end
|
880
|
+
|
881
|
+
def run_doc_code(code, expected, index, instance, &block)
|
882
|
+
lib = File.expand_path(File.dirname(__FILE__) + "/../lib")
|
883
|
+
header = %{
|
884
|
+
$LOAD_PATH.unshift "#{lib}"
|
885
|
+
require 'rubygems'
|
886
|
+
begin
|
887
|
+
}
|
888
|
+
footer = %{
|
889
|
+
rescue Exception => __jumpstart_exception
|
890
|
+
puts "raises \#{__jumpstart_exception.class}"
|
891
|
+
end
|
892
|
+
}
|
893
|
+
final_code = header + code + footer
|
894
|
+
|
895
|
+
# Sometimes code is required to be inside a file.
|
896
|
+
actual = nil
|
897
|
+
require 'tempfile'
|
898
|
+
Tempfile.open("run-rdoc-code") { |temp_file|
|
899
|
+
temp_file.print(final_code)
|
900
|
+
temp_file.close
|
901
|
+
actual = Ruby.run_file_and_capture(temp_file.path).chomp
|
902
|
+
}
|
903
|
+
|
904
|
+
instance_eval_with_args(instance, expected, actual, index, &block)
|
905
|
+
end
|
906
|
+
|
907
|
+
def run_doc_section(file, section, instance, &block)
|
908
|
+
contents = File.read(file)
|
909
|
+
if section_contents = contents[%r!^=+[ \t]#{section}.*?\n(.*?)^=!m, 1]
|
910
|
+
index = 0
|
911
|
+
section_contents.scan(%r!^( \S.*?)(?=(^\S|\Z))!m) { |indented, unused|
|
912
|
+
code_sections = indented.split(%r!^ \#\#\#\# output:\s*$!)
|
913
|
+
code, expected = (
|
914
|
+
case code_sections.size
|
915
|
+
when 1
|
916
|
+
[indented, indented.scan(%r!\# => (.*?)\n!).flatten.join("\n")]
|
917
|
+
when 2
|
918
|
+
code_sections
|
919
|
+
else
|
920
|
+
raise "parse error"
|
921
|
+
end
|
922
|
+
)
|
923
|
+
run_doc_code(code, expected, index, instance, &block)
|
924
|
+
index += 1
|
925
|
+
}
|
926
|
+
else
|
927
|
+
raise "couldn't find section `#{section}' of `#{file}'"
|
928
|
+
end
|
929
|
+
end
|
930
|
+
|
931
|
+
def doc_to_spec(file, *sections, &block)
|
932
|
+
jump = self
|
933
|
+
describe file do
|
934
|
+
sections.each { |section|
|
935
|
+
describe "section `#{section}'" do
|
936
|
+
it "should run as claimed" do
|
937
|
+
if block
|
938
|
+
jump.run_doc_section(file, section, self, &block)
|
939
|
+
else
|
940
|
+
jump.run_doc_section(file, section, self) {
|
941
|
+
|expected, actual, index|
|
942
|
+
actual.should == expected
|
943
|
+
}
|
944
|
+
end
|
945
|
+
end
|
946
|
+
end
|
947
|
+
}
|
948
|
+
end
|
949
|
+
end
|
950
|
+
|
951
|
+
def doc_to_test(file, *sections, &block)
|
952
|
+
jump = self
|
953
|
+
klass = Class.new Test::Unit::TestCase do
|
954
|
+
sections.each { |section|
|
955
|
+
define_method "test_#{file}_#{section}" do
|
956
|
+
if block
|
957
|
+
jump.run_doc_section(file, section, self, &block)
|
958
|
+
else
|
959
|
+
jump.run_doc_section(file, section, self) {
|
960
|
+
|expected, actual, index|
|
961
|
+
assert_equal expected, actual
|
962
|
+
}
|
963
|
+
end
|
964
|
+
end
|
965
|
+
}
|
966
|
+
end
|
967
|
+
Object.const_set("Test#{file}".gsub(".", ""), klass)
|
968
|
+
end
|
969
|
+
end
|
970
|
+
end
|