File Coverage

lib/Badger/Debug.pm
Criterion Covered Total %
statement 125 174 71.8
branch 43 74 58.1
condition 15 32 46.8
subroutine 24 33 72.7
pod 17 17 100.0
total 224 330 67.8


line stmt bran cond sub pod time code
1             #========================================================================
2             #
3             # Badger::Debug
4             #
5             # DESCRIPTION
6             # Mixin module implementing functionality for debugging.
7             #
8             # AUTHOR
9             # Andy Wardley
10             #
11             #========================================================================
12              
13             package Badger::Debug;
14              
15 70     70   7764 use Carp;
  70         140  
  70         3992  
16             use Badger::Rainbow
17 70     70   22264 ANSI => 'bold red yellow green cyan white';
  70         153  
  70         451  
18 70     70   386 use Scalar::Util qw( blessed refaddr );
  70         181  
  70         12658  
19             use Badger::Class
20 70         1621 base => 'Badger::Exporter',
21             version => 0.01,
22             constants => 'PKG REFS SCALAR ARRAY HASH CODE REGEX DELIMITER',
23             words => 'DEBUG',
24             import => 'class',
25             constant => {
26             UNDEF => '',
27             },
28             exports => {
29             tags => {
30             debug => 'debugging debug debugf debug_up debug_at debug_caller
31             debug_callers debug_args',
32             dump => 'dump dump_data dump_data_inline
33             dump_ref dump_hash dump_list dump_text'
34             },
35             hooks => {
36             color => \&enable_colour,
37             colour => \&enable_colour,
38             dumps => [\&_export_debug_dumps, 1], # expects 1 arguments
39             default => [\&_export_debug_default, 1],
40             modules => [\&_export_debug_modules, 1],
41             'DEBUG' => [\&_export_debug_constant, 1],
42             '$DEBUG' => [\&_export_debug_variable, 1],
43             },
44 70     70   462 };
  70         133  
45              
46             our $PAD = ' ';
47             our $MAX_TEXT = 48;
48             our $MAX_DEPTH = 3; # prevent runaways in debug/dump
49             our $FORMAT = "[ line ]\n"
50             unless defined $FORMAT;
51             our $PROMPT = '> '
52             unless defined $PROMPT;
53             our $MESSAGE = "$PROMPT%s";
54             our $HIDE_UNDER = 1;
55             our $CALLER_UP = 0; # hackola to allow debug() to use a different caller
56             our $CALLER_AT = { }; # ditto
57             our $DUMPING = { };
58             our $DEBUG = 0 unless defined $DEBUG;
59             our $DUMP_METHOD = 'dump';
60              
61             #-----------------------------------------------------------------------
62             # export hooks
63             #-----------------------------------------------------------------------
64              
65             sub _export_debug_dumps {
66 141     141   353 my ($self, $target, $symbol, $value, $symbols) = @_;
67             $self->export_symbol($target, dumper => sub {
68 1     1   4 $_[0]->dump_hash($_[0],$_[1],$value);
69 141         998 });
70 141         344 unshift(@$symbols, ':dump');
71 141         293 return $self;
72             }
73              
74              
75             sub _export_debug_default {
76 1255     1255   2560 my ($self, $target, $symbol, $value, $symbols) = @_;
77 1255         3937 unshift(
78             @$symbols,
79             '$DEBUG' => $value,
80             'DEBUG' => $value,
81             'debug',
82             'debugging'
83             );
84 1255         2359 return $self;
85             }
86              
87              
88             sub _export_debug_variable {
89 1257     1257   2211 my ($self, $target, $symbol, $value) = @_;
90 70     70   3001 no strict REFS;
  70         246  
  70         8726  
91              
92             # use any existing value in $DEBUG
93 3         10 $value = ${ $target.PKG.DEBUG }
94 1257 100       1265 if defined ${ $target.PKG.DEBUG };
  1257         6217  
95              
96 1257 50       2281 $self->debug("$symbol option setting $target \$DEBUG to $value\n") if $DEBUG;
97 1257         1546 *{ $target.PKG.DEBUG } = \$value;
  1257         4500  
98             }
99              
100              
101             sub _export_debug_constant {
102 1257     1257   2093 my ($self, $target, $symbol, $value) = @_;
103 70     70   453 no strict REFS;
  70         347  
  70         11721  
104              
105             # use any existing value in $DEBUG
106 1256         2497 $value = ${ $target.PKG.DEBUG }
107 1257 100       1302 if defined ${ $target.PKG.DEBUG };
  1257         3658  
108              
109 1257 50       2065 $self->debug("$symbol option setting $target DEBUG to $value\n") if $DEBUG;
110 1257         1408 my $temp = $value; # make sure this is a const sub on 5.22
111 1257     0   8633 *{ $target.PKG.DEBUG } = sub () { $temp };
  1257         4437  
  0         0  
112             }
113              
114              
115             sub _export_debug_modules {
116 2     2   7 my ($self, $target, $symbol, $modules) = @_;
117 2         8 $self->debug_modules($modules);
118             }
119              
120              
121             #-----------------------------------------------------------------------
122             # exportable debugging methods
123             #-----------------------------------------------------------------------
124              
125             sub debugging {
126 15     15 1 34 my $self = shift;
127 15   66     49 my $pkg = ref $self || $self;
128 70     70   434 no strict REFS;
  70         109  
  70         61014  
129              
130             # return current $DEBUG value when called without args
131 15 100 100     30 return ${ $pkg.PKG.DEBUG } || 0
132             unless @_;
133              
134             # set new debug value when called with an argument
135 8         9 my $debug = shift;
136 8 50       16 $debug = 0 if $debug =~ /^off$/i;
137              
138             # TODO: consider setting different parts of the flag, like TT2,
139              
140 8 50       14 $self->debug("debugging() Setting $pkg debug to $debug\n") if $DEBUG;
141              
142 8 50       9 if (defined ${ $pkg.PKG.DEBUG }) {
  8         37  
143             # update existing variable
144 8         10 ${ $pkg.PKG.DEBUG } = $debug;
  8         19  
145             }
146             else {
147             # define new variable, poking it into the symbol table using
148             # *{...} rather than ${...} so that it's visible at compile time,
149             # thus preventing any "Variable $DEBUG not defined errors
150 0         0 *{ $pkg.PKG.DEBUG } = \$debug;
  0         0  
151             }
152 8         23 return $debug;
153             }
154              
155              
156             sub debug {
157 9     9 1 37 my $self = shift;
158 9   66     29 my $msg = join('', @_),
159             my $class = ref $self || $self;
160 9   66     30 my $format = $CALLER_AT->{ format } || $FORMAT;
161 9         52 my ($pkg, $file, $line) = caller($CALLER_UP);
162 9         22 my (undef, undef, undef, $sub) = caller($CALLER_UP + 1);
163 9 100       16 if (defined $sub) {
164 2         30 $sub =~ s/.*?([^:]+)$/::$1()/;
165             }
166             else {
167 7         19 $sub = '';
168             }
169 9 100       25 my $where = ($class eq $pkg)
170             ? $class . $sub
171             : $pkg . $sub . " ($class)";
172              
173 9         24 $msg = join("\n", map { sprintf($MESSAGE, $_) } split("\n", $msg));
  9         31  
174             # $msg =~ s/^/$PROMPT/gm;
175              
176             # We load this dynamically because it uses Badger::Debug and we don't
177             # want to end up in a gruesome birth spiral
178 9         42 require Badger::Timestamp;
179 9         31 my $now = Badger::Timestamp->now;
180 9         27 my $data = {
181             msg => $msg,
182             where => $where,
183             class => $class,
184             file => $file,
185             line => $line,
186             pkg => $pkg,
187             sub => $sub,
188             date => $now->date,
189             time => $now->time,
190             pid => $$,
191             %$CALLER_AT,
192             };
193 9 50       57 $format =~ s/<(\w+)>/defined $data->{ $1 } ? $data->{ $1 } : "<$1 undef>"/eg;
  28         103  
194 9 50       27 $format .= "\n" unless $format =~ /\n$/;
195              
196 9         32 print STDERR $format;
197             }
198              
199              
200             sub debugf {
201 1     1 1 7 local $CALLER_UP = 1;
202 1         5 shift->debug( sprintf(shift, @_) );
203             }
204              
205              
206             sub debug_up {
207 0     0 1 0 my $self = shift;
208 0         0 local $CALLER_UP = shift;
209 0         0 $self->debug(@_);
210             }
211              
212              
213             sub debug_at {
214 3     3 1 21 my $self = shift;
215 3         3 local $CALLER_AT = shift;
216 3         4 local $CALLER_UP = 1;
217 3         7 $self->debug(@_);
218             }
219              
220              
221             sub debug_caller {
222 0     0 1 0 my $self = shift;
223 0         0 my ($pkg, $file, $line, $sub) = caller(1);
224 0         0 my $msg = "$sub called from ";
225 0         0 ($pkg, undef, undef, $sub) = caller(2);
226 0         0 $msg .= "$sub in $file at line $line\n";
227 0         0 $self->debug($msg);
228             }
229              
230              
231             sub debug_callers {
232 0     0 1 0 my $self = shift;
233 0         0 my $msg = '';
234 0         0 my $i = 1;
235              
236 0         0 while (1) {
237 0         0 my @info = caller($i);
238 0 0       0 last unless @info;
239 0         0 my ($pkg, $file, $line, $sub) = @info;
240 0         0 $msg .= sprintf(
241             "%4s: Called from %s in %s at line %s\n",
242             '#' . $i++, $sub, $file, $line
243             );
244             }
245 0         0 $self->debug($msg);
246             }
247              
248              
249             sub debug_args {
250 0     0 1 0 my $self = shift;
251             $self->debug_up(
252             2, "args: ",
253 0         0 join(', ', map { $self->dump_data_inline($_) } @_),
  0         0  
254             "\n"
255             );
256             }
257              
258              
259             sub debug_modules {
260 2     2 1 3 my $self = shift;
261 2 50       8 my $modules = @_ == 1 ? shift : [ @_ ];
262 2         2 my $debug = 1;
263              
264 2 50       26 $modules = [ split(DELIMITER, $modules) ]
265             unless ref $modules eq ARRAY;
266              
267             # TODO: handle other refs?
268              
269 2         17 foreach my $pkg (@$modules) {
270 70     70   465 no strict REFS;
  70         115  
  70         74614  
271 2         4 *{ $pkg.PKG.DEBUG } = \$debug;
  2         21  
272             }
273             }
274              
275              
276             #-----------------------------------------------------------------------
277             # data dumping methods
278             #-----------------------------------------------------------------------
279              
280             sub dump {
281 3     3 1 6 my $self = shift;
282 3         12 my $code = $self->can('dumper');
283 3 100       9 return $code
284             ? $code->($self, @_)
285             : $self->dump_ref($self, @_);
286             }
287              
288              
289             sub dump_data {
290 0     0 1 0 local $DUMPING = { };
291 0         0 _dump_data(@_);
292             }
293              
294              
295             sub _dump_data {
296 13 50 66 13   40 if (! defined $_[1]) {
    100          
    100          
297 0         0 return UNDEF;
298             }
299             elsif (! ref $_[1]) {
300 9         45 return $_[1];
301             }
302             elsif (blessed($_[1]) && (my $code = $_[1]->can($DUMP_METHOD))) {
303 2         2 shift; # remove $self object, leave target object first
304 2         6 return $code->(@_);
305             }
306             else {
307 2         8 goto &dump_ref;
308             }
309             }
310              
311              
312             sub dump_ref {
313 4     4 1 7 my ($self, $data, $indent) = @_;
314 4 50       11 return "<$data>" if $DUMPING->{ $data }++;
315              
316             # TODO: change these to reftype
317 4 100       11 if (UNIVERSAL::isa($data, HASH)) {
    50          
    0          
    0          
318 3         6 return dump_hash($self, $data, $indent);
319             }
320             elsif (UNIVERSAL::isa($data, ARRAY)) {
321 1         3 return dump_list($self, $data, $indent);
322             }
323             elsif (UNIVERSAL::isa($data, REGEX)) {
324 0         0 return dump_text($self, $data);
325             }
326             elsif (UNIVERSAL::isa($data, SCALAR)) {
327 0         0 return dump_text($self, $$data);
328             }
329             else {
330 0         0 return $data;
331             }
332             }
333              
334              
335             sub dump_data_inline {
336 0     0 1 0 local $PAD = '';
337 0         0 my $text = shift->dump_data(@_);
338 0         0 $text =~ s/\n/ /g;
339 0         0 return $text;
340             }
341              
342              
343             sub dump_hash {
344 4     4 1 7 my ($self, $hash, $indent, $keys) = @_;
345 4   100     9 $indent ||= 0;
346 4 50       6 return "..." if $indent > $MAX_DEPTH;
347 4         7 my $pad = $PAD x $indent;
348              
349 4 50 33     13 return '{ }' unless $hash && %$hash;
350              
351 4 100       6 if ($keys) {
352 1 50       8 $keys = [ split(DELIMITER, $keys) ]
353             unless ref $keys;
354 1 50       4 $keys = { map { $_ => 1 } @$keys }
  2         5  
355             if ref $keys eq ARRAY;
356 1 50       4 return $self->error("Invalid keys passed to dump_hash(): $keys")
357             unless ref $keys eq HASH;
358              
359 1 50       3 $self->debug("constructed hash keys: ", join(', ', %$keys)) if $DEBUG;
360             }
361              
362             return "\{\n"
363             . join( ",\n",
364 11         24 map { "$pad$PAD$_ => " . _dump_data($self, $hash->{$_}, $indent + 1) }
365             sort
366 12 100       19 grep { $keys ? $keys->{ $_ } : 1 }
367 4 50 33     10 grep { (/^_/ && $HIDE_UNDER) ? 0 : 1 }
  12         24  
368             keys %$hash
369             )
370             . "\n$pad}";
371             }
372              
373              
374             sub dump_list {
375 1     1 1 3 my ($self, $list, $indent) = @_;
376 1   50     3 $indent ||= 0;
377 1         2 my $pad = $PAD x $indent;
378              
379 1 50       2 return '[ ]' unless @$list;
380             return "\[\n$pad$PAD"
381             . ( @$list
382 1 50       9 ? join(",\n$pad$PAD", map { _dump_data($self, $_, $indent + 1) } @$list)
  2         4  
383             : '' )
384             . "\n$pad]";
385             }
386              
387              
388             sub dump_text {
389 0     0 1   my ($self, $text, $length) = @_;
390 0 0         $text = $$text if ref $text;
391 0   0       $length ||= $MAX_TEXT;
392 0           my $snippet = substr($text, 0, $length);
393 0 0         $snippet .= '...' if length $text > $length;
394 0           $snippet =~ s/\n/\\n/g;
395 0           return $snippet;
396             }
397              
398              
399              
400             #-----------------------------------------------------------------------
401             # enable_colour()
402             #
403             # Export hook which gets called when the Badger::Debug module is
404             # used with the 'colour' or 'color' option. It redefines the formats
405             # for $Badger::Base::DEBUG_FORMAT and $Badger::Exception::FORMAT
406             # to display in glorious ANSI technicolor.
407             #-----------------------------------------------------------------------
408              
409             sub enable_colour {
410 0     0 1   my ($class, $target, $symbol) = @_;
411 0   0       $target ||= (caller())[0];
412 0   0       $symbol ||= 'colour';
413              
414 0           print bold green "Enabling debug in $symbol from $target\n";
415              
416             # colour the debug format
417 0           $MESSAGE = cyan($PROMPT) . yellow('%s');
418 0           $FORMAT
419             = cyan('[ line ]')
420             . "\n";
421              
422             # exceptions are in red
423 0           $Badger::Exception::FORMAT
424             = bold red $Badger::Exception::FORMAT;
425              
426             $Badger::Exception::MESSAGES->{ caller }
427 0           = yellow('<4>') . cyan(' called from ')
428             . yellow("<1>\n") . cyan(' in ')
429             . white('<2>') . cyan(' at line ')
430             . white('<3>');
431             }
432              
433              
434              
435             1;
436              
437             __END__