biblesearch / mojo / lib / Mojo / Exception.pm /
Newer Older
212 lines | 4.387kb
add files
Yuki Kimoto authored on 2014-03-26
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