File Coverage

blib/lib/Devel/REPL/Plugin/Carp/REPL.pm
Criterion Covered Total %
statement 17 20 85.0
branch 0 2 0.0
condition n/a
subroutine 6 7 85.7
pod 0 2 0.0
total 23 31 74.1


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