cond 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
+
@@ -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