File Coverage

blib/lib/Runtime/Debugger.pm
Criterion Covered Total %
statement 29 79 36.7
branch 0 16 0.0
condition 0 5 0.0
subroutine 10 19 52.6
pod 1 1 100.0
total 40 120 33.3


line stmt bran cond sub pod time code
1             package Runtime::Debugger;
2              
3             =head1 LOGO
4              
5             ____ _ _
6             | _ \ _ _ _ __ | |_(_)_ __ ___ ___
7             | |_) | | | | '_ \| __| | '_ ` _ \ / _ \
8             | _ <| |_| | | | | |_| | | | | | | __/
9             |_| \_\\__,_|_| |_|\__|_|_| |_| |_|\___|
10              
11             ____ _
12             | _ \ ___| |__ _ _ __ _ __ _ ___ _ __
13             | | | |/ _ \ '_ \| | | |/ _` |/ _` |/ _ \ '__|
14             | |_| | __/ |_) | |_| | (_| | (_| | __/ |
15             |____/ \___|_.__/ \__,_|\__, |\__, |\___|_|
16             |___/ |___/
17              
18             =cut
19              
20 1     1   68824 use 5.012;
  1         4  
21 1     1   5 use strict;
  1         2  
  1         22  
22 1     1   5 use warnings;
  1         3  
  1         36  
23 1     1   641 use Data::Dumper;
  1         7019  
  1         81  
24 1     1   514 use Term::ReadLine;
  1         2719  
  1         38  
25 1     1   633 use Term::ANSIColor qw( colored );
  1         8400  
  1         791  
26 1     1   548 use PadWalker qw( peek_my );
  1         703  
  1         63  
27 1     1   6 use feature qw( say );
  1         3  
  1         130  
28 1     1   451 use parent qw( Exporter );
  1         307  
  1         5  
29 1     1   589 use subs qw( p uniq );
  1         23  
  1         7  
30              
31             our @EXPORT = qw(
32             run
33             p
34             uniq
35             );
36              
37             =head1 NAME
38              
39             Runtime::Debugger - Debug perl while its running.
40              
41             =head1 VERSION
42              
43             Version 0.03
44              
45             =cut
46              
47             our $VERSION = '0.03';
48              
49              
50             =head1 SYNOPSIS
51              
52             One can usually just do this:
53              
54             # Insert this where you want to pause:
55             DB::single = 1;
56              
57             # Then run the perl debugger to navigate there quickly:
58             PERLDBOPT='Nonstop' perl -d my_script
59              
60             If that works for then great and dont' bother using this module!
61              
62             Unfortunately for me, it was not working due to the scenario
63             in which a script evals another perl test file and I would have
64             liked to pause inside the test and see whats going on without
65             having to keep rerunning the whole test over and over.
66              
67             This module basically drops in a read,evaludate,print loop (REPL)
68             whereever you need like so:
69              
70             use Runtime::Debugger;
71             eval run; # Not sure how to avoid using eval here while
72             # also being able to keep the lexical scope.
73             # Any ideas ? :)
74              
75             Try with this command line:
76              
77             perl -MRuntime::Debugger -E 'my $str1 = "str-1"; my $str2 = "str-2"; my @arr1 = "arr-1"; my @arr2 = "arr-2"; my %hash1 = qw(hash 1); my %hash2 = qw(hash 2); eval run; say $@'
78              
79             Press tab to autocomplete any lexical variables in scope (where "eval run" is found).
80              
81             Saves history locally.
82              
83             Can use 'p' to pretty print a variable or structure.
84              
85             =head1 SUBROUTINES/METHODS
86              
87             =cut
88              
89             #
90             # API
91             #
92              
93             =head2 run
94              
95             Runs the REPL (dont forget eval!)
96              
97             eval run
98              
99             Sets C<$@> to the exit reason like 'INT' (Control-C) or 'q' (Normal exit/quit).
100              
101             =cut
102              
103             sub run {
104 0     0 1   <<'CODE';
105             my $repl = Runtime::Debugger->_init;
106             while ( 1 ) {
107             eval $repl->_step;
108             $repl->_show_error($@) if $@;
109             }
110             CODE
111             }
112              
113             =head2 p
114              
115             Data::Dumper::Dump anything.
116              
117             p 123
118             p [1, 2, 3]
119              
120             Can adjust the maxdepth (default is 1) to see with: "#Number".
121              
122             p { a => [1, 2, 3] } #1
123              
124             Output:
125              
126             {
127             'a' => 'ARRAY(0x55fd914a3d80)'
128             }
129              
130             Set maxdepth to '0' to show all nested structures.
131              
132             =cut
133              
134             sub p {
135              
136             # Use same function to change maxdepth of whats shown.
137 0     0     my $maxdepth =
138             1; # Good default to often having to change it during display.
139 0 0 0       if ( @_ > 1 and $_[-1] =~ / ^ --maxdepth=(\d+) $ /x )
140             { # Like with "tree" command.
141 0           $maxdepth = $1;
142 0           pop @_;
143             }
144              
145 0           my $d = Data::Dumper
146             ->new( \@_ )
147             ->Sortkeys( 1 )
148             ->Terse( 1 )
149             ->Indent( 1 )
150             ->Maxdepth( $maxdepth );
151              
152 0 0         return $d->Dump if wantarray;
153 0           print $d->Dump;
154             }
155              
156             =head2 uniq
157              
158             Return a list of uniq values.
159              
160             =cut
161              
162             sub uniq (@) {
163 0     0     my %h;
164 0           grep { not $h{$_}++ } @_;
  0            
165             }
166              
167             #
168             # Internal
169             #
170              
171             sub _init {
172 0     0     my ( $class ) = @_;
173 0           my $self = bless {
174             history_file => "$ENV{HOME}/.runtime_debugger.info",
175             term => Term::ReadLine->new( $class ),
176             }, $class;
177 0           my $attribs = $self->{attribs} = $self->{term}->Attribs;
178              
179 0           $self->{term}->ornaments( 0 ); # Remove underline from terminal.
180              
181             # Restore last history.
182 0 0         if ( -e $self->{history_file} ) {
183 0           my @history;
184 0 0         open my $fh, '<', $self->{history_file} or die $!;
185 0           while ( <$fh> ) {
186 0           chomp;
187 0           push @history, $_;
188             }
189 0           close $fh;
190 0           $self->_history( @history );
191             }
192              
193             # https://metacpan.org/pod/Term::ReadLine::Gnu#Custom-Completion
194             # Definition for list_completion_function is here: Term/ReadLine/Gnu/XS.pm
195             $attribs->{completion_entry_function} =
196 0           $attribs->{list_completion_function};
197              
198             # Remove these as break chars so that we can complete:
199             # "$scalar", "@array", "%hash"
200             # ("%" was already not in the list).
201 0           $attribs->{completer_word_break_characters} =~ s/ [\$@] //xg;
202              
203             # Setup some signal hnndling.
204 0           for my $signal ( qw( INT TERM HUP ) ) {
205 0     0     $SIG{$signal} = sub { $self->_exit( $signal ) };
  0            
206             }
207              
208 0           $self;
209             }
210              
211             sub _exit {
212 0     0     my ( $self, $how ) = @_;
213              
214             # Save current history.
215 0 0         open my $fh, '>', $self->{history_file} or die $!;
216 0           say $fh $_ for $self->_history;
217 0           close $fh;
218              
219             # This will reset the terminal similar to
220             # what these should do:
221             # - "reset"
222             # - "tset"
223             # - "stty echo"
224 0           $self->{term}->deprep_terminal;
225              
226 0           die "Exit via '$how'\n";
227             }
228              
229             sub _history {
230 0     0     my $self = shift;
231              
232             # Setter.
233 0 0         return $self->{term}->SetHistory( @_ ) if @_;
234              
235             # Getter.
236             # Last command should be the first you see upon hiting arrow up
237             # and also without any duplicates.
238 0           reverse uniq reverse $self->{term}->GetHistory;
239             }
240              
241             sub _step {
242 0     0     my ( $self ) = @_;
243              
244             # Current lexical variables in scope.
245 0           my $lexicals = peek_my( 1 );
246 0           my @words = sort keys %$lexicals;
247 0           $self->{attribs}->{completion_word} = \@words;
248              
249 0   0       my $input = $self->{term}->readline( "perl>" ) // '';
250              
251             # Change '#1' to '--maxdepth=1'
252 0 0         if ( $input =~ / ^ p\b /x ) {
253 0           $input =~ s/ \s* \#(\d) \s* $ /, '--maxdepth=$1'/x;
254             }
255              
256 0 0         $self->_exit( $input ) if $input eq 'q';
257              
258 0           $input;
259             }
260              
261             sub _show_error {
262 0     0     my ( $self, $error ) = @_;
263              
264             # Remove eval line numbers.
265 0           $error =~ s/ at \(eval .+//;
266              
267 0           say colored( $error, "RED" );
268             }
269              
270             =head1 SEE ALSO
271              
272             =head2 L
273              
274             Great extendable module!
275              
276             Unfortunately, I did not find a way to get the lexical variables
277             in a scope. (maybe missed a plugin?!)
278              
279             =head2 L
280              
281             This module also looked nice, but same issue.
282              
283             =head1 AUTHOR
284              
285             Tim Potapov, C<< >>
286              
287             =head1 BUGS
288              
289             - no new lexicals
290              
291             Currently its not possible to create any new lexicals variables
292             while I have not yet found a way to run "eval" with a higher scope of lexicals.
293             (perhaps there is another way?perhaps there is another way?)
294              
295             You can make global variables though (with "our" keyword).
296              
297             Please report any (other) bugs or feature requests to L.
298              
299              
300             =head1 SUPPORT
301              
302             You can find documentation for this module with the perldoc command.
303              
304             perldoc Runtime::Debugger
305              
306              
307             You can also look for information at:
308              
309             L
310             L
311              
312              
313             =head1 LICENSE AND COPYRIGHT
314              
315             This software is Copyright (c) 2022 by Tim Potapov.
316              
317             This is free software, licensed under:
318              
319             The Artistic License 2.0 (GPL Compatible)
320              
321              
322             =cut
323              
324             1; # End of Runtime::Debugger