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/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'
|