File Coverage

blib/lib/Class/Throwable.pm
Criterion Covered Total %
statement 108 108 100.0
branch 49 50 98.0
condition 14 22 63.6
subroutine 19 19 100.0
pod 10 10 100.0
total 200 209 95.6


line stmt bran cond sub pod time code
1              
2             package Class::Throwable;
3              
4 6     6   148314 use strict;
  6         15  
  6         252  
5 6     6   33 use warnings;
  6         11  
  6         307  
6              
7             our $VERSION = '0.11';
8              
9 6     6   36 use Scalar::Util qw(blessed);
  6         18  
  6         2108  
10              
11             our $DEFAULT_VERBOSITY = 1;
12              
13             my %VERBOSITY;
14              
15             # allow the creation of exceptions
16             # without having to actually create
17             # a package for them
18             sub import {
19 15     15   64889 my $class = shift;
20 15 100       89 return unless @_;
21 13 100       59 if ($_[0] eq 'VERBOSE') {
    100          
22 5 100       25 (defined $_[1]) || die "You must specify a level of verbosity with Class::Throwable\n";
23             # make sure its not a refernce
24 4   33     118 $class = ref($class) || $class;
25             # and then store it
26 4         24 $VERBOSITY{$class} = $_[1];
27             }
28             elsif ($_[0] eq 'retrofit') {
29 3 100       25 (defined $_[1]) || die "You must specify a module for Class::Throwable to retrofit\n";
30 2         4 my $package = $_[1];
31 2     1   12 my $retrofitter = sub { Class::Throwable->throw(@_) };
  1         19  
32 2 100 66     18 $retrofitter = $_[2] if defined $_[2] && ref($_[2]) eq 'CODE';
33 2         6 eval {
34 6     6   53 no strict 'refs';
  6         10  
  6         1131  
35 2         3 *{"${package}::die"} = $retrofitter;
  2         29  
36             };
37 2 50       25 die "Could not retrofit '$package' with Class::Throwable : $@\n" if $@;
38             }
39             else {
40 5 100       23 ($class eq 'Class::Throwable')
41             || die "Inline Exceptions can only be created with Class::Throwable\n";
42 4         13 my @exceptions = @_;
43 4         10 foreach my $exception (@exceptions) {
44 7 100       38 next unless $exception;
45 4         541 eval "package ${exception}; \@${exception}::ISA = qw(Class::Throwable);";
46 4 100       63 die "An error occured while constructing Class::Throwable exception ($exception) : $@\n" if $@;
47             }
48             }
49             }
50              
51             # overload the stringify operation
52 6     6   8573 use overload q|""| => "toString", fallback => 1;
  6         7484  
  6         44  
53              
54             # a class method to set the verbosity
55             # of inline exceptions
56             sub setVerbosity {
57 3     3 1 1651 my ($class, $verbosity) = @_;
58 3 100       547 (!ref($class)) || die "setVerbosity is a class method only, it cannot be used on an instance\n";
59 2 100       13 (defined($verbosity)) || die "You must specify a level of verbosity with Class::Throwable\n";
60 1         4 $VERBOSITY{$class} = $verbosity;
61             }
62              
63             # create an exception without
64             # any stack trace information
65             sub new {
66 2     2 1 685 my ($class, $message, $sub_exception) = @_;
67 2         5 my $exception = {};
68 2   33     18 bless($exception, ref($class) || $class);
69 2         10 $exception->_init($message, $sub_exception);
70 2         6 return $exception;
71             }
72              
73             # throw an exception with this
74             sub throw {
75 19     19 1 3289 my ($class, $message, $sub_exception) = @_;
76             # if i am being re-thrown, then just die with the class
77 19 100 66     125 if (blessed($class) && $class->isa("Class::Throwable")) {
78             # first make sure we have a stack trace, if we
79             # don't then we were likely created with 'new'
80             # and not 'throw', and so we need to gather the
81             # stack information from here
82 2 100       7 $class->_initStackTrace() unless my @s = $class->getStackTrace();
83 2         13 die $class;
84             }
85             # otherwise i am being thrown for the first time so
86             # create a new 'me' and then die after i am blessed
87 17         31 my $exception = {};
88 17         45 bless($exception, $class);
89 17         66 $exception->_init($message, $sub_exception);
90             # init our stack trace
91 17         62 $exception->_initStackTrace();
92 17         89 die $exception;
93             }
94              
95             ## initializers
96              
97             sub _init {
98 19     19   39 my ($self, $message, $sub_exception) = @_;
99             # the sub-exception is another exception
100             # which has already been caught, and is
101             # the cause of this exception being thrown
102             # so we dont want to loose that information
103             # so we store it here
104             # NOTE:
105             # we do not enforce the type of exception here
106             # becuase it is possible this was thrown by
107             # perl itself and therefore could be a string
108 19         458 $self->{sub_exception} = $sub_exception;
109 19   66     102 $self->{message} = $message || "An ". ref($self) . " Exception has been thrown";
110 19         50 $self->{stack_trace} = [];
111             }
112              
113             sub _initStackTrace {
114 18     18   34 my ($self) = @_;
115 18         25 my @stack_trace;
116             # these are the 10 values returned from caller():
117             # $package, $filename, $line, $subroutine, $hasargs,
118             # $wantarray, $evaltext, $is_require, $hints, $bitmask
119             # we do not bother to capture the last two as they are
120             # subject to change and not meant for internal use
121             {
122 18         21 package DB;
123 18         25 my $i = 1;
124 18         26 my @c;
125 18         192 while (@c = caller($i++)) {
126             # dont bother to get our caller
127 51 100       318 next if $c[3] =~ /Class\:\:Throwable\:\:throw/;
128 33         267 push @stack_trace, [ @c[0 .. 7] ];
129             }
130             }
131 18         49 $self->{stack_trace} = \@stack_trace;
132             }
133              
134             # accessors
135              
136             sub hasSubException {
137 15     15 1 1931 my ($self) = @_;
138 15 100       80 return defined $self->{sub_exception} ? 1 : 0;
139             }
140              
141             sub getSubException {
142 8     8 1 14 my ($self) = @_;
143 8         34 return $self->{sub_exception};
144             }
145              
146             sub getMessage {
147 9     9 1 4779 my ($self) = @_;
148 9         55 return $self->{"message"};
149             }
150              
151             sub getStackTrace {
152 6     6 1 323 my ($self) = @_;
153             return wantarray ?
154 6 100       24 @{$self->{stack_trace}}
  4         26  
155             :
156             $self->{stack_trace};
157             }
158              
159             sub stackTraceToString {
160 12     12 1 38 my ($self, $depth) = @_;
161 12         14 my @output;
162 12   100     31 $depth ||= 1;
163 12         41 my $indent = " " x $depth;
164 12         24 foreach my $frame (@{$self->{stack_trace}}) {
  12         32  
165 21         20 my ($package, $filename, $line, $subroutine) = @{$frame};
  21         65  
166 21 100       66 $subroutine = "${package}::${subroutine}" if ($subroutine eq '(eval)');
167 21         82 push @output, "$indent|--[ $subroutine called in $filename line $line ]"
168             }
169 12         71 return (join "\n" => @output);
170             }
171              
172             sub toString {
173 16     16 1 1223 my ($self, $verbosity, $depth) = @_;
174 16 100       44 unless (defined $verbosity) {
175 9 100       29 if (exists $VERBOSITY{ref($self)}) {
176 7         15 $verbosity = $VERBOSITY{ref($self)};
177             }
178             else {
179 2         3 $verbosity = $DEFAULT_VERBOSITY;
180             }
181             }
182             # get out of here quick if
183             # exception handling is off
184 16 100       649 return "" if $verbosity <= 0;
185             # otherwise construct our output
186 15         50 my $output = ref($self) . " : " . $self->{"message"};
187             # if we VERBOSE is set to 1, then
188             # we just return the message
189 15 100       53 return $output if $verbosity <= 1;
190 11   100     40 $depth ||= 1;
191 11 100       61 if ($depth > 1) {
192 4         16 $output = (" " x ($depth - 1)) . "+ $output";
193 4         6 $depth++;
194             }
195             # however, if VERBOSE is 2 or above
196             # then we include the stack trace
197 11         40 $output .= "\n" . (join "\n" => $self->stackTraceToString($depth)) . "\n";
198             # now we gather any sub-exceptions too
199 11 100       38 if ($self->hasSubException()) {
200 5         13 my $e = $self->getSubException();
201             # make sure the sub-exception is one
202             # of our objects, and ....
203 5 100 66     46 if (blessed($e) && $e->isa("Class::Throwable")) {
204             # deal with it appropriately
205 4         37 $output .= $e->toString($verbosity, $depth + 1);
206             }
207             # otherwise ...
208             else {
209             # just stringify it
210 1         5 $output .= (" " x ($depth)) . "+ $e";
211             }
212             }
213 11         60 return $output;
214             }
215              
216             sub stringValue {
217 6     6 1 441 my ($self) = @_;
218 6         20 return overload::StrVal($self);
219             }
220              
221             1;
222              
223             __END__