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.59';
3 34     34   220 use strict;
  34         70  
  34         949  
4 34     34   173 use warnings;
  34         56  
  34         5481  
5              
6             my %e;
7              
8             BEGIN
9             {
10 34     34   1955 %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   16689 use Exception::Class (%e);
  34         293699  
  34         294  
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   1649 my ($class, %args) = @_;
84              
85 381         1760 my $caller = caller;
86 381 100       1593 if ($args{abbr})
87             {
88 377         615 foreach my $name (@{$args{abbr}})
  377         1045  
89             {
90 34     34   114963 no strict 'refs';
  34         82  
  34         3272  
91 917 50       1358 die "Unknown exception abbreviation '$name'" unless defined &{$name};
  917         2864  
92 917         1324 *{"${caller}::$name"} = \&{$name};
  917         6027  
  917         2131  
93             }
94             }
95             {
96 34     34   288 no strict 'refs';
  34         90  
  34         5176  
  381         785  
97 381         661 *{"${caller}::isa_mason_exception"} = \&isa_mason_exception;
  381         1684  
98 381         837 *{"${caller}::rethrow_exception"} = \&rethrow_exception;
  381         27463  
99             }
100             }
101              
102             sub isa_mason_exception
103             {
104 1297     1297 0 2539 my ($err, $name) = @_;
105 1297 100       2592 return unless defined $err;
106              
107 1157 100       2680 $name = $name ? "HTML::Mason::Exception::$name" : "HTML::Mason::Exception";
108 34     34   239 no strict 'refs';
  34         108  
  34         6443  
109 1157 50       7138 die "no such exception class $name" unless $name->isa('HTML::Mason::Exception');
110              
111 1157         5573 return UNIVERSAL::isa($err, $name);
112             }
113              
114             sub rethrow_exception
115             {
116 2022     2022 0 5501 my ($err) = @_;
117 2022 100       8173 return unless $err;
118              
119 512 100       3366 if ( UNIVERSAL::can($err, 'rethrow') ) {
    100          
120 469         999 $err->rethrow;
121             }
122             elsif ( ref $err ) {
123 1         7 die $err;
124             }
125 42         293 HTML::Mason::Exception->throw(error => $err);
126             }
127              
128             package HTML::Mason::Exception;
129             $HTML::Mason::Exception::VERSION = '1.59';
130             use HTML::Mason::MethodMaker
131 34     34   16138 ( read_write => [ qw ( format ) ] );
  34         89  
  34         240  
132              
133             sub new
134             {
135 140     140 1 1604 my ($class, %params) = @_;
136              
137 140         548 my $self = $class->SUPER::new(%params);
138 140         223764 $self->format('text');
139 140         1443 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 924 my $class = shift;
148 140 100       813 my %params = @_ == 1 ? ( error => $_[0] ) : @_;
149              
150 140 100       451 if (HTML::Mason::Exceptions::isa_mason_exception($params{error})) {
151 12         63 $params{error} = $params{error}->error;
152             }
153 140 50       563 if (HTML::Mason::Exceptions::isa_mason_exception($params{message})) {
154 0         0 $params{message} = $params{message}->error;
155             }
156 140         1171 $class->SUPER::throw(%params);
157             }
158              
159             sub filtered_frames
160             {
161 99     99 0 216 my ($self) = @_;
162              
163 99         180 my (@frames);
164 99         547 my $trace = $self->trace;
165 99         638 my %ignore_subs = map { $_ => 1 }
  792         1965  
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         527 while (my $frame = $trace->next_frame)
177             {
178 1555 100       151475 last if ($frame->subroutine eq 'HTML::Mason::Request::exec');
179 1504 100 100     7589 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         11539 push(@frames, $frame);
183             }
184             }
185 99 100       1206 @frames = grep { $_->filename !~ /Mason\/Exceptions\.pm/ } $trace->frames if !@frames;
  300         1432  
186 99         527 return @frames;
187             }
188              
189             sub analyze_error
190             {
191 100     100 0 242 my ($self) = @_;
192 100         188 my ($file, @lines, @frames);
193              
194 100 100       287 return $self->{_info} if $self->{_info};
195              
196 99         358 @frames = $self->filtered_frames;
197 99 100       1045 if ($self->isa('HTML::Mason::Exception::Syntax')) {
    100          
    50          
198 16         415 $file = $self->comp_name;
199 16         337 push(@lines, $self->line_number);
200             } elsif ($self->isa('HTML::Mason::Exception::Compilation')) {
201 24         569 $file = $self->filename;
202 24         152 my $msg = $self->full_message;
203 24         594 while ($msg =~ /at .* line (\d+)./g) {
204 33         161 push(@lines, $1);
205             }
206             } elsif (@frames) {
207 59         245 $file = $frames[0]->filename;
208 59         372 @lines = $frames[0]->line;
209             }
210 99         445 my @context;
211 99 50       523 @context = $self->get_file_context($file, \@lines) if @lines;
212              
213             $self->{_info} = {
214 99         780 file => $file,
215             frames => \@frames,
216             lines => \@lines,
217             context => \@context,
218             };
219 99         358 return $self->{_info};
220             }
221              
222             sub get_file_context
223             {
224 99     99 0 278 my ($self, $file, $line_nums) = @_;
225              
226 99         178 my @context;
227 99         162 my $fh = do { local *FH; *FH; };
  99         252  
  99         479  
228 99 100 100     4134 unless (defined($file) and open($fh, $file)) {
229 15         82 @context = (['unable to open file', '']);
230             } else {
231             # Put the file into a list, indexed at 1.
232 84         21334 my @file = <$fh>;
233 84         2461 chomp(@file);
234 84         736 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         208 my (%marks, %red);
241 84         161 my $delta = 4;
242 84         294 foreach my $line_num (@$line_nums) {
243 89         391 foreach my $l (($line_num - $delta) .. ($line_num + $delta)) {
244 801 100 100     2412 next if ($l <= 0 or $l > @file);
245 499         1298 $marks{$l}++;
246             }
247 89         244 $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         163 my $last_num = 0;
254 84         601 foreach my $line_num (sort { $a <=> $b } keys %marks) {
  841         1378  
255 480 100       989 push(@context, ["...", "", 0]) unless $last_num == ($line_num - 1);
256 480         1244 push(@context, ["$line_num:", $file[$line_num], $red{$line_num}]);;
257 480         836 $last_num = $line_num;
258             }
259 84 100       322 push(@context, ["...", "", 0]) unless $last_num == @file;
260 84         3657 close $fh;
261             }
262 99         682 return @context;
263             }
264              
265             # basically the same as as_string in Exception::Class::Base
266             sub raw_text
267             {
268 2     2 0 21 my ($self) = @_;
269              
270 2         10 return $self->full_message . "\n\n" . $self->trace->as_string;
271             }
272              
273             sub as_string
274             {
275 103     103 1 794 my ($self) = @_;
276              
277 103         318 my $stringify_function = "as_" . $self->{format};
278 103         503 return $self->$stringify_function();
279             }
280              
281             sub as_brief
282             {
283 1     1 0 4 my ($self) = @_;
284 1         5 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 231 my ($self) = @_;
300 98         393 my $info = $self->analyze_error;
301              
302 98         693 my $msg = $self->full_message;
303 98         1811 my $stack = join("\n", map { sprintf(" [%s:%d]", $_->filename, $_->line) } @{$info->{frames}});
  1125         8539  
  98         274  
304 98         2818 return sprintf("%s\nStack:\n%s\n", $msg, $stack);
305             }
306              
307             sub as_html
308             {
309 2     2 0 9 my ($self) = @_;
310              
311 2         6 my $out;
312 2         14 my $interp = HTML::Mason::Interp->new(out_method => \$out);
313              
314 2         12 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         12 $interp->exec($comp,
413             msg => $self->full_message,
414             info => $self->analyze_error,
415             error => $self);
416              
417 2         99 return $out;
418             }
419              
420             package HTML::Mason::Exception::Compilation;
421             $HTML::Mason::Exception::Compilation::VERSION = '1.59';
422             sub full_message
423             {
424 48     48 1 85 my $self = shift;
425              
426 48   100     858 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.59';
431             sub full_message
432             {
433 16     16 1 37 my $self = shift;
434              
435 16   50     114 return sprintf("%s at %s line %d", $self->message || '', $self->comp_name || '', $self->line_number);
      50        
436             }
437              
438             1;
439              
440             __END__