ugh 1.0.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.
- checksums.yaml +7 -0
- data/GPL-3 +674 -0
- data/Manifest.txt +7 -0
- data/build.sh +9 -0
- data/lib/ugh.rb +89 -0
- data/test-ugh.rb +190 -0
- data/ugh.fab +287 -0
- data/ugh.gemspec +18 -0
- metadata +67 -0
data/Manifest.txt
ADDED
data/build.sh
ADDED
data/lib/ugh.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
class StandardError
|
2
|
+
def initialize short_message = 'unspecified ugh', **attributes
|
3
|
+
super short_message
|
4
|
+
@short_message = short_message
|
5
|
+
@attributes = attributes
|
6
|
+
return
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :short_message
|
10
|
+
attr_accessor :attributes
|
11
|
+
|
12
|
+
def to_s attributes_in_parentheses: true
|
13
|
+
s = '' << @short_message
|
14
|
+
firstp = true
|
15
|
+
@attributes.each_pair do |name, value|
|
16
|
+
if firstp then
|
17
|
+
s << (attributes_in_parentheses ? ' (' : ', ')
|
18
|
+
firstp = false
|
19
|
+
else
|
20
|
+
s << ', '
|
21
|
+
end
|
22
|
+
firstp = false
|
23
|
+
s << name.to_s << ': ' << value.inspect
|
24
|
+
end
|
25
|
+
s << ')' if attributes_in_parentheses and !firstp
|
26
|
+
return s
|
27
|
+
end
|
28
|
+
|
29
|
+
def inspect
|
30
|
+
return '#<' + to_s(attributes_in_parentheses: false) + '>'
|
31
|
+
end
|
32
|
+
|
33
|
+
def [] name
|
34
|
+
return @attributes[name.to_sym]
|
35
|
+
end
|
36
|
+
|
37
|
+
def []= name, value
|
38
|
+
return @attributes[name.to_sym] = value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def ugh short_message = 'unspecified ugh', **attr
|
43
|
+
if short_message.is_a? Class then
|
44
|
+
# Uh-oh, it's not a short message at all but an exception
|
45
|
+
# class (or so we should hope). Let's instantiate it.
|
46
|
+
raise short_message.new(**attr)
|
47
|
+
else
|
48
|
+
raise Ugh.new(short_message, **attr)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Ugh < RuntimeError
|
53
|
+
end
|
54
|
+
|
55
|
+
def ugh? klass = Ugh, **attributes
|
56
|
+
begin
|
57
|
+
return yield
|
58
|
+
rescue klass => exception
|
59
|
+
evaluated_attributes = {}
|
60
|
+
attributes.each_pair do |name, value|
|
61
|
+
if value.is_a? Proc then
|
62
|
+
unless exception.attributes.has_key? name then
|
63
|
+
value = value.call
|
64
|
+
else
|
65
|
+
value = nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
evaluated_attributes[name] = value
|
69
|
+
end
|
70
|
+
exception.attributes =
|
71
|
+
evaluated_attributes.merge exception.attributes
|
72
|
+
raise exception
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class SystemCallError
|
77
|
+
def strerror
|
78
|
+
# Remove in-sentence bits of context such as filename(s)
|
79
|
+
# by looking up the error message without context:
|
80
|
+
m = SystemCallError.new(errno).message
|
81
|
+
|
82
|
+
# Fix message case by downcasing the initial uppercase
|
83
|
+
# letter except if it's immediately followed by another
|
84
|
+
# uppercase letter, as in [["RPC version wrong"]]:
|
85
|
+
m = m.sub(/\A[[:upper:]](?![[:upper:]])/){$&.downcase}
|
86
|
+
|
87
|
+
return m
|
88
|
+
end
|
89
|
+
end
|
data/test-ugh.rb
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
#! /usr/bin/ruby -Ilib
|
2
|
+
|
3
|
+
require 'ugh'
|
4
|
+
|
5
|
+
require 'test/unit'
|
6
|
+
|
7
|
+
class Ughish < Ugh
|
8
|
+
end
|
9
|
+
|
10
|
+
class UghTests < Test::Unit::TestCase
|
11
|
+
def test_everything
|
12
|
+
e = Ugh.new 'harumph'
|
13
|
+
assert e.is_a? Ugh
|
14
|
+
assert e.is_a? Exception
|
15
|
+
assert e.is_a? StandardError
|
16
|
+
assert e.is_a? RuntimeError
|
17
|
+
|
18
|
+
assert e.short_message.is_a? String
|
19
|
+
assert_equal e.short_message, 'harumph'
|
20
|
+
assert e.attributes.is_a? Hash
|
21
|
+
assert e.attributes.empty?
|
22
|
+
|
23
|
+
e = Ugh.new 'harrumph', to_wit: 'arrgh'
|
24
|
+
assert_equal e.short_message, 'harrumph'
|
25
|
+
assert_equal e.attributes.keys, [:to_wit]
|
26
|
+
assert e[:to_wit].is_a? String
|
27
|
+
assert_equal e[:to_wit], 'arrgh'
|
28
|
+
assert e['to_wit'].is_a? String
|
29
|
+
assert_equal e['to_wit'], 'arrgh'
|
30
|
+
|
31
|
+
e[:during] = :mumbling
|
32
|
+
assert_equal e.attributes.keys, [:to_wit, :during]
|
33
|
+
assert_equal e[:during], :mumbling
|
34
|
+
|
35
|
+
assert_equal e.to_s,
|
36
|
+
'harrumph (to_wit: "arrgh", during: :mumbling)'
|
37
|
+
assert_equal e.to_s(attributes_in_parentheses: false),
|
38
|
+
'harrumph, to_wit: "arrgh", during: :mumbling'
|
39
|
+
assert_equal e.inspect,
|
40
|
+
'#<harrumph, to_wit: "arrgh", during: :mumbling>'
|
41
|
+
|
42
|
+
e['loudness'] = :slight
|
43
|
+
assert_equal e.attributes.keys,
|
44
|
+
[:to_wit, :during, :loudness]
|
45
|
+
assert_equal e[:loudness], :slight
|
46
|
+
|
47
|
+
e.attributes = {:something_else => 'entirely'}
|
48
|
+
assert_equal e.to_s, 'harrumph (something_else: "entirely")'
|
49
|
+
|
50
|
+
e.short_message = 'rah-rah'
|
51
|
+
assert_equal e.to_s, 'rah-rah (something_else: "entirely")'
|
52
|
+
|
53
|
+
e.attributes.delete :something_else
|
54
|
+
assert_equal e.to_s, 'rah-rah'
|
55
|
+
|
56
|
+
assert_raise Ugh do
|
57
|
+
ugh
|
58
|
+
end
|
59
|
+
|
60
|
+
assert_raise Ughish do
|
61
|
+
ugh Ughish
|
62
|
+
end
|
63
|
+
|
64
|
+
begin
|
65
|
+
ugh colour: 'yellow'
|
66
|
+
rescue Ugh => e
|
67
|
+
assert e.is_a? Ugh
|
68
|
+
assert_equal e.attributes.keys, [:colour]
|
69
|
+
assert_equal e[:colour], 'yellow'
|
70
|
+
end
|
71
|
+
|
72
|
+
begin
|
73
|
+
ugh? bird: 'crow' do
|
74
|
+
ugh colour: 'yellow'
|
75
|
+
end
|
76
|
+
rescue Ugh => e
|
77
|
+
assert_equal e.attributes.keys, [:bird, :colour]
|
78
|
+
assert_equal e[:bird], 'crow'
|
79
|
+
assert_equal e[:colour], 'yellow'
|
80
|
+
end
|
81
|
+
|
82
|
+
begin
|
83
|
+
ugh? bird: 'crow', colour: 'yellow' do
|
84
|
+
ugh colour: 'purple'
|
85
|
+
end
|
86
|
+
rescue Ugh => e
|
87
|
+
assert_equal e.attributes.keys, [:bird, :colour]
|
88
|
+
assert_equal e[:bird], 'crow'
|
89
|
+
assert_equal e[:colour], 'purple'
|
90
|
+
end
|
91
|
+
|
92
|
+
begin
|
93
|
+
ugh? Ughish, bird: 'crow', colour: 'yellow' do
|
94
|
+
ugh colour: 'purple'
|
95
|
+
end
|
96
|
+
rescue Ugh => e
|
97
|
+
assert_equal e.attributes.keys, [:colour]
|
98
|
+
assert_equal e[:bird], nil
|
99
|
+
assert_equal e[:colour], 'purple'
|
100
|
+
end
|
101
|
+
|
102
|
+
assert_equal ugh?(bird: 'crow'){3}, 3
|
103
|
+
|
104
|
+
begin
|
105
|
+
i = 1
|
106
|
+
counter_evaluated = false
|
107
|
+
ugh? counter: proc{counter_evaluated = true; i} do
|
108
|
+
i += 1
|
109
|
+
ugh
|
110
|
+
i += 1
|
111
|
+
end
|
112
|
+
rescue Ugh => e
|
113
|
+
assert_equal counter_evaluated, true
|
114
|
+
assert_equal e.attributes.keys, [:counter]
|
115
|
+
assert_equal e[:counter], 2
|
116
|
+
end
|
117
|
+
|
118
|
+
begin
|
119
|
+
i = 1
|
120
|
+
counter_evaluated = false
|
121
|
+
ugh? counter: proc{counter_evaluated = true; i} do
|
122
|
+
i += 1
|
123
|
+
ugh counter: 7
|
124
|
+
i += 1
|
125
|
+
end
|
126
|
+
rescue Ugh => e
|
127
|
+
assert_equal counter_evaluated, false
|
128
|
+
assert_equal e.attributes.keys, [:counter]
|
129
|
+
assert_equal e[:counter], 7
|
130
|
+
end
|
131
|
+
|
132
|
+
begin
|
133
|
+
ugh? foo: 1 do
|
134
|
+
ugh? bar: 2 do
|
135
|
+
ugh? foo: 3 do
|
136
|
+
ugh
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
rescue Ugh => e
|
141
|
+
assert_equal e.attributes.keys, [:foo, :bar]
|
142
|
+
end
|
143
|
+
|
144
|
+
begin
|
145
|
+
ugh? bar: 2 do
|
146
|
+
ugh? foo: 3 do
|
147
|
+
ugh
|
148
|
+
end
|
149
|
+
end
|
150
|
+
rescue Ugh => e
|
151
|
+
assert_equal e.attributes.keys, [:bar, :foo]
|
152
|
+
end
|
153
|
+
|
154
|
+
assert_raise Errno::ENOTDIR do
|
155
|
+
File.read 'test-ugh.rb/file-that-can-not-exist'
|
156
|
+
end
|
157
|
+
|
158
|
+
begin
|
159
|
+
File.read 'test-ugh.rb/file-that-can-not-exist'
|
160
|
+
rescue StandardError => e
|
161
|
+
assert e.respond_to? :strerror
|
162
|
+
assert e.strerror.is_a? String
|
163
|
+
assert_equal e.strerror, 'not a directory'
|
164
|
+
end
|
165
|
+
|
166
|
+
begin
|
167
|
+
raise Errno::EBADRPC
|
168
|
+
rescue StandardError => e
|
169
|
+
assert_equal e.strerror, 'RPC struct is bad'
|
170
|
+
end
|
171
|
+
|
172
|
+
begin
|
173
|
+
ugh? SystemCallError, filename: 'something/something' do
|
174
|
+
File.read 'test-ugh.rb/file-that-can-not-exist'
|
175
|
+
end
|
176
|
+
rescue StandardError => e
|
177
|
+
assert_equal e[:filename], 'something/something'
|
178
|
+
end
|
179
|
+
|
180
|
+
begin
|
181
|
+
ugh? filename: 'something/something' do
|
182
|
+
File.read 'test-ugh.rb/file-that-can-not-exist'
|
183
|
+
end
|
184
|
+
rescue StandardError => e
|
185
|
+
assert_equal e[:filename], nil
|
186
|
+
end
|
187
|
+
|
188
|
+
return
|
189
|
+
end
|
190
|
+
end
|
data/ugh.fab
ADDED
@@ -0,0 +1,287 @@
|
|
1
|
+
This is the [[ugh]] Rubygem. It provides facilities for
|
2
|
+
attaching attributes to Ruby exceptions (to be exact, those
|
3
|
+
exceptions that inherit from [[StandardError]]) at creation time
|
4
|
+
and via dynamic scoping. It also defines
|
5
|
+
[[SystemCallError#strerror]] to produce a description of a
|
6
|
+
low-level I/O operation in a manner suitable for embedding into
|
7
|
+
a traditional Unix command line interface style error message.
|
8
|
+
|
9
|
+
|
10
|
+
* BEWARE!
|
11
|
+
|
12
|
+
In order to achieve these goals, [[ugh.rb]] defines or redefines
|
13
|
+
certain methods in builtin Ruby classes. Under certain
|
14
|
+
hypothetical conditions, this may theoretically lead to
|
15
|
+
conflicts with other libraries also meddling in these areas,
|
16
|
+
perhaps for overlapping purposes, perhaps for incompatible
|
17
|
+
purposes. It is unlikely but possible.
|
18
|
+
|
19
|
+
|
20
|
+
== Attributes for [[StandardError]]
|
21
|
+
|
22
|
+
We'll start by augmenting [[StandardError]].
|
23
|
+
|
24
|
+
<< .file lib/ugh.rb >>:
|
25
|
+
class StandardError
|
26
|
+
<< @ [[StandardError]] >>
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
Its constructor shall accept an optional and arbitrary set of
|
31
|
+
attributes. It shall store the message argument passed to it
|
32
|
+
(if any) in [[@short_message]] (as contrary to the 'long
|
33
|
+
message' returned by [[#message]] that will also represent the
|
34
|
+
attributes) and these attributes in [[@attributes]] as a
|
35
|
+
[[Hash]] keyed by [[Symbol]]:s. (Note that Ruby's runtime
|
36
|
+
engine does not directly reveal the message passed to
|
37
|
+
[[Exception#initialize]]; instead, one has to call either
|
38
|
+
[[#to_s]], [[#inspect]], or [[#message]].)
|
39
|
+
|
40
|
+
We'll implement this by defining [[StandardError#initialize]].
|
41
|
+
Unfortunately, it's unreasonably tricky to 'properly' get the
|
42
|
+
replaced constructor to chain to the original [[StandardError]]
|
43
|
+
constructor. Fortunately, it turns out that [[StandardError]]
|
44
|
+
does not /have/ a built-in constructor; it just inherits one
|
45
|
+
from [[Exception]]. (See [[error.cc]]'s [[rb_eStandardError]].)
|
46
|
+
Thus, our new method can just call the inherited constructor via
|
47
|
+
[[super]], and as long as no other piece of code attempts to
|
48
|
+
hijack [[StandardError]]'s constructor in the same Ruby engine,
|
49
|
+
things should work out fine.
|
50
|
+
|
51
|
+
<< @ [[StandardError]] >>:
|
52
|
+
|
53
|
+
def initialize short_message = 'unspecified ugh', **attributes
|
54
|
+
super short_message
|
55
|
+
@short_message = short_message
|
56
|
+
@attributes = attributes
|
57
|
+
return
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
Both [[@short_message]] and [[@attributes]] shall be revealed to
|
62
|
+
the user as appropriate methods, in read/write mode. We do this
|
63
|
+
by [[attr_accessor]].
|
64
|
+
|
65
|
+
attr_accessor :short_message
|
66
|
+
attr_accessor :attributes
|
67
|
+
|
68
|
+
|
69
|
+
[[StandardError#to_s]] shall provide a reasonable representation
|
70
|
+
of both the short message and the attributes.
|
71
|
+
|
72
|
+
We'll generate a single-line string. (Multiple lines is
|
73
|
+
tempting if there are many attributes, but it would violate
|
74
|
+
several implicit assumptions about [[Object#to_s]].) At the
|
75
|
+
beginning of the string will be the short message. If there are
|
76
|
+
any attributes, we'll append these to the string, by default
|
77
|
+
wrapped in parentheses. The attributes will be separated from
|
78
|
+
each other by commas, and each name-value pair will be joined
|
79
|
+
together with a colon. We'll use [[#inspect]] to convert the
|
80
|
+
attributes' values into string form but embed the attributes'
|
81
|
+
names as they are. We'll permit the caller to turn off the
|
82
|
+
parentheses via a named parameter, which we'll call
|
83
|
+
[[attributes_in_parentheses]]. When it's turned off, the short
|
84
|
+
message will be separated from the first attribute by a
|
85
|
+
comma instead of an opening paren.
|
86
|
+
|
87
|
+
def to_s attributes_in_parentheses: true
|
88
|
+
s = '' << @short_message
|
89
|
+
firstp = true
|
90
|
+
@attributes.each_pair do |name, value|
|
91
|
+
if firstp then
|
92
|
+
s << (attributes_in_parentheses ? ' (' : ', ')
|
93
|
+
firstp = false
|
94
|
+
else
|
95
|
+
s << ', '
|
96
|
+
end
|
97
|
+
firstp = false
|
98
|
+
s << name.to_s << ': ' << value.inspect
|
99
|
+
end
|
100
|
+
s << ')' if attributes_in_parentheses and !firstp
|
101
|
+
return s
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
[[StandardError#inspect]] shall provide a reasonable
|
106
|
+
representation of both the short message and the attributes; one
|
107
|
+
that won't be easily confused with external representation of an
|
108
|
+
object.
|
109
|
+
|
110
|
+
We'll implement this by calling [[#to_s]] and wrapping its
|
111
|
+
result into [[#<...>]]. Because this wrapper already provides a
|
112
|
+
suitable enclosure, we'll turn [[#to_s]]'s
|
113
|
+
[[attributes_in_parentheses]] feature off in [[#inspect]].
|
114
|
+
|
115
|
+
def inspect
|
116
|
+
return '#<' + to_s(attributes_in_parentheses: false) + '>'
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
There's no need to define [[StandardError#message]]. The way
|
121
|
+
the runtime defines [[Exception#message]], it just calls
|
122
|
+
[[#to_s]], which does the right thing.
|
123
|
+
|
124
|
+
|
125
|
+
We'll also provide syntactic sugar, in the form of [[#[]]] and
|
126
|
+
[[#[]=]], for accessing and replacing the attributes. If the
|
127
|
+
user needs more complex operations, the method [[attributes]]
|
128
|
+
should be used explicitly. In these methods, which for the main
|
129
|
+
part are just delegation wrappers, we'll convert the [[name]]
|
130
|
+
argument into a [[Symbol]] by calling its [[#to_sym]]. This
|
131
|
+
way, the user can pass either strings rather than symbols as
|
132
|
+
names, and the right thing will still happen.
|
133
|
+
|
134
|
+
def [] name
|
135
|
+
return @attributes[name.to_sym]
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
def []= name, value
|
140
|
+
return @attributes[name.to_sym] = value
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
== Raising attributeful exceptions
|
145
|
+
|
146
|
+
Next, we'll provide an attribute-friendly way to raise run-time
|
147
|
+
errors. A tempting approach would be to replace the builtin
|
148
|
+
[[raise]]; unfortunately, given its nontrivial signature _and_
|
149
|
+
the relation to [[Thread#raise]], the KISS principle would
|
150
|
+
strongly suggest another way. Thus, we'll define a method with
|
151
|
+
a simple, easy to pronounce and intuitively obvious name.
|
152
|
+
|
153
|
+
<< .file lib/ugh.rb >>:
|
154
|
+
|
155
|
+
def ugh short_message = 'unspecified ugh', **attr
|
156
|
+
if short_message.is_a? Class then
|
157
|
+
# Uh-oh, it's not a short message at all but an exception
|
158
|
+
# class (or so we should hope). Let's instantiate it.
|
159
|
+
raise short_message.new(**attr)
|
160
|
+
else
|
161
|
+
raise Ugh.new(short_message, **attr)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
The exception class [[ugh]] uses inherits from [[RuntimeError]].
|
167
|
+
However, note that since we attached the attribute mechanism to
|
168
|
+
its parent class, [[StandardError]], it is quite possible, if it
|
169
|
+
would be more appropriate, to define new exception classes that
|
170
|
+
do not inherit from [[RuntimeError]] and still benefit from the
|
171
|
+
attribution facility. The user will just have to manually
|
172
|
+
construct an instance and [[raise]] it.
|
173
|
+
|
174
|
+
|
175
|
+
class Ugh < RuntimeError
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
== Defining exception attributes via dynamic scope
|
180
|
+
|
181
|
+
Next, we'll define a method that will take arbitrary attributes
|
182
|
+
and a block, and attach these attributes to any exception that
|
183
|
+
might get raised from this block. We'll call it [[ugh?]]. The
|
184
|
+
name violates conventions somewhat, in that the trailing ques
|
185
|
+
does not indicate it's a predicate, rather it implies an
|
186
|
+
interrogation in the form of "So, ugh happened? Well, about
|
187
|
+
this: ...".
|
188
|
+
|
189
|
+
There are a few nuances about attributing and re-attributing
|
190
|
+
exceptions. For one, we'll want to give precedence to the
|
191
|
+
attributes that the exception already has -- since they were set
|
192
|
+
up closer to the exception happening, perhaps as it was raised,
|
193
|
+
perhaps in an inner [[ugh?]] block, they're presumedly more
|
194
|
+
specific and more useful than what we would have at the current
|
195
|
+
[[ugh?]] block.
|
196
|
+
|
197
|
+
For another, it sometimes happens that a block knows that it
|
198
|
+
wants to attach an attribute to exceptions raised from it, but
|
199
|
+
doesn't know the attribute's value at the start of the block.
|
200
|
+
Such a conundrum might be solved by nesting several [[ugh?]]
|
201
|
+
blocks, in accordance with the information becoming available,
|
202
|
+
and this is often the recommended approach. However, there are
|
203
|
+
some patterns -- such as the standard line-by-line parser -- in
|
204
|
+
which case it might be more appropriate to define an attribute
|
205
|
+
in an [[ugh?]] block not by a constant value but by an
|
206
|
+
expression, to be evaluated when the exception gets actually
|
207
|
+
thrown. In order to support this pattern, we'll check, when
|
208
|
+
handling an exception, whether any of the attributes passed to
|
209
|
+
[[ugh?]] have a value that is an instance of [[Proc]], and if
|
210
|
+
so, execute it and use the returned value rather than the
|
211
|
+
[[Proc]] itself as the attribute to be attached to the
|
212
|
+
exception.
|
213
|
+
|
214
|
+
Note that this means that it won't be possible to attach
|
215
|
+
[[Proc]] instances to an exception as attributes deliberately
|
216
|
+
via [[ugh?]] blocks. It's a bit of a wart, but considering that
|
217
|
+
attributes of exceptions are primarily meant for a user's eyes,
|
218
|
+
to help with diagnosing the issue, and automated handling of
|
219
|
+
formal attributes should in general not do anything more than
|
220
|
+
present the attributes to the user in a suitable form, the cost
|
221
|
+
is probably acceptable. In the rare cases when this won't hold,
|
222
|
+
the user can easily resort to a [[begin]] ... [[rescue]] ...
|
223
|
+
[[end]] block instead of [[ugh?]].
|
224
|
+
|
225
|
+
For third, not all attributions apply to all exceptions alike.
|
226
|
+
To that end, while [[ugh?]] by default handles instances of
|
227
|
+
[[Ugh]], it shall accept an optional exception class to narrow
|
228
|
+
(or possibly widen) the set of attributes subject to relabelling
|
229
|
+
by this [[ugh?]] block.
|
230
|
+
|
231
|
+
<< .file lib/ugh.rb >>:
|
232
|
+
def ugh? klass = Ugh, **attributes
|
233
|
+
begin
|
234
|
+
return yield
|
235
|
+
rescue klass => exception
|
236
|
+
evaluated_attributes = {}
|
237
|
+
attributes.each_pair do |name, value|
|
238
|
+
<< ? Evaluate [[value]] of this attribute >>
|
239
|
+
evaluated_attributes[name] = value
|
240
|
+
end
|
241
|
+
exception.attributes =
|
242
|
+
evaluated_attributes.merge exception.attributes
|
243
|
+
raise exception
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
We won't execute [[value.call]] even if [[value]] is a [[Proc]]
|
249
|
+
if we would be immediately discarding its result afterwards for
|
250
|
+
the reason that such an attribute has already been attached to
|
251
|
+
the exception. However, we don't want to let this affect the
|
252
|
+
order of keys in this [[Hash]] -- recall that modern Ruby
|
253
|
+
versions retain a hash's key order --, so in such a case we'll
|
254
|
+
use [[nil]] as the placeholder.
|
255
|
+
|
256
|
+
<< ? Evaluate [[value]] of this attribute >>:
|
257
|
+
if value.is_a? Proc then
|
258
|
+
unless exception.attributes.has_key? name then
|
259
|
+
value = value.call
|
260
|
+
else
|
261
|
+
value = nil
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
|
266
|
+
== [[strerror]]-like error messages for system calls
|
267
|
+
|
268
|
+
Finally, on a perihperally related topic, we'll define
|
269
|
+
[[SystemCallError#strerror]] to return an error message somewhat
|
270
|
+
more conforming to Unix's command line interface error reporting
|
271
|
+
customs than what Ruby provides by default.
|
272
|
+
|
273
|
+
<< .file lib/ugh.rb >>:
|
274
|
+
class SystemCallError
|
275
|
+
def strerror
|
276
|
+
# Remove in-sentence bits of context such as filename(s)
|
277
|
+
# by looking up the error message without context:
|
278
|
+
m = SystemCallError.new(errno).message
|
279
|
+
|
280
|
+
# Fix message case by downcasing the initial uppercase
|
281
|
+
# letter except if it's immediately followed by another
|
282
|
+
# uppercase letter, as in [["RPC version wrong"]]:
|
283
|
+
m = m.sub(/\A[[:upper:]](?![[:upper:]])/){$&.downcase}
|
284
|
+
|
285
|
+
return m
|
286
|
+
end
|
287
|
+
end
|