File Coverage

blib/lib/Data/Dump/Ruby.pm
Criterion Covered Total %
statement 208 264 78.7
branch 88 142 61.9
condition 32 66 48.4
subroutine 14 14 100.0
pod 2 7 28.5
total 344 493 69.7


line stmt bran cond sub pod time code
1             package Data::Dump::Ruby;
2 6     6   44587 use strict;
  6         12  
  6         226  
3 6     6   77 use vars qw(@EXPORT @EXPORT_OK $DEBUG);
  6         11  
  6         423  
4 6     6   5713 use subs qq(dump);
  6         134  
  6         29  
5              
6             # to make Test::Pod::Coverage happy
7              
8              
9             require Exporter;
10             *import = \&Exporter::import;
11             @EXPORT = qw(dd_ruby ddx_ruby);
12             @EXPORT_OK = qw(dump_ruby pp_ruby quote_ruby);
13              
14             our $VERSION = '0.02'; # VERSION
15              
16             $DEBUG = 0;
17              
18 6     6   10832 use overload ();
  6         6789  
  6         164  
19 6     6   36 use vars qw(%seen %refcnt @dump @fixup %require $TRY_BASE64 $USE_LAMBDA);
  6         9  
  6         26008  
20              
21             $USE_LAMBDA = 0;
22             $TRY_BASE64 = 50 unless defined $TRY_BASE64;
23              
24             my %is_perl_keyword = map { $_ => 1 }
25             qw( __FILE__ __LINE__ __PACKAGE__ __DATA__ __END__ AUTOLOAD BEGIN CORE
26             DESTROY END EQ GE GT INIT LE LT NE abs accept alarm and atan2 bind
27             binmode bless caller chdir chmod chomp chop chown chr chroot close
28             closedir cmp connect continue cos crypt dbmclose dbmopen defined
29             delete die do dump each else elsif endgrent endhostent endnetent
30             endprotoent endpwent endservent eof eq eval exec exists exit exp fcntl
31             fileno flock for foreach fork format formline ge getc getgrent
32             getgrgid getgrnam gethostbyaddr gethostbyname gethostent getlogin
33             getnetbyaddr getnetbyname getnetent getpeername getpgrp getppid
34             getpriority getprotobyname getprotobynumber getprotoent getpwent
35             getpwnam getpwuid getservbyname getservbyport getservent getsockname
36             getsockopt glob gmtime goto grep gt hex if index int ioctl join keys
37             kill last lc lcfirst le length link listen local localtime lock log
38             lstat lt m map mkdir msgctl msgget msgrcv msgsnd my ne next no not oct
39             open opendir or ord pack package pipe pop pos print printf prototype
40             push q qq qr quotemeta qw qx rand read readdir readline readlink
41             readpipe recv redo ref rename require reset return reverse rewinddir
42             rindex rmdir s scalar seek seekdir select semctl semget semop send
43             setgrent sethostent setnetent setpgrp setpriority setprotoent setpwent
44             setservent setsockopt shift shmctl shmget shmread shmwrite shutdown
45             sin sleep socket socketpair sort splice split sprintf sqrt srand stat
46             study sub substr symlink syscall sysopen sysread sysseek system
47             syswrite tell telldir tie tied time times tr truncate uc ucfirst umask
48             undef unless unlink unpack unshift untie until use utime values vec
49             wait waitpid wantarray warn while write x xor y);
50              
51              
52             sub dump
53             {
54 36     36   5545 local %seen;
55 36         49 local %refcnt;
56 36         48 local %require;
57 36         50 local @fixup;
58              
59 36         55 my $name = "a";
60 36         40 my @dump;
61              
62 36         95 for my $v (@_) {
63 47         125 my $val = _dump($v, $name, [], tied($v));
64 47         127 push(@dump, [$name, $val]);
65             } continue {
66 47         107 $name++;
67             }
68              
69 36         53 my $out = "";
70 36 50       84 if (%require) {
71 0         0 die "BUG: should not require() for Ruby";
72 0         0 for (sort keys %require) {
73 0         0 $out .= "require $_;\n";
74             }
75             }
76 36 100       78 if (%refcnt) {
77             # output all those with refcounts first
78 1         3 for (@dump) {
79 1         2 my $name = $_->[0];
80 1 50       4 if ($refcnt{$name}) {
81 1         4 $out .= "$name = $_->[1]\n";
82 1         5 undef $_->[1];
83             }
84             }
85 1         3 for (@fixup) {
86 1         4 $out .= "$_\n";
87             }
88             }
89              
90 36         55 my $paren = (@dump != 1);
91 36 50       85 $out .= (@fixup ? "":"")."[" if $paren;
    100          
92 47 50 33     194 $out .= format_list($paren, undef,
    100          
93 36         86 map {defined($dump[$_][1]) ? $dump[$_][1] : (!$paren && $_ == @dump-1 ? "":"").$dump[$_][0]}
94             0..$#dump
95             );
96 36 100       94 $out .= "]" if $paren;
97              
98 36 100 66     190 if (%refcnt || %require) {
99 1         2 $out .= "\n";
100 1         8 $out =~ s/^/ /gm; # indent
101 1         5 $out = "(Proc.new { $out }).call";
102             }
103              
104             #use Data::Dumper; print Dumper(\%refcnt);
105             #use Data::Dumper; print Dumper(\%seen);
106              
107 36 50       78 print STDERR "$out\n" unless defined wantarray;
108 36         1545 $out;
109             }
110              
111             *dump_ruby = \&dump;
112             *pp_ruby = \&dump;
113              
114             sub dd_ruby {
115 1     1 1 2519 print dump(@_), "\n";
116             }
117              
118             sub ddx_ruby {
119 1     1 1 71 my(undef, $file, $line) = caller;
120 1         11 $file =~ s,.*[\\/],,;
121 1         10 my $out = "$file:$line: " . dump(@_) . "\n";
122 1         6 $out =~ s/^/# /gm;
123 1         47 print $out;
124             }
125              
126             sub _dump
127             {
128 79     79   111 my $ref = ref $_[0];
129 79 100       152 my $rval = $ref ? $_[0] : \$_[0];
130 79         88 shift;
131              
132 79         114 my($name, $idx, $dont_remember) = @_;
133              
134 79         92 my($class, $type, $id);
135 79 50       190 if (overload::StrVal($rval) =~ /^(?:([^=]+)=)?([A-Z]+)\(0x([^\)]+)\)$/) {
136 79         635 $class = $1;
137 79         110 $type = $2;
138 79         117 $id = $3;
139             } else {
140 0         0 die "Can't parse " . overload::StrVal($rval);
141             }
142 79 50 33     221 if ($] < 5.008 && $type eq "SCALAR") {
143 0 0       0 $type = "REF" if $ref eq "REF";
144             }
145 79 50       140 warn "\$$name(@$idx) $class $type $id ($ref)" if $DEBUG;
146              
147 79 50       150 unless ($dont_remember) {
148 79 100       164 if (my $s = $seen{$id}) {
149 1         2 my($sname, $sidx) = @$s;
150 1         3 $refcnt{$sname}++;
151 1   33     10 my $sref = fullname($sname, $sidx,
152             ($ref && $type eq "SCALAR"));
153 1 50       3 warn "SEEN: [\$$name(@$idx)] => [\$$sname(@$sidx)] ($ref,$sref)" if $DEBUG;
154 1 50       3 return $sref unless $sname eq $name;
155 1         2 $refcnt{$name}++;
156 1         3 push(@fixup, fullname($name,$idx) . " = " . $sref);
157 1 50 33     9 die "Can't handle returning references for Ruby yet" if @$idx && $idx->[-1] eq '$';
158             #return "do{my \$fix}" if @$idx && $idx->[-1] eq '$';
159 1         6 return "'fix'";
160             }
161 78         240 $seen{$id} = [$name, $idx];
162             }
163              
164 78         90 my $out;
165 78 100 66     310 if ($type eq "SCALAR" || $type eq "REF" || $type eq "REGEXP") {
    50 100        
    100          
    50          
    0          
166 69 100       105 if ($ref) {
167 8 50 33     31 if ($class && $class eq "Regexp") {
168 8         14 my $v = "$rval";
169              
170 8         10 my $mod = "";
171 8 50       31 if ($v =~ /^\(\?\^?([msix-]*):([\x00-\xFF]*)\)\z/) {
172 8         11 $mod = $1;
173 8         14 $v = $2;
174 8         9 $mod =~ s/-.*//;
175 8         16 $mod =~ s/([sm])/
176 3 50       16 $1 eq 'm' ? '' :
    100          
177             ($1 eq 's' ? 'm' : '')/eg;
178             }
179              
180 8         8 my $sep = '/';
181 8         12 my $sep_count = ($v =~ tr/\///);
182 8 100       15 if ($sep_count) {
183             # see if we can find a better one
184 4         6 for ('|', ',', ':', '#') {
185 10         479 my $c = eval "\$v =~ tr/\Q$_\E//";
186             #print "SEP $_ $c $sep_count\n";
187 10 100       33 if ($c < $sep_count) {
188 3         4 $sep = $_;
189 3         5 $sep_count = $c;
190 3 50       8 last if $sep_count == 0;
191             }
192             }
193             }
194 8         58 $v =~ s/\Q$sep\E/\\$sep/g;
195              
196 8         19 $out = "\%r$sep$v$sep$mod";
197 8         15 undef($class);
198             }
199             else {
200 0         0 die "Can't handle non-Regexp builtin object (class $class) for Ruby yet";
201 0 0       0 delete $seen{$id} if $type eq "SCALAR"; # will be seen again shortly
202 0         0 my $val = _dump($$rval, $name, [@$idx, "\$"]);
203 0 0       0 $out = $class ? "do{\\(my \$o = $val)}" : "\\$val";
204             }
205             } else {
206 61 100 100     343 if (!defined $$rval) {
    100          
207 2         5 $out = "nil";
208             }
209             elsif ($$rval =~ /^-?[1-9]\d{0,9}\z/ || $$rval eq "0") {
210 40         55 $out = $$rval;
211             }
212             else {
213 19         39 $out = str($$rval);
214             }
215 61 50 33     148 if ($class && !@$idx) {
216 0         0 die "Can't handle nonref, class, nonidx for Ruby yet";
217             # Top is an object, not a reference to one as perl needs
218 0         0 $refcnt{$name}++;
219 0         0 my $obj = fullname($name, $idx);
220 0         0 my $cl = quote($class);
221 0         0 push(@fixup, "bless \\$obj, $cl");
222             }
223             }
224             }
225             elsif ($type eq "GLOB") {
226 0         0 die "Can't handle glob for Ruby yet";
227 0 0       0 if ($ref) {
228 0         0 delete $seen{$id};
229 0         0 my $val = _dump($$rval, $name, [@$idx, "*"]);
230 0         0 $out = "\\$val";
231 0 0       0 if ($out =~ /^\\\*Symbol::/) {
232 0         0 $require{Symbol}++;
233 0         0 $out = "Symbol::gensym()";
234             }
235             } else {
236 0         0 my $val = "$$rval";
237 0         0 $out = "$$rval";
238              
239 0         0 for my $k (qw(SCALAR ARRAY HASH)) {
240 0         0 my $gval = *$$rval{$k};
241 0 0       0 next unless defined $gval;
242 0 0 0     0 next if $k eq "SCALAR" && ! defined $$gval; # always there
243 0         0 my $f = scalar @fixup;
244 0         0 push(@fixup, "RESERVED"); # overwritten after _dump() below
245 0         0 $gval = _dump($gval, $name, [@$idx, "*{$k}"]);
246 0         0 $refcnt{$name}++;
247 0         0 my $gname = fullname($name, $idx);
248 0         0 $fixup[$f] = "$gname = $gval"; #XXX indent $gval
249             }
250             }
251             }
252             elsif ($type eq "ARRAY") {
253 1         1 my @vals;
254 1         4 my $tied = tied_str(tied(@$rval));
255 1 50       3 die "Can't handle tied arrayref for Ruby yet" if $tied;
256 1         2 my $i = 0;
257 1         2 for my $v (@$rval) {
258 5         30 push(@vals, _dump($v, $name, [@$idx, "[$i]"], $tied));
259 5         8 $i++;
260             }
261 1         4 $out = "[" . format_list(1, $tied, @vals) . "]";
262             }
263             elsif ($type eq "HASH") {
264 8         8 my(@keys, @vals);
265 8         19 my $tied = tied_str(tied(%$rval));
266 8 50       52 die "Can't handle tied hashref for Ruby yet" if $tied;
267              
268             # statistics to determine variation in key lengths
269 8         11 my $kstat_max = 0;
270 8         10 my $kstat_sum = 0;
271 8         8 my $kstat_sum2 = 0;
272              
273 8         29 my @orig_keys = keys %$rval;
274 8         12 my $text_keys = 0;
275 8         45 for (@orig_keys) {
276 12 100       50 $text_keys++, last unless /^[-+]?(?:0|[1-9]\d*)(?:\.\d+)?\z/;
277             }
278              
279 8 100       17 if ($text_keys) {
280 6         21 @orig_keys = sort @orig_keys;
281             }
282             else {
283 2         7 @orig_keys = sort { $a <=> $b } @orig_keys;
  6         11  
284             }
285              
286 8         15 for my $key (@orig_keys) {
287 27         45 my $val = \$rval->{$key};
288 27 100       90 $key = quote($key) if #$is_perl_keyword{$key} ||
289             !(#$key =~ /^[a-zA-Z_]\w{0,19}\z/ ||
290             $key =~ /^-?[1-9]\d{0,8}\z/
291             );
292              
293 27 100       54 $kstat_max = length($key) if length($key) > $kstat_max;
294 27         32 $kstat_sum += length($key);
295 27         32 $kstat_sum2 += length($key)*length($key);
296              
297 27         41 push(@keys, $key);
298 27         105 push(@vals, _dump($$val, $name, [@$idx, "[$key]"], $tied));
299             }
300 8         14 my $nl = "";
301 8         8 my $klen_pad = 0;
302 8         31 my $tmp = "@keys @vals";
303 8 50 66     50 if (length($tmp) > 60 || $tmp =~ /\n/ || $tied) {
      66        
304 3         3 $nl = "\n";
305              
306             # Determine what padding to add
307 3 100       14 if ($kstat_max < 4) {
    50          
308 1         2 $klen_pad = $kstat_max;
309             }
310             elsif (@keys >= 2) {
311 2         3 my $n = @keys;
312 2         4 my $avg = $kstat_sum/$n;
313 2         12 my $stddev = sqrt(($kstat_sum2 - $n * $avg * $avg) / ($n - 1));
314              
315             # I am not actually very happy with this heuristics
316 2 100       6 if ($stddev / $kstat_max < 0.25) {
317 1         2 $klen_pad = $kstat_max;
318             }
319 2 50       5 if ($DEBUG) {
320 0         0 push(@keys, "__S");
321 0         0 push(@vals, sprintf("%.2f (%d/%.1f/%.1f)",
322             $stddev / $kstat_max,
323             $kstat_max, $avg, $stddev));
324             }
325             }
326             }
327 8         12 $out = "{$nl";
328 8 50       14 $out .= " # $tied$nl" if $tied;
329 8         17 while (@keys) {
330 27         38 my $key = shift @keys;
331 27         30 my $val = shift @vals;
332 27         43 my $pad = " " x ($klen_pad + 6);
333 27         35 $val =~ s/\n/\n$pad/gm;
334 27 100       68 $key = " $key" . " " x ($klen_pad - length($key)) if $nl;
335 27         75 $out .= " $key => $val,$nl";
336             }
337 8 100       33 $out =~ s/,$/ / unless $nl;
338 8         24 $out .= "}";
339             }
340             elsif ($type eq "CODE") {
341 0         0 $out = "(Proc.new {})";
342             }
343             else {
344 0         0 warn "Can't handle $type data";
345 0         0 $out = "'#$type#'";
346             }
347              
348 78 50 33     169 if ($class && $ref) {
349 0         0 die "Can't handle object (class $class) for Ruby yet";
350 0         0 $out = "bless($out, " . quote($class) . ")";
351             }
352 78         192 return $out;
353             }
354              
355             sub tied_str {
356 9     9 0 12 my $tied = shift;
357 9 50       17 if ($tied) {
358 0 0       0 if (my $tied_ref = ref($tied)) {
359 0         0 $tied = "tied $tied_ref";
360             }
361             else {
362 0         0 $tied = "tied";
363             }
364             }
365 9         15 return $tied;
366             }
367              
368             sub fullname
369             {
370 2     2 0 3 my($name, $idx, $ref) = @_;
371 2         6 substr($name, 0, 0) = "";
372              
373 2         15 my @i = @$idx; # need copy in order to not modify @$idx
374 2         5 my @ci = @i;
375 2 0 33     8 if ($ref && @i && $i[0] eq "\$") {
      33        
376 0         0 shift(@i); # remove one deref
377 0         0 $ref = 0;
378             }
379 2   66     16 while (@i && $i[0] eq "\$") {
380 0         0 shift @i;
381 0         0 $name = "$name";
382             }
383              
384 2         6 my $last_was_index;
385 2         4 for my $i (@i) {
386 1 50 33     14 if ($i eq "*" || $i eq "\$") {
    50          
387 0         0 $last_was_index = 0;
388 0         0 $name = "$i\[".quote($name)."]";
389             } elsif ($i =~ s/^\*//) {
390 0         0 $name .= $i;
391 0         0 $last_was_index++;
392             } else {
393             #$name .= "->" unless $last_was_index++;
394 1         4 $name .= $i;
395             }
396             }
397 2 50       5 $name = "\\$name" if $ref;
398 2         7 "*".join("", map {"<$_>"} @ci)."*$name*"; #X#
  1         5  
399 2         9 $name;
400             }
401              
402             sub format_list
403             {
404 37     37 0 51 my $paren = shift;
405 37         44 my $comment = shift;
406 37 100       73 my $indent_lim = $paren ? 0 : 1;
407 37         86 my $tmp = "@_";
408 37 50 33     187 if ($comment || (@_ > $indent_lim && (length($tmp) > 60 || $tmp =~ /\n/))) {
      66        
      33        
409 0         0 my @elem = @_;
410 0         0 for (@elem) { s/^/ /gm; } # indent
  0         0  
411 0 0       0 return "\n" . ($comment ? " # $comment\n" : "") .
412             join(",\n", @elem, "");
413             } else {
414 37         115 return join(", ", @_);
415             }
416             }
417              
418             sub str {
419 19 100   19 0 54 if (length($_[0]) > 30) {
420 9         21 for ($_[0]) {
421             # Check for repeated string
422 9 100       150 if (/^(.)\1\1\1/s) {
423             # seems to be a repating sequence, let's check if it really is
424             # without backtracking
425 4 50       68703 unless (/[^\Q$1\E]/) {
426 4         17 my $base = quote($1);
427 4         9 my $repeat = length;
428 4         20 return "$base * $repeat"
429             }
430             }
431             # Length protection because the RE engine will blow the stack [RT#33520]
432 5 100 66     61 if (length($_) < 16 * 1024 && /^(.{2,5}?)\1*\z/s) {
433 2         7 my $base = quote($1);
434 2         6 my $repeat = length($_)/length($1);
435 2         24 return "$base * $repeat";
436             }
437             }
438             }
439              
440 13         30 local $_ = "e;
441              
442             # XXX disabled because base64 decoding requires module
443 13         17 if (0 && length($_) > 40 && !/\\x\{/ && length($_) > (length($_[0]) * 2)) {
444             # too much binary data, better to represent as a hex/base64 string
445              
446             # Base64 is more compact than hex when string is longer than
447             # 17 bytes (not counting any require statement needed).
448             # But on the other hand, hex is much more readable.
449             if ($TRY_BASE64 && length($_[0]) > $TRY_BASE64 &&
450             eval { require MIME::Base64 })
451             {
452             #$require{"base64"}++;
453             return "Base64.decode64(\"" .
454             MIME::Base64::encode($_[0],"") .
455             "\")";
456             }
457             return "pack(\"H*\",\"" . unpack("H*", $_[0]) . "\")";
458             }
459              
460 13         24 return $_;
461             }
462              
463             my %esc = (
464             "\t" => "\\t",
465             "\n" => "\\n",
466             "\r" => "\\r",
467             "\f" => "\\f",
468             "\b" => "\\b",
469             "\a" => "\\a",
470             "\e" => "\\e",
471             );
472              
473             # put a string value in double quotes
474             sub quote {
475 49     49 0 127 local($_) = $_[0];
476             # If there are many '"' we might want to use qq() instead
477 49         116 s/([\\\"\@\$])/\\$1/g;
478 49 100       209 return qq("$_") unless /[^\040-\176]/; # fast exit
479              
480 12         63 s/([\t\n\r\f\b\a\e])/$esc{$1}/g;
481              
482             # no need for 3 digits in escape for these
483 12         31 s/([\0-\037])(?!\d)/sprintf('\\%o',ord($1))/eg;
  1028         5622  
484              
485 12         227 s/([\0-\037\177-\377])/sprintf('\\x%02X',ord($1))/eg;
  6         30  
486 12         557 s/([^\040-\176])/sprintf('\\x{%X}',ord($1))/eg;
  105         223  
487              
488 12         2093 return qq("$_");
489             }
490              
491             *quote_ruby = \"e;
492              
493             1;
494              
495             __END__