Mojo::Exception - Exceptions With Context


Mojolicious documentation Contained in the Mojolicious distribution.

Index


Code Index:

NAME

Top

Mojo::Exception - Exceptions With Context

SYNOPSIS

Top

  use Mojo::Exception;
  my $e = Mojo::Exception->new;

DESCRIPTION

Top

Mojo::Exception is a container for exceptions with context information.

ATTRIBUTES

Top

Mojo::Exception implements the following attributes.

frames

  my $frames = $e->frames;
  $e         = $e->frames($frames);

Stacktrace.

line

  my $line = $e->line;
  $e       = $e->line([3, 'foo']);

The line where the exception occured.

lines_after

  my $lines = $e->lines_after;
  $e        = $e->lines_after([[1, 'bar'], [2, 'baz']]);

Lines after the line where the exception occured.

lines_before

  my $lines = $e->lines_before;
  $e        = $e->lines_before([[4, 'bar'], [5, 'baz']]);

Lines before the line where the exception occured.

message

  my $message = $e->message;
  $e          = $e->message('Oops!');

Exception message.

raw_message

  my $message = $e->raw_message;
  $e          = $e->raw_message('Oops!');

Raw unprocessed exception message.

verbose

  my $verbose = $e->verbose;
  $e          = $e->verbose(1);

Activate verbose rendering.

METHODS

Top

Mojo::Exception inherits all methods from Mojo::Base and implements the following new ones.

new

  my $e = Mojo::Exception->new('Oops!');
  my $e = Mojo::Exception->new('Oops!', $files, $name);

Construct a new Mojo::Exception object.

throw

  Mojo::Exception->throw('Oops!');
  Mojo::Exception->throw('Oops!', $files, $name);

Throw exception with stacktrace.

trace

  $e = $e->trace;

Perform stacktrace.

to_string

  my $string = $e->to_string;
  my $string = "$e";

Render exception with context.

SEE ALSO

Top

Mojolicious, Mojolicious::Guides, http://mojolicio.us.


Mojolicious documentation Contained in the Mojolicious distribution.

package Mojo::Exception;
use Mojo::Base -base;
use overload
  'bool'   => sub {1},
  '""'     => sub { shift->to_string },
  fallback => 1;

use IO::File;
use Scalar::Util 'blessed';

has [qw/frames line lines_before lines_after/] => sub { [] };
has [qw/message raw_message/] => 'Exception!';
has verbose => sub { $ENV{MOJO_EXCEPTION_VERBOSE} || 0 };

# "Attempted murder? Now honestly, what is that?
#  Do they give a Nobel Prize for attempted chemistry?"
sub new {
  my $self = shift->SUPER::new();

  # Message
  return $self unless @_;

  # Detect
  $self->_detect(@_);
}

sub throw {
  my $self = shift;
  my $e    = Mojo::Exception->new;
  $e->trace(2);
  die $e->_detect(@_);
}

sub trace {
  my ($e, $start) = @_;
  $start = 1 unless defined $start;

  # Trace
  my @frames;
  while (my ($p, $f, $l) = caller($start++)) {
    push @frames, [$p, $f, $l];

    # Line
    if (-r $f) {
      next unless my $handle = IO::File->new("< $f");
      my @lines = <$handle>;
      push @{$frames[-1]}, $lines[$l - 1];
    }
  }
  $e->frames(\@frames);

  $e;
}

sub _detect {
  my $self = shift;

  # Message
  my $message = shift;
  return $message if blessed $message && $message->isa('Mojo::Exception');
  $self->message($message);
  $self->raw_message($message);

  # Trace name and line
  my @trace;
  while ($message =~ /at\s+(.+?)\s+line\s+(\d+)/g) {
    push @trace, {file => $1, line => $2};
  }

  # Stacktrace
  if (my $first = $self->frames->[0]) {
    unshift @trace, {file => $first->[1], line => $first->[2]}
      if $first->[1];
  }

  # Frames
  foreach my $frame (reverse @trace) {
    my $file = $frame->{file};
    my $line = $frame->{line};

    # Readable
    if (-r $file) {

      # Slurp
      my $handle = IO::File->new($file, '<:utf8');
      my @lines = <$handle>;

      # Line
      $self->_parse_context($line, [\@lines]);
      return $self;
    }
  }

  # Parse specific file
  return $self unless my $files = shift;
  my @lines;
  for my $lines (@$files) { push @lines, [split /\n/, $lines] }

  # Cleanup plain messages
  return $self unless my $name = shift;
  unless (ref $message) {
    my $filter = sub {
      my $num  = shift;
      my $new  = "$name line $num";
      my $line = $lines[0]->[$num];
      $new .= qq/, near "$line"/ if defined $line;
      $new .= '.';
      return $new;
    };
    $message =~ s/\(eval\s+\d+\) line (\d+).*/$filter->($1)/ge;
    $self->message($message);
  }

  # Parse message
  $name = quotemeta $name;
  my $line;
  $line = $1 if $self->message =~ /at\s+$name\s+line\s+(\d+)/;

  # Stacktrace
  unless ($line) {
    for my $frame (@{$self->frames}) {
      if ($frame->[1] =~ /^\(eval\ \d+\)$/) {
        $line = $frame->[2];
        last;
      }
    }
  }

  # Context
  $self->_parse_context($line, \@lines) if $line;

  $self;
}

# "You killed zombie Flanders!
#  He was a zombie?"
sub to_string {
  my $self = shift;

  # Message only
  return $self->message unless $self->verbose;

  # Start with message
  my $string = '';
  $string .= $self->message if $self->message;

  # Before
  for my $line (@{$self->lines_before}) {
    $string .= $line->[0] . ': ' . $line->[1] . "\n";
  }

  # Line
  $string .= ($self->line->[0] . ': ' . $self->line->[1] . "\n")
    if $self->line->[0];

  # After
  for my $line (@{$self->lines_after}) {
    $string .= $line->[0] . ': ' . $line->[1] . "\n";
  }

  $string;
}

sub _parse_context {
  my ($self, $line, $lines) = @_;

  # Wrong file
  return unless defined $lines->[0]->[$line - 1];

  # Context
  $self->line([$line]);
  for my $l (@$lines) {
    my $code = $l->[$line - 1];
    chomp $code;
    push @{$self->line}, $code;
  }

  # Cleanup
  $self->lines_before([]);
  $self->lines_after([]);

  # Before
  for my $i (2 .. 6) {
    my $previous = $line - $i;
    last if $previous < 0;
    if (defined($lines->[0]->[$previous])) {
      unshift @{$self->lines_before}, [$previous + 1];
      for my $l (@$lines) {
        my $code = $l->[$previous];
        chomp $code;
        push @{$self->lines_before->[0]}, $code;
      }
    }
  }

  # After
  for my $i (0 .. 4) {
    my $next = $line + $i;
    next if $next < 0;
    if (defined($lines->[0]->[$next])) {
      push @{$self->lines_after}, [$next + 1];
      for my $l (@$lines) {
        next unless defined(my $code = $l->[$next]);
        chomp $code;
        push @{$self->lines_after->[-1]}, $code;
      }
    }
  }

  $self;
}

1;
__END__