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   8220 use Carp;
  70         151  
  70         4423  
16             use Badger::Rainbow
17 70     70   26589 ANSI => 'bold red yellow green cyan white';
  70         156  
  70         446  
18 70     70   450 use Scalar::Util qw( blessed refaddr );
  70         203  
  70         14507  
19             use Badger::Class
20 70         1778 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   518 };
  70         136  
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   527 my ($self, $target, $symbol, $value, $symbols) = @_;
67             $self->export_symbol($target, dumper => sub {
68 1     1   6 $_[0]->dump_hash($_[0],$_[1],$value);
69 141         1608 });
70 141         392 unshift(@$symbols, ':dump');
71 141         361 return $self;
72             }
73              
74              
75             sub _export_debug_default {
76 1255     1255   2684 my ($self, $target, $symbol, $value, $symbols) = @_;
77 1255         4282 unshift(
78             @$symbols,
79             '$DEBUG' => $value,
80             'DEBUG' => $value,
81             'debug',
82             'debugging'
83             );
84 1255         2691 return $self;
85             }
86              
87              
88             sub _export_debug_variable {
89 1257     1257   2323 my ($self, $target, $symbol, $value) = @_;
90 70     70   3292 no strict REFS;
  70         208  
  70         9960  
91              
92             # use any existing value in $DEBUG
93 3         12 $value = ${ $target.PKG.DEBUG }
94 1257 100       1448 if defined ${ $target.PKG.DEBUG };
  1257         7201  
95              
96 1257 50       2453 $self->debug("$symbol option setting $target \$DEBUG to $value\n") if $DEBUG;
97 1257         1735 *{ $target.PKG.DEBUG } = \$value;
  1257         5124  
98             }
99              
100              
101             sub _export_debug_constant {
102 1257     1257   2307 my ($self, $target, $symbol, $value) = @_;
103 70     70   512 no strict REFS;
  70         390  
  70         13505  
104              
105             # use any existing value in $DEBUG
106 1256         2682 $value = ${ $target.PKG.DEBUG }
107 1257 100       1566 if defined ${ $target.PKG.DEBUG };
  1257         3934  
108              
109 1257 50       2338 $self->debug("$symbol option setting $target DEBUG to $value\n") if $DEBUG;
110 1257         1532 my $temp = $value; # make sure this is a const sub on 5.22
111 1257     0   9122 *{ $target.PKG.DEBUG } = sub () { $temp };
  1257         5028  
  0         0  
112             }
113              
114              
115             sub _export_debug_modules {
116 2     2   6 my ($self, $target, $symbol, $modules) = @_;
117 2         7 $self->debug_modules($modules);
118             }
119              
120              
121             #-----------------------------------------------------------------------
122             # exportable debugging methods
123             #-----------------------------------------------------------------------
124              
125             sub debugging {
126 15     15 1 43 my $self = shift;
127 15   66     45 my $pkg = ref $self || $self;
128 70     70   485 no strict REFS;
  70         140  
  70         70814  
129              
130             # return current $DEBUG value when called without args
131 15 100 100     32 return ${ $pkg.PKG.DEBUG } || 0
132             unless @_;
133              
134             # set new debug value when called with an argument
135 8         12 my $debug = shift;
136 8 50       22 $debug = 0 if $debug =~ /^off$/i;
137              
138             # TODO: consider setting different parts of the flag, like TT2,
139              
140 8 50       15 $self->debug("debugging() Setting $pkg debug to $debug\n") if $DEBUG;
141              
142 8 50       9 if (defined ${ $pkg.PKG.DEBUG }) {
  8         35  
143             # update existing variable
144 8         10 ${ $pkg.PKG.DEBUG } = $debug;
  8         23  
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         36 return $debug;
153             }
154              
155              
156             sub debug {
157 9     9 1 41 my $self = shift;
158 9   66     31 my $msg = join('', @_),
159             my $class = ref $self || $self;
160 9   66     30 my $format = $CALLER_AT->{ format } || $FORMAT;
161 9         58 my ($pkg, $file, $line) = caller($CALLER_UP);
162 9         25 my (undef, undef, undef, $sub) = caller($CALLER_UP + 1);
163 9 100       17 if (defined $sub) {
164 2         26 $sub =~ s/.*?([^:]+)$/::$1()/;
165             }
166             else {
167 7         11 $sub = '';
168             }
169 9 100       27 my $where = ($class eq $pkg)
170             ? $class . $sub
171             : $pkg . $sub . " ($class)";
172              
173 9         23 $msg = join("\n", map { sprintf($MESSAGE, $_) } split("\n", $msg));
  9         34  
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         45 require Badger::Timestamp;
179 9         29 my $now = Badger::Timestamp->now;
180 9         26 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       61 $format =~ s/<(\w+)>/defined $data->{ $1 } ? $data->{ $1 } : "<$1 undef>"/eg;
  28         124  
194 9 50       32 $format .= "\n" unless $format =~ /\n$/;
195              
196 9         32 print STDERR $format;
197             }
198              
199              
200             sub debugf {
201 1     1 1 8 local $CALLER_UP = 1;
202 1         4 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 24 my $self = shift;
215 3         4 local $CALLER_AT = shift;
216 3         5 local $CALLER_UP = 1;
217 3         6 $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 4 my $self = shift;
261 2 50       6 my $modules = @_ == 1 ? shift : [ @_ ];
262 2         3 my $debug = 1;
263              
264 2 50       17 $modules = [ split(DELIMITER, $modules) ]
265             unless ref $modules eq ARRAY;
266              
267             # TODO: handle other refs?
268              
269 2         7 foreach my $pkg (@$modules) {
270 70     70   544 no strict REFS;
  70         157  
  70         86168  
271 2         3 *{ $pkg.PKG.DEBUG } = \$debug;
  2         17  
272             }
273             }
274              
275              
276             #-----------------------------------------------------------------------
277             # data dumping methods
278             #-----------------------------------------------------------------------
279              
280             sub dump {
281 3     3 1 7 my $self = shift;
282 3         14 my $code = $self->can('dumper');
283 3 100       12 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   54 if (! defined $_[1]) {
    100          
    100          
297 0         0 return UNDEF;
298             }
299             elsif (! ref $_[1]) {
300 9         60 return $_[1];
301             }
302             elsif (blessed($_[1]) && (my $code = $_[1]->can($DUMP_METHOD))) {
303 2         5 shift; # remove $self object, leave target object first
304 2         6 return $code->(@_);
305             }
306             else {
307 2         9 goto &dump_ref;
308             }
309             }
310              
311              
312             sub dump_ref {
313 4     4 1 7 my ($self, $data, $indent) = @_;
314 4 50       12 return "<$data>" if $DUMPING->{ $data }++;
315              
316             # TODO: change these to reftype
317 4 100       15 if (UNIVERSAL::isa($data, HASH)) {
    50          
    0          
    0          
318 3         8 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 9 my ($self, $hash, $indent, $keys) = @_;
345 4   100     10 $indent ||= 0;
346 4 50       8 return "..." if $indent > $MAX_DEPTH;
347 4         9 my $pad = $PAD x $indent;
348              
349 4 50 33     14 return '{ }' unless $hash && %$hash;
350              
351 4 100       8 if ($keys) {
352 1 50       12 $keys = [ split(DELIMITER, $keys) ]
353             unless ref $keys;
354 1 50       5 $keys = { map { $_ => 1 } @$keys }
  2         6  
355             if ref $keys eq ARRAY;
356 1 50       5 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         26 map { "$pad$PAD$_ => " . _dump_data($self, $hash->{$_}, $indent + 1) }
365             sort
366 12 100       24 grep { $keys ? $keys->{ $_ } : 1 }
367 4 50 33     10 grep { (/^_/ && $HIDE_UNDER) ? 0 : 1 }
  12         31  
368             keys %$hash
369             )
370             . "\n$pad}";
371             }
372              
373              
374             sub dump_list {
375 1     1 1 4 my ($self, $list, $indent) = @_;
376 1   50     4 $indent ||= 0;
377 1         2 my $pad = $PAD x $indent;
378              
379 1 50       3 return '[ ]' unless @$list;
380             return "\[\n$pad$PAD"
381             . ( @$list
382 1 50       12 ? 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__