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.
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