File Coverage

blib/lib/Devel/REPL/Plugin/Carp/REPL.pm
Criterion Covered Total %
statement 14 17 82.3
branch 0 2 0.0
condition n/a
subroutine 5 6 83.3
pod 0 2 0.0
total 19 27 70.3


line stmt bran cond sub pod time code
1             package Devel::REPL::Plugin::Carp::REPL;
2 1     1   1115 use Devel::REPL::Plugin;
  1         6682  
  1         5  
3             #use namespace::clean -except => [ 'meta' ];
4 1     1   5078 use Devel::LexAlias;
  1         13815  
  1         67  
5 1     1   507 use Devel::StackTrace::WithLexicals;
  1         4301  
  1         22  
6 1     1   895 use Data::Dump::Streamer;
  1         49710  
  1         6  
7              
8             sub BEFORE_PLUGIN {
9 2     2 0 20971 my $self = shift;
10 2         9 $self->load_plugin('LexEnv');
11             }
12              
13             has stacktrace => (
14             is => 'ro',
15             isa => 'Devel::StackTrace::WithLexicals',
16             handles => [qw/frame_count/],
17             default => sub {
18             my $stacktrace = Devel::StackTrace::WithLexicals->new(
19             ignore_class => ['Carp::REPL', __PACKAGE__],
20             unsafe_ref_capture => 1,
21             );
22              
23             # skip all the Moose metaclass frames
24             shift @{ $stacktrace->{raw} }
25             until @{ $stacktrace->{raw} } == 0
26             || $stacktrace->{raw}[0]{caller}[3] eq 'Carp::REPL::repl';
27              
28             # get out of Carp::
29             shift @{ $stacktrace->{raw} }
30             until @{ $stacktrace->{raw} } == 0
31             || $stacktrace->{raw}[0]{caller}[0] !~ /^Carp(?:::|$)/;
32              
33             shift @{ $stacktrace->{raw} }
34             until @{ $stacktrace->{raw} } == 0
35             || $Carp::REPL::bottom_frame-- <= 0;
36              
37             return $stacktrace;
38             },
39             );
40              
41             has frame_index => (
42             is => 'rw',
43             isa => 'Int',
44             default => 0,
45             );
46              
47             sub frame {
48 0     0 0   my $self = shift;
49 0 0         my $i = @_ ? shift : $self->frame_index;
50              
51 0           return $self->stacktrace->frame($i);
52             }
53              
54             around 'frame_index' => sub {
55             my $orig = shift;
56             my ($self, $index) = @_;
57              
58             return $orig->(@_) if !defined($index);
59              
60             if ($index < 0) {
61             warn "You're already at the bottom frame.\n";
62             }
63             elsif ($index >= $self->frame_count) {
64             warn "You're already at the top frame.\n";
65             }
66             else {
67             $orig->(@_);
68             my $frame = $self->frame;
69             my ($file, $line) = ($frame->filename, $frame->line);
70             $self->print("Now at $file:$line (frame $index).");
71             }
72             };
73              
74             # this is totally the wrong spot for this. oh well.
75             around 'read' => sub {
76             my $orig = shift;
77             my ($self, @rest) = @_;
78             my $line = $self->$orig(@rest);
79              
80             return if !defined($line) || $line =~ /^\s*:q\s*/;
81              
82             if ($line =~ /^\s*:b?t\b/) {
83             $self->print($self->stacktrace);
84             return '';
85             }
86              
87             if ($line =~ /^\s*:top\b/) {
88             $self->frame_index($self->frame_count - 1);
89             return '';
90             }
91              
92             if ($line =~ /^\s*:b(?:ot(?:tom)?)?\b/) {
93             $self->frame_index(0);
94             return '';
95             }
96              
97             if ($line =~ /^\s*:up?\b/) {
98             $self->frame_index($self->frame_index + 1);
99             return '';
100             }
101              
102             if ($line =~ /^\s*:d(?:own)?\b/) {
103             $self->frame_index($self->frame_index - 1);
104             return '';
105             }
106              
107             if ($line =~ /^\s*:l(?:ist)?\b/) {
108             my $frame = $self->frame;
109             my ($file, $num) = ($frame->filename, $frame->line);
110             open my $handle, '<', $file or do {
111             warn "Unable to open $file for reading: $!\n";
112             return '';
113             };
114             my @code = <$handle>;
115             chomp @code;
116              
117             my $min = $num - 6;
118             my $max = $num + 4;
119             $min = 0 if $min < 0;
120             $max = $#code if $max > $#code;
121              
122             my @lines;
123             $self->print("File $file:\n");
124             for my $cur ($min .. $max) {
125             next if !defined($code[$cur]);
126              
127             push @lines, sprintf "%s%*d: %s",
128             $cur + 1 == $num ? '*' : ' ',
129             length($max),
130             $cur + 1,
131             $code[$cur];
132             }
133              
134             $self->print(join "\n", @lines);
135              
136             return '';
137             }
138              
139             if ($line =~ /^\s*:e(?:nv)?\s*/) {
140             $self->print(Dump($self->frame->lexicals)->Names('Env')->Out);
141             return '';
142             }
143              
144             return $line;
145             };
146              
147             # Provide an alias for each lexical in the current stack frame
148             around 'mangle_line' => sub {
149             my $orig = shift;
150             my ($self, @rest) = @_;
151             my $line = $self->$orig(@rest);
152              
153             my $frame = $self->frame;
154             my $package = $frame->package;
155             my $lexicals = $frame->lexicals;
156              
157             my $declarations = join "\n",
158             map {"my $_;"}
159             keys %$lexicals;
160              
161             my $aliases = << ' ALIASES';
162             while (my ($k, $v) = each %{ $_REPL->frame->lexicals }) {
163             Devel::LexAlias::lexalias 0, $k, $v;
164             }
165             my $_a; Devel::LexAlias::lexalias 0, '$_a', \$_REPL->frame->{args};
166             ALIASES
167              
168             return << " CODE";
169             package $package;
170             no warnings 'misc'; # declaration in same scope masks earlier instance
171             no strict 'vars'; # so we get all the global variables in our package
172             $declarations
173             $aliases
174             $line
175             CODE
176             };
177              
178             1;
179              
180             __END__
181              
182             =head1 NAME
183              
184             Devel::REPL::Plugin::Carp::REPL - Devel::REPL plugin for Carp::REPL
185              
186             =head1 SYNOPSIS
187              
188             This sets up the environment captured by L<Carp::REPL>. This plugin
189             isn't intended for use by anything else. There are plans to move some features
190             from this into a generic L<Devel::REPL> plugin.
191              
192             This plugin also adds a few extra commands like :up and :down to move up and
193             down the stack.
194              
195             =head1 AUTHOR
196              
197             Shawn M Moore, C<< <sartak at gmail.com> >>
198              
199             =head1 BUGS
200              
201             Please report any bugs to a medium given by Carp::REPL.
202              
203             =head1 COPYRIGHT & LICENSE
204              
205             Copyright 2007-2008 Best Practical Solutions, all rights reserved.
206              
207             This program is free software; you can redistribute it and/or modify it
208             under the same terms as Perl itself.
209              
210             =cut
211