File Coverage

blib/lib/Debug/Xray.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Debug::Xray;
2 1     1   61587 use strict;
  1         2  
  1         41  
3 1     1   6 use warnings;
  1         2  
  1         32  
4            
5 1     1   5 use feature qw(state);
  1         5  
  1         89  
6            
7 1     1   4 use Exporter qw(import);
  1         2  
  1         71  
8            
9             our $VERSION = 0.05;
10             our @ISA = qw(Exporter);
11             our @EXPORT_OK;
12            
13 1     1   382 no Carp::Assert;
  0            
  0            
14             use Hook::LexWrap;
15             use Data::Dumper;
16             use PPI;
17             use PadWalker qw(var_name);
18             use Debug::Xray::WatchScalar qw( set_log_handler TIESCALAR STORE FETCH );
19            
20            
21             # TODO Oranize subs into EXPORT_TAGS
22             # CONFIGURATION
23             push @EXPORT_OK, qw{
24             &set_debug_verbose
25             &set_debug_quiet
26             &watch_subs
27             &watch_all_subs
28             };
29            
30            
31             # TRACK SUBROUTINE EXECUTION
32             push @EXPORT_OK, qw{
33             &start_sub
34             &end_sub
35             &dprint
36             };
37            
38             # WATCH VARIABLE ROUTINES
39             push @EXPORT_OK, qw{
40             &add_watch_var
41             };
42            
43             # TESTING OF THIS MODULE
44             push @EXPORT_OK, qw{
45             &is_carp_debug
46             };
47            
48             # WARNING AND ERROR HANDLING
49             push @EXPORT_OK, qw{
50             &debug_warn_handling
51             &default_warn_handling
52             &debug_error_handling
53             &default_error_handling
54             };
55            
56            
57             my $Verbose = 1;
58             my $SUB_NEST_LIMIT = 200;
59            
60             my $LogFile = '/home/dave/Desktop/Jobs/computer_exercises/perl/debug/Debug.log';
61            
62             my $VOID_CONTEXT_ERROR_MESSAGE = 'The caller of this function must assign the return value. ' .
63             'The hooks remain in effect only when the returned value is in lexical scope.';
64            
65             my @SubStack;
66            
67             Debug::Xray::WatchScalar->set_log_handler(\&dprint);
68            
69             sub set_debug_verbose { $Verbose = 1 };
70             sub set_debug_quiet { $Verbose = 0 };
71             sub is_verbose { return $Verbose };
72             sub is_carp_debug {
73             return 1 if DEBUG;
74             return 0;
75             }
76            
77            
78             # MESSAGE PRINT ROUTINES
79            
80             sub dprint($) {
81             return unless $Verbose;
82            
83             my ($mesg) = shift;
84             my $print_line = _indentation() . $mesg;
85            
86             print "$print_line\n";
87            
88             _log_to_file($print_line) if $LogFile;
89             return $print_line;
90             }
91            
92            
93             sub _log_to_file {
94             assert ( $#_==0, 'Parms' ) if DEBUG;
95             state $HLog;
96            
97             unless ($HLog) {open ( $HLog, ">$LogFile" ) or die "Could not open log file $LogFile: $!"};
98            
99             my $print_line = shift;
100             print $HLog "$print_line\n";
101             }
102            
103            
104            
105             sub debug_warn_handling {
106             $SIG{__WARN__} = sub { &_warn_handler(@_); };
107            
108             }
109             sub default_warn_handling {
110             $SIG{__WARN__} = 'DEFAULT';
111             }
112            
113            
114             sub debug_error_handling {
115             $SIG{__DIE__} = sub { &_error_handler(@_); };
116             }
117             sub default_error_handling {
118             $SIG{__DIE__} = 'DEFAULT';
119             }
120            
121             # TODO Call Stack for error handlers
122             sub _warn_handler {
123             my @msgs = @_;
124            
125             for my $msg (@msgs) {
126             dprint ("Warning: $msg");
127             }
128             #return @_;
129             }
130            
131             sub _error_handler {
132             my @msgs = @_;
133             for my $msg (@msgs) {
134             dprint ("Error: $msg");
135             }
136             #return @_;
137             }
138            
139            
140            
141            
142             sub start_sub {
143             return unless $Verbose;
144            
145             my $msg = shift || (caller(1))[3];
146             assert ( $#SubStack < $SUB_NEST_LIMIT, "Too many subs on stack " . Dumper \@SubStack) if DEBUG;
147             assert ( defined $msg ) if DEBUG;
148            
149             dprint "SUB: $msg";
150             push @SubStack, $msg;
151             }
152            
153            
154             sub end_sub {
155             return unless $Verbose;
156            
157             my $msg = shift || (caller(1))[3];
158             assert ( $msg !~ m/start_sub/) if DEBUG;
159             assert ( $msg !~ m/end_sub/) if DEBUG;
160             assert ( $SubStack[$#SubStack] eq $msg,
161             "Stack of size $#SubStack out of synch. Popping $SubStack[$#SubStack], expected $msg\nStack is " .
162             Dumper (\@SubStack) . "\n" ) if DEBUG;
163            
164             pop @SubStack;
165            
166             dprint "END: $msg";
167             }
168            
169            
170             sub _indentation() {
171             return " " x ($#SubStack+1);
172             }
173            
174            
175            
176             # SUBROUTINE HOOK ROUTINES
177            
178             sub watch_subs { # NOTE: Hooks stay in effect within the lexical scope of the return value
179             assert ( defined wantarray, $VOID_CONTEXT_ERROR_MESSAGE ) if DEBUG;
180            
181             my @sub_names = @_;
182            
183             my $hooks;
184             for my $sub_name (@sub_names) {
185             push @$hooks, wrap $sub_name,
186             pre => sub { start_sub ($sub_name) },
187             post => sub { end_sub ($sub_name) };
188             }
189            
190             return $hooks;
191             }
192            
193            
194             sub watch_all_subs { # NOTE: Hooks stay in effect within the lexical scope of the return value
195             assert ( defined wantarray, $VOID_CONTEXT_ERROR_MESSAGE ) if DEBUG;
196            
197             my @caller = caller();
198             my $Document = PPI::Document->new("$caller[1]");
199             my $sub_nodes = $Document->find(
200             sub { $_[1]->isa('PPI::Statement::Sub') }
201             );
202            
203             my @sub_names;
204             for my $sub_node (@$sub_nodes) {
205             next if $sub_node->name eq 'BEGIN';
206             push @sub_names, $caller[0].'::'.$sub_node->name;
207             }
208            
209             return watch_subs(@sub_names);
210             }
211            
212            
213             sub add_watch_var {
214             assert ( $#_==0, 'Parms' ) if DEBUG;
215             my $var_ref = shift;
216             my $var_name = var_name(1, $var_ref);
217             assert ( $var_name, "var_name has a value: $var_name]" ) if DEBUG;
218            
219             if ($var_name =~ /^\$/) {
220             tie $$var_ref, 'Debug::Xray::WatchScalar', $var_name, $$var_ref;
221             }
222             elsif ($var_name =~ /^\@/) { die 'Not implemented yet' }
223             elsif ($var_name =~ /^\%/) { die 'Not implemented yet' }
224             else { die "Invalid variable name '$var_name'" if DEBUG }
225            
226             return $var_name if DEBUG;
227             }
228            
229            
230             __END__