File Coverage

blib/lib/HTML/Mason/Exceptions.pm
Criterion Covered Total %
statement 137 145 94.4
branch 41 46 89.1
condition 20 23 86.9
subroutine 23 24 95.8
pod 5 15 33.3
total 226 253 89.3


line stmt bran cond sub pod time code
1             package HTML::Mason::Exceptions;
2             $HTML::Mason::Exceptions::VERSION = '1.58';
3 34     34   186 use strict;
  34         56  
  34         845  
4 34     34   158 use warnings;
  34         57  
  34         4343  
5              
6             my %e;
7              
8             BEGIN
9             {
10 34     34   1467 %e = ( 'HTML::Mason::Exception' =>
11             { description => 'generic base class for all Mason exceptions',
12             alias => 'error'},
13              
14             'HTML::Mason::Exception::Abort' =>
15             { isa => 'HTML::Mason::Exception',
16             fields => [qw(aborted_value)],
17             description => 'a component called $m->abort' },
18              
19             'HTML::Mason::Exception::Decline' =>
20             { isa => 'HTML::Mason::Exception',
21             fields => [qw(declined_value)],
22             description => 'a component called $m->decline' },
23              
24             'HTML::Mason::Exception::Compiler' =>
25             { isa => 'HTML::Mason::Exception',
26             alias => 'compiler_error',
27             description => 'error thrown from the compiler' },
28              
29             'HTML::Mason::Exception::Compilation' =>
30             { isa => 'HTML::Mason::Exception',
31             alias => 'compilation_error',
32             fields => [qw(filename)],
33             description => "error thrown in eval of the code for a component" },
34              
35             'HTML::Mason::Exception::Compilation::IncompatibleCompiler' =>
36             { isa => 'HTML::Mason::Exception::Compilation',
37             alias => 'wrong_compiler_error',
38             description => "a component was compiled by a compiler/lexer with incompatible options. recompilation is needed" },
39              
40             'HTML::Mason::Exception::Params' =>
41             { isa => 'HTML::Mason::Exception',
42             alias => 'param_error',
43             description => 'invalid parameters were given to a method/function' },
44              
45             'HTML::Mason::Exception::Syntax' =>
46             { isa => 'HTML::Mason::Exception',
47             alias => 'syntax_error',
48             fields => [qw(source_line comp_name line_number)],
49             description => 'invalid syntax was found in a component' },
50              
51             'HTML::Mason::Exception::System' =>
52             { isa => 'HTML::Mason::Exception',
53             alias => 'system_error',
54             description => 'a system call of some sort failed' },
55              
56             'HTML::Mason::Exception::TopLevelNotFound' =>
57             { isa => 'HTML::Mason::Exception',
58             alias => 'top_level_not_found_error',
59             description => 'the top level component could not be found' },
60              
61             'HTML::Mason::Exception::VirtualMethod' =>
62             { isa => 'HTML::Mason::Exception',
63             alias => 'virtual_error',
64             description => 'a virtual method was not overridden' },
65              
66             );
67             }
68              
69 34     34   18697 use Exception::Class (%e);
  34         220858  
  34         316  
70              
71             HTML::Mason::Exception->Trace(1);
72              
73             # To avoid circular reference between exception and request.
74             HTML::Mason::Exception->NoRefs(1);
75              
76             # The import() method allows this:
77             # use HTML::Mason::Exceptions(abbr => ['error1', 'error2', ...]);
78             # ...
79             # error1 "something went wrong";
80              
81             sub import
82             {
83 381     381   1422 my ($class, %args) = @_;
84              
85 381         1947 my $caller = caller;
86 381 100       1353 if ($args{abbr})
87             {
88 377         572 foreach my $name (@{$args{abbr}})
  377         1025  
89             {
90 34     34   97894 no strict 'refs';
  34         75  
  34         2668  
91 917 50       1239 die "Unknown exception abbreviation '$name'" unless defined &{$name};
  917         2653  
92 917         1304 *{"${caller}::$name"} = \&{$name};
  917         4373  
  917         1864  
93             }
94             }
95             {
96 34     34   206 no strict 'refs';
  34         81  
  34         3943  
  381         718  
97 381         646 *{"${caller}::isa_mason_exception"} = \&isa_mason_exception;
  381         1469  
98 381         757 *{"${caller}::rethrow_exception"} = \&rethrow_exception;
  381         21730  
99             }
100             }
101              
102             sub isa_mason_exception
103             {
104 1297     1297 0 2583 my ($err, $name) = @_;
105 1297 100       2501 return unless defined $err;
106              
107 1157 100       2647 $name = $name ? "HTML::Mason::Exception::$name" : "HTML::Mason::Exception";
108 34     34   207 no strict 'refs';
  34         67  
  34         5015  
109 1157 50       7122 die "no such exception class $name" unless $name->isa('HTML::Mason::Exception');
110              
111 1157         5352 return UNIVERSAL::isa($err, $name);
112             }
113              
114             sub rethrow_exception
115             {
116 2022     2022 0 5287 my ($err) = @_;
117 2022 100       7881 return unless $err;
118              
119 512 100       3272 if ( UNIVERSAL::can($err, 'rethrow') ) {
    100          
120 469         951 $err->rethrow;
121             }
122             elsif ( ref $err ) {
123 1         6 die $err;
124             }
125 42         232 HTML::Mason::Exception->throw(error => $err);
126             }
127              
128             package HTML::Mason::Exception;
129             $HTML::Mason::Exception::VERSION = '1.58';
130             use HTML::Mason::MethodMaker
131 34     34   12184 ( read_write => [ qw ( format ) ] );
  34         87  
  34         218  
132              
133             sub new
134             {
135 140     140 1 1355 my ($class, %params) = @_;
136              
137 140         533 my $self = $class->SUPER::new(%params);
138 140         129018 $self->format('text');
139 140         1231 return $self;
140             }
141              
142             # If we create a new exception from a Mason exception, just use the
143             # short error message, not the stringified exception. Otherwise
144             # exceptions can get stringified more than once.
145             sub throw
146             {
147 140     140 1 747 my $class = shift;
148 140 100       670 my %params = @_ == 1 ? ( error => $_[0] ) : @_;
149              
150 140 100       443 if (HTML::Mason::Exceptions::isa_mason_exception($params{error})) {
151 12         50 $params{error} = $params{error}->error;
152             }
153 140 50       527 if (HTML::Mason::Exceptions::isa_mason_exception($params{message})) {
154 0         0 $params{message} = $params{message}->error;
155             }
156 140         861 $class->SUPER::throw(%params);
157             }
158              
159             sub filtered_frames
160             {
161 99     99 0 213 my ($self) = @_;
162              
163 99         151 my (@frames);
164 99         452 my $trace = $self->trace;
165 99         602 my %ignore_subs = map { $_ => 1 }
  792         1608  
166             qw[
167             (eval)
168             Exception::Class::Base::throw
169             Exception::Class::__ANON__
170             HTML::Mason::Commands::__ANON__
171             HTML::Mason::Component::run
172             HTML::Mason::Exception::throw
173             HTML::Mason::Exceptions::__ANON__
174             HTML::Mason::Request::_run_comp
175             ];
176 99         441 while (my $frame = $trace->next_frame)
177             {
178 1555 100       142997 last if ($frame->subroutine eq 'HTML::Mason::Request::exec');
179 1504 100 100     7816 unless ($frame->filename =~ /Mason\/Exceptions\.pm/ or
      100        
      100        
180             $ignore_subs{ $frame->subroutine } or
181             ($frame->subroutine eq 'HTML::Mason::Request::comp' and $frame->filename =~ /Request\.pm/)) {
182 818         10756 push(@frames, $frame);
183             }
184             }
185 99 100       1085 @frames = grep { $_->filename !~ /Mason\/Exceptions\.pm/ } $trace->frames if !@frames;
  300         1243  
186 99         475 return @frames;
187             }
188              
189             sub analyze_error
190             {
191 100     100 0 217 my ($self) = @_;
192 100         167 my ($file, @lines, @frames);
193              
194 100 100       269 return $self->{_info} if $self->{_info};
195              
196 99         295 @frames = $self->filtered_frames;
197 99 100       993 if ($self->isa('HTML::Mason::Exception::Syntax')) {
    100          
    50          
198 16         356 $file = $self->comp_name;
199 16         293 push(@lines, $self->line_number);
200             } elsif ($self->isa('HTML::Mason::Exception::Compilation')) {
201 24         612 $file = $self->filename;
202 24         151 my $msg = $self->full_message;
203 24         578 while ($msg =~ /at .* line (\d+)./g) {
204 33         153 push(@lines, $1);
205             }
206             } elsif (@frames) {
207 59         181 $file = $frames[0]->filename;
208 59         341 @lines = $frames[0]->line;
209             }
210 99         391 my @context;
211 99 50       482 @context = $self->get_file_context($file, \@lines) if @lines;
212              
213             $self->{_info} = {
214 99         579 file => $file,
215             frames => \@frames,
216             lines => \@lines,
217             context => \@context,
218             };
219 99         291 return $self->{_info};
220             }
221              
222             sub get_file_context
223             {
224 99     99 0 281 my ($self, $file, $line_nums) = @_;
225              
226 99         165 my @context;
227 99         148 my $fh = do { local *FH; *FH; };
  99         256  
  99         396  
228 99 100 100     3120 unless (defined($file) and open($fh, $file)) {
229 15         65 @context = (['unable to open file', '']);
230             } else {
231             # Put the file into a list, indexed at 1.
232 84         13965 my @file = <$fh>;
233 84         1538 chomp(@file);
234 84         478 unshift(@file, undef);
235              
236             # Mark the important context lines.
237             # We do this by going through the error lines and incrementing hash keys to
238             # keep track of which lines we eventually need to print, and we color the
239             # line which the error actually occured on in red.
240 84         195 my (%marks, %red);
241 84         159 my $delta = 4;
242 84         221 foreach my $line_num (@$line_nums) {
243 89         324 foreach my $l (($line_num - $delta) .. ($line_num + $delta)) {
244 801 100 100     2090 next if ($l <= 0 or $l > @file);
245 499         1070 $marks{$l}++;
246             }
247 89         228 $red{$line_num} = 1;
248             }
249              
250             # Create the context list.
251             # By going through the keys of the %marks hash, we can tell which lines need
252             # to be printed. We add a '...' line if we skip numbers in the context.
253 84         156 my $last_num = 0;
254 84         466 foreach my $line_num (sort { $a <=> $b } keys %marks) {
  869         1243  
255 480 100       879 push(@context, ["...", "", 0]) unless $last_num == ($line_num - 1);
256 480         1023 push(@context, ["$line_num:", $file[$line_num], $red{$line_num}]);;
257 480         729 $last_num = $line_num;
258             }
259 84 100       270 push(@context, ["...", "", 0]) unless $last_num == @file;
260 84         2301 close $fh;
261             }
262 99         518 return @context;
263             }
264              
265             # basically the same as as_string in Exception::Class::Base
266             sub raw_text
267             {
268 2     2 0 16 my ($self) = @_;
269              
270 2         8 return $self->full_message . "\n\n" . $self->trace->as_string;
271             }
272              
273             sub as_string
274             {
275 103     103 1 600 my ($self) = @_;
276              
277 103         286 my $stringify_function = "as_" . $self->{format};
278 103         413 return $self->$stringify_function();
279             }
280              
281             sub as_brief
282             {
283 1     1 0 3 my ($self) = @_;
284 1         3 return $self->full_message;
285             }
286              
287             sub as_line
288             {
289 0     0 0 0 my ($self) = @_;
290 0         0 my $info = $self->analyze_error;
291              
292 0         0 (my $msg = $self->full_message) =~ s/\n/\t/g;
293 0         0 my $stack = join(", ", map { sprintf("[%s:%d]", $_->filename, $_->line) } @{$info->{frames}});
  0         0  
  0         0  
294 0         0 return sprintf("%s\tStack: %s\n", $msg, $stack);
295             }
296              
297             sub as_text
298             {
299 98     98 0 195 my ($self) = @_;
300 98         305 my $info = $self->analyze_error;
301              
302 98         491 my $msg = $self->full_message;
303 98         1589 my $stack = join("\n", map { sprintf(" [%s:%d]", $_->filename, $_->line) } @{$info->{frames}});
  1125         7687  
  98         270  
304 98         2127 return sprintf("%s\nStack:\n%s\n", $msg, $stack);
305             }
306              
307             sub as_html
308             {
309 2     2 0 5 my ($self) = @_;
310              
311 2         3 my $out;
312 2         10 my $interp = HTML::Mason::Interp->new(out_method => \$out);
313              
314 2         8 my $comp = $interp->make_component(comp_source => <<'EOF');
315              
316             <%args>
317             $msg
318             $info
319             $error
320            
321             <%filter>
322             s/(]+>)/$1/g;
323             s/<\/td>/<\/font><\/td>/g;
324            
325              
326             % HTML::Mason::Escapes::basic_html_escape(\$msg);
327             % $msg =~ s/\n/
/g;
328              
329            
330              
331            

System error

332            
333            
334             error: 
335             <% $msg %>
336            
337            
338             context: 
339            
340            
341              
342             % foreach my $entry (@{$info->{context}}) {
343             % my ($line_num, $line, $highlight) = @$entry;
344             % $line = '' unless defined $line;
345             % HTML::Mason::Escapes::basic_html_escape(\$line);
346            
347             <% $line_num %> 
348             <% $highlight ? "" : "" %><% $line %><% $highlight ? "" : "" %>
349            
350              
351             % }
352              
353            
354            
355            
356            
357             code stack: 
358            
359             % foreach my $frame (@{$info->{frames}}) {
360             % my $f = $frame->filename; HTML::Mason::Escapes::basic_html_escape(\$f);
361             % my $l = $frame->line; HTML::Mason::Escapes::basic_html_escape(\$l);
362             <% $f %>:<% $l %>
363             % }
364            
365            
366            
367              
368             raw error
369              
370            
371            
372            
373            
374            
375            
376            
377            
378            
379            
380            
381            
382            
383            
384            
385            
386            
387            
388            
389            
390            
391            
392            
393            
394            
395            
396            
397            
398            
399            
400              
401             % my $raw = $error->raw_text;
402             % HTML::Mason::Escapes::basic_html_escape(\$raw);
403             % $raw =~ s/\t//g;
404              
405            
406              
407            
<% $raw %>
408              
409            
410             EOF
411              
412 2         9 $interp->exec($comp,
413             msg => $self->full_message,
414             info => $self->analyze_error,
415             error => $self);
416              
417 2         72 return $out;
418             }
419              
420             package HTML::Mason::Exception::Compilation;
421             $HTML::Mason::Exception::Compilation::VERSION = '1.58';
422             sub full_message
423             {
424 48     48 1 82 my $self = shift;
425              
426 48   100     872 return sprintf("Error during compilation of %s:\n%s\n", $self->filename || '', $self->message || '');
      50        
427             }
428              
429             package HTML::Mason::Exception::Syntax;
430             $HTML::Mason::Exception::Syntax::VERSION = '1.58';
431             sub full_message
432             {
433 16     16 1 31 my $self = shift;
434              
435 16   50     89 return sprintf("%s at %s line %d", $self->message || '', $self->comp_name || '', $self->line_number);
      50        
436             }
437              
438             1;
439              
440             __END__