package Devel::ebug::HTTP;
use strict;
use warnings;
use Catalyst qw/Static/;
#use Catalyst qw/-Debug Static/;
use Catalyst::View::TT;
use Cwd;
use Devel::ebug;
use HTML::Prototype;
use List::Util qw(max);
use Path::Class;
use PPI;
use PPI::HTML;
use Storable qw(dclone);
our $VERSION = "0.32";
# globals for now, sigh
my $codelines_cache;
our $ebug;
my $lines_visible_above_count = 10;
my $sequence = 1;
my $vars;
Devel::ebug::HTTP->config(
name => 'Devel::ebug::HTTP',
);
# Catalyst has new template bundling code, but the following is
# necessary for now as our distribution is Devel::ebug but the
# application is Devel::ebug::HTTP (sigh)
my $root = Devel::ebug::HTTP->config->{root};
unless (-d $root) {
my $home = Devel::ebug::HTTP->config->{home};
$home = dir($home)->parent;
$root = dir($home)->subdir('root');
unless (-d $root) {
$root = dir($home)->parent->parent->parent->subdir('root');
}
Devel::ebug::HTTP->config(
home => $home,
root => $root,
);
}
Devel::ebug::HTTP->setup;
sub default : Private {
my($self, $c) = @_;
$c->stash->{template} = 'index';
$c->forward('do_the_request');
}
sub ajax_variable : Regex('^ajax_variable$') {
my ($self, $context, $variable) = @_;
$variable = '\\' . $variable if $variable =~ /^[%@]/;
my $value = $ebug->yaml($variable);
$value =~ s/^--- // unless $variable =~ /^[%@]/;
$value = "Not defined" if $value =~ /^Global symbol/;
$value =~ s{\n}{
}g;
my $xml = qq{
$variable
};
$context->response->content_type("text/xml");
$context->response->output($xml);
}
sub ajax_eval : Regex('^ajax_eval$') {
my ($self, $context) = @_;
my $eval = $context->request->parameters->{eval};
my $result = $ebug->eval($eval) || "No output";
$result =~ s/ at \(eval .+$//;
$context->response->content_type("text/html");
$context->response->output($result);
}
sub css : Regex('(?i)\.(?:css)') {
my($self, $c) = @_;
$c->res->headers->header('Cache-Control' => 'max-age=60');
$c->serve_static("text/css");
}
sub js : Regex('(?i)\.(?:js)') {
my($self, $c) = @_;
$c->res->headers->header('Cache-Control' => 'max-age=60');
$c->serve_static("application/x-javascript");
}
sub ico : Regex('(?i)\.(?:ico)') {
my($self, $c) = @_;
$c->res->headers->header('Cache-Control' => 'max-age=60');
$c->serve_static("image/vnd.microsoft.icon");
}
sub images : Regex('(?i)\.(?:gif|jpg|png)') {
my($self, $c) = @_;
$c->res->headers->header('Cache-Control' => 'max-age=60');
$c->serve_static;
}
sub end : Private {
my($self, $c) = @_;
if ($c->stash->{template}) {
$c->response->content_type("text/html");
$c->forward('Devel::ebug::HTTP::View::TT');
}
}
sub do_the_request : Private {
my($self, $c) = @_;
my $params = $c->request->parameters;
# clear out template variables
$vars = {};
# pass commands we've been passed to the ebug
my $action = lc($params->{myaction} || '');
tell_ebug($c, $action);
# check we're doing things in the right order
my $cgi_sequence = $params->{sequence};
if (defined $cgi_sequence && $cgi_sequence < $sequence) {
$ebug->undo($sequence - $cgi_sequence);
$sequence = $cgi_sequence;
}
$sequence++;
set_up_stash($c);
}
sub tell_ebug {
my ($c, $action) = @_;
my $params = $c->request->parameters;
if ($ebug->finished &&
($action ne "restart") &&
($action ne "undo")) {
return;
}
if ($action eq 'break point:') {
$ebug->break_point($params->{'break_point'});
} elsif ($action eq 'break_point') {
$ebug->break_point($params->{line});
} elsif ($action eq 'break_point_delete') {
$ebug->break_point_delete($params->{line});
} if ($action eq 'next') {
$ebug->next;
} elsif ($action eq 'restart') {
$ebug->load;
} elsif ($action eq 'return') {
$ebug->return;
} elsif ($action eq 'run') {
$ebug->run;
} if ($action eq 'step') {
$ebug->step;
} elsif ($action eq 'undo') {
$ebug->undo;
}
}
sub set_up_stash {
my($c) = @_;
my $params = $c->request->parameters;
my $break_points;
$break_points->{$_}++ foreach $ebug->break_points;
my $url = $c->request->base;
my($stdout, $stderr) = $ebug->output;
my $codelines = codelines($c);
$vars = {
%$vars,
break_points => $break_points,
codelines => $codelines,
ebug => $ebug,
sequence => $sequence,
stack_trace_human => [$ebug->stack_trace_human],
stdout => $stdout,
stderr => $stderr,
subroutine => $ebug->subroutine,
top_visible_line => max(1, $ebug->line - $lines_visible_above_count + 1),
url => $url,
};
foreach my $k (keys %$vars) {
$c->stash->{$k} = $vars->{$k};
}
}
sub codelines {
my($c) = @_;
my $filename = $ebug->filename;
return $codelines_cache->{$filename} if exists $codelines_cache->{$filename};
my $url = $c->request->base;
my $code = join "\n", $ebug->codelines;
my $document = PPI::Document->new(\$code);
my $highlight = PPI::HTML->new(line_numbers => 1);
my $pretty = $highlight->html($document);
my $split = '';
# turn significant whitespace into
my @lines = map {
$_ =~ s{( +)}{"" . (" " x length($1))}e;
"$split$_";
} split /$split/, $pretty;
# right-justify the line number
@lines = map {
s{ ?(\d+) ?:}{
my $line = $1;
my $size = 4 - (length($1));
$size = 0 if $size < 0;
$line = line_html($url, $line);
'' . (" " x $size) . "$line:"}e;
$_;
} @lines;
# add the dynamic tooltips
@lines = map {
s{(.+?)}{
'' . variable_html($url, $1) . ""
}eg;
$_;
} @lines;
# make us slightly more XHTML
$_ =~ s{
}{
} foreach @lines;
# link module names to search.cpan.org
@lines = map {
$_ =~ s{([^<]+?::[^<]+?)}{$1};
$_;
} @lines;
$codelines_cache->{$filename} = \@lines;
return \@lines;
}
sub variable_html {
my($url, $variable) = @_;
return qq{$variable};
}
sub line_html {
my($url, $line) = @_;
return qq{$line};
}
1;
__END__
=head1 NAME
Devel::ebug::HTTP - A web front end to a simple, extensible Perl debugger
=head1 SYNOPSIS
ebug_http calc.pl
=head1 DESCRIPTION
A debugger is a computer program that is used to debug other
programs. L is a simple, extensible Perl debugger with a
clean API. L is a web-based frontend to L which
presents a simple, pretty way to debug programs. L is
the command line program to launch the debugger. It will return a URL
which you should point a web browser to.
=head1 SEE ALSO
L, L
=head1 AUTHOR
Leon Brocard, C<< >>
=head1 COPYRIGHT
Copyright (C) 2005, Leon Brocard
=head1 LICENSE
This module is free software; you can redistribute it or modify it
under the same terms as Perl itself.