File Coverage

blib/lib/Runtime/Debugger.pm
Criterion Covered Total %
statement 29 70 41.4
branch 0 12 0.0
condition 0 2 0.0
subroutine 10 18 55.5
pod 1 1 100.0
total 40 103 38.8


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   76102 use 5.012;
  1         4  
21 1     1   17 use strict;
  1         2  
  1         18  
22 1     1   3 use warnings;
  1         3  
  1         54  
23 1     1   510 use Data::Dumper;
  1         5622  
  1         59  
24 1     1   431 use Term::ReadLine;
  1         2673  
  1         32  
25 1     1   528 use Term::ANSIColor qw( colored );
  1         7192  
  1         635  
26 1     1   452 use PadWalker qw( peek_my );
  1         587  
  1         49  
27 1     1   6 use feature qw( say );
  1         1  
  1         120  
28 1     1   366 use parent qw( Exporter );
  1         255  
  1         4  
29 1     1   519 use subs qw( p uniq );
  1         19  
  1         5  
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.02
44              
45             =cut
46              
47             our $VERSION = '0.02';
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.
80              
81             Saves history locally.
82              
83             =head1 SUBROUTINES/METHODS
84              
85             =head2 run
86              
87             Runs the REPL (dont forget eval!)
88              
89             eval run
90              
91             Sets C<$@> to the exit reason like 'INT' (Control-C) or 'q' (Normal exit/quit).
92              
93             =cut
94              
95             #
96             # API
97             #
98              
99             sub run {
100 0     0 1   <<'CODE';
101             my $repl = Runtime::Debugger->_init;
102             while ( 1 ) {
103             eval $repl->_step;
104             }
105             CODE
106             }
107              
108             =head2 p
109              
110             Data::Dumper::Dump anything.
111              
112             p 123
113             p [1 ,2, 3]
114              
115             =cut
116              
117             sub p {
118 0     0     my $d = Data::Dumper
119             ->new( \@_ )
120             ->Sortkeys( 1 )
121             ->Terse( 1 )
122             ->Indent( 1 )
123             ->Maxdepth( 1 );
124              
125 0 0         return $d->Dump if wantarray;
126 0           print $d->Dump;
127             }
128              
129             =head2 uniq
130              
131             Return a list of uniq values.
132              
133             =cut
134              
135             sub uniq (@) {
136 0     0     my %h;
137 0           grep { not $h{$_}++ } @_;
  0            
138             }
139              
140             #
141             # Internal
142             #
143              
144             sub _init {
145 0     0     my ( $class ) = @_;
146 0           my $self = bless {
147             history_file => "$ENV{HOME}/.runtime_debugger.info",
148             term => Term::ReadLine->new( $class ),
149             }, $class;
150 0           my $attribs = $self->{attribs} = $self->{term}->Attribs;
151              
152 0           $self->{term}->ornaments( 0 ); # Remove underline from terminal.
153              
154             # Restore last history.
155 0 0         if ( -e $self->{history_file} ) {
156 0           my @history;
157 0 0         open my $fh, '<', $self->{history_file} or die $!;
158 0           while ( <$fh> ) {
159 0           chomp;
160 0           push @history, $_;
161             }
162 0           close $fh;
163 0           $self->_history( @history );
164             }
165              
166             # https://metacpan.org/pod/Term::ReadLine::Gnu#Custom-Completion
167             # Definition for list_completion_function is here: Term/ReadLine/Gnu/XS.pm
168             $attribs->{completion_entry_function} =
169 0           $attribs->{list_completion_function};
170              
171             # Remove these as break chars so that we can complete:
172             # "$scalar", "@array", "%hash"
173             # ("%" was already not in the list).
174 0           $attribs->{completer_word_break_characters} =~ s/ [\$@] //xg;
175              
176             # Setup some signal hnndling.
177 0           for my $signal ( qw( INT TERM HUP ) ) {
178 0     0     $SIG{$signal} = sub { $self->_exit( $signal ) };
  0            
179             }
180              
181 0           $self;
182             }
183              
184             sub _exit {
185 0     0     my ( $self, $how ) = @_;
186              
187             # Save current history.
188 0 0         open my $fh, '>', $self->{history_file} or die $!;
189 0           say $fh $_ for $self->_history;
190 0           close $fh;
191              
192             # This will reset the terminal similar to
193             # what these should do:
194             # - "reset"
195             # - "tset"
196             # - "stty echo"
197 0           $self->{term}->deprep_terminal;
198              
199 0           die "Exit via '$how'\n";
200             }
201              
202             sub _history {
203 0     0     my $self = shift;
204              
205             # Setter.
206 0 0         return $self->{term}->SetHistory( @_ ) if @_;
207              
208             # Getter.
209             # Last command should be the first you see upon hiting arrow up
210             # and also without any duplicates.
211 0           reverse uniq reverse $self->{term}->GetHistory;
212             }
213              
214             sub _step {
215 0     0     my ( $self ) = @_;
216              
217             # Current lexical variables in scope.
218 0           my $lexicals = peek_my( 1 );
219 0           my @words = sort keys %$lexicals;
220 0           $self->{attribs}->{completion_word} = \@words;
221              
222 0   0       my $input = $self->{term}->readline( "perl>" ) // '';
223              
224 0 0         $self->_exit( $input ) if $input eq 'q';
225              
226 0           $input;
227             }
228              
229             =head1 SEE ALSO
230              
231             =head2 L
232              
233             Great extendable module!
234              
235             Unfortunately, I did not find a way to get the lexical variables
236             in a scope. (maybe missed a plugin?!)
237              
238             =head2 L
239              
240             This module also looked nice, but same issue.
241              
242             =head1 AUTHOR
243              
244             Tim Potapov, C<< >>
245              
246             =head1 BUGS
247              
248             Please report any bugs or feature requests to L.
249              
250              
251             =head1 SUPPORT
252              
253             You can find documentation for this module with the perldoc command.
254              
255             perldoc Runtime::Debugger
256              
257              
258             You can also look for information at:
259              
260             L
261             L
262              
263              
264             =head1 LICENSE AND COPYRIGHT
265              
266             This software is Copyright (c) 2022 by Tim Potapov.
267              
268             This is free software, licensed under:
269              
270             The Artistic License 2.0 (GPL Compatible)
271              
272              
273             =cut
274              
275             1; # End of Runtime::Debugger