cond 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +334 -0
- data/Rakefile +195 -0
- data/cond.gemspec +37 -0
- data/examples/bad_example.rb +51 -0
- data/examples/calc_example.rb +84 -0
- data/examples/readme_example.rb +27 -0
- data/examples/restarts_example.rb +26 -0
- data/examples/seibel_example.rb +18 -0
- data/install.rb +3 -0
- data/lib/cond.rb +456 -0
- data/lib/cond/cond_private/defaults.rb +78 -0
- data/lib/cond/cond_private/symbol_generator.rb +45 -0
- data/lib/cond/cond_private/thread_local.rb +77 -0
- data/readmes/restarts.rb +84 -0
- data/readmes/seibel_pcl.rb +129 -0
- data/spec/basic_spec.rb +66 -0
- data/spec/common.rb +49 -0
- data/spec/error_spec.rb +55 -0
- data/spec/leave_again_spec.rb +88 -0
- data/spec/matching_spec.rb +86 -0
- data/spec/raise_spec.rb +69 -0
- data/spec/reraise_spec.rb +101 -0
- data/spec/specs_spec.rb +16 -0
- data/spec/symbols_spec.rb +33 -0
- data/spec/thread_local_spec.rb +29 -0
- data/spec/wrapping_spec.rb +93 -0
- data/support/quix/ruby.rb +51 -0
- data/support/quix/simple_installer.rb +88 -0
- metadata +93 -0
data/cond.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
|
2
|
+
Gem::Specification.new { |t|
|
3
|
+
t.author = "James M. Lawrence"
|
4
|
+
t.email = "quixoticsycophant@gmail.com"
|
5
|
+
t.summary = "Resolve errors without unwinding the stack."
|
6
|
+
t.name = "cond"
|
7
|
+
t.rubyforge_project = t.name
|
8
|
+
t.homepage = "#{t.name}.rubyforge.org"
|
9
|
+
t.version = "0.2.0"
|
10
|
+
t.description = <<-EOS
|
11
|
+
+Cond+ allows errors to be handled at the place where they occur.
|
12
|
+
You decide whether or not the stack should be unwound, depending on
|
13
|
+
the circumstance and the error.
|
14
|
+
EOS
|
15
|
+
t.files = (
|
16
|
+
%W[README #{t.name}.gemspec] +
|
17
|
+
Dir["./**/*.rb"] +
|
18
|
+
Dir["./**/Rakefile"]
|
19
|
+
)
|
20
|
+
rdoc_exclude = %w[
|
21
|
+
spec
|
22
|
+
examples
|
23
|
+
readmes
|
24
|
+
support
|
25
|
+
lib/cond/cond_private
|
26
|
+
]
|
27
|
+
t.has_rdoc = true
|
28
|
+
t.extra_rdoc_files = %w[README]
|
29
|
+
t.rdoc_options += [
|
30
|
+
"--main",
|
31
|
+
"README",
|
32
|
+
"--title",
|
33
|
+
"#{t.name}: #{t.summary}",
|
34
|
+
] + rdoc_exclude.inject(Array.new) { |acc, pattern|
|
35
|
+
acc + ["--exclude", pattern]
|
36
|
+
}
|
37
|
+
}
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec/common"
|
2
|
+
|
3
|
+
#
|
4
|
+
# This is a bad example because a handler should re-raise if no
|
5
|
+
# restart is provided. All bets are off if code directly after a
|
6
|
+
# 'raise' gets executed. But for fun let's see what it looks like.
|
7
|
+
#
|
8
|
+
|
9
|
+
include Cond
|
10
|
+
|
11
|
+
module BadExample
|
12
|
+
A, B = (1..2).map { Class.new RuntimeError }
|
13
|
+
|
14
|
+
memo = []
|
15
|
+
|
16
|
+
describe "bad example" do
|
17
|
+
it "should demonstrate how not to use Cond" do
|
18
|
+
handling do
|
19
|
+
handle A do
|
20
|
+
memo.push :ignore_a
|
21
|
+
end
|
22
|
+
|
23
|
+
handle B do
|
24
|
+
memo.push :reraise_b
|
25
|
+
raise
|
26
|
+
end
|
27
|
+
|
28
|
+
raise A
|
29
|
+
# ... still going!
|
30
|
+
|
31
|
+
handling do
|
32
|
+
handle B do
|
33
|
+
memo.push :ignore_b
|
34
|
+
end
|
35
|
+
raise B
|
36
|
+
# ... !
|
37
|
+
end
|
38
|
+
|
39
|
+
begin
|
40
|
+
raise B, "should not be ignored"
|
41
|
+
rescue B => e
|
42
|
+
if e.message == "should not be ignored"
|
43
|
+
memo.push :rescued_b
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
memo.should == [:ignore_a, :ignore_b, :reraise_b, :rescued_b]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec/common"
|
2
|
+
|
3
|
+
include Cond
|
4
|
+
|
5
|
+
class DivergedError < StandardError
|
6
|
+
attr_reader :epsilon
|
7
|
+
|
8
|
+
def initialize(epsilon)
|
9
|
+
super()
|
10
|
+
@epsilon = epsilon
|
11
|
+
end
|
12
|
+
|
13
|
+
def message
|
14
|
+
"Failed to converge with epsilon #{@epsilon}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def calc(x, y, epsilon)
|
19
|
+
restartable do
|
20
|
+
restart :change_epsilon do |new_epsilon|
|
21
|
+
epsilon = new_epsilon
|
22
|
+
again
|
23
|
+
end
|
24
|
+
restart :give_up do
|
25
|
+
leave
|
26
|
+
end
|
27
|
+
# ...
|
28
|
+
# ... some calculation
|
29
|
+
# ...
|
30
|
+
if epsilon < 0.01
|
31
|
+
raise DivergedError.new(epsilon)
|
32
|
+
end
|
33
|
+
42
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "A calculation which can raise a divergent error," do
|
38
|
+
describe "with a handler which increases epsilon" do
|
39
|
+
before :all do
|
40
|
+
handling do
|
41
|
+
@memo = []
|
42
|
+
@result = nil
|
43
|
+
epsilon = 0.0005
|
44
|
+
handle DivergedError do
|
45
|
+
epsilon += 0.001
|
46
|
+
@memo.push :increase
|
47
|
+
invoke_restart :change_epsilon, epsilon
|
48
|
+
end
|
49
|
+
@result = calc(3, 4, epsilon)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should converge after repeated epsilon increases" do
|
54
|
+
@memo.should == (1..10).map { :increase }
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should obtain a result" do
|
58
|
+
@result.should == 42
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "with a give-up handler and a too-small epsilon" do
|
63
|
+
before :all do
|
64
|
+
handling do
|
65
|
+
@result = 9999
|
66
|
+
@memo = []
|
67
|
+
epsilon = 1e-10
|
68
|
+
handle DivergedError do
|
69
|
+
@memo.push :give_up
|
70
|
+
invoke_restart :give_up
|
71
|
+
end
|
72
|
+
@result = calc(3, 4, epsilon)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should give up" do
|
77
|
+
@memo.should == [:give_up]
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should obtain a nil result" do
|
81
|
+
@result.should == nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec/common"
|
2
|
+
|
3
|
+
require 'quix/ruby'
|
4
|
+
|
5
|
+
root = Pathname(__FILE__).dirname + ".."
|
6
|
+
file = root + "README"
|
7
|
+
lib = root + "lib"
|
8
|
+
|
9
|
+
describe file do
|
10
|
+
["Synopsis",
|
11
|
+
"Raw Form",
|
12
|
+
"Synopsis 2.0",
|
13
|
+
].each { |section|
|
14
|
+
it "#{section} should run as claimed" do
|
15
|
+
contents = file.read
|
16
|
+
|
17
|
+
code = %{
|
18
|
+
$LOAD_PATH.unshift "#{lib.expand_path}"
|
19
|
+
require 'cond'
|
20
|
+
include Cond
|
21
|
+
} + contents.match(%r!== #{section}.*?\n(.*?)^\S!m)[1]
|
22
|
+
|
23
|
+
expected = code.scan(%r!\# => (.*?)\n!).flatten.join("\n")
|
24
|
+
pipe_to_ruby(code).chomp.should == expected
|
25
|
+
end
|
26
|
+
}
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
here = File.dirname(__FILE__)
|
2
|
+
require here + "/../spec/common"
|
3
|
+
|
4
|
+
RESTARTS_FILE = here + "/../readmes/restarts.rb"
|
5
|
+
|
6
|
+
def run_restarts(input_string)
|
7
|
+
capture(input_string) {
|
8
|
+
load RESTARTS_FILE
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
describe RESTARTS_FILE do
|
13
|
+
it "should fetch with with alternate hash" do
|
14
|
+
hash = { "mango" => "mangoish fruit" }
|
15
|
+
re = %r!#{hash.values.first}!
|
16
|
+
run_restarts(%{2\n#{hash.inspect}\n}).should match(re)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should fetch with alternate value" do
|
20
|
+
class Cond::Restart
|
21
|
+
# coverage hack
|
22
|
+
undef :message
|
23
|
+
end
|
24
|
+
run_restarts(%{3\n"apple"\n}).should match(%r!value: "fruit"!)
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
here = File.dirname(__FILE__)
|
2
|
+
require here + "/../spec/common"
|
3
|
+
|
4
|
+
seibel_file = here + "/../readmes/seibel_pcl.rb"
|
5
|
+
|
6
|
+
include Cond
|
7
|
+
|
8
|
+
if RUBY_VERSION > "1.8.6"
|
9
|
+
describe seibel_file do
|
10
|
+
it "should run" do
|
11
|
+
lambda {
|
12
|
+
capture("0\n") {
|
13
|
+
load seibel_file
|
14
|
+
}
|
15
|
+
}.should_not raise_error
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/install.rb
ADDED
data/lib/cond.rb
ADDED
@@ -0,0 +1,456 @@
|
|
1
|
+
|
2
|
+
require 'cond/cond_private/thread_local'
|
3
|
+
require 'cond/cond_private/symbol_generator'
|
4
|
+
require 'cond/cond_private/defaults'
|
5
|
+
|
6
|
+
#
|
7
|
+
# Resolve errors without unwinding the stack.
|
8
|
+
#
|
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
|
+
|
399
|
+
module Kernel
|
400
|
+
remove_method :raise
|
401
|
+
def raise(*args)
|
402
|
+
if Cond.handlers_stack.last.empty?
|
403
|
+
# not using Cond
|
404
|
+
Cond.original_raise(*args)
|
405
|
+
else
|
406
|
+
last_exception, current_handler = Cond.exception_stack.last
|
407
|
+
exception = (
|
408
|
+
if last_exception and args.empty?
|
409
|
+
last_exception
|
410
|
+
else
|
411
|
+
begin
|
412
|
+
Cond.original_raise(*args)
|
413
|
+
rescue Exception => e
|
414
|
+
e
|
415
|
+
end
|
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
|