Hack #55. Show Source Code on Errors

Don't guess which line is the problem—see it!

Debugging errors and warning messages isn't often fun. Instead, it can be tedious. Often even finding the problem takes too long.

Perl can reveal the line number of warnings and errors (with warn and die and the warnings pragma in effect); why can't it show the source code of the affected line?

The Hack

The code to do this is pretty easy, if unsubtle:

package SourceCarp;

use strict;
use warnings;

sub import
{
    my ($class, %args) = @_;

    $SIG{__DIE__}  = sub { report( shift, 2 ); exit } if $args{fatal};
    $SIG{__WARN__} = \\&report                         if $args{warnings};
}

sub report
{
    my ($message, $level)  = @_;
    $level               ||= 1;
    my ($filename, $line)  = ( caller( $level - 1 ) )[1, 2];
    warn $message, show_source( $filename, $line );
}

sub show_source
{
    my ($filename, $line) = @_;
    return '' unless open( my $fh, $filename );

    my $start = $line - 2;
    my $end   = $line + 2;

    local $.;
    my @text;
    while (<$fh>)
    {
        next unless $. >= $start;
        last if     $. >  $end;
        my $highlight   = $. = = $line ? '*' : ' ';
        push @text, sprintf( "%s%04d: %s", $highlight, $., $_ );
    }

    return join( '', @text, "\\n" );
}

1;

The magic here is in three places. report( ) looks at the call stack leading to its current position, extracting the name of the file and the line number of the calling code. It's possible to call this function directly with a message to display (and an optional level of calls to ignore).

show_source( ) simply reads the named file and returns ...

Get Perl Hacks now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.