cond 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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/install.rb
CHANGED
@@ -1,3 +1,2 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
Quix::SimpleInstaller.new.run
|
1
|
+
load './devel/jumpstart.rb'
|
2
|
+
Jumpstart::SimpleInstaller.new.run
|
data/lib/cond.rb
CHANGED
@@ -1,456 +1,41 @@
|
|
1
|
-
|
2
|
-
require 'cond/cond_private/thread_local'
|
3
|
-
require 'cond/cond_private/symbol_generator'
|
4
|
-
require 'cond/cond_private/defaults'
|
5
|
-
|
6
1
|
#
|
7
|
-
#
|
2
|
+
# Copyright (c) 2008, 2009 James M. Lawrence. All rights reserved.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person
|
5
|
+
# obtaining a copy of this software and associated documentation files
|
6
|
+
# (the "Software"), to deal in the Software without restriction,
|
7
|
+
# including without limitation the rights to use, copy, modify, merge,
|
8
|
+
# publish, distribute, sublicense, and/or sell copies of the Software,
|
9
|
+
# and to permit persons to whom the Software is furnished to do so,
|
10
|
+
# subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
19
|
+
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
20
|
+
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
21
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
22
|
+
# SOFTWARE.
|
8
23
|
#
|
9
|
-
module Cond
|
10
|
-
module CondPrivate
|
11
|
-
class MessageProc < Proc
|
12
|
-
def initialize(message = "", &block)
|
13
|
-
@message = message
|
14
|
-
end
|
15
|
-
|
16
|
-
def message
|
17
|
-
@message
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
#
|
23
|
-
# A restart. Use of this class is optional: you could pass lambdas
|
24
|
-
# to Cond.with_restarts, but you'll miss the description string
|
25
|
-
# shown inside Cond.default_handler.
|
26
|
-
#
|
27
|
-
class Restart < CondPrivate::MessageProc
|
28
|
-
end
|
29
|
-
|
30
|
-
#
|
31
|
-
# A handler. Use of this class is optional: you could pass lambdas
|
32
|
-
# to Cond.with_handlers, but you'll miss the description string
|
33
|
-
# shown by whichever tools might use it (currently none).
|
34
|
-
#
|
35
|
-
class Handler < CondPrivate::MessageProc
|
36
|
-
end
|
37
|
-
|
38
|
-
######################################################################
|
39
|
-
# errors
|
40
|
-
|
41
|
-
#
|
42
|
-
# Cond.invoke_restart was called with an unknown restart.
|
43
|
-
#
|
44
|
-
class NoRestartError < StandardError
|
45
|
-
end
|
46
|
-
|
47
|
-
#
|
48
|
-
# `handle', `restart', `leave', or `again' called out of context.
|
49
|
-
#
|
50
|
-
class ContextError < StandardError
|
51
|
-
end
|
52
|
-
|
53
|
-
######################################################################
|
54
|
-
# singleton methods
|
55
|
-
|
56
|
-
class << self
|
57
|
-
#
|
58
|
-
# Register a set of handlers. The given hash is merged with the
|
59
|
-
# set of current handlers.
|
60
|
-
#
|
61
|
-
# When the block exits, the previous set of handlers (if any) are
|
62
|
-
# restored.
|
63
|
-
#
|
64
|
-
def with_handlers(handlers)
|
65
|
-
# note: leave unfactored due to notable yield vs &block performance
|
66
|
-
handlers_stack.push(handlers_stack.last.merge(handlers))
|
67
|
-
begin
|
68
|
-
yield
|
69
|
-
ensure
|
70
|
-
handlers_stack.pop
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
#
|
75
|
-
# Register a set of restarts. The given hash is merged with the
|
76
|
-
# set of current restarts.
|
77
|
-
#
|
78
|
-
# When the block exits, the previous set of restarts (if any) are
|
79
|
-
# restored.
|
80
|
-
#
|
81
|
-
def with_restarts(restarts)
|
82
|
-
# note: leave unfactored due to notable yield vs &block performance
|
83
|
-
restarts_stack.push(restarts_stack.last.merge(restarts))
|
84
|
-
begin
|
85
|
-
yield
|
86
|
-
ensure
|
87
|
-
restarts_stack.pop
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
#
|
92
|
-
# A default handler is provided which runs a simple
|
93
|
-
# choose-a-restart input loop when +raise+ is called.
|
94
|
-
#
|
95
|
-
def with_default_handlers
|
96
|
-
# note: leave unfactored due to notable yield vs &block performance
|
97
|
-
with_handlers(defaults.handlers) {
|
98
|
-
yield
|
99
|
-
}
|
100
|
-
end
|
101
|
-
|
102
|
-
#
|
103
|
-
# The current set of restarts which have been registered.
|
104
|
-
#
|
105
|
-
def available_restarts
|
106
|
-
restarts_stack.last
|
107
|
-
end
|
108
|
-
|
109
|
-
#
|
110
|
-
# Find the closest-matching handler for the given Exception.
|
111
|
-
#
|
112
|
-
def find_handler(target) #:nodoc:
|
113
|
-
find_handler_from(handlers_stack.last, target)
|
114
|
-
end
|
115
|
-
|
116
|
-
def find_handler_from(handlers, target) #:nodoc:
|
117
|
-
handlers.fetch(target) {
|
118
|
-
found = handlers.inject(Array.new) { |acc, (klass, func)|
|
119
|
-
index = target.ancestors.index(klass)
|
120
|
-
if index
|
121
|
-
acc << [index, func]
|
122
|
-
else
|
123
|
-
acc
|
124
|
-
end
|
125
|
-
}.sort_by { |t| t.first }.first
|
126
|
-
found and found[1]
|
127
|
-
}
|
128
|
-
end
|
129
|
-
|
130
|
-
def run_code_section(klass, &block) #:nodoc:
|
131
|
-
section = klass.new(&block)
|
132
|
-
Cond.code_section_stack.push(section)
|
133
|
-
begin
|
134
|
-
section.instance_eval { run }
|
135
|
-
ensure
|
136
|
-
Cond.code_section_stack.pop
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
def check_context(keyword) #:nodoc:
|
141
|
-
section = Cond.code_section_stack.last
|
142
|
-
case keyword
|
143
|
-
when :restart
|
144
|
-
unless section.is_a? CondPrivate::RestartableSection
|
145
|
-
Cond.original_raise(
|
146
|
-
ContextError,
|
147
|
-
"`#{keyword}' called outside of `restartable' block"
|
148
|
-
)
|
149
|
-
end
|
150
|
-
when :handle
|
151
|
-
unless section.is_a? CondPrivate::HandlingSection
|
152
|
-
Cond.original_raise(
|
153
|
-
ContextError,
|
154
|
-
"`#{keyword}' called outside of `handling' block"
|
155
|
-
)
|
156
|
-
end
|
157
|
-
when :leave, :again
|
158
|
-
unless section
|
159
|
-
Cond.original_raise(
|
160
|
-
ContextError,
|
161
|
-
"`#{keyword}' called outside of `handling' or `restartable' block"
|
162
|
-
)
|
163
|
-
end
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
###############################################
|
168
|
-
# wrapping
|
169
|
-
|
170
|
-
#
|
171
|
-
# Allow handlers to be called from C code by wrapping a method with
|
172
|
-
# begin/rescue. Returns the aliased name of the original method.
|
173
|
-
#
|
174
|
-
# See the README.
|
175
|
-
#
|
176
|
-
# Example:
|
177
|
-
#
|
178
|
-
# Cond.wrap_instance_method(Fixnum, :/)
|
179
|
-
#
|
180
|
-
def wrap_instance_method(mod, method)
|
181
|
-
original = "cond_original_#{mod.inspect}_#{method.inspect}"
|
182
|
-
# TODO: jettison 1.8.6, remove eval and use |&block|
|
183
|
-
# TODO: fix rcov bug -- does not see %{}
|
184
|
-
mod.module_eval <<-eval_end
|
185
|
-
alias_method :'#{original}', :'#{method}'
|
186
|
-
def #{method}(*args, &block)
|
187
|
-
begin
|
188
|
-
send(:'#{original}', *args, &block)
|
189
|
-
rescue Exception => e
|
190
|
-
raise e
|
191
|
-
end
|
192
|
-
end
|
193
|
-
eval_end
|
194
|
-
original
|
195
|
-
end
|
196
|
-
|
197
|
-
#
|
198
|
-
# Allow handlers to be called from C code by wrapping a method with
|
199
|
-
# begin/rescue. Returns the aliased name of the original method.
|
200
|
-
#
|
201
|
-
# See the README.
|
202
|
-
#
|
203
|
-
# Example:
|
204
|
-
#
|
205
|
-
# Cond.wrap_singleton_method(IO, :read)
|
206
|
-
#
|
207
|
-
def wrap_singleton_method(mod, method)
|
208
|
-
singleton_class = class << mod ; self ; end
|
209
|
-
wrap_instance_method(singleton_class, method)
|
210
|
-
end
|
211
|
-
|
212
|
-
###############################################
|
213
|
-
# original raise
|
214
|
-
|
215
|
-
define_method :original_raise, Kernel.instance_method(:raise)
|
216
|
-
|
217
|
-
######################################################################
|
218
|
-
# data -- all data is per-thread and fetched from the singleton class
|
219
|
-
#
|
220
|
-
# Cond.defaults contains the default handlers. To replace it,
|
221
|
-
# call
|
222
|
-
#
|
223
|
-
# Cond.defaults.clear(&block)
|
224
|
-
#
|
225
|
-
# where &block creates a new instance of your class which
|
226
|
-
# implements the method 'handlers'.
|
227
|
-
#
|
228
|
-
# Note that &block should return a brand new instance. Otherwise
|
229
|
-
# the returned object will be shared across threads.
|
230
|
-
#
|
231
|
-
|
232
|
-
stack_0 = lambda { Array.new }
|
233
|
-
stack_1 = lambda { Array.new.push(Hash.new) }
|
234
|
-
defaults = lambda { CondPrivate::Defaults.new }
|
235
|
-
{
|
236
|
-
:code_section_stack => stack_0,
|
237
|
-
:exception_stack => stack_0,
|
238
|
-
:handlers_stack => stack_1,
|
239
|
-
:restarts_stack => stack_1,
|
240
|
-
:defaults => defaults,
|
241
|
-
}.each_pair { |name, create|
|
242
|
-
include CondPrivate::ThreadLocal.reader_module(name) {
|
243
|
-
create.call
|
244
|
-
}
|
245
|
-
}
|
246
|
-
|
247
|
-
include CondPrivate::ThreadLocal.accessor_module(:reraise_count) { 0 }
|
248
|
-
end
|
249
|
-
|
250
|
-
######################################################################
|
251
|
-
|
252
|
-
module CondPrivate
|
253
|
-
class CodeSection #:nodoc:
|
254
|
-
include SymbolGenerator
|
255
|
-
|
256
|
-
def initialize(with, &block)
|
257
|
-
@with = with
|
258
|
-
@block = block
|
259
|
-
@again_args = []
|
260
|
-
@leave, @again = gensym, gensym
|
261
|
-
SymbolGenerator.track(self, [@leave, @again])
|
262
|
-
end
|
263
|
-
|
264
|
-
def again(*args)
|
265
|
-
@again_args = (
|
266
|
-
case args.size
|
267
|
-
when 0
|
268
|
-
[]
|
269
|
-
when 1
|
270
|
-
args.first
|
271
|
-
else
|
272
|
-
args
|
273
|
-
end
|
274
|
-
)
|
275
|
-
throw @again
|
276
|
-
end
|
277
|
-
|
278
|
-
def leave(*args)
|
279
|
-
case args.size
|
280
|
-
when 0
|
281
|
-
throw @leave
|
282
|
-
when 1
|
283
|
-
throw @leave, args.first
|
284
|
-
else
|
285
|
-
throw @leave, args
|
286
|
-
end
|
287
|
-
end
|
288
|
-
|
289
|
-
def run
|
290
|
-
catch(@leave) {
|
291
|
-
while true
|
292
|
-
catch(@again) {
|
293
|
-
Cond.send(@with, Hash.new) {
|
294
|
-
throw @leave, @block.call(*@again_args)
|
295
|
-
}
|
296
|
-
}
|
297
|
-
end
|
298
|
-
}
|
299
|
-
end
|
300
|
-
end
|
301
|
-
|
302
|
-
class RestartableSection < CodeSection #:nodoc:
|
303
|
-
def initialize(&block)
|
304
|
-
super(:with_restarts, &block)
|
305
|
-
end
|
306
|
-
|
307
|
-
def restart(sym, message, &block)
|
308
|
-
Cond.restarts_stack.last[sym] = Restart.new(message, &block)
|
309
|
-
end
|
310
|
-
end
|
311
|
-
|
312
|
-
class HandlingSection < CodeSection #:nodoc:
|
313
|
-
def initialize(&block)
|
314
|
-
super(:with_handlers, &block)
|
315
|
-
end
|
316
|
-
|
317
|
-
def handle(sym, message, &block)
|
318
|
-
Cond.handlers_stack.last[sym] = Handler.new(message, &block)
|
319
|
-
end
|
320
|
-
end
|
321
|
-
end
|
322
|
-
|
323
|
-
######################################################################
|
324
|
-
# shiny exterior
|
325
|
-
|
326
|
-
module_function
|
327
|
-
|
328
|
-
#
|
329
|
-
# Begin a handling block. Inside this block, a matching handler
|
330
|
-
# gets called when +raise+ gets called.
|
331
|
-
#
|
332
|
-
def handling(&block)
|
333
|
-
Cond.run_code_section(CondPrivate::HandlingSection, &block)
|
334
|
-
end
|
335
|
-
|
336
|
-
#
|
337
|
-
# Begin a restartable block. A handler may transfer control to one
|
338
|
-
# of the restarts in this block.
|
339
|
-
#
|
340
|
-
def restartable(&block)
|
341
|
-
Cond.run_code_section(CondPrivate::RestartableSection, &block)
|
342
|
-
end
|
343
|
-
|
344
|
-
#
|
345
|
-
# Define a handler.
|
346
|
-
#
|
347
|
-
# The exception instance is passed to the block.
|
348
|
-
#
|
349
|
-
def handle(arg, message = "", &block)
|
350
|
-
Cond.check_context(:handle)
|
351
|
-
Cond.code_section_stack.last.handle(arg, message, &block)
|
352
|
-
end
|
353
|
-
|
354
|
-
#
|
355
|
-
# Define a restart.
|
356
|
-
#
|
357
|
-
# When a handler calls invoke_restart, it may pass additional
|
358
|
-
# arguments which are in turn passed to &block.
|
359
|
-
#
|
360
|
-
def restart(arg, message = "", &block)
|
361
|
-
Cond.check_context(:restart)
|
362
|
-
Cond.code_section_stack.last.restart(arg, message, &block)
|
363
|
-
end
|
364
|
-
|
365
|
-
#
|
366
|
-
# Leave the current handling or restartable block, optionally
|
367
|
-
# providing a value for the block.
|
368
|
-
#
|
369
|
-
# The semantics are the same as 'return'. When given multiple
|
370
|
-
# arguments, it returns an array. When given one argument, it
|
371
|
-
# returns only that argument (not an array).
|
372
|
-
#
|
373
|
-
def leave(*args)
|
374
|
-
Cond.check_context(:leave)
|
375
|
-
Cond.code_section_stack.last.leave(*args)
|
376
|
-
end
|
377
|
-
|
378
|
-
#
|
379
|
-
# Run the handling or restartable block again.
|
380
|
-
#
|
381
|
-
# Optionally pass arguments which are given to the block.
|
382
|
-
#
|
383
|
-
def again(*args)
|
384
|
-
Cond.check_context(:again)
|
385
|
-
Cond.code_section_stack.last.again(*args)
|
386
|
-
end
|
387
|
-
|
388
|
-
#
|
389
|
-
# Call a restart from a handler; optionally pass it some arguments.
|
390
|
-
#
|
391
|
-
def invoke_restart(name, *args, &block)
|
392
|
-
Cond.available_restarts.fetch(name) {
|
393
|
-
raise NoRestartError,
|
394
|
-
"Did not find `#{name.inspect}' in available restarts"
|
395
|
-
}.call(*args, &block)
|
396
|
-
end
|
397
|
-
end
|
398
24
|
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
end
|
417
|
-
)
|
418
|
-
if current_handler
|
419
|
-
# inside a handler
|
420
|
-
handler = loop {
|
421
|
-
Cond.reraise_count += 1
|
422
|
-
handlers = Cond.handlers_stack[-1 - Cond.reraise_count]
|
423
|
-
if handlers.nil?
|
424
|
-
break nil
|
425
|
-
end
|
426
|
-
found = Cond.find_handler_from(handlers, exception.class)
|
427
|
-
if found and found != current_handler
|
428
|
-
break found
|
429
|
-
end
|
430
|
-
}
|
431
|
-
if handler
|
432
|
-
handler.call(exception)
|
433
|
-
else
|
434
|
-
Cond.reraise_count = 0
|
435
|
-
Cond.original_raise(exception)
|
436
|
-
end
|
437
|
-
else
|
438
|
-
# not inside a handler
|
439
|
-
Cond.reraise_count = 0
|
440
|
-
handler = Cond.find_handler(exception.class)
|
441
|
-
if handler
|
442
|
-
Cond.exception_stack.push([exception, handler])
|
443
|
-
begin
|
444
|
-
handler.call(exception)
|
445
|
-
ensure
|
446
|
-
Cond.exception_stack.pop
|
447
|
-
end
|
448
|
-
else
|
449
|
-
Cond.original_raise(exception)
|
450
|
-
end
|
451
|
-
end
|
452
|
-
end
|
453
|
-
end
|
454
|
-
remove_method :fail
|
455
|
-
alias_method :fail, :raise
|
456
|
-
end
|
25
|
+
require 'thread'
|
26
|
+
require 'enumerator' if RUBY_VERSION <= "1.8.6"
|
27
|
+
|
28
|
+
require 'cond/symbol_generator'
|
29
|
+
require 'cond/thread_local'
|
30
|
+
require 'cond/defaults'
|
31
|
+
require 'cond/message_proc'
|
32
|
+
require 'cond/restart'
|
33
|
+
require 'cond/handler'
|
34
|
+
require 'cond/error'
|
35
|
+
require 'cond/code_section'
|
36
|
+
require 'cond/restartable_section'
|
37
|
+
require 'cond/handling_section'
|
38
|
+
require 'cond/dsl_definition'
|
39
|
+
require 'cond/wrapping'
|
40
|
+
require 'cond/cond'
|
41
|
+
require 'cond/kernel_raise'
|