File Coverage

blib/lib/Data/PrettyPrintObjects.pm
Criterion Covered Total %
statement 245 262 93.5
branch 113 134 84.3
condition 12 15 80.0
subroutine 20 20 100.0
pod 3 3 100.0
total 393 434 90.5


line stmt bran cond sub pod time code
1             package Data::PrettyPrintObjects;
2             # Copyright (c) 2010-2010 Sullivan Beck. All rights reserved.
3             # This program is free software; you can redistribute it and/or modify
4             # it under the same terms as Perl itself.
5              
6             ########################################################################
7             # PREREQUISITES
8             ########################################################################
9              
10 12     12   800611 use warnings;
  12         32  
  12         603  
11 12     12   69 use strict;
  12         27  
  12         654  
12              
13             require Exporter;
14 12     12   67 use Scalar::Util qw(reftype blessed);
  12         23  
  12         1886  
15              
16             our (@ISA,@EXPORT);
17             @ISA = qw(Exporter);
18             @EXPORT = qw(PPO
19             PPO_Options
20             PPO_OptionsFile
21             );
22              
23 12     12   11629 use YAML::Syck;
  12         30375  
  12         17785  
24              
25             our $VERSION;
26             $VERSION='1.00';
27              
28             ########################################################################
29             # INITIALIZATION
30             ########################################################################
31              
32             my $config_file = '.ppo.yaml';
33              
34             our(%Options,%Refs,$Links,%Defaults,%ObjDefaults,%Printed);
35              
36             # %Options = ( OPTION => VAL,
37             # ...
38             # objs => { OBJECT => { OBJ_OPTION => VAL,
39             # ...
40             # },
41             # ...
42             # }
43             # )
44             #
45             # OPTION is any key included in %Defaults
46             # OBJ_OPTION is any key included in %ObjDefautls
47             # OBJECT is any value returned by ref($object)
48              
49             # %Refs = ( REF => [ LINK, N ],
50             # ...
51             # )
52             #
53             # REF is ARRAY(0x111111)
54             # LINK is $VAR->[0]
55             # N is the number of times this reference appears in
56             # the data structure
57              
58             # $Links = 1 if ciruclar or duplicate references are found
59             # in the data structure
60              
61             # %Printed = ( REF => 1,
62             # ...
63             # )
64             #
65             # This is a list of all references (keys from %Refs)
66             # which have already been printed.
67              
68             %Defaults = ( 'indent' => 2,
69             'list_format' => 'standard',
70             'max_depth' => 0,
71             'max_depth_method' => 'ref',
72             'duplicates' => 'link',
73             );
74              
75             %ObjDefaults = ( 'print' => 'ref',
76             'type' => 'scalar',
77             'ref' => 0,
78             'args' => [],
79             'func' => '',
80             );
81              
82             if (-f $config_file) {
83             PPO_OptionsFile($config_file);
84             }
85              
86             ########################################################################
87             # BASE METHODS
88             ########################################################################
89              
90             sub PPO_Options {
91 7     7 1 1785 my(%options) = @_;
92 7         32 foreach my $key (keys %options) {
93 8 100       36 if ($key eq 'objs') {
94 1         2 foreach my $obj (keys %{ $options{$key} }) {
  1         8  
95 15         22 my $val = $options{$key}{$obj};
96 15         30 $Options{$key}{$obj} = $val;
97             }
98             } else {
99 7         17 my $val = $options{$key};
100 7         35 $Options{$key} = $val;
101             }
102             }
103             }
104              
105             sub PPO_OptionsFile {
106 1     1 1 265 my($file) = @_;
107 1         5 my $opts = LoadFile($file);
108              
109 1         206 foreach my $key (keys %$opts) {
110 1 50       5 if ($key eq 'objs') {
111 0         0 foreach my $obj (keys %{ $$opts{$key} }) {
  0         0  
112 0         0 my $val = $$opts{$key}{$obj};
113 0         0 $Options{$key}{$obj} = $val;
114             }
115             } else {
116 1         2 my $val = $$opts{$key};
117 1         6 $Options{$key} = $val;
118             }
119             }
120             }
121              
122             sub PPO {
123 100     100 1 220177 my ($val) = @_;
124              
125 100         205 _refs($val);
126              
127 100         139 my $depth = 1;
128 100         1485 my $type = ref($val);
129 100         631 my @str;
130              
131 100 100       1394 if (! $type) {
    100          
    50          
132 43         87 @str = _print_scalar($val);
133              
134             } elsif ($type eq "ARRAY") {
135 30         710 @str = _print_array($val,$depth);
136              
137             } elsif ($type eq "HASH") {
138 27         65 @str = _print_hash($val,$depth);
139              
140             } else {
141 0         0 @str = _print_object($val,$depth);
142             }
143              
144 100         333 my $str = join("\n",@str) . "\n";
145 100         334 return $str;
146             }
147              
148             ########################################################################
149             ########################################################################
150              
151             sub _option {
152 490     490   618 my($opt,$obj) = @_;
153              
154 490 100       813 if (defined $obj) {
155 90 100       317 if (exists $Options{'objs'}{$obj}{$opt}) {
    50          
156 48         129 return $Options{'objs'}{$obj}{$opt};
157             } elsif (exists $ObjDefaults{$opt}) {
158 42         115 return $ObjDefaults{$opt};
159             } else {
160 0         0 return undef;
161             }
162              
163             } else {
164 400 100       1352 if (exists $Options{$opt}) {
    50          
165 73         161 return $Options{$opt};
166             } elsif (exists $Defaults{$opt}) {
167 327         719 return $Defaults{$opt};
168             } else {
169 0         0 return undef;
170             }
171             }
172             }
173              
174             # This recurses through a structure and gets a list of
175             # refs and the path to each.
176             #
177             sub _refs {
178 100     100   127 my($var) = @_;
179 100         244 %Refs = ();
180 100         168 $Links = 0;
181 100         194 __refs($var,'$VAR');
182             }
183             sub __refs {
184 309     309   438 my($var,$link) = @_;
185              
186 309         434 my $type = ref($var);
187 309 100       1582 return if (! $type);
188              
189             # Check to see if we've encountered this reference before... i.e. a
190             # circular link, or a reference embedded multiple times.
191 130         160 my $ref = scalar($var);
192 130 100       542 if (exists($Refs{$ref})) {
193 6         12 $Links = 1;
194 6         30 $Refs{$ref}[1]++;
195 6         32 return;
196             }
197              
198 124         393 $Refs{$ref} = [$link,1];
199              
200 124 100       472 if ($type eq 'ARRAY') {
    100          
201 67         185 for (my $i=0; $i<@$var; $i++) {
202 131         449 __refs($$var[$i],$link . "->[$i]");
203             }
204              
205             } elsif ($type eq 'HASH') {
206 39         134 foreach my $key (keys %$var) {
207 78         288 __refs($$var{$key},$link . "->{$key}");
208             }
209             }
210             }
211              
212             sub _print_object {
213 18     18   26 my($val,$depth) = @_;
214              
215 18         23 my $type = ref($val);
216              
217 18         41 my $opt_print = _option('print',$type);
218 18         40 my $opt_func = _option('func',$type);
219 18         34 my $opt_args = _option('args',$type);
220 18         70 my $opt_type = _option('type',$type);
221 18         35 my $opt_ref = _option('ref',$type);
222              
223 18 100 100     70 if ($opt_print eq 'ref') {
    100          
    50          
224 3         13 return (scalar($val));
225              
226             } elsif ($opt_print eq 'method' ||
227             $opt_print eq 'func') {
228 14         15 my @str;
229              
230 14         16 my $func_defined = 0;
231              
232 14 100       32 if ($opt_print eq 'func') {
233 2         7 my ($caller) = caller;
234 2         8 my ($bless) = blessed($val);
235              
236 2         9 my @func = ("${caller}::$opt_func",
237             "${bless}::$opt_func",
238             "::$opt_func",
239             );
240 2         4 foreach my $func (@func) {
241 5 100       27 if (defined &$func) {
242 2         4 $opt_func = $func;
243 2         3 $func_defined = 1;
244 2         5 last;
245             }
246             }
247             }
248              
249 14 100       28 if ($opt_print eq 'method') {
250 12 100       106 $func_defined = 1 if ($val->can($opt_func));
251             }
252              
253 14 100       30 if (! $func_defined) {
254 2         9 return ('*** NO FUNCTION ***');
255             }
256              
257 12 100       28 if ($opt_ref) {
258 1         4 push(@str,scalar($val) . ' ');
259             }
260              
261 12         27 my @args = @$opt_args;
262 12 100       26 if ($opt_print eq 'func') {
263 2         3 foreach my $arg (@args) {
264 8 100       19 $arg = $val if ($arg eq '$OBJ');
265             }
266             }
267              
268 12 100       25 if ($opt_type eq 'list') {
    100          
269 7         8 my @list;
270 7 100       16 if ($opt_print eq 'method') {
271 5         21 @list = $val->$opt_func(@args);
272             } else {
273 12     12   220 no strict 'refs';
  12         25  
  12         1682  
274 2         8 @list = &$opt_func(@args);
275             }
276 7 100 66     114 if (@list == 1 && ref($list[0]) eq 'ARRAY') {
277 1         2 @list = @{ $list[0] };
  1         4  
278             }
279              
280 7         20 _append(\@str,_print_array(\@list,$depth+1));
281              
282             } elsif ($opt_type eq 'hash') {
283 2         3 my @list;
284             my %hash;
285 2 50       5 if ($opt_print eq 'method') {
286 2         8 @list = $val->$opt_func(@args);
287             } else {
288 12     12   69 no strict 'refs';
  12         21  
  12         25622  
289 0         0 @list = &$opt_func(@args);
290             }
291 2 50 33     41 if (@list == 1 && ref($list[0]) eq 'HASH') {
292 0         0 %hash = %{ $list[0] };
  0         0  
293             } else {
294 2         7 %hash = @list;
295             }
296              
297 2         6 _append(\@str,_print_hash(\%hash,$depth+1));
298              
299             } else {
300 3 50       7 if ($opt_print eq 'method') {
301 3         15 _append(\@str,scalar($val->$opt_func(@args)));
302             } else {
303 0         0 _append(\@str,scalar(&$opt_func(@args)));
304             }
305             }
306              
307 12         74 return @str;
308              
309             } elsif ($opt_print eq 'data') {
310 1         6 $type = reftype($val);
311 1         2 my @str;
312              
313 1 50       3 if ($opt_ref) {
314 0         0 push(@str,scalar($val) . ' ');
315             }
316              
317 1 50       6 if ($type eq "ARRAY") {
    50          
318 0         0 _append(\@str,_print_array($val,$depth));
319              
320             } elsif ($type eq "HASH") {
321 1         10 _append(\@str,_print_hash($val,$depth));
322              
323             } else {
324 0         0 _append(\@str,_print_scalar($val));
325             }
326              
327 1         31 return @str;
328             }
329             }
330              
331             # indexed:
332             # [
333             # 0 : VAL|STRUCT,
334             # 1 : VAL|STRUCT,
335             # ...
336             # ]
337             #
338             # standard:
339             # [
340             # VAL|STRUCT,
341             # VAL|STRUCT,
342             # ...
343             # ]
344             #
345             sub _print_array {
346 72     72   227 my($listref,$depth) = @_;
347              
348             # handle duplicates
349 72         131 my ($done,@str) = _duplicates($listref);
350 72 100       164 return @str if ($done);
351              
352 66         116 my $opt_indent = _option('indent');
353 66         146 my $opt_maxdep = _option('max_depth');
354 66         107 my $opt_format = _option('list_format');
355 66 50       162 $opt_indent = 1 if (! $opt_indent); # To handle the [ ]
356              
357             # Determine how much to indent the list, an index, and a value
358             # ..... [
359             # [IDX: ]VAL,
360             # }
361             # ^ ^
362             # | |
363             # | idxindent + maxidxlen
364             # $opt_indent
365              
366 66         140 my @vals = @$listref;
367 66         167 my $maxidxlen = length(scalar(@vals)) + 2;
368 66         93 my $idxindent = $opt_indent;
369 66 100       138 my $valindent = ($opt_format eq 'indexed' ?
370             $idxindent + $maxidxlen + 3 :
371             $idxindent);
372 66         88 my $nextindent = $idxindent + $opt_indent;
373 66         119 my $idxindentstr = " "x$idxindent;
374 66         108 my $valindentstr = " "x$valindent;
375              
376 66         139 _append(\@str,'[');
377              
378 66         192 for (my $i=0; $i<=$#vals; $i++) {
379 136         252 my $val = $vals[$i];
380 136         194 my $type = ref($val);
381              
382             # Print indentationsIDX:
383              
384 136 100       336 if ($opt_format eq 'indexed') {
    50          
385 17         47 push(@str,"$idxindentstr$i: " . " "x($maxidxlen-length($i)-2));
386             } elsif ($opt_format eq 'standard') {
387 119         203 push(@str,$valindentstr);
388             }
389              
390             # Print val
391              
392 136         192 my ($first,@tmp,$indentstr);
393 136         296 $indentstr = $idxindentstr;
394              
395 136 100       300 if (! $type) {
    100          
    100          
    50          
396 97         212 ($first,@tmp) = _print_scalar($val);
397 97         143 $indentstr = $valindentstr;
398              
399             } elsif ($depth == $opt_maxdep) {
400 4         23 ($first,@tmp) = _print_maxdepth($val);
401              
402             } elsif ($type eq "ARRAY") {
403 29         296 ($first,@tmp) = _print_array($val,$depth+1);
404              
405             } elsif ($type eq "HASH") {
406 6         19 ($first,@tmp) = _print_hash($val,$depth+1);
407              
408             } else {
409 0         0 ($first,@tmp) = _print_object($val,$depth+1);
410             }
411              
412 136         357 @tmp = map { "$indentstr$_" } @tmp;
  113         252  
413 136         273 _append(\@str,$first,@tmp);
414              
415             # The last value won't get a comma
416 136 100       478 _append(\@str,',') if ($i < $#vals);
417             }
418              
419 66         106 push(@str,']');
420 66         391 return @str;
421             }
422              
423             # {
424             # key => val, val is a scalar
425             # key => REF, we're at max_depth, val is a ref
426             # key => STRUCT, otherwise
427             # }
428             #
429             sub _print_hash {
430 42     42   65 my($hashref,$depth) = @_;
431              
432             # handle duplicates
433 42         111 my ($done,@str) = _duplicates($hashref);
434 42 50       90 return @str if ($done);
435              
436 42         75 my $opt_indent = _option('indent');
437 42         77 my $opt_maxdep = _option('max_depth');
438 42 50       94 $opt_indent = 1 if (! $opt_indent); # To handle the { }
439              
440             # Determine how much to indent the hash, a key, and a value
441             # (for multiline scalars).
442             # ..... {
443             # key => val
444             # }
445             # ^ ^ ^
446             # | | |
447             # | | keyindent + maxkeylen + 3
448             # | keyindent + maxkeylen
449             # $opt_indent
450              
451 42         117 my @keys = keys %$hashref;
452 42         98 my $maxkeylen = _maxLength(@keys) + 1;
453 42         99 my $keyindent = $opt_indent;
454 42         56 my $valindent = $keyindent + $maxkeylen + 3;
455 42         80 my $keyindentstr = " "x$keyindent;
456 42         73 my $valindentstr = " "x$valindent;
457              
458 42         95 _append(\@str,'{');
459              
460 42         61 my $i = 0;
461 42         127 foreach my $key (sort @keys) {
462 84         92 $i++;
463 84         140 my $val = $$hashref{$key};
464 84         125 my $type = ref($val);
465              
466             # Print key =>
467              
468 84         224 my @tmp = map { "$keyindentstr$_" } _print_scalar($key);
  84         245  
469 84         130 my $tmp = pop(@tmp);
470 84         191 $tmp .= " "x($keyindent+$maxkeylen-length($tmp)) . '=> ';
471 84         131 push(@str,@tmp,$tmp);
472              
473             # Print val
474              
475 84         92 my ($first,$indentstr);
476 84         105 $indentstr = $keyindentstr;
477              
478 84 100       243 if (! $type) {
    50          
    100          
    100          
479 54         101 ($first,@tmp) = _print_scalar($val);
480 54         84 $indentstr = $valindentstr;
481              
482             } elsif ($depth == $opt_maxdep) {
483 0         0 ($first,@tmp) = _print_maxdepth($val);
484              
485             } elsif ($type eq "ARRAY") {
486 6         20 ($first,@tmp) = _print_array($val,$depth+1);
487              
488             } elsif ($type eq "HASH") {
489 6         55 ($first,@tmp) = _print_hash($val,$depth+1);
490              
491             } else {
492 18         45 ($first,@tmp) = _print_object($val,$depth+1);
493             }
494              
495 84         139 @tmp = map { "$indentstr$_" } @tmp;
  69         195  
496 84         171 _append(\@str,$first,@tmp);
497              
498             # The last key/val pair won't get a comma
499 84 100       312 _append(\@str,',') if ($i < @keys);
500             }
501              
502 42         117 push(@str,'}');
503 42         319 return @str;
504             }
505              
506             sub _print_scalar {
507 278     278   414 my($val) = @_;
508 278         305 my @str;
509              
510 278 100       1556 if (! defined $val) {
    100          
    100          
511 6         17 @str = ('undef');
512              
513             } elsif ($val eq '') {
514 6         13 @str = ("''");
515              
516             } elsif ($val =~ /[,'\s\n]/s) {
517              
518             # Trailing newlines are displayed as '\n' only
519 25 50       131 if ($val =~ m,(\n*)$,) {
520 25         63 my $tmp = $1;
521 25         109 $tmp =~ s,\n,\\n,g;
522 25         103 $val =~ s,\n*$,$tmp,;
523             }
524              
525             # Intermediate newlines are displayed as '\n' + newline
526 25         42 $val =~ s,\n,\\n\n,g;
527              
528             # Split it into a list of strings
529 25         71 @str = split(/\n/,$val);
530              
531             # Quotes are added. The lines look like:
532             # >'LINE1
533             # > LINE2
534             # > ...
535             # > LINEn'
536             #
537 25         41 my $tmp = shift(@str);
538 25         51 $tmp = "'$tmp";
539 25         51 @str = map { " $_" } @str;
  1         5  
540 25         40 unshift(@str,$tmp);
541 25         40 $str[$#str] .= "'";
542              
543             } else {
544 241         430 @str = ($val);
545             }
546              
547 278         737 return @str;
548             }
549              
550             sub _append {
551 453     453   892 my($listref,@newlist) = @_;
552              
553 453 100       978 if (@$listref) {
554 335         847 $$listref[$#$listref] .= shift(@newlist);
555             }
556 453         1462 push (@$listref,@newlist);
557             }
558              
559             sub _maxLength {
560 42     42   73 my(@list) = @_;
561 42         51 my $max = 0;
562 42         69 foreach my $ele (@list) {
563 84         88 my $len;
564 84 50       138 if (ref($ele)) {
565 0         0 $len = length(scalar($ele));
566             } else {
567 84         124 $len = length($ele);
568             }
569 84 100       284 $max = $len if ($len > $max);
570             }
571 42         106 return $max;
572             }
573              
574             sub _duplicates {
575 114     114   148 my($val) = @_;
576 114         260 my $opt_dupl = _option('duplicates');
577 114         162 my $ref = scalar($val);
578 114 100 100     1023 return (0) if (! exists $Refs{$ref} ||
579             $Refs{$ref}[1] == 1);
580              
581 12 100       32 if (exists $Printed{$ref}) {
582              
583 6 100       29 if ($opt_dupl eq 'link') {
    100          
    50          
584 2         17 return (1,$Refs{$ref}[0]);
585              
586             } elsif ($opt_dupl eq 'reflink') {
587 2         9 return (1,"$ref " . $Refs{$ref}[0]);
588              
589             } elsif ($opt_dupl eq 'ref') {
590 2         6 return (1,$ref);
591             }
592              
593             } else {
594 6         13 $Printed{$ref} = 1;
595              
596 6 100 100     30 if ($opt_dupl eq 'link' ||
    50          
597             $opt_dupl eq 'ref') {
598 4         11 return (0);
599              
600             } elsif ($opt_dupl eq 'reflink') {
601 2         8 return (0,"$ref ");
602             }
603             }
604             }
605              
606             sub _print_maxdepth {
607 4     4   6 my($var) = @_;
608 4         8 my $opt_maxmeth = _option('max_depth_method');
609              
610 4 100       10 if ($opt_maxmeth eq 'ref') {
611 2         4 return (scalar($var));
612              
613             } else {
614 2         7 return (ref($var));
615              
616             }
617             }
618              
619             1;
620             # Local Variables:
621             # mode: cperl
622             # indent-tabs-mode: nil
623             # cperl-indent-level: 3
624             # cperl-continued-statement-offset: 2
625             # cperl-continued-brace-offset: 0
626             # cperl-brace-offset: 0
627             # cperl-brace-imaginary-offset: 0
628             # cperl-label-offset: -2
629             # End: