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
@@ -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,2 @@
1
+ require 'cond'
2
+ include Cond::DSL
@@ -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
@@ -0,0 +1,10 @@
1
+
2
+ module Cond
3
+ #
4
+ # A handler. Use of this class is optional: you could pass lambdas
5
+ # to Cond.with_handlers, but you'll miss the description string
6
+ # shown by whichever tools might use it (currently none).
7
+ #
8
+ class Handler < MessageProc
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+
2
+ module Cond
3
+ class HandlingSection < CodeSection #:nodoc:
4
+ def initialize(&block)
5
+ super(:with_handlers, &block)
6
+ end
7
+
8
+ def handle(sym, message, &block)
9
+ Cond.handlers_stack.last[sym] = Handler.new(message, &block)
10
+ end
11
+ end
12
+ end
@@ -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