cond 0.2.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/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
|