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