cond 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,78 @@
|
|
1
|
+
|
2
|
+
require 'cond/cond_private/symbol_generator'
|
3
|
+
require 'enumerator' if RUBY_VERSION <= "1.8.6"
|
4
|
+
|
5
|
+
module Cond
|
6
|
+
module CondPrivate
|
7
|
+
class Defaults
|
8
|
+
def initialize
|
9
|
+
@stream_in = STDIN
|
10
|
+
@stream_out = STDERR
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :stream_in, :stream_out
|
14
|
+
|
15
|
+
def handlers
|
16
|
+
{
|
17
|
+
Exception => method(:handler)
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def handler(exception)
|
22
|
+
stream_out.puts exception.backtrace.last
|
23
|
+
|
24
|
+
if exception.respond_to? :message
|
25
|
+
stream_out.puts exception.message, ""
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# Show restarts in the order they appear on the stack (via
|
30
|
+
# partial differences).
|
31
|
+
#
|
32
|
+
# grr:
|
33
|
+
#
|
34
|
+
# % ruby186 -ve 'p({:x => 33}.eql?({:x => 33}))'
|
35
|
+
# ruby 1.8.6 (2009-03-10 patchlevel 362) [i686-darwin9.6.0]
|
36
|
+
# false
|
37
|
+
#
|
38
|
+
# % ruby187 -ve 'p({:x => 33}.eql?({:x => 33}))'
|
39
|
+
# ruby 1.8.7 (2009-03-09 patchlevel 150) [i686-darwin9.6.0]
|
40
|
+
# true
|
41
|
+
#
|
42
|
+
stack_arrays = Cond.restarts_stack.map { |level|
|
43
|
+
level.to_a.sort_by { |t| t.first.to_s }
|
44
|
+
}
|
45
|
+
restart_names = stack_arrays.to_enum(:each_with_index).map {
|
46
|
+
|level, index|
|
47
|
+
if index == 0
|
48
|
+
level
|
49
|
+
else
|
50
|
+
level - stack_arrays[index - 1]
|
51
|
+
end
|
52
|
+
}.map { |level| level.map { |t| t.first } }.flatten
|
53
|
+
|
54
|
+
restart_index = loop {
|
55
|
+
restart_names.each_with_index { |name, index|
|
56
|
+
func = Cond.available_restarts[name]
|
57
|
+
message = (
|
58
|
+
if func.respond_to?(:message) and func.message != ""
|
59
|
+
func.message + " "
|
60
|
+
else
|
61
|
+
""
|
62
|
+
end
|
63
|
+
)
|
64
|
+
stream_out.printf("%3d: %s(%s)\n", index, message, name.inspect)
|
65
|
+
}
|
66
|
+
stream_out.print "Choose number: "
|
67
|
+
stream_out.flush
|
68
|
+
input = stream_in.readline.strip
|
69
|
+
if input =~ %r!\A\d+\Z! and
|
70
|
+
(0...restart_names.size).include?(input.to_i)
|
71
|
+
break input.to_i
|
72
|
+
end
|
73
|
+
}
|
74
|
+
Cond.invoke_restart(restart_names[restart_index])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module Cond
|
5
|
+
module CondPrivate
|
6
|
+
module SymbolGenerator
|
7
|
+
@count = 'a'
|
8
|
+
@mutex = Mutex.new
|
9
|
+
@recycled = []
|
10
|
+
@object_id_to_sym_list = Hash.new
|
11
|
+
@finalizer = lambda { |id|
|
12
|
+
recycle(@object_id_to_sym_list.delete(id))
|
13
|
+
}
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def gensym
|
17
|
+
@mutex.synchronize {
|
18
|
+
if @recycled.empty?
|
19
|
+
@count.succ!
|
20
|
+
:"|#{@count}"
|
21
|
+
else
|
22
|
+
@recycled.shift
|
23
|
+
end
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def recycle(syms)
|
28
|
+
@mutex.synchronize {
|
29
|
+
@recycled.concat(syms)
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def track(object, syms)
|
34
|
+
@mutex.synchronize {
|
35
|
+
@object_id_to_sym_list[object.object_id] = syms.dup
|
36
|
+
ObjectSpace.define_finalizer(object, @finalizer)
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
define_method :gensym, &method(:gensym)
|
42
|
+
private :gensym
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
|
2
|
+
require 'thread'
|
3
|
+
require 'cond/cond_private/symbol_generator'
|
4
|
+
|
5
|
+
module Cond
|
6
|
+
module CondPrivate
|
7
|
+
#
|
8
|
+
# Thread-local variable.
|
9
|
+
#
|
10
|
+
class ThreadLocal
|
11
|
+
include SymbolGenerator
|
12
|
+
|
13
|
+
#
|
14
|
+
# If +value+ is called before +value=+ then the result of
|
15
|
+
# &default is used.
|
16
|
+
#
|
17
|
+
# &default normally creates a new object, otherwise the returned
|
18
|
+
# object will be shared across threads.
|
19
|
+
#
|
20
|
+
def initialize(&default)
|
21
|
+
@name = gensym
|
22
|
+
@accessed = gensym
|
23
|
+
@default = default
|
24
|
+
SymbolGenerator.track(self, [@name, @accessed])
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# Reset to just-initialized state for all threads.
|
29
|
+
#
|
30
|
+
def clear(&default)
|
31
|
+
Thread.exclusive {
|
32
|
+
@default = default
|
33
|
+
Thread.list.each { |thread|
|
34
|
+
thread[@accessed] = nil
|
35
|
+
thread[@name] = nil
|
36
|
+
}
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def value
|
41
|
+
unless Thread.current[@accessed]
|
42
|
+
if @default
|
43
|
+
Thread.current[@name] = @default.call
|
44
|
+
end
|
45
|
+
Thread.current[@accessed] = true
|
46
|
+
end
|
47
|
+
Thread.current[@name]
|
48
|
+
end
|
49
|
+
|
50
|
+
def value=(value)
|
51
|
+
Thread.current[@accessed] = true
|
52
|
+
Thread.current[@name] = value
|
53
|
+
end
|
54
|
+
|
55
|
+
class << self
|
56
|
+
def accessor_module(name, subclass = self, &block)
|
57
|
+
var = subclass.new(&block)
|
58
|
+
Module.new {
|
59
|
+
define_method(name) {
|
60
|
+
var.value
|
61
|
+
}
|
62
|
+
define_method("#{name}=") { |value|
|
63
|
+
var.value = value
|
64
|
+
}
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
def reader_module(name, subclass = self, &block)
|
69
|
+
accessor_module(name, subclass, &block).instance_eval {
|
70
|
+
remove_method "#{name}="
|
71
|
+
self
|
72
|
+
}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/readmes/restarts.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
|
2
|
+
|
3
|
+
#
|
4
|
+
# http://c2.com/cgi/wiki?LispRestartExample
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'pp'
|
8
|
+
require 'cond'
|
9
|
+
include Cond
|
10
|
+
|
11
|
+
class RestartableFetchError < RuntimeError
|
12
|
+
end
|
13
|
+
|
14
|
+
def read_new_value(what)
|
15
|
+
print("Enter a new #{what}: ")
|
16
|
+
eval($stdin.readline.chomp)
|
17
|
+
end
|
18
|
+
|
19
|
+
def restartable_fetch(hash, key, default = nil)
|
20
|
+
restartable do
|
21
|
+
restart :continue, "Return not having found the value." do
|
22
|
+
return default
|
23
|
+
end
|
24
|
+
restart :try_again, "Try getting the key from the hash again." do
|
25
|
+
again
|
26
|
+
end
|
27
|
+
restart :use_new_key, "Use a new key." do
|
28
|
+
key = read_new_value("key")
|
29
|
+
again
|
30
|
+
end
|
31
|
+
restart :use_new_hash, "Use a new hash." do
|
32
|
+
hash = read_new_value("hash")
|
33
|
+
again
|
34
|
+
end
|
35
|
+
hash.fetch(key) {
|
36
|
+
raise RestartableFetchError,
|
37
|
+
"Error getting #{key.inspect} from:\n#{hash.pretty_inspect}"
|
38
|
+
}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
fruits_and_vegetables = Hash[*%w[
|
43
|
+
apple fruit
|
44
|
+
orange fruit
|
45
|
+
lettuce vegetable
|
46
|
+
tomato depends_on_who_you_ask
|
47
|
+
]]
|
48
|
+
|
49
|
+
Cond.with_default_handlers {
|
50
|
+
puts("value: " + restartable_fetch(fruits_and_vegetables, "mango").inspect)
|
51
|
+
}
|
52
|
+
|
53
|
+
# % ruby readmes/restarts.rb
|
54
|
+
# readmes/restarts.rb:49:in `<main>'
|
55
|
+
# Error getting "mango" from:
|
56
|
+
# {"apple"=>"fruit",
|
57
|
+
# "orange"=>"fruit",
|
58
|
+
# "lettuce"=>"vegetable",
|
59
|
+
# "tomato"=>"depends_on_who_you_ask"}
|
60
|
+
#
|
61
|
+
# 0: Return not having found the value. (:continue)
|
62
|
+
# 1: Try getting the key from the hash again. (:try_again)
|
63
|
+
# 2: Use a new hash. (:use_new_hash)
|
64
|
+
# 3: Use a new key. (:use_new_key)
|
65
|
+
# Choose number: 3
|
66
|
+
# Enter a new key: "apple"
|
67
|
+
# value: "fruit"
|
68
|
+
#
|
69
|
+
# % ruby readmes/restarts.rb
|
70
|
+
# readmes/restarts.rb:49:in `<main>'
|
71
|
+
# Error getting "mango" from:
|
72
|
+
# {"apple"=>"fruit",
|
73
|
+
# "orange"=>"fruit",
|
74
|
+
# "lettuce"=>"vegetable",
|
75
|
+
# "tomato"=>"depends_on_who_you_ask"}
|
76
|
+
#
|
77
|
+
# 0: Return not having found the value. (:continue)
|
78
|
+
# 1: Try getting the key from the hash again. (:try_again)
|
79
|
+
# 2: Use a new hash. (:use_new_hash)
|
80
|
+
# 3: Use a new key. (:use_new_key)
|
81
|
+
# Choose number: 2
|
82
|
+
# Enter a new hash: { "mango" => "mangoish fruit" }
|
83
|
+
# value: "mangoish fruit"
|
84
|
+
|
@@ -0,0 +1,129 @@
|
|
1
|
+
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
|
2
|
+
|
3
|
+
#
|
4
|
+
# http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html
|
5
|
+
#
|
6
|
+
|
7
|
+
#
|
8
|
+
# Note:
|
9
|
+
# This file is ruby-1.8.7+ only. For 1.8.6, require 'enumerator' and
|
10
|
+
# change input.each_line to input.to_enum(:each_line).
|
11
|
+
#
|
12
|
+
|
13
|
+
##########################################################
|
14
|
+
# setup
|
15
|
+
|
16
|
+
require 'cond'
|
17
|
+
require 'time'
|
18
|
+
include Cond
|
19
|
+
|
20
|
+
class MalformedLogEntryError < StandardError
|
21
|
+
end
|
22
|
+
|
23
|
+
LOG_FILE = "log.txt"
|
24
|
+
|
25
|
+
LOG_FILE_CONTENTS = <<EOS
|
26
|
+
2007-01-12 05:20:03|event w
|
27
|
+
2008-02-22 11:11:41|event x
|
28
|
+
2008-09-04 15:30:43|event y
|
29
|
+
I like pancakes
|
30
|
+
2009-02-12 07:29:33|event z
|
31
|
+
EOS
|
32
|
+
|
33
|
+
File.open(LOG_FILE, "w") { |out|
|
34
|
+
out.print LOG_FILE_CONTENTS
|
35
|
+
}
|
36
|
+
|
37
|
+
END { File.unlink(LOG_FILE) }
|
38
|
+
|
39
|
+
def parse_log_entry(text)
|
40
|
+
time, event = text.split("|")
|
41
|
+
if event
|
42
|
+
return Time.parse(time), event
|
43
|
+
else
|
44
|
+
raise MalformedLogEntryError
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def find_all_logs
|
49
|
+
[LOG_FILE]
|
50
|
+
end
|
51
|
+
|
52
|
+
def analyze_log(log)
|
53
|
+
parse_log_file(log)
|
54
|
+
end
|
55
|
+
|
56
|
+
##########################################################
|
57
|
+
# book examples
|
58
|
+
#
|
59
|
+
|
60
|
+
#
|
61
|
+
# (defun parse-log-file (file)
|
62
|
+
# (with-open-file (in file :direction :input)
|
63
|
+
# (loop for text = (read-line in nil nil) while text
|
64
|
+
# for entry = (handler-case (parse-log-entry text)
|
65
|
+
# (malformed-log-entry-error () nil))
|
66
|
+
# when entry collect it)))
|
67
|
+
#
|
68
|
+
def parse_log_file0(file)
|
69
|
+
File.open(file) { |input|
|
70
|
+
input.each_line.inject(Array.new) { |acc, text|
|
71
|
+
entry = handling do
|
72
|
+
handle MalformedLogEntryError do
|
73
|
+
end
|
74
|
+
parse_log_entry(text)
|
75
|
+
end
|
76
|
+
entry ? acc << entry : acc
|
77
|
+
}
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
parse_log_file0(LOG_FILE)
|
82
|
+
|
83
|
+
#
|
84
|
+
# (defun parse-log-file (file)
|
85
|
+
# (with-open-file (in file :direction :input)
|
86
|
+
# (loop for text = (read-line in nil nil) while text
|
87
|
+
# for entry = (restart-case (parse-log-entry text)
|
88
|
+
# (skip-log-entry () nil))
|
89
|
+
# when entry collect it)))
|
90
|
+
#
|
91
|
+
def parse_log_file(file)
|
92
|
+
File.open(file) { |input|
|
93
|
+
input.each_line.inject(Array.new) { |acc, text|
|
94
|
+
entry = restartable do
|
95
|
+
restart :skip_log_entry do
|
96
|
+
leave
|
97
|
+
end
|
98
|
+
parse_log_entry(text)
|
99
|
+
end
|
100
|
+
entry ? acc << entry : acc
|
101
|
+
}
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
Cond.with_default_handlers {
|
106
|
+
parse_log_file(LOG_FILE)
|
107
|
+
}
|
108
|
+
|
109
|
+
#
|
110
|
+
# (defun log-analyzer ()
|
111
|
+
# (handler-bind ((malformed-log-entry-error
|
112
|
+
# #'(lambda (c)
|
113
|
+
# (invoke-restart 'skip-log-entry))))
|
114
|
+
# (dolist (log (find-all-logs))
|
115
|
+
# (analyze-log log))))
|
116
|
+
#
|
117
|
+
def log_analyzer
|
118
|
+
handling do
|
119
|
+
handle MalformedLogEntryError do
|
120
|
+
invoke_restart :skip_log_entry
|
121
|
+
end
|
122
|
+
find_all_logs.each { |log|
|
123
|
+
analyze_log(log)
|
124
|
+
}
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
log_analyzer
|
129
|
+
|
data/spec/basic_spec.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/common"
|
2
|
+
|
3
|
+
class ExampleError < RuntimeError
|
4
|
+
end
|
5
|
+
|
6
|
+
include Cond
|
7
|
+
|
8
|
+
describe "basic handler/restart functionality" do
|
9
|
+
it "should work using the raw form" do
|
10
|
+
memo = []
|
11
|
+
handlers = {
|
12
|
+
ExampleError => lambda { |exception|
|
13
|
+
memo.push :handler
|
14
|
+
invoke_restart(:example_restart, :x, :y)
|
15
|
+
}
|
16
|
+
}
|
17
|
+
restarts = {
|
18
|
+
:example_restart => lambda { |*args|
|
19
|
+
memo.push :restart
|
20
|
+
memo.push args
|
21
|
+
}
|
22
|
+
}
|
23
|
+
f = lambda {
|
24
|
+
memo.push :f
|
25
|
+
Cond.with_restarts(restarts) {
|
26
|
+
memo.push :raise
|
27
|
+
raise ExampleError
|
28
|
+
}
|
29
|
+
}
|
30
|
+
memo.push :first
|
31
|
+
Cond.with_handlers(handlers) {
|
32
|
+
f.call
|
33
|
+
}
|
34
|
+
memo.push :last
|
35
|
+
|
36
|
+
memo.should == [:first, :f, :raise, :handler, :restart, [:x, :y], :last]
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should work using the shiny form" do
|
40
|
+
memo = []
|
41
|
+
|
42
|
+
f = lambda {
|
43
|
+
memo.push :f
|
44
|
+
restartable do
|
45
|
+
restart :example_restart do |*args|
|
46
|
+
memo.push :restart
|
47
|
+
memo.push args
|
48
|
+
end
|
49
|
+
memo.push :raise
|
50
|
+
raise ExampleError
|
51
|
+
end
|
52
|
+
}
|
53
|
+
|
54
|
+
memo.push :first
|
55
|
+
handling do
|
56
|
+
handle ExampleError do
|
57
|
+
memo.push :handler
|
58
|
+
invoke_restart :example_restart, :x, :y
|
59
|
+
end
|
60
|
+
f.call
|
61
|
+
end
|
62
|
+
memo.push :last
|
63
|
+
|
64
|
+
memo.should == [:first, :f, :raise, :handler, :restart, [:x, :y], :last]
|
65
|
+
end
|
66
|
+
end
|