File Coverage

lib/Devel/Trepan/CmdProcessor/Command/List.pm
Criterion Covered Total %
statement 88 344 25.5
branch 0 152 0.0
condition 0 42 0.0
subroutine 30 40 75.0
pod 0 10 0.0
total 118 588 20.0


line stmt bran cond sub pod time code
1             # -*- coding: utf-8 -*-
2             # Copyright (C) 2011-2012, 2014-2015 Rocky Bernstein <rocky@cpan.org>
3 12     12   94 use warnings; no warnings 'redefine';
  12     12   28  
  12     1   439  
  12     1   62  
  12         25  
  12         352  
  1         6  
  1         2  
  1         23  
  1         4  
  1         2  
  1         25  
4 12     12   57 use rlib '../../../..';
  12     1   24  
  12         64  
  1         5  
  1         1  
  1         4  
5              
6             # require_relative '../../app/condition'
7              
8             package Devel::Trepan::CmdProcessor::Command::List;
9 12     12   4759 use English qw( -no_match_vars );
  12     1   26  
  12         89  
  1         625  
  1         2  
  1         6  
10 12     12   4758 use 5.010;
  12     1   43  
  1         271  
  1         4  
11 12     12   73 use Devel::Trepan::DB::LineCache;
  12     1   24  
  12         2189  
  1         7  
  1         1  
  1         118  
12 12     12   84 use Devel::Trepan::CmdProcessor::Validate;
  12     1   23  
  12         467  
  1         6  
  1         2  
  1         39  
13 12     12   61 use if !@ISA, Devel::Trepan::CmdProcessor::Command;
  12     1   39  
  12         64  
  1         5  
  1         2  
  1         5  
14 12     12   8047 use Devel::Trepan::CmdProcessor::Parse::Range;
  12     1   4207538  
  12         1609  
  1         37  
  1         2  
  1         77  
15              
16             unless (@ISA) {
17 12     12   85 eval <<'EOE';
  12     12   27  
  12     12   903  
  12     12   69  
  12     12   26  
  12     12   528  
  12         66  
  12         25  
  12         455  
  12         63  
  12         29  
  12         462  
  12         62  
  12         25  
  12         480  
  12         66  
  12         27  
  12         491  
18             use constant ALIASES => qw(l list> l>);
19             use constant CATEGORY => 'files';
20             use constant SHORT_HELP => 'List source code';
21             use constant MIN_ARGS => 0; # Need at least this many
22             use constant MAX_ARGS => 3; # Need at most this many - undef -> unlimited.
23             use constant NEED_STACK => 0;
24             EOE
25             }
26              
27 12     12   126 use strict; use vars qw(@ISA); @ISA = @CMD_ISA;
  12     12   26  
  12     1   347  
  12     1   64  
  12         27  
  12         624  
  1         5  
  1         2  
  1         30  
  1         4  
  1         2  
  1         41  
28 12     12   66 use vars @CMD_VARS; # Value inherited from parent
  12     1   24  
  12         18363  
  1         5  
  1         2  
  1         1713  
29              
30             our $NAME = set_name();
31             =head2 Synopsis:
32              
33             =cut
34             our $HELP = <<'HELP';
35             =pod
36              
37             B<list>[E<gt>] I<location> [I<number>]
38              
39             List Perl source code.
40              
41             Without arguments, prints lines centered around the current
42             line. If this is the first list command issued since the debugger
43             command loop was entered, then the current line is the current
44             frame. If a subsequent list command was issued with no intervening
45             frame changing, then that is start the line after we last one
46             previously shown.
47              
48             If the command has a '>' suffix, then line centering is disabled and
49             listing begins at the specificed location.
50              
51             The number of lines to show is controlled by the debugger "listsize"
52             setting. Use L<C<set max
53             list>|Devel::Trepan::CmdProcessor::Set::Max::List> or L<C<show max
54             list>|Devel::Trepan::CmdProcessor::Show::Max::List> to see or set the
55             value.
56              
57             If the location form is used with a subsequent parameter, the
58             parameter is the starting line number. When there two numbers are
59             given, the last number value is treated as a stopping line unless it
60             is less than the start line, in which case it is taken to mean the
61             number of lines to list instead.
62              
63             =head2 Examples:
64              
65             list 5 # List centered around line 5
66             list foo.pl:5 # List foo.pl at line 5
67             list , 16 # list lines ending in line 16
68             list . # List lines centered from where we currently are stopped
69             list . 3 # List 3 lines starting from where we currently are stopped
70             # if . > 3. Otherwise we list from . to 3.
71             list - # List lines previous to those just shown
72              
73             The output of the list command give a line number, and some status
74             information about the line and the text of the line. Here is some
75             hypothetical list output modeled roughly around line 251 of one
76             version of this code:
77              
78             251 cmd.proc.frame_setup(tf)
79             252 -> brkpt_cmd.run(['break'])
80             253 B01 line = __LINE__
81             254 b02 cmd.run(['list', __LINE__.to_s])
82             255 t03 puts '--' * 10
83              
84             Line 251 has nothing special about it. Line 252 is where we are
85             currently stopped. On line 253 there is a breakpoint 1 which is
86             enabled, while at line 255 there is an breakpoint 2 which is
87             disabled.
88              
89             =head2 See also:
90              
91             L<C<set
92             autolist>|Devel::Trepan::CmdProcessor::Command::Set::Auto::List>,
93             L<C<help syntax
94             location>|Devel::Trepan::CmdProcessor::Command::Help::location>,
95             L<C<disassemble>|Devel::Trepan::CmdProcessor::Command::Disassemble>,
96             and L<C<deparse>|Devel::Trepan::CmdProcessor::Command::Deparse>.
97              
98             =cut
99             HELP
100              
101             # FIXME: Should we include all files?
102             # Combine with BREAK completion.
103             sub complete($$)
104             {
105 0     0 0   my ($self, $prefix) = @_;
  0     0 0    
106 0           my $filename = $self->{proc}->filename;
  0            
107             # For line numbers we'll use stoppable line number even though one
108             # can enter line numbers that don't have breakpoints associated with them
109 0           my @completions = sort(('.', '-', file_list, DB::subs(),
  0            
110             trace_line_numbers($filename)));
111 0           Devel::Trepan::Complete::complete_token(\@completions, $prefix);
  0            
112             }
113              
114             # If last is less than first, assume last is a count rather than an
115             # end line number.
116             sub adjust_end($$)
117             {
118 0     0 0   my ($start, $end) = @_;
  0     0 0    
119 0 0         return ($start < $end ) ? $start + $end - 1 : $end;
  0 0          
120             }
121              
122             sub no_frame_msg($)
123             {
124 0     0 0   my $self = shift;
  0     0 0    
125 0           $self->errmsg("No Perl program loaded.");
  0            
126 0           return (undef, undef, undef);
  0            
127             }
128              
129              
130             # What a mess. Necessitated I suppose because we want to allow
131             # somewhat flexible parsing with either module names, files or none
132             # and optional line counts or end-line numbers.
133             # TODO: allow a negative start to count from the end of the file.
134              
135             # Parses arguments for the "list" command and returns the tuple:
136             # filename, start, last
137             # or sets these to nil if there was some problem.
138             sub parse_list_cmd($$$$)
139             {
140 0     0 0   my ($self, $args, $listsize, $center_correction) = @_;
  0     0 0    
141              
142 0           my @args = @$args;
  0            
143 0           shift @args;
  0            
144              
145 0           my $proc = $self->{proc};
  0            
146 0           my $frame = $proc->{frame};
  0            
147 0           my $filename = $proc->{list_filename};
  0            
148 0           my $fn;
  0            
149 0           my ($start, $end);
  0            
150              
151 0 0 0       if (scalar @args > 0) {
  0 0 0        
    0 0        
    0 0        
152              
153 0           my $command = $proc->{current_command};
  0            
154 0           $command = substr($command, index($command, ' '));
  0            
155 0           my $value_ref;
  0            
156              
157             # Parse input and build tree
158 0           my $eval_ok = eval { $value_ref = parse_range( \$command ); 1; };
  0            
  0            
  0            
  0            
  0            
159 0 0         if ( !$eval_ok ) {
  0 0          
160 0           my $eval_error = $EVAL_ERROR;
  0            
161             PARSE_EVAL_ERROR: {
162 0           $self->errmsg("list parsing error: $EVAL_ERROR");
  0            
  0            
  0            
163 0           return undef, undef, undef;
  0            
164             }
165             }
166 0           my @ary = @$$value_ref;
  0            
167 0           my $start_symbol_name = shift @ary;
  0            
168 0 0         unless ($start_symbol_name eq 'range') {
  0 0          
169 0           $proc->errmsg("List expecting a range parse");
  0            
170 0           return undef, undef, undef;
  0            
171             }
172              
173 0           my %range = range_build(@ary);
  0            
174              
175 0           my $direction = $range{direction};
  0            
176 0 0         if (defined $direction) {
  0 0          
177 0 0         if ($direction eq '-') {
  0 0          
    0          
    0          
    0          
    0          
178 0 0         return $self->no_frame_msg() unless $proc->{list_line};
  0 0          
179 0           $start = $proc->{list_line} - 2*$listsize;
  0            
180 0 0         $start = 1 if $start < 1;
  0 0          
181             } elsif ($direction eq '+') {
182 0 0         return $self->no_frame_msg() unless $proc->{list_line};
  0 0          
183 0           $start = $proc->{list_line} - $center_correction;
  0            
184             } elsif ($direction eq '.') {
185 0           my $frame = $proc->{frame};
  0            
186 0 0         return $self->no_frame_msg() unless defined $frame;
  0 0          
187 0           $filename = $frame->{file};
  0            
188 0           $start = $frame->{line} - ($listsize / 2);
  0            
189             } else {
190 0           $proc->errmsg("List command expecting a range parse");
  0            
191 0           return undef, undef, undef;
  0            
192             }
193             } else {
194 0           my $loc_start = $range{start};
  0            
195 0 0         if (defined $loc_start) {
  0 0          
196 0 0         if (ref $loc_start eq 'HASH') {
  0 0          
197 0           my $location = $loc_start->{location};
  0            
198 0 0         unless (defined $location) {
  0 0          
199 0           $proc->errmsg("List command parse expected a location");
  0            
200 0           return undef, undef, undef;
  0            
201              
202             };
203 0 0         $filename = $location->{filename} if $location->{filename};
  0 0          
204 0 0         $start = $location->{line_num} if $location->{line_num};
  0 0          
205 0 0         $start = $location->{offset} if $location->{offset};
  0 0          
206             } else {
207 0           $start = $loc_start;
  0            
208             }
209             }
210 0           my $loc_end = $range{end};
  0            
211 0 0         if (defined $loc_end) {
  0 0          
212 0 0         $end = $loc_end->{line_num} if $loc_end->{line_num};
  0 0          
213             # FIXME handle offset
214             # $end = $loc_end->{offset} if $loc_end->{offset};
215 0 0         if (!defined $start) {
  0 0          
216 0           $start = $end - $listsize;
  0            
217             }
218             }
219             }
220             } elsif ($frame && !$frame->{line} and $proc->{frame}) {
221 0           $start = $frame->{line} - $center_correction;
  0            
222             } else {
223 0   0       $start = ($proc->{list_line} || $frame->{line}) - $center_correction;
  0   0        
224             }
225 0 0         $start = 1 if $start < 1;
  0 0          
226 0 0         $end = $start + $listsize - 1 unless $end;
  0 0          
227              
228 0 0         cache_file($filename) unless is_cached($filename);
  0 0          
229 0           return ($filename, $start, $end);
  0            
230             }
231              
232             # This method runs the command
233             sub run($$)
234             {
235 0     0 0   my ($self, $args) = @_;
  0     0 0    
236 0           my $proc = $self->{proc};
  0            
237              
238 0           my $listsize = $proc->{settings}{maxlist};
  0            
239 0 0         my $center_correction =
  0 0          
240             (substr($args->[0], -1, 1) eq '>') ? 0 : int(($listsize-1) / 2);
241              
242 0           my ($filename, $start, $end) = parse_list_cmd($self, $args, $listsize,
  0            
243             $center_correction);
244              
245 0 0         return unless $filename;
  0 0          
246              
247             # We now have range information. Do the listing.
248 0           my $max_line = Devel::Trepan::DB::LineCache::size($filename);
  0            
249 0           $filename = map_file($filename);
  0            
250 0 0 0       unless (defined $max_line && -r $filename) {
  0 0 0        
251 0           $proc->errmsg("File \"$filename\" not found.");
  0            
252 0           return;
  0            
253             }
254              
255 0 0         if ($start > $max_line) {
  0 0          
256 0           my $mess = sprintf('Bad line range [%d...%d]; file "%s" has only %d lines',
  0            
257             $start, $end, $proc->canonic_file($filename), $max_line);
258 0           $proc->errmsg($mess);
  0            
259 0           return;
  0            
260             }
261              
262 0 0         if ($end > $max_line) {
  0 0          
263             # msg('End position changed to end line %d ' % max_line)
264 0           $end = $max_line;
  0            
265             }
266              
267             # begin
268             my $opts = {
269             reload_on_change => $proc->{settings}{reload},
270             output => $proc->{settings}{highlight}
271 0           };
  0            
272 0           my $bp;
  0            
273 0           local(*DB::dbline) = "::_<$filename";
  0            
274 0           my $lineno;
  0            
275 0           my $msg = sprintf("%s [%d-%d]",
  0            
276             $proc->canonic_file($filename), $start, $end);
277              
278             # FIXME: put in frame?
279 0           my $frame_filename = $proc->filename();
  0            
280 0 0         $frame_filename = map_file($frame_filename)
  0 0          
281             if filename_is_eval($frame_filename);
282              
283 0           $self->section($msg);
  0            
284 0           for ($lineno = $start; $lineno <= $end; $lineno++) {
  0            
285 0           my $a_pad = ' ';
  0            
286 0           my $line = getline($filename, $lineno, $opts);
  0            
287 0 0         unless (defined $line) {
  0 0          
288 0 0         if ($lineno > $max_line) {
  0 0          
289 0           $proc->msg('[EOF]');
  0            
290 0           last;
  0            
291             } else {
292 0           $line = '';
  0            
293             }
294             }
295 0           chomp $line;
  0            
296 0           my $s = sprintf('%3d', $lineno);
  0            
297 0 0         $s = $s . ' ' if length($s) < 4;
  0 0          
298              
299             ## FIXME: move into DB::Breakpoint and adjust List.pm
300 0 0 0       if (exists($DB::dbline{$lineno}) and
  0 0 0        
301             my $brkpts = $DB::dbline{$lineno}) {
302 0           my $found = 0;
  0            
303 0           for my $bp (@{$brkpts}) {
  0            
  0            
  0            
304 0 0         if (defined($bp)) {
  0 0          
305 0           $a_pad = sprintf('%02d', $bp->id);
  0            
306 0           $s .= $bp->icon_char;
  0            
307 0           $found = 1;
  0            
308 0           last;
  0            
309             }
310             }
311 0 0         $s .= ' ' unless $found;
  0 0          
312             } else {
313 0           $s .= ' ';
  0            
314             }
315             ## FIXME move above code
316 0           my $opts = {unlimited => 1};
  0            
317 0           my $mess;
  0            
318 0 0 0       if ($proc->{frame} && $lineno == $proc->line &&
  0 0 0        
      0        
      0        
319             $frame_filename eq $filename) {
320 0           $s .= '->';
  0            
321 0           $s = $proc->bolden($s);
  0            
322             } else {
323 0           $s .= $a_pad;
  0            
324             }
325 0           $mess = "$s\t$line";
  0            
326 0           $proc->msg($mess, $opts);
  0            
327             }
328 0           $proc->{list_line} = $lineno + $center_correction;
  0            
329 0           $proc->{list_filename} = $filename;
  0            
330             # rescue => e
331             # errmsg e.to_s if settings[:debugexcept]
332             # end
333             }
334              
335             unless (caller) {
336             require Devel::Trepan::CmdProcessor::Mock;
337             my $proc = Devel::Trepan::CmdProcessor->new(undef, 'bogus');
338             my $cmd = __PACKAGE__->new($proc);
339             require Devel::Trepan::DB::Sub;
340             require Devel::Trepan::DB::LineCache;
341             cache_file(__FILE__);
342             my $frame_ary = Devel::Trepan::CmdProcessor::Mock::create_frame();
343             $proc->frame_setup($frame_ary);
344             $proc->{settings}{highlight} = undef;
345             $cmd->run([$NAME]);
346             print '-' x 20, "\n";
347             $proc->{current_command} = "$NAME";
348             $cmd->run([$NAME]);
349             print '-' x 20, "\n";
350             $proc->{current_command} = sprintf "%s %s:%s", $NAME, __FILE__, __LINE__;
351             $cmd->run(["{$NAME}>", __FILE__, __LINE__]);
352             print '-' x 20, "\n";
353             $proc->{current_command} = sprintf "%s %s:1", $NAME, __FILE__;
354             $cmd->run(["{$NAME}>", __FILE__, 1]);
355             }
356              
357             1;