cond 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/CHANGES.rdoc +12 -0
  2. data/MANIFEST +39 -0
  3. data/{README → README.rdoc} +23 -28
  4. data/Rakefile +23 -180
  5. data/devel/jumpstart.rb +970 -0
  6. data/install.rb +2 -3
  7. data/lib/cond.rb +38 -453
  8. data/lib/cond/code_section.rb +51 -0
  9. data/lib/cond/cond.rb +159 -0
  10. data/lib/cond/defaults.rb +73 -0
  11. data/lib/cond/dsl.rb +2 -0
  12. data/lib/cond/dsl_definition.rb +74 -0
  13. data/lib/cond/error.rb +17 -0
  14. data/lib/cond/handler.rb +10 -0
  15. data/lib/cond/handling_section.rb +12 -0
  16. data/lib/cond/kernel_raise.rb +59 -0
  17. data/lib/cond/message_proc.rb +15 -0
  18. data/lib/cond/restart.rb +10 -0
  19. data/lib/cond/restartable_section.rb +12 -0
  20. data/lib/cond/symbol_generator.rb +41 -0
  21. data/lib/cond/thread_local.rb +71 -0
  22. data/lib/cond/wrapping.rb +45 -0
  23. data/readmes/restarts.rb +1 -2
  24. data/readmes/seibel_pcl.rb +1 -2
  25. data/{examples/bad_example.rb → spec/bad_spec.rb} +2 -2
  26. data/spec/basic_spec.rb +2 -2
  27. data/{examples/calc_example.rb → spec/calc_spec.rb} +2 -2
  28. data/spec/{common.rb → cond_spec_base.rb} +3 -20
  29. data/spec/error_spec.rb +2 -2
  30. data/spec/leave_again_spec.rb +10 -10
  31. data/spec/matching_spec.rb +1 -1
  32. data/spec/raise_spec.rb +1 -1
  33. data/spec/readme_spec.rb +10 -0
  34. data/spec/reraise_spec.rb +2 -2
  35. data/{examples/restarts_example.rb → spec/restarts_spec.rb} +7 -4
  36. data/{examples/seibel_example.rb → spec/seibel_spec.rb} +4 -6
  37. data/spec/symbols_spec.rb +2 -2
  38. data/spec/thread_local_spec.rb +8 -8
  39. data/spec/wrapping_spec.rb +2 -2
  40. metadata +110 -42
  41. data/cond.gemspec +0 -37
  42. data/examples/readme_example.rb +0 -27
  43. data/lib/cond/cond_private/defaults.rb +0 -78
  44. data/lib/cond/cond_private/symbol_generator.rb +0 -45
  45. data/lib/cond/cond_private/thread_local.rb +0 -77
  46. data/spec/specs_spec.rb +0 -16
  47. data/support/quix/ruby.rb +0 -51
  48. data/support/quix/simple_installer.rb +0 -88
data/install.rb CHANGED
@@ -1,3 +1,2 @@
1
- $LOAD_PATH.unshift "./support"
2
- require 'quix/simple_installer'
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
- # Resolve errors without unwinding the stack.
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
- 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
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'