conditions 0.0.2.alpha
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/.gitignore +4 -0
- data/Gemfile +4 -0
- data/LICENSE +24 -0
- data/README.rdoc +37 -0
- data/Rakefile +2 -0
- data/conditions.gemspec +19 -0
- data/example/another-log +6 -0
- data/example/example.rb +72 -0
- data/example/log +5 -0
- data/example/newfile +0 -0
- data/lib/conditions.rb +16 -0
- data/lib/conditions/definitions/defaults.rb +38 -0
- data/lib/conditions/handles.rb +143 -0
- data/lib/conditions/lib/exceptions.rb +35 -0
- data/lib/conditions/lib/helpers.rb +226 -0
- data/lib/conditions/restarts.rb +76 -0
- data/lib/conditions/signals.rb +95 -0
- data/lib/conditions/version.rb +3 -0
- metadata +72 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Copyright (c) 2011, André Gawron
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
5
|
+
are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
- Redistributions of source code must retain the above copyright notice,
|
|
8
|
+
this list of conditions and the following disclaimer.
|
|
9
|
+
- Redistributions in binary form must reproduce the above copyright notice,
|
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
|
11
|
+
and/or other materials provided with the distribution.
|
|
12
|
+
- The names of its contributors may be used to endorse or promote products
|
|
13
|
+
derived from this software without specific prior written permission.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
16
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
17
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
18
|
+
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
|
19
|
+
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
20
|
+
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
21
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
22
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
|
23
|
+
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
|
24
|
+
OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.rdoc
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
= Conditions
|
|
2
|
+
|
|
3
|
+
Conditions is some kind of low-level condition-based
|
|
4
|
+
event-handler suitable for different kind of uses.
|
|
5
|
+
Typical use is error handling. it's ported from LISP's Condition System:
|
|
6
|
+
|
|
7
|
+
http://gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html
|
|
8
|
+
|
|
9
|
+
THIS IS HIGHLY ALPHA! DO NOT USE IN PRODUCTION!
|
|
10
|
+
(but syntax is most likely not going to change)
|
|
11
|
+
|
|
12
|
+
For more information, read covering blog posts on:
|
|
13
|
+
|
|
14
|
+
http://www.andre-gawron.de/123/the-diversity-of-error-handling
|
|
15
|
+
|
|
16
|
+
http://www.andre-gawron.de/187/road-to-condition
|
|
17
|
+
|
|
18
|
+
== Patching
|
|
19
|
+
|
|
20
|
+
It's currently extending the Object class. Why? Kinda easy.
|
|
21
|
+
I don't want to write Conditions::handle (etc).
|
|
22
|
+
|
|
23
|
+
The Object class is patched as soon as Conditions are required.
|
|
24
|
+
I'm lookin into actually check before patching if there are other
|
|
25
|
+
methods already in the Object class and probably not allow
|
|
26
|
+
patching then. But that's for the future, it's alpha, right?
|
|
27
|
+
|
|
28
|
+
== I'd like to patch and / or help maintain Conditions. How can I?
|
|
29
|
+
|
|
30
|
+
Are you sure? Ok, feel free to ...
|
|
31
|
+
|
|
32
|
+
* Fork the project: http://github.com/melkon/conditions
|
|
33
|
+
* Make your feature addition or bug fix.
|
|
34
|
+
|
|
35
|
+
== Copyright
|
|
36
|
+
|
|
37
|
+
Copyright (c) 2011 André Gawron. See LICENSE for details.
|
data/Rakefile
ADDED
data/conditions.gemspec
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
2
|
+
require "conditions/version"
|
|
3
|
+
|
|
4
|
+
Gem::Specification.new do |s|
|
|
5
|
+
s.name = "conditions"
|
|
6
|
+
s.version = Conditions::VERSION
|
|
7
|
+
s.platform = Gem::Platform::RUBY
|
|
8
|
+
s.authors = ["André Gawron"]
|
|
9
|
+
s.email = ["andre@ziemek.de"]
|
|
10
|
+
s.homepage = "https://github.com/melkon/conditions"
|
|
11
|
+
s.summary = %q{Implements the Lisp's condition system in Ruby}
|
|
12
|
+
s.description = %q{Implements the Lisp's condition system in Ruby}
|
|
13
|
+
s.license = 'BSD'
|
|
14
|
+
|
|
15
|
+
s.files = `git ls-files`.split("\n")
|
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
18
|
+
s.require_paths = ["lib"]
|
|
19
|
+
end
|
data/example/example.rb
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require "conditions"
|
|
2
|
+
|
|
3
|
+
class MalformedLogEntryError < Condition ; end
|
|
4
|
+
|
|
5
|
+
bind :NoticeSignaled => lambda { invoke :Suppress}
|
|
6
|
+
|
|
7
|
+
p (bind :NoticeSignaled => lambda { invoke :Suppress } do
|
|
8
|
+
"block value is returning!"
|
|
9
|
+
end)
|
|
10
|
+
|
|
11
|
+
def parse_log_entry text
|
|
12
|
+
|
|
13
|
+
if !well_formed? text then
|
|
14
|
+
|
|
15
|
+
restart :UseValue => proc { |value| text = value },
|
|
16
|
+
:ReparseEntry => proc { |fixed| text = parse_log_entry fixed } do
|
|
17
|
+
|
|
18
|
+
error MalformedLogEntryError
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
text
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def parse_log_file file
|
|
29
|
+
|
|
30
|
+
File.open(file).each do |line|
|
|
31
|
+
|
|
32
|
+
entry = (restart :SkipLogEntry => lambda { nil } do
|
|
33
|
+
parse_log_entry line
|
|
34
|
+
end)
|
|
35
|
+
|
|
36
|
+
yield entry if entry
|
|
37
|
+
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def log_analyzer
|
|
43
|
+
|
|
44
|
+
bind :ConditionNotDefined => lambda { invoke :Define },
|
|
45
|
+
:MalformedLogEntryError => lambda { invoke :UseValue, "failed\n" } do
|
|
46
|
+
find_logs do |log|
|
|
47
|
+
analyze_log log
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def analyze_log log
|
|
54
|
+
parse_log_file log do |entry|
|
|
55
|
+
analyze_entry entry
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def well_formed? text
|
|
60
|
+
"yes\n" == text ? true : false
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def analyze_entry entry
|
|
64
|
+
p entry
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def find_logs
|
|
68
|
+
yield "./log"
|
|
69
|
+
yield "./another-log"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
log_analyzer
|
data/example/newfile
ADDED
|
File without changes
|
data/lib/conditions.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# main api
|
|
2
|
+
require "conditions/signals"
|
|
3
|
+
require "conditions/handles"
|
|
4
|
+
require "conditions/restarts"
|
|
5
|
+
|
|
6
|
+
# base condition classes
|
|
7
|
+
require "conditions/definitions/defaults"
|
|
8
|
+
|
|
9
|
+
# helper functions and exception definitions
|
|
10
|
+
require "conditions/lib/helpers"
|
|
11
|
+
require "conditions/lib/exceptions"
|
|
12
|
+
|
|
13
|
+
# remove this and the puppy will cry
|
|
14
|
+
class Object
|
|
15
|
+
include Conditions
|
|
16
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
class Condition
|
|
2
|
+
|
|
3
|
+
attr_reader :dynamic, :trace, :message, :restarts
|
|
4
|
+
|
|
5
|
+
def initialize message = nil
|
|
6
|
+
|
|
7
|
+
@message = message
|
|
8
|
+
|
|
9
|
+
@trace = Kernel.caller
|
|
10
|
+
@dynamic = false
|
|
11
|
+
@restarts = Utils::Handler::get_restarts
|
|
12
|
+
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class ConditionDynamic < Condition
|
|
18
|
+
|
|
19
|
+
def initialize *params
|
|
20
|
+
|
|
21
|
+
super params
|
|
22
|
+
|
|
23
|
+
@params = params
|
|
24
|
+
@dynamic = true
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def get key
|
|
29
|
+
@params[key]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class DynamicConditionCreation < Condition ; end
|
|
35
|
+
class ConditionNotDefined < Condition ; end
|
|
36
|
+
class NoDynamicConditionAllowed < Condition ; end
|
|
37
|
+
|
|
38
|
+
class NoticeSignaled < Condition ; end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
module Conditions
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# handles a signaled condition
|
|
5
|
+
#
|
|
6
|
+
# handles a condition signaled by #signal or any other signaling method
|
|
7
|
+
# by registering handlers for given conditions.
|
|
8
|
+
#
|
|
9
|
+
# if a handler matches a condition, it will be executed and after that,
|
|
10
|
+
# the stack will be unwound to the point where the latest #handle registered
|
|
11
|
+
# a handler for given condition returning the value of the executed handler.
|
|
12
|
+
#
|
|
13
|
+
# @param *conditions conditions which shall be catched by #handle,
|
|
14
|
+
# @param &block block which will be executed normally if no condition will be signaled
|
|
15
|
+
#
|
|
16
|
+
# @return return value of &block
|
|
17
|
+
# @return if a condition is catched, the return value of the registered handler
|
|
18
|
+
#
|
|
19
|
+
# @raises ConditionHandled if the catched condition doesnt belong to current block
|
|
20
|
+
#
|
|
21
|
+
# @see #parse_handlers for syntax information on *conditions
|
|
22
|
+
#
|
|
23
|
+
def handle *conditions, &block
|
|
24
|
+
|
|
25
|
+
conditions = Utils::parse_handlers conditions
|
|
26
|
+
|
|
27
|
+
conditions.each do |condition|
|
|
28
|
+
Utils::Handler::set(:condition, condition.merge!(:raise => true))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
value = begin
|
|
32
|
+
block.call
|
|
33
|
+
rescue Exception::ConditionHandled => ex
|
|
34
|
+
|
|
35
|
+
# if condition doesnt belong to this block,
|
|
36
|
+
# continue unwinding the stack
|
|
37
|
+
if !Utils::find_handler(ex.condition[:name], conditions) then
|
|
38
|
+
raise Exception::ConditionHandled, :value => ex.value, :condition => ex.condition
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
ex.value
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
conditions.each do |condition|
|
|
46
|
+
Utils::Handler::unset(:condition, condition)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
value
|
|
50
|
+
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
#
|
|
54
|
+
# binds a handler to a condition
|
|
55
|
+
#
|
|
56
|
+
# #bind works similiar to #handle but instead of unwinding the stack,
|
|
57
|
+
# #bind returns the value of the last called handler.
|
|
58
|
+
#
|
|
59
|
+
# caveat:
|
|
60
|
+
#
|
|
61
|
+
# *if* there's a condition bound by #handle in the upper callstack
|
|
62
|
+
# *and* the same condition is bound by #bind inside of this #handle,
|
|
63
|
+
# the system will unwind the stack to #handle - but the condition's handler
|
|
64
|
+
# bound by #bind will be executed and properly unregistered.
|
|
65
|
+
#
|
|
66
|
+
# @param *conditions conditions which shall be bound by #bind,
|
|
67
|
+
# @param &block block which will be executed normally if no condition will be signaled
|
|
68
|
+
#
|
|
69
|
+
# @return return value of &block
|
|
70
|
+
# @return if a condition is signaled and bound by #bind, it returns value of the last registered handler
|
|
71
|
+
#
|
|
72
|
+
# @see #parse_handlers for syntax information on *conditions
|
|
73
|
+
#
|
|
74
|
+
def bind *conditions, &block
|
|
75
|
+
|
|
76
|
+
conditions = Utils::parse_handlers conditions
|
|
77
|
+
|
|
78
|
+
conditions.each do |condition|
|
|
79
|
+
Utils::Handler::set(:condition, condition.merge!(:raise => false))
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
value = true
|
|
83
|
+
if block_given? then
|
|
84
|
+
|
|
85
|
+
value = begin
|
|
86
|
+
|
|
87
|
+
# tmp save the value of the callback
|
|
88
|
+
# and return it after the conditions were unset
|
|
89
|
+
value = block.call
|
|
90
|
+
|
|
91
|
+
conditions.each do |condition|
|
|
92
|
+
Utils::Handler::unset(:condition, condition)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
value
|
|
96
|
+
|
|
97
|
+
# it is possible that a Condition has a handler registered by #handle
|
|
98
|
+
# and therefore will unwind the stack. but if a handler bound by #bind
|
|
99
|
+
# and #bind is used with a block, the bound handler will not be unregistered.
|
|
100
|
+
# therefore, catch the exception intended for #handle
|
|
101
|
+
# unregister the handler registered by #bind
|
|
102
|
+
# and rethrow the exception again to reach the proper #handle
|
|
103
|
+
rescue Exception::ConditionHandled => ex
|
|
104
|
+
|
|
105
|
+
conditions.each do |condition|
|
|
106
|
+
Utils::Handler::unset(:condition, condition)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
raise Exception::ConditionHandled, :value => ex.value, :condition => ex.condition
|
|
110
|
+
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
value
|
|
116
|
+
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
#
|
|
120
|
+
# unregisters a bound condition
|
|
121
|
+
#
|
|
122
|
+
# if bind is used without a block,
|
|
123
|
+
# #unbind is used to unregister the condition's handler
|
|
124
|
+
#
|
|
125
|
+
# @param *conditions conditions which shall be unregistered by #unbind,
|
|
126
|
+
#
|
|
127
|
+
# @return [Boolean] true
|
|
128
|
+
#
|
|
129
|
+
# @see #parse_handlers for syntax information on *conditions
|
|
130
|
+
#
|
|
131
|
+
def unbind *conditions
|
|
132
|
+
|
|
133
|
+
conditions = Utils::parse_handlers conditions
|
|
134
|
+
|
|
135
|
+
conditions.each do |condition|
|
|
136
|
+
Exception::Handler::unset(:condition, condition)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
true
|
|
140
|
+
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Conditions::Exception
|
|
2
|
+
|
|
3
|
+
class ConditionNotHandledError < StandardError ; end
|
|
4
|
+
class ConditionHandled < StandardError
|
|
5
|
+
|
|
6
|
+
attr_reader :value, :condition
|
|
7
|
+
|
|
8
|
+
def initialize info
|
|
9
|
+
|
|
10
|
+
super info[:value]
|
|
11
|
+
|
|
12
|
+
@value = info[:value]
|
|
13
|
+
@condition = info[:condition]
|
|
14
|
+
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class RestartNotFoundError < StandardError ; end
|
|
20
|
+
class RestartHandled < StandardError
|
|
21
|
+
|
|
22
|
+
attr_reader :value, :restart
|
|
23
|
+
|
|
24
|
+
def initialize info
|
|
25
|
+
|
|
26
|
+
super info[:value]
|
|
27
|
+
|
|
28
|
+
@value = info[:value]
|
|
29
|
+
@restart = info[:restart]
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
module Conditions::Utils
|
|
2
|
+
|
|
3
|
+
class Handler
|
|
4
|
+
|
|
5
|
+
@@types = {:condition => {},
|
|
6
|
+
:restart => {}}
|
|
7
|
+
|
|
8
|
+
#
|
|
9
|
+
# yields every registered condition for given type
|
|
10
|
+
#
|
|
11
|
+
# opts:
|
|
12
|
+
# :reverse => true|false [default: true] LIFO or FIFO of yielded handlers
|
|
13
|
+
#
|
|
14
|
+
# @param [String, Symbol] supported types: :condition, :restart
|
|
15
|
+
# @param [String, Symbol] name of the condition
|
|
16
|
+
# @param [Hash] opts current available: :reverse => true
|
|
17
|
+
#
|
|
18
|
+
def self.get(type, condition_name, opts = {})
|
|
19
|
+
|
|
20
|
+
condition_name = Utils::normalize(condition_name)
|
|
21
|
+
|
|
22
|
+
if (!@@types[type].has_key?(condition_name)) or (@@types[type][condition_name].empty?)
|
|
23
|
+
return nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
opts[:reserve] = true unless opts[:reserve] === false
|
|
27
|
+
|
|
28
|
+
if opts[:reserve] === true
|
|
29
|
+
direction = :reverse_each
|
|
30
|
+
else
|
|
31
|
+
direction = :each
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
@@types[type][condition_name].send(direction) do |condition|
|
|
35
|
+
|
|
36
|
+
condition[:name] = condition_name
|
|
37
|
+
|
|
38
|
+
yield condition
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.get_restarts()
|
|
45
|
+
return @@types[:restarts];
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.get_handlers()
|
|
49
|
+
return @@types[:condition];
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.set(type, condition)
|
|
53
|
+
|
|
54
|
+
condition[:name] = Utils::normalize(condition[:name])
|
|
55
|
+
|
|
56
|
+
@@types[type][condition[:name]] = [] unless @@types[type][condition[:name]]
|
|
57
|
+
@@types[type][condition[:name]].push({:block => condition[:block],
|
|
58
|
+
:raise => condition[:raise]})
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.unset(type, condition)
|
|
63
|
+
|
|
64
|
+
condition[:name] = Utils::normalize(condition[:name])
|
|
65
|
+
|
|
66
|
+
@@types[type][condition[:name]].each_with_index do |saved_condition, index|
|
|
67
|
+
|
|
68
|
+
if saved_condition[:block] == condition[:block]
|
|
69
|
+
|
|
70
|
+
# completely delete the restart-entry if no block is given anymore
|
|
71
|
+
if 2 >@@types[type][condition[:name]].size then
|
|
72
|
+
@@types[type].delete(condition[:name])
|
|
73
|
+
else
|
|
74
|
+
@@types[type][condition[:name]].delete_at(index)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# unset only one condition
|
|
78
|
+
return true
|
|
79
|
+
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
#
|
|
89
|
+
# parses magically the correct handlers / restarts
|
|
90
|
+
#
|
|
91
|
+
# #parse_handlers takes a hash as an argument and tries to identify
|
|
92
|
+
# the handlers / restarts and their callbacks. the syntax is the same for both.
|
|
93
|
+
#
|
|
94
|
+
# the simpliest is:
|
|
95
|
+
#
|
|
96
|
+
# :HandlerName => callback[, ...]
|
|
97
|
+
#
|
|
98
|
+
# class names are also supported:
|
|
99
|
+
#
|
|
100
|
+
# HandlerNAme => callback[, ...]
|
|
101
|
+
#
|
|
102
|
+
# class names as well as symbol will be casted to strings internally.
|
|
103
|
+
#
|
|
104
|
+
# currently, the callback has to be a lambda or
|
|
105
|
+
# a proc function to work within the condition system.
|
|
106
|
+
#
|
|
107
|
+
# it's also possible to define multiple handlers / restarts with the same callback:
|
|
108
|
+
#
|
|
109
|
+
# :HandlerName1, :HandlerName2, {:HandlerName3 => callback}[, ...]
|
|
110
|
+
#
|
|
111
|
+
# now let's mix everything together:
|
|
112
|
+
#
|
|
113
|
+
# :HandlerName => callback, HandlerName1, {:HandlerName2 => callback}, :HandlerName3 => callback
|
|
114
|
+
#
|
|
115
|
+
# why does this work?
|
|
116
|
+
#
|
|
117
|
+
# it's a lot of magic with hashes, since the #handle and #restart functions are using the * operator.
|
|
118
|
+
# this way, the parameters are wrapped in a Hash and can be magically transformed.
|
|
119
|
+
#
|
|
120
|
+
# @param [Hash] handlers a multidimensional Hash
|
|
121
|
+
#
|
|
122
|
+
# @return [Hash] a structured hash {{:name => :HandlerName, :block => callback}[, ...}}
|
|
123
|
+
#
|
|
124
|
+
def self.parse_handlers(handlers)
|
|
125
|
+
|
|
126
|
+
without_proc = []
|
|
127
|
+
parsed = []
|
|
128
|
+
|
|
129
|
+
handlers.each do |handler|
|
|
130
|
+
|
|
131
|
+
if handler.is_a? Hash
|
|
132
|
+
|
|
133
|
+
handler.each do |handler_name, handler_proc|
|
|
134
|
+
|
|
135
|
+
without_proc.each do |handler_name|
|
|
136
|
+
parsed.push(:name => handler_name, :block => handler_proc)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
parsed.push(:name => handler_name, :block => handler_proc)
|
|
140
|
+
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
without_proc.clear
|
|
144
|
+
|
|
145
|
+
else
|
|
146
|
+
|
|
147
|
+
without_proc.push(handler)
|
|
148
|
+
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
parsed
|
|
154
|
+
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
#
|
|
158
|
+
# creates a condition object
|
|
159
|
+
#
|
|
160
|
+
# creates a condition object of given conditon_name symbol or string-class
|
|
161
|
+
# if the requested condition_name is not a existing class,
|
|
162
|
+
# #generate_condition registers a restart offering to dynamically create class.
|
|
163
|
+
# currently, the new class will be created in the module Conditions.
|
|
164
|
+
#
|
|
165
|
+
# this condition class inherits the class ConditionDynamic.
|
|
166
|
+
#
|
|
167
|
+
# @param [String, Symbol] condition_name the condition class' name to instance. it must start with an upper case letter!
|
|
168
|
+
# @param *params parameters forwarded to the condition class' constructor
|
|
169
|
+
#
|
|
170
|
+
# @return [Condition] object of the demanded condition class
|
|
171
|
+
#
|
|
172
|
+
# @error :ConditionNotDefined if requested condition class does not exist
|
|
173
|
+
#
|
|
174
|
+
# @restart :Define creates a dynamic condition named like condition_name
|
|
175
|
+
#
|
|
176
|
+
def self.generate_condition(condition_name, *params)
|
|
177
|
+
|
|
178
|
+
if not condition_name.kind_of?(Class)
|
|
179
|
+
|
|
180
|
+
if !Conditions.const_defined? condition_name then
|
|
181
|
+
restart :WriteToFile => lambda { p "will be implemented anytime soon" },
|
|
182
|
+
:Define => lambda { Conditions::const_set(condition_name, Class.new(ConditionDynamic))
|
|
183
|
+
notice :DynamicConditionCreation, "#{condition_name} dynamically created" } do
|
|
184
|
+
|
|
185
|
+
error :ConditionNotDefined, condition_name
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
condition_name = Conditions::const_get(condition_name)
|
|
190
|
+
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
condition_name.new(*params)
|
|
194
|
+
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
#
|
|
198
|
+
# finds a handler in the given hash
|
|
199
|
+
#
|
|
200
|
+
# @param [String, Symbol] the handler / restart to find
|
|
201
|
+
# @param [Hash] a hash of handlers / restarts: {{:name => :Handlername}[, ...]}
|
|
202
|
+
#
|
|
203
|
+
# @return [Boolean] true if found
|
|
204
|
+
# @return [Boolean] false if not
|
|
205
|
+
#
|
|
206
|
+
def self.find_handler(name, handlers)
|
|
207
|
+
|
|
208
|
+
name = Utils::normalize(name)
|
|
209
|
+
|
|
210
|
+
handlers.each do |handler|
|
|
211
|
+
|
|
212
|
+
if name == handler[:name]
|
|
213
|
+
return true
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
false
|
|
219
|
+
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def self.normalize(name)
|
|
223
|
+
return name.to_s
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module Conditions
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# register a possible restart-option
|
|
5
|
+
#
|
|
6
|
+
# register a possible restart-option
|
|
7
|
+
# which can be invoked by an condition handler using #invoke
|
|
8
|
+
#
|
|
9
|
+
# since its possible to access the method's scope
|
|
10
|
+
# you can actually modify the method's behaviour from a handler
|
|
11
|
+
# which can be pretty dangerous but also very rewarding.
|
|
12
|
+
#
|
|
13
|
+
# if no restart was invoked, it returns the value of the block
|
|
14
|
+
# otherwise, it returns the value of invoked restart
|
|
15
|
+
#
|
|
16
|
+
# @param *restarts register restarts
|
|
17
|
+
# @param &block the code to execute normally
|
|
18
|
+
#
|
|
19
|
+
# @return value of the &block
|
|
20
|
+
# @return value of the invoked restart
|
|
21
|
+
#
|
|
22
|
+
# @raises RestartHandled if the catched restart doesnt belong to this block
|
|
23
|
+
#
|
|
24
|
+
# @see #parse_handlers for syntax information on *conditions
|
|
25
|
+
#
|
|
26
|
+
def restart *restarts, &block
|
|
27
|
+
|
|
28
|
+
restarts = Utils::parse_handlers restarts
|
|
29
|
+
|
|
30
|
+
restarts.each { |restart| Utils::Handler::set(:restart, restart)}
|
|
31
|
+
|
|
32
|
+
value = begin
|
|
33
|
+
block.call
|
|
34
|
+
rescue Exception::RestartHandled => ex
|
|
35
|
+
|
|
36
|
+
# if restart doesnt belong to this block,
|
|
37
|
+
# continue unwinding the stack
|
|
38
|
+
if !Utils::find_handler(ex.restart, restarts) then
|
|
39
|
+
raise Exception::RestartHandled, :value => ex.value, :restart => ex.restart
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
ex.value
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
restarts.each { |restart| Utils::Handler::unset(:restart, restart)}
|
|
47
|
+
|
|
48
|
+
value
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
#
|
|
53
|
+
# invokes a registered restart
|
|
54
|
+
#
|
|
55
|
+
# #invoke checks if a restart with restart_name was registered
|
|
56
|
+
# and calls it if found.
|
|
57
|
+
#
|
|
58
|
+
# @param [String] restart_name the restart which shall be invoked
|
|
59
|
+
# @param *params parameters which are needed by the restart
|
|
60
|
+
#
|
|
61
|
+
# @raises RestartHandled if a invoked restart is called
|
|
62
|
+
# @raises RestartNotFoundError if a demanded restart was not found
|
|
63
|
+
#
|
|
64
|
+
# @see #parse_handlers for syntax information on *conditions
|
|
65
|
+
#
|
|
66
|
+
def invoke restart_name, *params
|
|
67
|
+
|
|
68
|
+
Utils::Handler::get(:restart, restart_name) do |restart|
|
|
69
|
+
raise Exception::RestartHandled, :value => restart[:block].call(*params), :restart => restart_name
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
raise Exception::RestartNotFoundError, restart_name
|
|
73
|
+
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module Conditions
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# calls the registered handler for given condition
|
|
5
|
+
#
|
|
6
|
+
# if the registered handler is established using #handle,
|
|
7
|
+
# the call to #signal will not return and raise an exception instead.
|
|
8
|
+
#
|
|
9
|
+
# if it's established using bind, #signal will return normally with
|
|
10
|
+
# the handler's returned value.
|
|
11
|
+
#
|
|
12
|
+
# @param [Symbol] conditon_name the condition to signal
|
|
13
|
+
# @param *params additional information wich will be directly passed
|
|
14
|
+
# to the condition's constructor
|
|
15
|
+
#
|
|
16
|
+
# @return value the return value of the called handler.
|
|
17
|
+
#
|
|
18
|
+
# @raises ConditionHandled if a handler is established with #handle
|
|
19
|
+
#
|
|
20
|
+
def signal condition_name, *params
|
|
21
|
+
|
|
22
|
+
value = nil
|
|
23
|
+
|
|
24
|
+
condition = Utils::generate_condition condition_name, *params
|
|
25
|
+
|
|
26
|
+
Utils::Handler::get :condition, condition_name do |handler|
|
|
27
|
+
|
|
28
|
+
value = case handler[:block].arity
|
|
29
|
+
when 0 then handler[:block].call
|
|
30
|
+
when 1 then handler[:block].call condition
|
|
31
|
+
when 2 then handler[:block].call condition, value
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
raise(Exception::ConditionHandled, :value => value, :condition => handler) if handler[:raise]
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
value
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
#
|
|
43
|
+
# errors a condition
|
|
44
|
+
#
|
|
45
|
+
# it's build on top of #signal but will raise an ConditionNotHandledError
|
|
46
|
+
# if #signal returns normally. This ensures that a condition errored using
|
|
47
|
+
# #error has to be handled by a condition handler with an non-local exit
|
|
48
|
+
# as it's done by using #handle.
|
|
49
|
+
#
|
|
50
|
+
# @param [Symbol] condition condition's name
|
|
51
|
+
# @param *params additional information wich will be directly passed
|
|
52
|
+
# to the condition's constructor
|
|
53
|
+
#
|
|
54
|
+
# @raises ConditionNotHandledError if the condition was not handled
|
|
55
|
+
#
|
|
56
|
+
def error condition, *params
|
|
57
|
+
|
|
58
|
+
signal condition, *params
|
|
59
|
+
|
|
60
|
+
raise Exception::ConditionNotHandledError, "condition #{condition} was not handled"
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
#
|
|
65
|
+
# notices a condition
|
|
66
|
+
#
|
|
67
|
+
# it's build on top of #signal and will print the given message
|
|
68
|
+
# it can be suppressed by invoking the restart :Suppress.
|
|
69
|
+
#
|
|
70
|
+
# since notice is calling #signal,
|
|
71
|
+
# the given condition can also be handled
|
|
72
|
+
#
|
|
73
|
+
# if no handler is bound to given condition, #notice returns normally.
|
|
74
|
+
#
|
|
75
|
+
# @param [Symbol] condition condition's name
|
|
76
|
+
# @param [String] message
|
|
77
|
+
#
|
|
78
|
+
# @return nil or bound handler's returned value
|
|
79
|
+
#
|
|
80
|
+
# @signal :NoticeSignaled if #notice got called.
|
|
81
|
+
#
|
|
82
|
+
# @restart :Suppress message will not be printed
|
|
83
|
+
#
|
|
84
|
+
def notice condition, message
|
|
85
|
+
|
|
86
|
+
restart :Suppress => lambda { nil } do
|
|
87
|
+
signal :NoticeSignaled, condition
|
|
88
|
+
print "Notice: #{message}"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
signal condition
|
|
92
|
+
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: conditions
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
prerelease: 6
|
|
5
|
+
version: 0.0.2.alpha
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- "Andr\xC3\xA9 Gawron"
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
|
|
13
|
+
date: 2011-08-31 00:00:00 Z
|
|
14
|
+
dependencies: []
|
|
15
|
+
|
|
16
|
+
description: Implements the Lisp's condition system in Ruby
|
|
17
|
+
email:
|
|
18
|
+
- andre@ziemek.de
|
|
19
|
+
executables: []
|
|
20
|
+
|
|
21
|
+
extensions: []
|
|
22
|
+
|
|
23
|
+
extra_rdoc_files: []
|
|
24
|
+
|
|
25
|
+
files:
|
|
26
|
+
- .gitignore
|
|
27
|
+
- Gemfile
|
|
28
|
+
- LICENSE
|
|
29
|
+
- README.rdoc
|
|
30
|
+
- Rakefile
|
|
31
|
+
- conditions.gemspec
|
|
32
|
+
- example/another-log
|
|
33
|
+
- example/example.rb
|
|
34
|
+
- example/log
|
|
35
|
+
- example/newfile
|
|
36
|
+
- lib/conditions.rb
|
|
37
|
+
- lib/conditions/definitions/defaults.rb
|
|
38
|
+
- lib/conditions/handles.rb
|
|
39
|
+
- lib/conditions/lib/exceptions.rb
|
|
40
|
+
- lib/conditions/lib/helpers.rb
|
|
41
|
+
- lib/conditions/restarts.rb
|
|
42
|
+
- lib/conditions/signals.rb
|
|
43
|
+
- lib/conditions/version.rb
|
|
44
|
+
homepage: https://github.com/melkon/conditions
|
|
45
|
+
licenses:
|
|
46
|
+
- BSD
|
|
47
|
+
post_install_message:
|
|
48
|
+
rdoc_options: []
|
|
49
|
+
|
|
50
|
+
require_paths:
|
|
51
|
+
- lib
|
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
53
|
+
none: false
|
|
54
|
+
requirements:
|
|
55
|
+
- - ">="
|
|
56
|
+
- !ruby/object:Gem::Version
|
|
57
|
+
version: "0"
|
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
|
+
none: false
|
|
60
|
+
requirements:
|
|
61
|
+
- - ">"
|
|
62
|
+
- !ruby/object:Gem::Version
|
|
63
|
+
version: 1.3.1
|
|
64
|
+
requirements: []
|
|
65
|
+
|
|
66
|
+
rubyforge_project:
|
|
67
|
+
rubygems_version: 1.8.5
|
|
68
|
+
signing_key:
|
|
69
|
+
specification_version: 3
|
|
70
|
+
summary: Implements the Lisp's condition system in Ruby
|
|
71
|
+
test_files: []
|
|
72
|
+
|