File Coverage

blib/lib/Data/Dump/Color.pm
Criterion Covered Total %
statement 299 482 62.0
branch 122 260 46.9
condition 39 105 37.1
subroutine 20 23 86.9
pod 3 10 30.0
total 483 880 54.8


line stmt bran cond sub pod time code
1             ## no critic: Modules::ProhibitAutomaticExportation
2             ## no critic: BuiltinFunctions::RequireBlockGrep
3             ## no critic: ValuesAndExpressions::ProhibitCommaSeparatedStatements
4              
5             package Data::Dump::Color;
6              
7 3     3   653937 use 5.010001;
  3         13  
8 3     3   23 use strict 'subs', 'vars';
  3         7  
  3         228  
9 3     3   1632 use subs qq(dump);
  3         1102  
  3         21  
10 3     3   245 use vars qw(@EXPORT @EXPORT_OK $VERSION $DEBUG);
  3         7  
  3         261  
11 3     3   21 use warnings;
  3         9  
  3         415  
12              
13             require Exporter;
14             *import = \&Exporter::import;
15             @EXPORT = qw(dd ddx);
16             @EXPORT_OK = qw(dump pp dumpf quote);
17              
18             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
19             our $DATE = '2025-02-21'; # DATE
20             our $DIST = 'Data-Dump-Color'; # DIST
21             our $VERSION = '0.251'; # VERSION
22              
23             $DEBUG = $ENV{DEBUG};
24              
25 3     3   23 use overload ();
  3         5  
  3         138  
26 3     3   19 use vars qw(%seen %refcnt @fixup @cfixup %require $TRY_BASE64 @FILTERS $INDENT);
  3         5  
  3         327  
27 3     3   26 use vars qw($COLOR $COLOR_THEME $INDEX $LENTHRESHOLD);
  3         7  
  3         32278  
28              
29             require # hide from cpanspec
30             Win32::Console::ANSI if $^O =~ /Win/;
31              
32             my $lan_available;
33             eval {
34             require Scalar::Util::LooksLikeNumber;
35             *looks_like_number = \&Scalar::Util::LooksLikeNumber::looks_like_number;
36             $lan_available = 1;
37             1;
38             } or do {
39             require Scalar::Util;
40             *looks_like_number = \&Scalar::Util::looks_like_number;
41             };
42              
43             $TRY_BASE64 = 50 unless defined $TRY_BASE64;
44             $INDENT = " " unless defined $INDENT;
45             $INDEX = 1 unless defined $INDEX;
46             $LENTHRESHOLD = 500 unless defined $LENTHRESHOLD;
47             $COLOR = (defined $ENV{NO_COLOR} ? 0 : undef) //
48             $ENV{COLOR} // (-t STDOUT) // 1;
49             $COLOR_THEME = $ENV{DATA_DUMP_COLOR_THEME} //
50             (($ENV{TERM} // "") =~ /256/ ? 'Default256' : 'Default16');
51             our $ct_obj;
52              
53             # from List::Util::PP
54             sub max {
55 59 50   59 0 140 return undef unless @_;
56 59         110 my $max = shift;
57             $_ > $max and $max = $_
58 59   66     188 foreach @_;
59 59         168 return $max;
60             }
61              
62             sub _get_color_theme_obj {
63 0     0   0 require Module::Load::Util;
64 0         0 Module::Load::Util::instantiate_class_with_optional_args(
65             {ns_prefixes=>['ColorTheme::Data::Dump::Color','ColorTheme','']}, $COLOR_THEME);
66             }
67              
68             sub _col {
69 256     256   2604 require ColorThemeUtil::ANSI;
70 256         1760 my ($item, $str) = @_;
71              
72 256 50       1741 return $str unless $COLOR;
73              
74 0 0       0 local $ct_obj = _get_color_theme_obj() unless defined $ct_obj;
75              
76 0         0 my $ansi = '';
77 0         0 $item = $ct_obj->get_item_color($item);
78 0 0       0 if (defined $item) {
79 0         0 $ansi = ColorThemeUtil::ANSI::item_color_to_ansi($item);
80             }
81 0 0       0 if (length $ansi) {
82 0         0 $ansi . $str . "\e[0m";
83             } else {
84 0         0 $str;
85             }
86             }
87              
88             sub dump
89             {
90 8     8   869381 local %seen;
91 8         15 local %refcnt;
92 8         13 local %require;
93 8         11 local @fixup;
94 8         11 local @cfixup;
95              
96 8 50 33     27 local $ct_obj = _get_color_theme_obj() if $COLOR && !(defined $ct_obj);
97 8 50       20 require Data::Dump::FilterContext if @FILTERS;
98              
99 8         14 my $name = "var";
100 8         15 my @dump;
101             my @cdump;
102              
103 8         17 for my $v (@_) {
104 8         51 my ($val, $cval) = _dump($v, $name, [], tied($v));
105 8         23 push(@dump , [$name, $val]);
106 8         17 push(@cdump, [$name, $cval]);
107             } continue {
108 8         20 $name++;
109             }
110              
111 8         11 my $out = "";
112 8         11 my $cout = "";
113 8 50       20 if (%require) {
114 0         0 for (sort keys %require) {
115 0         0 $out .= "require $_;\n";
116 0         0 $cout .= _col(keyword=>"require")." "._col(symbol=>$_).";\n";
117             }
118             }
119 8 100       17 if (%refcnt) {
120             # output all those with refcounts first
121 1         3 for my $i (0..$#dump) {
122 1         2 my $name = $dump[ $i][0];
123 1         2 my $cname = $cdump[$i][0];
124 1 50       3 if ($refcnt{$name}) {
125 1         2 $out .= "my \$$name = $dump[$i][1];\n";
126 1         2 $cout .= _col(keyword=>"my")." "._col(symbol=>"\$$cname")." = $cdump[$i][1];\n";
127 1         2 undef $dump[ $i][1];
128 1         3 undef $cdump[$i][1];
129             }
130             }
131 1         2 for my $i (0..$#fixup) {
132 1         2 $out .= "$fixup[$i];\n";
133 1         2 $cout .= "$cfixup[$i];\n";
134             }
135             }
136              
137 8         15 my $paren = (@dump != 1);
138 8 50       17 $out .= "(" if $paren;
139 8 50       22 $cout .= "(" if $paren;
140             my ($f, $cf) = format_list($paren, undef,
141             [0],
142 8 100       34 [map {defined($_->[1]) ? $_->[1] : "\$".$_->[0]} @dump ],
143 8 100       25 [map {defined($_->[1]) ? $_->[1] : "\$".$_->[0]} @cdump],
  8         37  
144             \@_,
145             );
146 8         35 $out .= $f;
147 8         20 $cout .= $cf;
148 8 50       15 $out .= ")" if $paren;
149 8 50       12 $cout .= ")" if $paren;
150              
151 8 100 66     25 if (%refcnt || %require) {
152 1         2 $out .= ";\n";
153 1         2 $cout .= ";\n";
154 1         6 $out =~ s/^/$INDENT/gm;
155 1         4 $cout =~ s/^/$INDENT/gm;
156 1         2 $out = "do {\n$out}";
157 1         2 $cout = _col(keyword=>"do")." {\n$cout}";
158             }
159              
160 8 50       21 print STDERR "$cout\n" unless defined wantarray;
161 8         528 $cout;
162             }
163              
164             *pp = \&dump;
165              
166             sub dd {
167 1     1 1 4605 print dump(@_), "\n";
168 1         8 @_;
169             }
170              
171             sub ddx {
172 1     1 1 4919 my(undef, $file, $line) = caller;
173 1         43 $file =~ s,.*[\\/],,;
174 1         3 my $out = dump(@_) . "\n";
175 1         5 $out =~ s/^/# /gm;
176 1         4 $out = _col(linum=>"$file:$line: ") . $out;
177 1         37 print $out;
178             }
179              
180             sub dumpf {
181 0     0 0 0 require Data::Dump::Filtered;
182 0         0 goto &Data::Dump::Filtered::dump_filtered;
183             }
184              
185             # return two result: (uncolored dump, colored dump)
186             sub _dump
187             {
188 80     80   167 my $ref = ref $_[0];
189 80 100       205 my $rval = $ref ? $_[0] : \$_[0];
190 80         121 shift;
191              
192             # compared to Data::Dump, each @$idx element is also a [uncolored,colored]
193             # instead of just a scalar.
194 80         175 my($name, $idx, $dont_remember, $pclass, $pidx) = @_;
195              
196 80         120 my($class, $type, $id);
197 80         246 my $strval = overload::StrVal($rval);
198             # Parse $strval without using regexps, in order not to clobber $1, $2,...
199 80 100       490 if ((my $i = rindex($strval, "=")) >= 0) {
200 1         3 $class = substr($strval, 0, $i);
201 1         2 $strval = substr($strval, $i+1);
202             }
203 80 50       198 if ((my $i = index($strval, "(0x")) >= 0) {
204 80         149 $type = substr($strval, 0, $i);
205 80         173 $id = substr($strval, $i + 2, -1);
206             }
207             else {
208 0         0 die "Can't parse " . overload::StrVal($rval);
209             }
210 80 50 33     211 if ($] < 5.008 && $type eq "SCALAR") {
211 0 0       0 $type = "REF" if $ref eq "REF";
212             }
213 80 50       164 warn "\$$name(@$idx) $class $type $id ($ref)" if $DEBUG;
214              
215 80         239 my $out;
216             my $cout;
217 80         0 my $comment;
218 80         0 my $hide_keys;
219 80 50       164 if (@FILTERS) {
220 0         0 my $pself = "";
221 0 0       0 ($pself, undef) = fullname("self", [@$idx[$pidx..(@$idx - 1)]]) if $pclass;
222 0         0 my $ctx = Data::Dump::FilterContext->new($rval, $class, $type, $ref, $pclass, $pidx, $idx);
223 0         0 my @bless;
224 0         0 for my $filter (@FILTERS) {
225 0 0       0 if (my $f = $filter->($ctx, $rval)) {
226 0 0       0 if (my $v = $f->{object}) {
227 0         0 local @FILTERS;
228 0         0 ($out, $cout) = _dump($v, $name, $idx, 1);
229 0         0 $dont_remember++;
230             }
231 0 0       0 if (defined(my $c = $f->{bless})) {
232 0         0 push(@bless, $c);
233             }
234 0 0       0 if (my $c = $f->{comment}) {
235 0         0 $comment = $c;
236             }
237 0 0       0 if (defined(my $c = $f->{dump})) {
238 0         0 $out = $c;
239 0         0 $cout = $c; # XXX where's the colored version?
240 0         0 $dont_remember++;
241             }
242 0 0       0 if (my $h = $f->{hide_keys}) {
243 0 0       0 if (ref($h) eq "ARRAY") {
244             $hide_keys = sub {
245 0     0   0 for my $k (@$h) {
246 0 0       0 return (1, 1) if $k eq $_[0]; # XXX color?
247             }
248 0         0 return (0, 0); # XXX color?
249 0         0 };
250             }
251             }
252             }
253             }
254 0 0 0     0 push(@bless, "") if defined($out) && !@bless;
255 0 0       0 if (@bless) {
256 0         0 $class = shift(@bless);
257 0 0       0 warn "More than one filter callback tried to bless object" if @bless;
258             }
259             }
260              
261 80 50       203 unless ($dont_remember) {
262 80 100       213 if (my $s = $seen{$id}) {
263 1         2 my($sname, $sidx) = @$s;
264 1         3 $refcnt{$sname}++;
265 1   33     6 my ($sref, $csref) = fullname($sname, $sidx,
266             ($ref && $type eq "SCALAR"));
267 1 50       3 warn "SEEN: [\$$name(@$idx)] => [\$$sname(@$sidx)] ($ref,$sref)" if $DEBUG;
268 1 50       2 return ($sref, $csref) unless $sname eq $name; # XXX color?
269 1         2 $refcnt{$name}++;
270 1         3 my ($fn, $cfn) = fullname($name, $idx);
271 1         3 push(@fixup , "$fn = $sref");
272 1         3 push(@cfixup, "$cfn = $csref");
273             return (
274 1 50 33     5 "do{my \$fix}",
275             _col(keyword=>"do")."{"._col(keyword=>"my")." "._col(symbol=>"\$fix")."}",
276             ) if @$idx && $idx->[-1] eq '$';
277 1         16 my $str = squote($sref);
278             return (
279 1         2 $str,
280             _col(string => $str),
281             );
282             }
283 79         336 $seen{$id} = [$name, $idx];
284             }
285              
286 79 100       177 if ($class) {
287 1         2 $pclass = $class;
288 1         2 $pidx = @$idx;
289             }
290              
291 79 50 100     377 if (defined $out) {
    100 66        
    50          
    100          
    50          
    0          
    0          
292             # keep it
293             }
294             elsif ($type eq "SCALAR" || $type eq "REF" || $type eq "REGEXP") {
295 62 100       141 if ($ref) {
296 3 50 66     10 if ($class && $class eq "Regexp") {
297 0         0 my $v = "$rval";
298              
299 0         0 my $mod = "";
300 0 0       0 if ($v =~ /^\(\?\^?([msix-]*):([\x00-\xFF]*)\)\z/) {
301 0         0 $mod = $1;
302 0         0 $v = $2;
303 0         0 $mod =~ s/-.*//;
304             }
305              
306 0         0 my $sep = '/';
307 0         0 my $sep_count = ($v =~ tr/\///);
308 0 0       0 if ($sep_count) {
309             # see if we can find a better one
310 0         0 for ('|', ',', ':', '#') {
311 0         0 my $c = eval "\$v =~ tr/\Q$_\E//";
312             #print "SEP $_ $c $sep_count\n";
313 0 0       0 if ($c < $sep_count) {
314 0         0 $sep = $_;
315 0         0 $sep_count = $c;
316 0 0       0 last if $sep_count == 0;
317             }
318             }
319             }
320 0         0 $v =~ s/\Q$sep\E/\\$sep/g;
321              
322 0         0 $out = "qr$sep$v$sep$mod";
323 0         0 $cout = _col('Regexp', $out);
324 0         0 undef($class);
325             }
326             else {
327 3 100       8 delete $seen{$id} if $type eq "SCALAR"; # will be seen again shortly
328 3         34 my ($val, $cval) = _dump($$rval, $name, [@$idx, ["\$","\$"]], 0, $pclass, $pidx);
329 3 100       11 $out = $class ? "do{\\(my \$o = $val)}" : "\\$val";
330 3 100       7 $cout = $class ? _col(keyword=>"do")."{\\("._col(keyword=>"my")." "._col(symbol=>"\$o")." = $cval)}" : "\\$cval";
331             }
332             } else {
333 59 50       315 if (!defined $$rval) {
    100          
334 0         0 $out = 'undef';
335 0         0 $cout = _col('undef', "undef");
336             }
337             elsif (my $ntype = looks_like_number($$rval)) {
338 16 50       35 if ($lan_available) {
339             # ntype returns details of the nature of numeric value in
340             # scalar, including the ability to differentiate stringy
341             # number "123" vs 123.
342 16 100       50 my $val = $ntype < 20 ? qq("$$rval") : $$rval;
343 16 100       92 my $col = $ntype =~ /^(5|13|8704)$/ ? "float":"number";
344 16         26 $out = $val;
345 16         31 $cout = _col($col => $val);
346             } else {
347 0         0 my $val = $$rval;
348 0         0 my $col = "number";
349 0         0 $out = $val;
350 0         0 $cout = _col($col => $val);
351             }
352             }
353             else {
354 43         105 $out = str($$rval);
355 43         122 $cout = _col(string => $out);
356             }
357 59 50 33     190 if ($class && !@$idx) {
358             # Top is an object, not a reference to one as perl needs
359 0         0 $refcnt{$name}++;
360 0         0 my ($obj, $cobj) = fullname($name, $idx);
361 0         0 my $cl = quote($class);
362 0         0 push(@fixup , "bless \\$obj, $cl");
363 0         0 push(@cfixup, _col(keyword => "bless")." \\$cobj, "._col(string=>$cl));
364             }
365             }
366             }
367             elsif ($type eq "GLOB") {
368 0 0       0 if ($ref) {
369 0         0 delete $seen{$id};
370 0         0 my ($val, $cval) = _dump($$rval, $name, [@$idx, ["*","*"]], 0, $pclass, $pidx);
371 0         0 $out = "\\$val";
372 0         0 $cout = "\\$cval";
373 0 0       0 if ($out =~ /^\\\*Symbol::/) {
374 0         0 $require{Symbol}++;
375 0         0 $out = "Symbol::gensym()";
376 0         0 $cout = _col(glob => $out);
377             }
378             } else {
379 0         0 my $val = "$$rval";
380 0         0 $out = "$$rval";
381 0         0 $cout = _col(glob => $out);
382              
383 0         0 for my $k (qw(SCALAR ARRAY HASH)) {
384 0         0 my $gval = *$$rval{$k};
385 0 0       0 next unless defined $gval;
386 0 0 0     0 next if $k eq "SCALAR" && ! defined $$gval; # always there
387 0         0 my $f = scalar @fixup;
388 0         0 push(@fixup, "RESERVED"); # overwritten after _dump() below
389 0         0 my $cgval;
390 0         0 ($gval, $cgval) = _dump($gval, $name, [@$idx, ["*{$k}", "*{"._col(string=>$k)."}"]], 0, $pclass, $pidx);
391 0         0 $refcnt{$name}++;
392 0         0 my ($gname, $cgname) = fullname($name, $idx);
393 0         0 $fixup[ $f] = "$gname = $gval" ; #XXX indent $gval
394 0         0 $cfixup[$f] = "$gname = $cgval"; #XXX indent $gval
395             }
396             }
397             }
398             elsif ($type eq "ARRAY") {
399 6         9 my @vals;
400             my @cvals;
401 6         9 my $tied = tied_str(tied(@$rval));
402 6         7 my $i = 0;
403 6         8 for my $v (@$rval) {
404 10         22 my ($d, $cd) = _dump($v, $name, [@$idx, ["[$i]","["._col(number=>$i)."]"]], $tied, $pclass, $pidx);
405 10         16 push @vals , $d;
406 10         14 push @cvals, $cd;
407 10         16 $i++;
408             }
409 6         16 my ($f, $cf) = format_list(1, $tied, [scalar(@$idx)], \@vals, \@cvals, $rval);
410 6         12 $out = "[$f]";
411 6         9 $cout = "[$cf]";
412             }
413             elsif ($type eq "HASH") {
414 11         24 my(@keys, @vals, @cvals, @origk, @origv);
415 11         35 my $tied = tied_str(tied(%$rval));
416              
417             # statistics to determine variation in key lengths
418 11         38 my $kstat_max = 0;
419 11         20 my $kstat_sum = 0;
420 11         34 my $kstat_sum2 = 0;
421              
422 11         73 my @orig_keys = keys %$rval;
423 11 50       30 if ($hide_keys) {
424 0         0 @orig_keys = grep !$hide_keys->($_), @orig_keys;
425             }
426 11         19 my $text_keys = 0;
427 11         52 for (@orig_keys) {
428 12 100       84 $text_keys++, last unless /^[-+]?(?:0|[1-9]\d*)(?:\.\d+)?\z/;
429             }
430              
431 11 100       29 if ($text_keys) {
432 10         58 @orig_keys = sort { lc($a) cmp lc($b) } @orig_keys;
  101         270  
433             }
434             else {
435 1         4 @orig_keys = sort { $a <=> $b } @orig_keys;
  1         8  
436             }
437              
438 11         19 my $quote;
439 11         23 for my $key (@orig_keys) {
440 59 100       216 next if $key =~ /^-?[a-zA-Z_]\w*\z/;
441 2 50       12 next if $key =~ /^-?[1-9]\d{0,8}\z/;
442 0         0 $quote++;
443 0         0 last;
444             }
445              
446 11         23 my @lenvlastline;
447 11         20 for my $key (@orig_keys) {
448 59         152 my $val = \$rval->{$key}; # capture value before we modify $key
449 59         123 push(@origk, $key);
450 59 50       144 $key = quote($key) if $quote;
451 59 100       149 $kstat_max = length($key) if length($key) > $kstat_max;
452 59         107 $kstat_sum += length($key);
453 59         128 $kstat_sum2 += length($key)*length($key);
454              
455 59         108 push(@keys, $key);
456 59         197 my ($v, $cv) = _dump($$val, $name, [@$idx, ["{$key}","{"._col(string=>$key)."}"]], $tied, $pclass, $pidx);
457 59         171 push(@vals , $v);
458 59         126 push(@cvals, $cv);
459 59         123 push(@origv, $$val);
460              
461 59         564 my ($vlastline) = $v =~ /(.*)\z/;
462             #say "DEBUG: v=<$v>, vlastline=<$vlastline>" if $DEBUG;
463 59         143 my $lenvlastline = length($vlastline);
464 59         169 push @lenvlastline, $lenvlastline;
465             }
466             #$maxvlen += length($INDENT);
467             #say "maxvlen=$maxvlen"; #TMP
468 11         21 my $nl = "";
469 11         20 my $klen_pad = 0;
470 11         118 my $tmp = "@keys @vals";
471 11 0 33     42 if (length($tmp) > 60 || $tmp =~ /\n/ || $tied) {
      33        
472 11         23 $nl = "\n";
473              
474             # Determine what padding to add
475 11 100       47 if ($kstat_max < 4) {
    50          
476 1         4 $klen_pad = $kstat_max;
477             }
478             elsif (@keys >= 2) {
479 10         19 my $n = @keys;
480 10         29 my $avg = $kstat_sum/$n;
481 10         43 my $stddev = sqrt(($kstat_sum2 - $n * $avg * $avg) / ($n - 1));
482              
483             # I am not actually very happy with this heuristics
484 10 100       35 if ($stddev / $kstat_max < 0.25) {
485 7         18 $klen_pad = $kstat_max;
486             }
487 10 50       45 if ($DEBUG) {
488 0         0 push(@keys, "__S");
489 0         0 push(@vals, sprintf("%.2f (%d/%.1f/%.1f)",
490             $stddev / $kstat_max,
491             $kstat_max, $avg, $stddev));
492 0         0 push(@cvals, sprintf("%.2f (%d/%.1f/%.1f)",
493             $stddev / $kstat_max,
494             $kstat_max, $avg, $stddev));
495             }
496             }
497             }
498              
499 11         23 my $maxkvlen = 0;
500 11         44 for (0..$#keys) {
501 59         119 my $klen = length($keys[$_]);
502 59 100       1478 $klen = $klen_pad if $klen < $klen_pad;
503 59         102 my $kvlen = $klen + $lenvlastline[$_];
504 59 100       169 $maxkvlen = $kvlen if $maxkvlen < $kvlen;
505             }
506 11 50       32 $maxkvlen = 80 if $maxkvlen > 80;
507              
508 11         27 $out = "{$nl";
509 11         22 $cout = "{$nl";
510 11 50       28 $out .= "$INDENT# $tied$nl" if $tied;
511 11 50       26 $cout .= $INDENT._col(comment=>"# $tied").$nl if $tied;
512 11         17 my $i = 0;
513 11         52 my $idxwidth = length(~~@keys);
514 11         28 while (@keys) {
515 59         118 my $key = shift(@keys);
516 59         121 my $val = shift @vals;
517 59         111 my $cval = shift @cvals;
518 59         2413 my $origk = shift @origk;
519 59         117 my $origv = shift @origv;
520 59         102 my $lenvlastline = shift @lenvlastline;
521 59         116 my $vmultiline = length($val) > $lenvlastline;
522 59 100       170 my $vpad = $INDENT . (" " x ($klen_pad ? $klen_pad + 4 : 0));
523 59         285 $val =~ s/\n/\n$vpad/gm;
524 59         198 $cval =~ s/\n/\n$vpad/gm;
525 59 50       138 my $kpad = $nl ? $INDENT : " ";
526 59         108 my $pad_len = ($klen_pad - length($key));
527 59 100       138 if ($pad_len < 0) { $pad_len = 0; }
  18         25  
528 59 50       197 $key .= " " x $pad_len if $nl;
529 59 100       285 my $cpad = " " x max(0, $maxkvlen - ($vmultiline ? -6+length($vpad) : length($key)) - $lenvlastline);
530             #say "DEBUG: key=<$key>, vpad=<$vpad>, val=<$val>, lenvlastline=<$lenvlastline>, cpad=<$cpad>" if $DEBUG;
531 59         112 my $visaid = "";
532 59 50       278 $visaid .= sprintf("%s{%${idxwidth}i}", "." x @$idx, $i) if $INDEX;
533 59 50 33     238 $visaid .= " klen=".length($origk) if defined $origk && length($origk) >= $LENTHRESHOLD;
534 59 50 33     250 $visaid .= " vlen=".length($origv) if defined $origv && length($origv) >= $LENTHRESHOLD;
535 59 50 33     415 $out .= "$kpad$key => $val," . ($nl && length($visaid) ? " $cpad# $visaid" : "") . $nl;
536 59 50 33     160 $cout .= $kpad._col(key=>$key)." => $cval,".($nl && length($visaid) ? " $cpad"._col(comment => "# $visaid") : "") . $nl;
537 59         235 $i++;
538             }
539 11 50       32 $out =~ s/,$/ / unless $nl;
540 11 50       70 $cout =~ s/,$/ / unless $nl;
541 11         20 $out .= "}";
542 11         49 $cout .= "}";
543             }
544             elsif ($type eq "CODE") {
545 0         0 $out = 'sub { ... }';
546 0         0 $cout = _col(keyword=>'sub').' { ... }';
547             }
548             elsif ($type eq "VSTRING") {
549 0 0       0 $out = sprintf +($ref ? '\v%vd' : 'v%vd'), $$rval;
550 0         0 $cout = _col(string => $out);
551             }
552             else {
553 0         0 warn "Can't handle $type data";
554 0         0 $out = "'#$type#'";
555 0         0 $cout = _col(comment => $out);
556             }
557              
558 79 100 66     200 if ($class && $ref) {
559 1         2 $cout = _col(keyword=>"bless")."($cout, " . _col(string => quote($class)) . ")";
560 1         3 $out = "bless($out, ".quote($class).")";
561             }
562 79 50       159 if ($comment) {
563 0         0 $comment =~ s/^/# /gm;
564 0 0       0 $comment .= "\n" unless $comment =~ /\n\z/;
565 0         0 $comment =~ s/^#[ \t]+\n/\n/;
566 0         0 $cout = _col(comment=>$comment).$out;
567 0         0 $out = "$comment$out";
568             }
569 79         413 return ($out, $cout);
570             }
571              
572             sub tied_str {
573 17     17 0 32 my $tied = shift;
574 17 50       36 if ($tied) {
575 0 0       0 if (my $tied_ref = ref($tied)) {
576 0         0 $tied = "tied $tied_ref";
577             }
578             else {
579 0         0 $tied = "tied";
580             }
581             }
582 17         33 return $tied;
583             }
584              
585             # return two result: (uncolored dump, colored dump)
586             sub fullname
587             {
588 2     2 0 4 my($name, $idx, $ref) = @_;
589 2         5 substr($name, 0, 0) = "\$";
590 2         2 my $cname = $name;
591              
592 2         5 my @i = @$idx; # need copy in order to not modify @$idx
593 2 0 33     4 if ($ref && @i && $i[0][0] eq "\$") {
      33        
594 0         0 shift(@i); # remove one deref
595 0         0 $ref = 0;
596             }
597 2   33     22 while (@i && $i[0][0] eq "\$") {
598 0         0 shift @i;
599 0         0 $name = "\$$name";
600 0         0 $cname = _col(symbol=>$name);
601             }
602              
603 2         3 my $last_was_index;
604 2         4 for my $i (@i) {
605 2 50 33     34 if ($i->[0] eq "*" || $i->[0] eq "\$") {
    50          
606 0         0 $last_was_index = 0;
607 0         0 $name = "$i->[0]\{$name}";
608 0         0 $cname = "$i->[1]\{$cname}";
609             } elsif ($i->[0] =~ s/^\*//) {
610 0         0 $name .= $i->[0];
611 0         0 $cname .= $i->[1];
612 0         0 $last_was_index++;
613             } else {
614 2         3 $name .= "->";
615 2         3 $cname .= "->";
616 2         3 $name .= $i->[0];
617 2         4 $cname .= $i->[1];
618             }
619             }
620 2 50       3 $name = "\\$name" if $ref;
621 2         6 ($name, $cname);
622             }
623              
624             # return two result: (uncolored dump, colored dump)
625             sub format_list
626             {
627 14     14 0 17 my $paren = shift;
628 14         15 my $comment = shift;
629 14         19 my $extra = shift; # [level, ]
630 14 100       23 my $indent_lim = $paren ? 0 : 1;
631 14         14 my @vals = @{ shift(@_) };
  14         27  
632 14         19 my @cvals = @{ shift(@_) };
  14         69  
633 14         17 my @orig = @{ shift(@_) };
  14         22  
634              
635 14 100       28 if (@vals > 3) {
636             # can we use range operator to shorten the list?
637 1         1 my $i = 0;
638 1         3 while ($i < @vals) {
639 4         4 my $j = $i + 1;
640 4         5 my $v = $vals[$i];
641 4         6 while ($j < @vals) {
642             # XXX allow string increment too?
643 3 100 66     20 if ($v eq "0" || $v =~ /^-?[1-9]\d{0,9}\z/) {
    50          
644 1         1 $v++;
645             }
646             elsif ($v =~ /^"([A-Za-z]{1,3}\d*)"\z/) {
647 0         0 $v = $1;
648 0         0 $v++;
649 0         0 $v = qq("$v");
650             }
651             else {
652 2         4 last;
653             }
654 1 50       3 last if $vals[$j] ne $v;
655 0         0 $j++;
656             }
657 4 50       7 if ($j - $i > 3) {
658 0         0 splice(@vals , $i, $j - $i, "$vals[$i] .. $vals[$j-1]");
659 0         0 splice(@cvals, $i, $j - $i, "$cvals[$i] .. $cvals[$j-1]");
660 0         0 splice(@orig , $i, $j - $i, [@orig[$i..$j-1]]);
661             }
662 4         12 $i++;
663             }
664             }
665 14         40 my $tmp = "@vals";
666 14 50 33     62 if ($comment || (@vals > $indent_lim && (length($tmp) > 60 || $tmp =~ /\n/))) {
      66        
      33        
667              
668 0         0 my $maxvlen = 0;
669 0         0 for (@vals) {
670 0         0 my ($vfirstline) = /\A(.*)/;
671 0         0 my $lenvfirstline = length($vfirstline);
672 0 0       0 $maxvlen = $lenvfirstline if $maxvlen < $lenvfirstline;
673             }
674 0 0       0 $maxvlen = 80 if $maxvlen > 80;
675 0         0 $maxvlen += length($INDENT);
676              
677 0 0       0 my @res = ("\n", $comment ? "$INDENT# $comment\n" : "");
678 0 0       0 my @cres = ("\n", $comment ? $INDENT._col("# $comment")."\n" : "");
679 0         0 my @elem = @vals;
680 0         0 my @celem = @cvals;
681 0         0 for (@elem ) { s/^/$INDENT/gm; }
  0         0  
682 0         0 for (@celem) { s/^/$INDENT/gm; }
  0         0  
683 0         0 my $idxwidth = length(~~@elem);
684 0         0 for my $i (0..$#elem) {
685 0         0 my ($vlastline) = $elem[$i] =~ /(.*)\z/;
686 0         0 my $cpad = " " x max(0, $maxvlen - length($vlastline));
687 0         0 my $visaid = "";
688 0 0       0 $visaid .= sprintf("%s[%${idxwidth}i]", "." x $extra->[0], $i) if $INDEX;
689 0 0 0     0 $visaid .= " len=".length($orig[$i]) if defined $orig[$i] && length($orig[$i]) >= $LENTHRESHOLD;
690 0 0       0 push @res , $elem[ $i], ",", (length($visaid) ? " $cpad# $visaid" : ""), "\n";
691 0 0       0 push @cres, $celem[$i], ",", (length($visaid) ? " $cpad"._col(comment => "# $visaid") : ""), "\n";
692             }
693 0         0 return (join("", @res), join("", @cres));
694             } else {
695 14         82 return (join(", ", @vals), join(", ", @cvals));
696             }
697             }
698              
699             sub str {
700 43 100   43 1 132 if (length($_[0]) > 20) {
701 3         11 for ($_[0]) {
702             # Check for repeated string
703 3 50       19 if (/^(.)\1\1\1/s) {
704             # seems to be a repeating sequence, let's check if it really is
705             # without backtracking
706 0 0       0 unless (/[^\Q$1\E]/) {
707 0         0 my $base = quote($1);
708 0         0 my $repeat = length;
709 0         0 return "($base x $repeat)"
710             }
711             }
712             # Length protection because the RE engine will blow the stack [RT#33520]
713 3 50 33     38 if (length($_) < 16 * 1024 && /^(.{2,5}?)\1*\z/s) {
714 0         0 my $base = quote($1);
715 0         0 my $repeat = length($_)/length($1);
716 0         0 return "($base x $repeat)";
717             }
718             }
719             }
720              
721 43         89 local $_ = "e;
722              
723 43 50 66     147 if (length($_) > 40 && !/\\x\{/ && length($_) > (length($_[0]) * 2)) {
      66        
724             # too much binary data, better to represent as a hex/base64 string
725              
726             # Base64 is more compact than hex when string is longer than
727             # 17 bytes (not counting any require statement needed).
728             # But on the other hand, hex is much more readable.
729 0 0 0     0 if ($TRY_BASE64 && length($_[0]) > $TRY_BASE64 &&
      0        
      0        
      0        
730             (defined &utf8::is_utf8 && !utf8::is_utf8($_[0])) &&
731 0         0 eval { require MIME::Base64 })
732             {
733 0         0 $require{"MIME::Base64"}++;
734 0         0 return "MIME::Base64::decode(\"" .
735             MIME::Base64::encode($_[0],"") .
736             "\")";
737             }
738 0         0 return "pack(\"H*\",\"" . unpack("H*", $_[0]) . "\")";
739             }
740              
741 43         100 return $_;
742             }
743              
744             my %esc = (
745             "\a" => "\\a",
746             "\b" => "\\b",
747             "\t" => "\\t",
748             "\n" => "\\n",
749             "\f" => "\\f",
750             "\r" => "\\r",
751             "\e" => "\\e",
752             );
753              
754             # put a string value in double quotes
755             sub quote {
756 45     45 0 83 local($_) = $_[0];
757             # If there are many '"' we might want to use qq() instead
758 45         134 s/([\\\"\@\$])/\\$1/g;
759 45 50       220 return qq("$_") unless /[^\040-\176]/; # fast exit
760              
761 0         0 s/([\a\b\t\n\f\r\e])/$esc{$1}/g;
762              
763             # no need for 3 digits in escape for these
764 0         0 s/([\0-\037])(?!\d)/sprintf('\\%o',ord($1))/eg;
  0         0  
765              
766 0         0 s/([\0-\037\177-\377])/sprintf('\\x%02X',ord($1))/eg;
  0         0  
767 0         0 s/([^\040-\176])/sprintf('\\x{%X}',ord($1))/eg;
  0         0  
768              
769 0         0 return qq("$_");
770             }
771              
772             # put a string value in single quotes
773             sub squote {
774 1     1 0 3 local($_) = $_[0];
775 1         3 s/([\\'])/\\$1/g;
776 1         2 return qq('$_');
777             }
778              
779             1;
780             # ABSTRACT: Like Data::Dump, but with color
781              
782             __END__