File Coverage

blib/lib/errors.pm
Criterion Covered Total %
statement 130 213 61.0
branch 37 114 32.4
condition 14 30 46.6
subroutine 23 32 71.8
pod 0 1 0.0
total 204 390 52.3


line stmt bran cond sub pod time code
1             # ToDo
2             # + Move Error.pm code into module
3             # + 'with' clashes with Moose
4             # + Remove Simple
5             # + Support $_ as error topic
6             #
7             # - Add system error classes
8             # - Support autodie
9             # - Move most Error stuff into errors package
10             # - Replace ObjectifyCallback
11             #
12             # == Tests
13             # + otherwise
14             # + except
15             # + -with_using
16             # + $_ is used
17             #
18             # - assert function works
19             # - $@ is always undef
20             # - nesting of try stuff
21             # - works with Moose
22             # - works with Error
23             # - with becomes using if with already exists
24              
25             #------------------------------------------------------------------------------
26 8     8   185170 use strict; use warnings;
  8     8   18  
  8         278  
  8         44  
  8         16  
  8         1307  
27             package errors;
28             our $VERSION = '0.13';
29              
30             sub import {
31 15     15   926 my ($class, $directive) = @_;
32 15 100       70 if (not $directive) {
    100          
    50          
33 7         31 $class->export_commands(
34             qw(try with except otherwise finally assert)
35             );
36             }
37             elsif ($directive eq '-with_using') {
38 1         5 $class->export_commands(
39             qw(try using except otherwise finally assert)
40             );
41             }
42             elsif ($directive eq '-class') {
43 7         26 my ($class, %fields) = @_[2..$#_];
44 7   100     133 my $isa = $fields{-isa} || 'Exception';
45 8     8   57 no strict 'refs';
  8         29  
  8         1646  
46 7         10 @{$class . '::ISA'} = ($isa);
  7         11759  
47             }
48             else {
49 0         0 die "Invalid usage of errors module: 'use errors @_[1..$#_]'";
50             }
51             }
52              
53             sub export_commands {
54 8     8 0 30 my ($class, @exports) = @_;
55 8         29 local @errors::subs::EXPORT = @exports;
56 8         20 local $Exporter::ExportLevel += 2;
57 8         11111 errors::subs->import();
58             }
59              
60             #------------------------------------------------------------------------------
61             # Inspired by code from Jesse Glick and Peter Seibel
62              
63             package errors::subs;
64              
65 8     8   52 use Exporter ();
  8         22  
  8         11203  
66             our @ISA = qw(Exporter);
67              
68             sub objectify {
69 1     1   21 my $msg = shift;
70 1         9 return RuntimeError->new($msg);
71             }
72              
73             sub run_clauses ($$$\@) {
74 8     8   62 my($clauses,$err,$wantarray,$result) = @_;
75 8         15 my $code = undef;
76              
77 8 100       39 $err = objectify($err) unless ref($err);
78              
79 8         10 CATCH: {
80              
81             # catch
82 8         13 my $catch;
83 8 50       35 if(defined($catch = $clauses->{'catch'})) {
84 8         19 my $i = 0;
85              
86             CATCHLOOP:
87 8         34 for( ; $i < @$catch ; $i += 2) {
88 9         22 my $pkg = $catch->[$i];
89 9 50 66     286 unless(defined $pkg) {
    100          
90             #except
91 0         0 splice(@$catch,$i,2,$catch->[$i+1]->($err));
92 0         0 $i -= 2;
93 0         0 next CATCHLOOP;
94             }
95             elsif(Scalar::Util::blessed($err) && $err->isa($pkg)) {
96 7         15 $code = $catch->[$i+1];
97 7         9 while(1) {
98 7         13 my $more = 0;
99 7         14 local($Exception::THROWN, $@);
100 7         13 $_ = $@ = $err;
101 7         15 my $ok = eval {
102 7         17 $@ = $err;
103 7 50       31 if($wantarray) {
    50          
104 0         0 @{$result} = $code->($err,\$more);
  0         0  
105             }
106             elsif(defined($wantarray)) {
107 0         0 @{$result} = ();
  0         0  
108 0         0 $result->[0] = $code->($err,\$more);
109             }
110             else {
111 7         27 $code->($err,\$more);
112             }
113 7         6379 1;
114             };
115 7 50       32 if( $ok ) {
116 7 50       26 next CATCHLOOP if $more;
117 7         17 undef $err;
118             }
119             else {
120 0   0     0 $err = $@ || $Exception::THROWN;
121 0 0       0 $err = objectify($err)
122             unless ref($err);
123             }
124 7         25 last CATCH;
125             };
126             }
127             }
128             }
129              
130             # otherwise
131 1         2 my $owise;
132 1 50       4 if(defined($owise = $clauses->{'otherwise'})) {
133 1         2 my $code = $clauses->{'otherwise'};
134 1         2 my $more = 0;
135 1         1 local($Exception::THROWN, $@);
136 1         2 $_ = $@ = $err;
137 1         2 my $ok = eval {
138 1         2 $@ = $err;
139 1 50       12 if($wantarray) {
    50          
140 0         0 @{$result} = $code->($err,\$more);
  0         0  
141             }
142             elsif(defined($wantarray)) {
143 0         0 @{$result} = ();
  0         0  
144 0         0 $result->[0] = $code->($err,\$more);
145             }
146             else {
147 1         3 $code->($err,\$more);
148             }
149 1         805 1;
150             };
151 1 50       4 if( $ok ) {
152 1         3 undef $err;
153             }
154             else {
155 0   0     0 $err = $@ || $Exception::THROWN;
156              
157 0 0       0 $err = objectify($err)
158             unless ref($err);
159             }
160             }
161             }
162 8         16 undef $_;
163 8         17 undef $@;
164 8         21 return $err;
165             }
166              
167             sub try (&;$) {
168 8     8   18 my $try = shift;
169 8 50       64 my $clauses = @_ ? shift : {};
170 8         38 my $ok = 0;
171 8         14 my $err = undef;
172 8         20 my @result = ();
173              
174 8         16 my $wantarray = wantarray();
175              
176 8         20 do {
177 8         26 local $Exception::THROWN = undef;
178 8         622 local $@ = undef;
179              
180 8         19 $ok = eval {
181 8 50       47 if($wantarray) {
    50          
182 0         0 @result = $try->();
183             }
184             elsif(defined $wantarray) {
185 0         0 $result[0] = $try->();
186             }
187             else {
188 8         27 $try->();
189             }
190 0         0 1;
191             };
192              
193 8 50 33     58 $err = $@ || $Exception::THROWN
194             unless $ok;
195             };
196              
197 8 50       55 $err = run_clauses($clauses,$err,wantarray,@result)
198             unless($ok);
199              
200 8 100       109 $clauses->{'finally'}->()
201             if(defined($clauses->{'finally'}));
202              
203 8 50       473 if (defined($err))
204             {
205 0 0 0     0 if (Scalar::Util::blessed($err) && $err->can('throw'))
206             {
207 0         0 throw $err;
208             }
209             else
210             {
211 0         0 die $err;
212             }
213             }
214              
215 8 50       811 wantarray ? @result : $result[0];
216             }
217              
218             # Each clause adds a sub to the list of clauses. The finally clause is
219             # always the last, and the otherwise clause is always added just before
220             # the finally clause.
221             #
222             # All clauses, except the finally clause, add a sub which takes one argument
223             # this argument will be the error being thrown. The sub will return a code ref
224             # if that clause can handle that error, otherwise undef is returned.
225             #
226             # The otherwise clause adds a sub which unconditionally returns the users
227             # code reference, this is why it is forced to be last.
228             #
229             # The catch clause is defined in Exception.pm, as the syntax causes it to
230             # be called as a method
231              
232             sub with (&;$) {
233             @_
234 9     9   119 }
235              
236             sub using (&;$) {
237             @_
238 1     1   1290 }
239              
240             sub finally (&) {
241 1     1   25 my $code = shift;
242 1         5 my $clauses = { 'finally' => $code };
243 1         6 $clauses;
244             }
245              
246             # The except clause is a block which returns a hashref or a list of
247             # key-value pairs, where the keys are the classes and the values are subs.
248              
249             sub except (&;$) {
250 3     3   7 my $code = shift;
251 3   100     17 my $clauses = shift || {};
252 3   50     33 my $catch = $clauses->{'catch'} ||= [];
253              
254             my $sub = sub {
255 0     0   0 my $ref;
256 0         0 my(@array) = $code->($_[0]);
257 0 0 0     0 if(@array == 1 && ref($array[0])) {
258 0         0 $ref = $array[0];
259 0 0       0 $ref = [ %$ref ]
260             if(UNIVERSAL::isa($ref,'HASH'));
261             }
262             else {
263 0         0 $ref = \@array;
264             }
265 0         0 @$ref
266 3         33 };
267              
268 3         7 unshift @{$catch}, undef, $sub;
  3         89  
269              
270 3         14 $clauses;
271             }
272              
273             sub otherwise (&;$) {
274 3     3   46 my $code = shift;
275 3   100     51 my $clauses = shift || {};
276              
277 3 50       19 if(exists $clauses->{'otherwise'}) {
278 0         0 require Carp;
279 0         0 Carp::croak("Multiple otherwise clauses");
280             }
281              
282 3         11 $clauses->{'otherwise'} = $code;
283              
284 3         14 $clauses;
285             }
286              
287             sub assert($$) {
288 2     2   13 my ($value, $msg) = @_;
289 2 100       14 return $value if $value;
290 1         8 throw AssertionError($msg);
291 0         0 die($msg);
292             }
293              
294             #------------------------------------------------------------------------------
295             package Exception;
296              
297             use overload (
298             '""' => 'stringify',
299             '0+' => 'value',
300 14     14   46 'bool' => sub { return 1; },
301 8         100 'fallback' => 1
302 8     8   14277 );
  8         18271  
303              
304             $Exception::Depth = 0; # Depth to pass to caller()
305             $Exception::Debug = 0; # Generate verbose stack traces
306             $Exception::THROWN = undef; # last error thrown, a workaround until die $ref works
307              
308             my $LAST; # Last error created
309             my %ERROR; # Last error associated with package
310              
311             # Exported subs are defined in errors::subs
312              
313 8     8   1324 use Scalar::Util ();
  8         17  
  8         10601  
314              
315             # I really want to use last for the name of this method, but it is a keyword
316             # which prevent the syntax last Exception
317              
318             sub prior {
319 0     0   0 shift; # ignore
320              
321 0 0       0 return $LAST unless @_;
322              
323 0         0 my $pkg = shift;
324 0 0       0 return exists $ERROR{$pkg} ? $ERROR{$pkg} : undef
    0          
325             unless ref($pkg);
326              
327 0         0 my $obj = $pkg;
328 0         0 my $err = undef;
329 0 0       0 if($obj->isa('HASH')) {
    0          
330 0 0       0 $err = $obj->{'__Error__'}
331             if exists $obj->{'__Error__'};
332             }
333             elsif($obj->isa('GLOB')) {
334 0         0 $err = ${*$obj}{'__Error__'}
  0         0  
335 0 0       0 if exists ${*$obj}{'__Error__'};
336             }
337              
338 0         0 $err;
339             }
340              
341             sub flush {
342 0     0   0 shift; #ignore
343              
344 0 0       0 unless (@_) {
345 0         0 $LAST = undef;
346 0         0 return;
347             }
348              
349 0         0 my $pkg = shift;
350 0 0       0 return unless ref($pkg);
351              
352 0 0       0 undef $ERROR{$pkg} if defined $ERROR{$pkg};
353             }
354              
355             # Return as much information as possible about where the error
356             # happened. The -stacktrace element only exists if $Exception::DEBUG
357             # was set when the error was created
358              
359             sub stacktrace {
360 0     0   0 my $self = shift;
361              
362 0 0       0 return $self->{'-stacktrace'}
363             if exists $self->{'-stacktrace'};
364              
365 0 0       0 my $text = exists $self->{'-text'} ? $self->{'-text'} : "Died";
366              
367 0 0       0 $text .= sprintf(" at %s line %d.\n", $self->file, $self->line)
368             unless($text =~ /\n$/s);
369              
370 0         0 $text;
371             }
372              
373              
374             sub associate {
375 0     0   0 my $err = shift;
376 0         0 my $obj = shift;
377              
378 0 0       0 return unless ref($obj);
379              
380 0 0       0 if($obj->isa('HASH')) {
    0          
381 0         0 $obj->{'__Error__'} = $err;
382             }
383             elsif($obj->isa('GLOB')) {
384 0         0 ${*$obj}{'__Error__'} = $err;
  0         0  
385             }
386 0         0 $obj = ref($obj);
387 0         0 $ERROR{ ref($obj) } = $err;
388              
389 0         0 return;
390             }
391              
392              
393             sub new {
394 8     8   18 my $self = shift;
395 8         59 my($pkg,$file,$line) = caller($Exception::Depth);
396              
397 8 50       665 my $err = bless {
398             '-package' => $pkg,
399             '-file' => $file,
400             '-line' => $line,
401             ((@_ % 2) ? ('-text') : ()),
402             @_
403             }, $self;
404              
405 8 50       496 $err->associate($err->{'-object'})
406             if(exists $err->{'-object'});
407              
408             # To always create a stacktrace would be very inefficient, so
409             # we only do it if $Exception::Debug is set
410              
411 8 50       30 if($Exception::Debug) {
412 0         0 require Carp;
413 0         0 local $Carp::CarpLevel = $Exception::Depth;
414 0 0       0 my $text = defined($err->{'-text'}) ? $err->{'-text'} : "Exception";
415 0         0 my $trace = Carp::longmess($text);
416             # Remove try calls from the trace
417 0         0 $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+errors::subs::try[^\n]+(?=\n)//sog;
418 0         0 $trace =~
419             s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+errors::subs::run_clauses[^\n]+\n\s+errors::subs::try[^\n]+(?=\n)//sog;
420 0         0 $err->{'-stacktrace'} = $trace
421             }
422              
423 8         35 $@ = $LAST = $ERROR{$pkg} = $err;
424             }
425              
426             # Throw an error. this contains some very gory code.
427              
428             sub throw {
429 7     7   915 my $self = shift;
430 7         73 local $Exception::Depth = $Exception::Depth + 1;
431              
432             # if we are not rethrow-ing then create the object to throw
433 7 50       65 $self = $self->new(@_) unless ref($self);
434              
435 7         60 die $Exception::THROWN = $self;
436             }
437              
438             # catch clause for
439             #
440             # try { ... } catch CLASS with { ... }
441              
442             sub catch {
443 10     10   22 my $pkg = shift;
444 10         21 my $code = shift;
445 10   100     52 my $clauses = shift || {};
446 10   100     63 my $catch = $clauses->{'catch'} ||= [];
447              
448 10         27 unshift @$catch, $pkg, $code;
449              
450 10         44 $clauses;
451             }
452              
453             # Object query methods
454              
455             sub object {
456 0     0   0 my $self = shift;
457 0 0       0 exists $self->{'-object'} ? $self->{'-object'} : undef;
458             }
459              
460             sub file {
461 0     0   0 my $self = shift;
462 0 0       0 exists $self->{'-file'} ? $self->{'-file'} : undef;
463             }
464              
465             sub line {
466 0     0   0 my $self = shift;
467 0 0       0 exists $self->{'-line'} ? $self->{'-line'} : undef;
468             }
469              
470             sub text {
471 2     2   814 my $self = shift;
472 2 50       15 exists $self->{'-text'} ? $self->{'-text'} : undef;
473             }
474              
475             # overload methods
476              
477             sub stringify {
478 2     2   1059 my $self = shift;
479 2 50       18 defined $self->{'-text'} ? $self->{'-text'} : "Died";
480             }
481              
482             sub value {
483 0     0     my $self = shift;
484 0 0         exists $self->{'-value'} ? $self->{'-value'} : undef;
485             }
486              
487             #------------------------------------------------------------------------------
488             package RuntimeError;
489             our @ISA = 'Exception';
490              
491             package AssertionError;
492             our @ISA = 'Exception';
493              
494             1;