add files
|
1 |
package Mojo::Exception; |
2 |
use Mojo::Base -base; |
|
3 |
use overload bool => sub {1}, '""' => sub { shift->to_string }, fallback => 1; |
|
4 | ||
5 |
use Scalar::Util 'blessed'; |
|
6 | ||
7 |
has [qw(frames line lines_before lines_after)] => sub { [] }; |
|
8 |
has message => 'Exception!'; |
|
9 |
has 'verbose'; |
|
10 | ||
11 |
sub new { |
|
12 |
my $self = shift->SUPER::new; |
|
13 |
return @_ ? $self->_detect(@_) : $self; |
|
14 |
} |
|
15 | ||
16 |
sub throw { die shift->new->trace(2)->_detect(@_) } |
|
17 | ||
18 |
sub to_string { |
|
19 |
my $self = shift; |
|
20 | ||
21 |
return $self->message unless $self->verbose; |
|
22 |
my $str = $self->message ? $self->message : ''; |
|
23 | ||
24 |
# Before |
|
25 |
$str .= $_->[0] . ': ' . $_->[1] . "\n" for @{$self->lines_before}; |
|
26 | ||
27 |
# Line |
|
28 |
$str .= ($self->line->[0] . ': ' . $self->line->[1] . "\n") |
|
29 |
if $self->line->[0]; |
|
30 | ||
31 |
# After |
|
32 |
$str .= $_->[0] . ': ' . $_->[1] . "\n" for @{$self->lines_after}; |
|
33 | ||
34 |
return $str; |
|
35 |
} |
|
36 | ||
37 |
sub trace { |
|
38 |
my ($self, $start) = @_; |
|
39 |
$start //= 1; |
|
40 |
my @frames; |
|
41 |
while (my @trace = caller($start++)) { push @frames, \@trace } |
|
42 |
return $self->frames(\@frames); |
|
43 |
} |
|
44 | ||
45 |
sub _context { |
|
46 |
my ($self, $num, $lines) = @_; |
|
47 | ||
48 |
# Line |
|
49 |
return unless defined $lines->[0][$num - 1]; |
|
50 |
$self->line([$num]); |
|
51 |
for my $line (@$lines) { |
|
52 |
chomp(my $code = $line->[$num - 1]); |
|
53 |
push @{$self->line}, $code; |
|
54 |
} |
|
55 | ||
56 |
# Before |
|
57 |
for my $i (2 .. 6) { |
|
58 |
last if ((my $previous = $num - $i) < 0); |
|
59 |
next unless defined $lines->[0][$previous]; |
|
60 |
unshift @{$self->lines_before}, [$previous + 1]; |
|
61 |
for my $line (@$lines) { |
|
62 |
chomp(my $code = $line->[$previous]); |
|
63 |
push @{$self->lines_before->[0]}, $code; |
|
64 |
} |
|
65 |
} |
|
66 | ||
67 |
# After |
|
68 |
for my $i (0 .. 4) { |
|
69 |
next if ((my $next = $num + $i) < 0); |
|
70 |
next unless defined $lines->[0][$next]; |
|
71 |
push @{$self->lines_after}, [$next + 1]; |
|
72 |
for my $line (@$lines) { |
|
73 |
last unless defined(my $code = $line->[$next]); |
|
74 |
chomp $code; |
|
75 |
push @{$self->lines_after->[-1]}, $code; |
|
76 |
} |
|
77 |
} |
|
78 |
} |
|
79 | ||
80 |
sub _detect { |
|
81 |
my ($self, $msg, $files) = @_; |
|
82 | ||
83 |
return $msg if blessed $msg && $msg->isa('Mojo::Exception'); |
|
84 |
$self->message($msg); |
|
85 | ||
86 |
# Extract file and line from message |
|
87 |
my @trace; |
|
88 |
while ($msg =~ /at\s+(.+?)\s+line\s+(\d+)/g) { unshift @trace, [$1, $2] } |
|
89 | ||
90 |
# Extract file and line from stacktrace |
|
91 |
my $first = $self->frames->[0]; |
|
92 |
unshift @trace, [$first->[1], $first->[2]] if $first; |
|
93 | ||
94 |
# Search for context in files |
|
95 |
for my $frame (@trace) { |
|
96 |
next unless -r $frame->[0] && open my $handle, '<:utf8', $frame->[0]; |
|
97 |
$self->_context($frame->[1], [[<$handle>]]); |
|
98 |
return $self; |
|
99 |
} |
|
100 | ||
101 |
# More context |
|
102 |
$self->_context($trace[0][1], [map { [split /\n/] } @$files]) if $files; |
|
103 | ||
104 |
return $self; |
|
105 |
} |
|
106 | ||
107 |
1; |
|
108 | ||
109 |
=encoding utf8 |
|
110 | ||
111 |
=head1 NAME |
|
112 | ||
113 |
Mojo::Exception - Exceptions with context |
|
114 | ||
115 |
=head1 SYNOPSIS |
|
116 | ||
117 |
use Mojo::Exception; |
|
118 | ||
119 |
# Throw exception |
|
120 |
Mojo::Exception->throw('Not again!'); |
|
121 | ||
122 |
# Customize exception |
|
123 |
die Mojo::Exception->new('Not again!')->trace(2)->verbose(1); |
|
124 | ||
125 |
=head1 DESCRIPTION |
|
126 | ||
127 |
L<Mojo::Exception> is a container for exceptions with context information. |
|
128 | ||
129 |
=head1 ATTRIBUTES |
|
130 | ||
131 |
L<Mojo::Exception> implements the following attributes. |
|
132 | ||
133 |
=head2 frames |
|
134 | ||
135 |
my $frames = $e->frames; |
|
136 |
$e = $e->frames($frames); |
|
137 | ||
138 |
Stacktrace. |
|
139 | ||
140 |
=head2 line |
|
141 | ||
142 |
my $line = $e->line; |
|
143 |
$e = $e->line([3 => 'foo']); |
|
144 | ||
145 |
The line where the exception occurred. |
|
146 | ||
147 |
=head2 lines_after |
|
148 | ||
149 |
my $lines = $e->lines_after; |
|
150 |
$e = $e->lines_after([[1 => 'bar'], [2 => 'baz']]); |
|
151 | ||
152 |
Lines after the line where the exception occurred. |
|
153 | ||
154 |
=head2 lines_before |
|
155 | ||
156 |
my $lines = $e->lines_before; |
|
157 |
$e = $e->lines_before([[4 => 'bar'], [5 => 'baz']]); |
|
158 | ||
159 |
Lines before the line where the exception occurred. |
|
160 | ||
161 |
=head2 message |
|
162 | ||
163 |
my $msg = $e->message; |
|
164 |
$e = $e->message('Oops!'); |
|
165 | ||
166 |
Exception message. |
|
167 | ||
168 |
=head2 verbose |
|
169 | ||
170 |
my $bool = $e->verbose; |
|
171 |
$e = $e->verbose($bool); |
|
172 | ||
173 |
Render exception with context. |
|
174 | ||
175 |
=head1 METHODS |
|
176 | ||
177 |
L<Mojo::Exception> inherits all methods from L<Mojo::Base> and implements the |
|
178 |
following new ones. |
|
179 | ||
180 |
=head2 new |
|
181 | ||
182 |
my $e = Mojo::Exception->new('Oops!'); |
|
183 |
my $e = Mojo::Exception->new('Oops!', $files); |
|
184 | ||
185 |
Construct a new L<Mojo::Exception> object. |
|
186 | ||
187 |
=head2 throw |
|
188 | ||
189 |
Mojo::Exception->throw('Oops!'); |
|
190 |
Mojo::Exception->throw('Oops!', $files); |
|
191 | ||
192 |
Throw exception with stacktrace. |
|
193 | ||
194 |
=head2 to_string |
|
195 | ||
196 |
my $str = $e->to_string; |
|
197 |
my $str = "$e"; |
|
198 | ||
199 |
Render exception. |
|
200 | ||
201 |
=head2 trace |
|
202 | ||
203 |
$e = $e->trace; |
|
204 |
$e = $e->trace(2); |
|
205 | ||
206 |
Store stacktrace. |
|
207 | ||
208 |
=head1 SEE ALSO |
|
209 | ||
210 |
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>. |
|
211 | ||
212 |
=cut |