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