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
@@ -0,0 +1,51 @@
|
|
1
|
+
|
2
|
+
module Cond
|
3
|
+
class CodeSection #:nodoc:
|
4
|
+
include SymbolGenerator
|
5
|
+
|
6
|
+
def initialize(with, &block)
|
7
|
+
@with = with
|
8
|
+
@block = block
|
9
|
+
@again_args = []
|
10
|
+
@leave, @again = gensym, gensym
|
11
|
+
SymbolGenerator.track(self, @leave, @again)
|
12
|
+
end
|
13
|
+
|
14
|
+
def again(*args)
|
15
|
+
@again_args = (
|
16
|
+
case args.size
|
17
|
+
when 0
|
18
|
+
[]
|
19
|
+
when 1
|
20
|
+
args.first
|
21
|
+
else
|
22
|
+
args
|
23
|
+
end
|
24
|
+
)
|
25
|
+
throw @again
|
26
|
+
end
|
27
|
+
|
28
|
+
def leave(*args)
|
29
|
+
case args.size
|
30
|
+
when 0
|
31
|
+
throw @leave
|
32
|
+
when 1
|
33
|
+
throw @leave, args.first
|
34
|
+
else
|
35
|
+
throw @leave, args
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def run
|
40
|
+
catch(@leave) {
|
41
|
+
while true
|
42
|
+
catch(@again) {
|
43
|
+
Cond.send(@with, Hash.new) {
|
44
|
+
throw @leave, @block.call(*@again_args)
|
45
|
+
}
|
46
|
+
}
|
47
|
+
end
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/cond/cond.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
|
2
|
+
#
|
3
|
+
# Resolve errors without unwinding the stack.
|
4
|
+
#
|
5
|
+
module Cond
|
6
|
+
VERSION = "0.3.0"
|
7
|
+
|
8
|
+
class << self
|
9
|
+
include DSL
|
10
|
+
include Wrapping
|
11
|
+
|
12
|
+
#
|
13
|
+
# Register a set of handlers. The given hash is merged with the
|
14
|
+
# set of current handlers.
|
15
|
+
#
|
16
|
+
# When the block exits, the previous set of handlers (if any) are
|
17
|
+
# restored.
|
18
|
+
#
|
19
|
+
def with_handlers(handlers)
|
20
|
+
# note: leave unfactored due to notable yield vs &block performance
|
21
|
+
handlers_stack.push(handlers_stack.last.merge(handlers))
|
22
|
+
begin
|
23
|
+
yield
|
24
|
+
ensure
|
25
|
+
handlers_stack.pop
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# Register a set of restarts. The given hash is merged with the
|
31
|
+
# set of current restarts.
|
32
|
+
#
|
33
|
+
# When the block exits, the previous set of restarts (if any) are
|
34
|
+
# restored.
|
35
|
+
#
|
36
|
+
def with_restarts(restarts)
|
37
|
+
# note: leave unfactored due to notable yield vs &block performance
|
38
|
+
restarts_stack.push(restarts_stack.last.merge(restarts))
|
39
|
+
begin
|
40
|
+
yield
|
41
|
+
ensure
|
42
|
+
restarts_stack.pop
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# A default handler is provided which runs a simple
|
48
|
+
# choose-a-restart input loop when +raise+ is called.
|
49
|
+
#
|
50
|
+
def with_default_handlers
|
51
|
+
# note: leave unfactored due to notable yield vs &block performance
|
52
|
+
with_handlers(defaults.handlers) {
|
53
|
+
yield
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# The current set of restarts which have been registered.
|
59
|
+
#
|
60
|
+
def available_restarts
|
61
|
+
restarts_stack.last
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# Find the closest-matching handler for the given Exception.
|
66
|
+
#
|
67
|
+
def find_handler(target) #:nodoc:
|
68
|
+
find_handler_from(handlers_stack.last, target)
|
69
|
+
end
|
70
|
+
|
71
|
+
def find_handler_from(handlers, target) #:nodoc:
|
72
|
+
handlers.fetch(target) {
|
73
|
+
found = handlers.inject(Array.new) { |acc, (klass, func)|
|
74
|
+
index = target.ancestors.index(klass)
|
75
|
+
if index
|
76
|
+
acc << [index, func]
|
77
|
+
else
|
78
|
+
acc
|
79
|
+
end
|
80
|
+
}.sort_by { |t| t.first }.first
|
81
|
+
found and found[1]
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
def run_code_section(klass, &block) #:nodoc:
|
86
|
+
section = klass.new(&block)
|
87
|
+
Cond.code_section_stack.push(section)
|
88
|
+
begin
|
89
|
+
section.instance_eval { run }
|
90
|
+
ensure
|
91
|
+
Cond.code_section_stack.pop
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def check_context(keyword) #:nodoc:
|
96
|
+
section = Cond.code_section_stack.last
|
97
|
+
case keyword
|
98
|
+
when :restart
|
99
|
+
unless section.is_a? RestartableSection
|
100
|
+
Cond.original_raise(
|
101
|
+
ContextError,
|
102
|
+
"`#{keyword}' called outside of `restartable' block"
|
103
|
+
)
|
104
|
+
end
|
105
|
+
when :handle
|
106
|
+
unless section.is_a? HandlingSection
|
107
|
+
Cond.original_raise(
|
108
|
+
ContextError,
|
109
|
+
"`#{keyword}' called outside of `handling' block"
|
110
|
+
)
|
111
|
+
end
|
112
|
+
when :leave, :again
|
113
|
+
unless section
|
114
|
+
Cond.original_raise(
|
115
|
+
ContextError,
|
116
|
+
"`#{keyword}' called outside of `handling' or `restartable' block"
|
117
|
+
)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
###############################################
|
123
|
+
# original raise
|
124
|
+
|
125
|
+
define_method :original_raise, Kernel.instance_method(:raise)
|
126
|
+
|
127
|
+
######################################################################
|
128
|
+
# data -- all data is per-thread and fetched from the singleton class
|
129
|
+
#
|
130
|
+
# Cond.defaults contains the default handlers. To replace it,
|
131
|
+
# call
|
132
|
+
#
|
133
|
+
# Cond.defaults.clear(&block)
|
134
|
+
#
|
135
|
+
# where &block creates a new instance of your class which
|
136
|
+
# implements the method 'handlers'.
|
137
|
+
#
|
138
|
+
# Note that &block should return a brand new instance. Otherwise
|
139
|
+
# the returned object will be shared across threads.
|
140
|
+
#
|
141
|
+
|
142
|
+
stack_0 = lambda { Array.new }
|
143
|
+
stack_1 = lambda { Array.new.push(Hash.new) }
|
144
|
+
defaults = lambda { Defaults.new }
|
145
|
+
{
|
146
|
+
:code_section_stack => stack_0,
|
147
|
+
:exception_stack => stack_0,
|
148
|
+
:handlers_stack => stack_1,
|
149
|
+
:restarts_stack => stack_1,
|
150
|
+
:defaults => defaults,
|
151
|
+
}.each_pair { |name, create|
|
152
|
+
include ThreadLocal.reader_module(name, &create)
|
153
|
+
}
|
154
|
+
|
155
|
+
include ThreadLocal.accessor_module(:reraise_count) { 0 }
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
|
@@ -0,0 +1,73 @@
|
|
1
|
+
|
2
|
+
module Cond
|
3
|
+
class Defaults
|
4
|
+
def initialize
|
5
|
+
@stream_in = STDIN
|
6
|
+
@stream_out = STDERR
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :stream_in, :stream_out
|
10
|
+
|
11
|
+
def handlers
|
12
|
+
{
|
13
|
+
Exception => method(:handler)
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def handler(exception)
|
18
|
+
stream_out.puts exception.backtrace.last
|
19
|
+
|
20
|
+
if exception.respond_to? :message
|
21
|
+
stream_out.puts exception.message, ""
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# Show restarts in the order they appear on the stack (via
|
26
|
+
# partial differences).
|
27
|
+
#
|
28
|
+
# grr:
|
29
|
+
#
|
30
|
+
# % ruby186 -ve 'p({:x => 33}.eql?({:x => 33}))'
|
31
|
+
# ruby 1.8.6 (2009-03-10 patchlevel 362) [i686-darwin9.6.0]
|
32
|
+
# false
|
33
|
+
#
|
34
|
+
# % ruby187 -ve 'p({:x => 33}.eql?({:x => 33}))'
|
35
|
+
# ruby 1.8.7 (2009-03-09 patchlevel 150) [i686-darwin9.6.0]
|
36
|
+
# true
|
37
|
+
#
|
38
|
+
stack_arrays = Cond.restarts_stack.map { |level|
|
39
|
+
level.to_a.sort_by { |t| t.first.to_s }
|
40
|
+
}
|
41
|
+
restart_names = stack_arrays.to_enum(:each_with_index).map {
|
42
|
+
|level, index|
|
43
|
+
if index == 0
|
44
|
+
level
|
45
|
+
else
|
46
|
+
level - stack_arrays[index - 1]
|
47
|
+
end
|
48
|
+
}.map { |level| level.map { |t| t.first } }.flatten
|
49
|
+
|
50
|
+
restart_index = loop {
|
51
|
+
restart_names.each_with_index { |name, index|
|
52
|
+
func = Cond.available_restarts[name]
|
53
|
+
message = (
|
54
|
+
if func.respond_to?(:message) and func.message != ""
|
55
|
+
func.message + " "
|
56
|
+
else
|
57
|
+
""
|
58
|
+
end
|
59
|
+
)
|
60
|
+
stream_out.printf("%3d: %s(%s)\n", index, message, name.inspect)
|
61
|
+
}
|
62
|
+
stream_out.print "Choose number: "
|
63
|
+
stream_out.flush
|
64
|
+
input = stream_in.readline.strip
|
65
|
+
if input =~ %r!\A\d+\Z! and
|
66
|
+
(0...restart_names.size).include?(input.to_i)
|
67
|
+
break input.to_i
|
68
|
+
end
|
69
|
+
}
|
70
|
+
Cond.invoke_restart(restart_names[restart_index])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/cond/dsl.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
|
2
|
+
module Cond
|
3
|
+
module DSL
|
4
|
+
#
|
5
|
+
# Begin a handling block. Inside this block, a matching handler
|
6
|
+
# gets called when +raise+ gets called.
|
7
|
+
#
|
8
|
+
def handling(&block)
|
9
|
+
Cond.run_code_section(HandlingSection, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
#
|
13
|
+
# Begin a restartable block. A handler may transfer control to one
|
14
|
+
# of the restarts in this block.
|
15
|
+
#
|
16
|
+
def restartable(&block)
|
17
|
+
Cond.run_code_section(RestartableSection, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Define a handler.
|
22
|
+
#
|
23
|
+
# The exception instance is passed to the block.
|
24
|
+
#
|
25
|
+
def handle(arg, message = "", &block)
|
26
|
+
Cond.check_context(:handle)
|
27
|
+
Cond.code_section_stack.last.handle(arg, message, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Define a restart.
|
32
|
+
#
|
33
|
+
# When a handler calls invoke_restart, it may pass additional
|
34
|
+
# arguments which are in turn passed to &block.
|
35
|
+
#
|
36
|
+
def restart(arg, message = "", &block)
|
37
|
+
Cond.check_context(:restart)
|
38
|
+
Cond.code_section_stack.last.restart(arg, message, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Leave the current handling or restartable block, optionally
|
43
|
+
# providing a value for the block.
|
44
|
+
#
|
45
|
+
# The semantics are the same as 'return'. When given multiple
|
46
|
+
# arguments, it returns an array. When given one argument, it
|
47
|
+
# returns only that argument (not an array).
|
48
|
+
#
|
49
|
+
def leave(*args)
|
50
|
+
Cond.check_context(:leave)
|
51
|
+
Cond.code_section_stack.last.leave(*args)
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Run the handling or restartable block again.
|
56
|
+
#
|
57
|
+
# Optionally pass arguments which are given to the block.
|
58
|
+
#
|
59
|
+
def again(*args)
|
60
|
+
Cond.check_context(:again)
|
61
|
+
Cond.code_section_stack.last.again(*args)
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# Call a restart from a handler; optionally pass it some arguments.
|
66
|
+
#
|
67
|
+
def invoke_restart(name, *args, &block)
|
68
|
+
Cond.available_restarts.fetch(name) {
|
69
|
+
raise NoRestartError,
|
70
|
+
"Did not find `#{name.inspect}' in available restarts"
|
71
|
+
}.call(*args, &block)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/cond/error.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
module Cond
|
3
|
+
class Error < RuntimeError
|
4
|
+
end
|
5
|
+
|
6
|
+
#
|
7
|
+
# Cond.invoke_restart was called with an unknown restart.
|
8
|
+
#
|
9
|
+
class NoRestartError < Error
|
10
|
+
end
|
11
|
+
|
12
|
+
#
|
13
|
+
# `handle', `restart', `leave', or `again' called out of context.
|
14
|
+
#
|
15
|
+
class ContextError < Error
|
16
|
+
end
|
17
|
+
end
|
data/lib/cond/handler.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
|
2
|
+
module Kernel
|
3
|
+
remove_method :raise
|
4
|
+
def raise(*args)
|
5
|
+
if Cond.handlers_stack.last.empty?
|
6
|
+
# not using Cond
|
7
|
+
Cond.original_raise(*args)
|
8
|
+
else
|
9
|
+
last_exception, current_handler = Cond.exception_stack.last
|
10
|
+
exception = (
|
11
|
+
if last_exception and args.empty?
|
12
|
+
last_exception
|
13
|
+
else
|
14
|
+
begin
|
15
|
+
Cond.original_raise(*args)
|
16
|
+
rescue Exception => e
|
17
|
+
e
|
18
|
+
end
|
19
|
+
end
|
20
|
+
)
|
21
|
+
if current_handler
|
22
|
+
# inside a handler
|
23
|
+
handler = loop {
|
24
|
+
Cond.reraise_count += 1
|
25
|
+
handlers = Cond.handlers_stack[-1 - Cond.reraise_count]
|
26
|
+
if handlers.nil?
|
27
|
+
break nil
|
28
|
+
end
|
29
|
+
found = Cond.find_handler_from(handlers, exception.class)
|
30
|
+
if found and found != current_handler
|
31
|
+
break found
|
32
|
+
end
|
33
|
+
}
|
34
|
+
if handler
|
35
|
+
handler.call(exception)
|
36
|
+
else
|
37
|
+
Cond.reraise_count = 0
|
38
|
+
Cond.original_raise(exception)
|
39
|
+
end
|
40
|
+
else
|
41
|
+
# not inside a handler
|
42
|
+
Cond.reraise_count = 0
|
43
|
+
handler = Cond.find_handler(exception.class)
|
44
|
+
if handler
|
45
|
+
Cond.exception_stack.push([exception, handler])
|
46
|
+
begin
|
47
|
+
handler.call(exception)
|
48
|
+
ensure
|
49
|
+
Cond.exception_stack.pop
|
50
|
+
end
|
51
|
+
else
|
52
|
+
Cond.original_raise(exception)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
remove_method :fail
|
58
|
+
alias_method :fail, :raise
|
59
|
+
end
|