File Coverage

lib/Badger/Exception.pm
Criterion Covered Total %
statement 57 62 91.9
branch 26 40 65.0
condition 16 23 69.5
subroutine 10 11 90.9
pod 9 9 100.0
total 118 145 81.3


line stmt bran cond sub pod time code
1             #========================================================================
2             #
3             # Badger::Exception
4             #
5             # DESCRIPTION
6             # Module implementing an exception class for reporting structured
7             # errors.
8             #
9             # AUTHOR
10             # Andy Wardley
11             #
12             #========================================================================
13              
14             package Badger::Exception;
15              
16             use Badger::Class
17             base => 'Badger::Base',
18             version => 0.01,
19             debug => 0,
20             mutators => 'type',
21             accessors => 'stack',
22             constants => 'TRUE ARRAY HASH DELIMITER',
23             import => 'class',
24             as_text => 'text', # overloaded text stringification
25             is_true => 1, # always evaluates to a true value
26             exports => {
27             hooks => {
28             trace => [
29             # args are ($self, $target, $symbol, $value)
30 1         3 sub { $TRACE = $_[3] },
31             # expects one value argument
32             1
33             ],
34             colour => [
35             # args are ($self, $target, $symbol, $value)
36 0         0 sub { $COLOUR = $_[3] },
37             # expects one value argument
38 70         974 1
39             ],
40             },
41             },
42             messages => {
43             caller => "<4> called from <1>\n in <2> at line <3>",
44 70     70   500 };
  70         119  
45              
46 70     70   509 use Badger::Rainbow ANSI => 'cyan yellow green';
  70         135  
  70         396  
47             our $FORMAT = ' error - ' unless defined $FORMAT;
48             our $TYPE = 'undef' unless defined $TYPE;
49             our $INFO = 'no information' unless defined $INFO;
50             our $ANON = 'unknown' unless defined $ANON;
51             our $TRACE = 0 unless defined $TRACE;
52             our $COLOUR = 0 unless defined $COLOUR;
53              
54              
55             sub init {
56 69     69 1 162 my ($self, $config) = @_;
57 69   66     1061 $self->{ type } = $config->{ type } || $self->class->any_var('TYPE');
58 69   100     268 $self->{ info } = $config->{ info } || '';
59 69         124 $self->{ file } = $config->{ file };
60 69         127 $self->{ line } = $config->{ line };
61             # watch out for the case where 'trace' is set explicitly to 0
62             $self->{ trace } =
63             exists $config->{ trace }
64             ? $config->{ trace }
65 69 100       184 : $TRACE;
66              
67 69         151 return $self;
68             }
69              
70              
71             sub info {
72 28     28 1 47 my $self = shift;
73             return @_
74             ? ($self->{ info } = shift)
75 28 100 66     220 : ($self->{ info } || $INFO);
76             }
77              
78              
79             sub file {
80 3     3 1 5 my $self = shift;
81             return @_
82             ? ($self->{ file } = shift)
83 3 50 66     18 : ($self->{ file } || $ANON);
84             }
85              
86              
87             sub line {
88 3     3 1 6 my $self = shift;
89             return @_
90             ? ($self->{ line } = shift)
91 3 50 66     17 : ($self->{ line } || $ANON);
92             }
93              
94              
95             sub text {
96 34     34 1 91 my $self = shift;
97 34   66     150 my $text = shift || $self->class->any_var('FORMAT');
98              
99             # TODO: extend Badger::Utils::xprintf to handle this
100 34 50       270 $text =~ s/<(\w+)>/defined $self->{ $1 } ? $self->{ $1 } : "(no $1)"/eg;
  68         387  
101              
102             # TODO: not sure we should add file and line automatically - better to
103             # leave it up to the $FORMAT
104 34 50       112 $text .= " in $self->{ file }" if $self->{ file };
105 34 50       80 $text .= " at line $self->{ line }" if $self->{ line };
106              
107 34 100 66     106 if ($self->{ trace } && (my $trace = $self->stack_trace)) {
108 2         9 $text .= "\n" . $trace;
109             }
110              
111 34         244 return $text;
112             }
113              
114              
115             sub stack_trace {
116 2     2 1 13 my $self = shift;
117 2         4 my @lines;
118              
119 2 50       12 if (my $stack = $self->{ stack }) {
120 2         6 foreach my $caller (@$stack) {
121 7 50       28 my @args = $COLOUR
122             ? (
123             cyan($caller->[0]),
124             cyan($caller->[1]),
125             yellow($caller->[2]),
126             yellow($caller->[3]),
127             )
128             : @$caller;
129 7         31 push(@lines, $self->message( caller => @args ));
130             }
131             }
132              
133 2         14 return join("\n", @lines);
134             }
135              
136              
137             sub trace {
138 0     0 1 0 my $self = shift;
139 0 0       0 if (ref $self) {
140             return @_
141             ? ($self->{ trace } = shift )
142 0 0       0 : $self->{ trace };
143             }
144             else {
145             return @_
146 0 0       0 ? $self->class->var( TRACE => shift )
147             : $self->class->var('TRACE');
148             }
149             }
150              
151             sub throw {
152 63     63 1 117 my $self = shift;
153              
154             # save relevant information from caller stack for enhanced debugging,
155             # but only the first time the exception is thrown
156 63 100 66     205 if ($self->{ trace } && ! $self->{ stack }) {
157 2         4 my @stack;
158 2         4 my $i = 1;
159 2         4 while (1) {
160 9         54 my @info = caller($i++);
161 9 100       25 last unless @info;
162 7         14 push(@stack, \@info);
163             }
164 2         7 $self->{ stack } = \@stack;
165             }
166              
167 63         517 die $self;
168             }
169              
170              
171              
172              
173             #------------------------------------------------------------------------
174             # match_type(@types)
175             #
176             # Selects the most appropriate handler for the current exception type,
177             # from the list of types passed in as arguments. The method returns the
178             # item which is an exact match for type or the closest, more
179             # generic handler (e.g. foo being more generic than foo.bar, etc.)
180             #------------------------------------------------------------------------
181              
182             sub match_type {
183 7     7 1 12 my $self = shift;
184 7 100       19 my $types = @_ == 1 ? shift : [@_];
185 7         24 my $type = $self->{ type };
186              
187 7 100       28 $types = [ split(DELIMITER, $types) ]
188             unless ref $types;
189              
190 7 100       21 $types = { map { $_ => $_ } @$types }
  24         47  
191             if ref $types eq ARRAY;
192              
193 7 50       17 return $self->error( invalid => 'type match' => $types )
194             unless ref $types eq HASH;
195              
196 7         14 while ($type) {
197             return $types->{ $type }
198 16 100       70 if $types->{ $type };
199              
200             # strip .element from the end of the exception type to find a
201             # more generic handler
202 10         59 $type =~ s/\.?[^\.]*$//;
203             }
204              
205 1         9 return undef;
206             }
207              
208              
209              
210             1;
211             __END__