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.60';
3 34     34   226 use strict;
  34         64  
  34         905  
4 34     34   171 use warnings;
  34         59  
  34         5890  
5              
6             my %e;
7              
8             BEGIN
9             {
10 34     34   1923 %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   17067 use Exception::Class (%e);
  34         300955  
  34         323  
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   1687 my ($class, %args) = @_;
84              
85 381         1606 my $caller = caller;
86 381 100       1505 if ($args{abbr})
87             {
88 377         629 foreach my $name (@{$args{abbr}})
  377         1091  
89             {
90 34     34   118194 no strict 'refs';
  34         92  
  34         3274  
91 917 50       1341 die "Unknown exception abbreviation '$name'" unless defined &{$name};
  917         2998  
92 917         1299 *{"${caller}::$name"} = \&{$name};
  917         5693  
  917         2012  
93             }
94             }
95             {
96 34     34   257 no strict 'refs';
  34         89  
  34         5406  
  381         789  
97 381         701 *{"${caller}::isa_mason_exception"} = \&isa_mason_exception;
  381         1666  
98 381         843 *{"${caller}::rethrow_exception"} = \&rethrow_exception;
  381         27889  
99             }
100             }
101              
102             sub isa_mason_exception
103             {
104 1297     1297 0 2604 my ($err, $name) = @_;
105 1297 100       2660 return unless defined $err;
106              
107 1157 100       2691 $name = $name ? "HTML::Mason::Exception::$name" : "HTML::Mason::Exception";
108 34     34   251 no strict 'refs';
  34         77  
  34         6612  
109 1157 50       7171 die "no such exception class $name" unless $name->isa('HTML::Mason::Exception');
110              
111 1157         5996 return UNIVERSAL::isa($err, $name);
112             }
113              
114             sub rethrow_exception
115             {
116 2022     2022 0 5711 my ($err) = @_;
117 2022 100       8650 return unless $err;
118              
119 512 100       3610 if ( UNIVERSAL::can($err, 'rethrow') ) {
    100          
120 469         1082 $err->rethrow;
121             }
122             elsif ( ref $err ) {
123 1         7 die $err;
124             }
125 42         280 HTML::Mason::Exception->throw(error => $err);
126             }
127              
128             package HTML::Mason::Exception;
129             $HTML::Mason::Exception::VERSION = '1.60';
130             use HTML::Mason::MethodMaker
131 34     34   16124 ( read_write => [ qw ( format ) ] );
  34         90  
  34         288  
132              
133             sub new
134             {
135 140     140 1 1650 my ($class, %params) = @_;
136              
137 140         634 my $self = $class->SUPER::new(%params);
138 140         237646 $self->format('text');
139 140         1536 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 1024 my $class = shift;
148 140 100       885 my %params = @_ == 1 ? ( error => $_[0] ) : @_;
149              
150 140 100       494 if (HTML::Mason::Exceptions::isa_mason_exception($params{error})) {
151 12         60 $params{error} = $params{error}->error;
152             }
153 140 50       616 if (HTML::Mason::Exceptions::isa_mason_exception($params{message})) {
154 0         0 $params{message} = $params{message}->error;
155             }
156 140         1212 $class->SUPER::throw(%params);
157             }
158              
159             sub filtered_frames
160             {
161 99     99 0 220 my ($self) = @_;
162              
163 99         183 my (@frames);
164 99         537 my $trace = $self->trace;
165 99         671 my %ignore_subs = map { $_ => 1 }
  792         2044  
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         536 while (my $frame = $trace->next_frame)
177             {
178 1555 100       160242 last if ($frame->subroutine eq 'HTML::Mason::Request::exec');
179 1504 100 100     7937 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         12094 push(@frames, $frame);
183             }
184             }
185 99 100       1304 @frames = grep { $_->filename !~ /Mason\/Exceptions\.pm/ } $trace->frames if !@frames;
  300         1508  
186 99         573 return @frames;
187             }
188              
189             sub analyze_error
190             {
191 100     100 0 232 my ($self) = @_;
192 100         180 my ($file, @lines, @frames);
193              
194 100 100       335 return $self->{_info} if $self->{_info};
195              
196 99         369 @frames = $self->filtered_frames;
197 99 100       1142 if ($self->isa('HTML::Mason::Exception::Syntax')) {
    100          
    50          
198 16         452 $file = $self->comp_name;
199 16         378 push(@lines, $self->line_number);
200             } elsif ($self->isa('HTML::Mason::Exception::Compilation')) {
201 24         646 $file = $self->filename;
202 24         201 my $msg = $self->full_message;
203 24         633 while ($msg =~ /at .* line (\d+)./g) {
204 33         165 push(@lines, $1);
205             }
206             } elsif (@frames) {
207 59         196 $file = $frames[0]->filename;
208 59         411 @lines = $frames[0]->line;
209             }
210 99         471 my @context;
211 99 50       581 @context = $self->get_file_context($file, \@lines) if @lines;
212              
213             $self->{_info} = {
214 99         751 file => $file,
215             frames => \@frames,
216             lines => \@lines,
217             context => \@context,
218             };
219 99         380 return $self->{_info};
220             }
221              
222             sub get_file_context
223             {
224 99     99 0 286 my ($self, $file, $line_nums) = @_;
225              
226 99         171 my @context;
227 99         172 my $fh = do { local *FH; *FH; };
  99         275  
  99         529  
228 99 100 100     4467 unless (defined($file) and open($fh, $file)) {
229 15         91 @context = (['unable to open file', '']);
230             } else {
231             # Put the file into a list, indexed at 1.
232 84         21948 my @file = <$fh>;
233 84         2048 chomp(@file);
234 84         749 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         192 my (%marks, %red);
241 84         169 my $delta = 4;
242 84         282 foreach my $line_num (@$line_nums) {
243 89         350 foreach my $l (($line_num - $delta) .. ($line_num + $delta)) {
244 801 100 100     2450 next if ($l <= 0 or $l > @file);
245 499         1381 $marks{$l}++;
246             }
247 89         283 $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         192 my $last_num = 0;
254 84         620 foreach my $line_num (sort { $a <=> $b } keys %marks) {
  853         1438  
255 480 100       1037 push(@context, ["...", "", 0]) unless $last_num == ($line_num - 1);
256 480         1286 push(@context, ["$line_num:", $file[$line_num], $red{$line_num}]);;
257 480         830 $last_num = $line_num;
258             }
259 84 100       347 push(@context, ["...", "", 0]) unless $last_num == @file;
260 84         3665 close $fh;
261             }
262 99         696 return @context;
263             }
264              
265             # basically the same as as_string in Exception::Class::Base
266             sub raw_text
267             {
268 2     2 0 19 my ($self) = @_;
269              
270 2         7 return $self->full_message . "\n\n" . $self->trace->as_string;
271             }
272              
273             sub as_string
274             {
275 103     103 1 774 my ($self) = @_;
276              
277 103         307 my $stringify_function = "as_" . $self->{format};
278 103         514 return $self->$stringify_function();
279             }
280              
281             sub as_brief
282             {
283 1     1 0 3 my ($self) = @_;
284 1         4 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 232 my ($self) = @_;
300 98         392 my $info = $self->analyze_error;
301              
302 98         761 my $msg = $self->full_message;
303 98         2010 my $stack = join("\n", map { sprintf(" [%s:%d]", $_->filename, $_->line) } @{$info->{frames}});
  1125         9088  
  98         287  
304 98         2533 return sprintf("%s\nStack:\n%s\n", $msg, $stack);
305             }
306              
307             sub as_html
308             {
309 2     2 0 7 my ($self) = @_;
310              
311 2         5 my $out;
312 2         12 my $interp = HTML::Mason::Interp->new(out_method => \$out);
313              
314 2         26 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         135 return $out;
418             }
419              
420             package HTML::Mason::Exception::Compilation;
421             $HTML::Mason::Exception::Compilation::VERSION = '1.60';
422             sub full_message
423             {
424 48     48 1 88 my $self = shift;
425              
426 48   100     993 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.60';
431             sub full_message
432             {
433 16     16 1 41 my $self = shift;
434              
435 16   50     1153 return sprintf("%s at %s line %d", $self->message || '', $self->comp_name || '', $self->line_number);
      50        
436             }
437              
438             1;
439              
440             __END__