File Coverage

blib/lib/Data/Dmp.pm
Criterion Covered Total %
statement 95 108 87.9
branch 45 52 86.5
condition 17 21 80.9
subroutine 12 14 85.7
pod 4 4 100.0
total 173 199 86.9


line stmt bran cond sub pod time code
1             ## no critic: Modules::ProhibitAutomaticExportation
2              
3             package Data::Dmp;
4              
5             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
6             our $DATE = '2021-06-24'; # DATE
7             our $DIST = 'Data-Dmp'; # DIST
8             our $VERSION = '0.241'; # VERSION
9              
10 1     1   612 use 5.010001;
  1         8  
11 1     1   6 use strict;
  1         1  
  1         19  
12 1     1   4 use warnings;
  1         1  
  1         40  
13              
14 1     1   6 use Scalar::Util qw(looks_like_number blessed reftype refaddr);
  1         2  
  1         1747  
15              
16             require Exporter;
17             our @ISA = qw(Exporter);
18             our @EXPORT = qw(dd dmp);
19             our @EXPORT_OK = qw(dd_ellipsis dmp_ellipsis);
20              
21             # for when dealing with circular refs
22             our %_seen_refaddrs;
23             our %_subscripts;
24             our @_fixups;
25              
26             our $OPT_MAX_DUMP_LEN_BEFORE_ELLIPSIS = 70;
27             our $OPT_PERL_VERSION = "5.010";
28             our $OPT_REMOVE_PRAGMAS = 0;
29             our $OPT_DEPARSE = 1;
30             our $OPT_STRINGIFY_NUMBERS = 0;
31              
32             # BEGIN COPY PASTE FROM Data::Dump
33             my %esc = (
34             "\a" => "\\a",
35             "\b" => "\\b",
36             "\t" => "\\t",
37             "\n" => "\\n",
38             "\f" => "\\f",
39             "\r" => "\\r",
40             "\e" => "\\e",
41             );
42              
43             # put a string value in double quotes
44             sub _double_quote {
45 16     16   27 local($_) = $_[0];
46              
47             # If there are many '"' we might want to use qq() instead
48 16         36 s/([\\\"\@\$])/\\$1/g;
49 16 100       65 return qq("$_") unless /[^\040-\176]/; # fast exit
50              
51 1         8 s/([\a\b\t\n\f\r\e])/$esc{$1}/g;
52              
53             # no need for 3 digits in escape for these
54 1         3 s/([\0-\037])(?!\d)/sprintf('\\%o',ord($1))/eg;
  0         0  
55              
56 1         2 s/([\0-\037\177-\377])/sprintf('\\x%02X',ord($1))/eg;
  0         0  
57 1         2 s/([^\040-\176])/sprintf('\\x{%X}',ord($1))/eg;
  0         0  
58              
59 1         4 return qq("$_");
60             }
61             # END COPY PASTE FROM Data::Dump
62              
63             # BEGIN COPY PASTE FROM String::PerlQuote
64             sub _single_quote {
65 3     3   4 local($_) = $_[0];
66 3         6 s/([\\'])/\\$1/g;
67 3         10 return qq('$_');
68             }
69             # END COPY PASTE FROM String::PerlQuote
70              
71             sub _dump_code {
72 4     4   9 my $code = shift;
73              
74 4         6 state $deparse = do {
75 1         10 require B::Deparse;
76 1         74 B::Deparse->new("-l"); # -i option doesn't have any effect?
77             };
78              
79 4         4018 my $res = $deparse->coderef2text($code);
80              
81 4         62 my ($res_before_first_line, $res_after_first_line) =
82             $res =~ /(.+?)^(#line .+)/ms;
83              
84 4 100       17 if ($OPT_REMOVE_PRAGMAS) {
    50          
85 3         6 $res_before_first_line = "{";
86             } elsif ($OPT_PERL_VERSION < 5.016) {
87             # older perls' feature.pm doesn't yet support q{no feature ':all';}
88             # so we replace it with q{no feature}.
89 1         6 $res_before_first_line =~ s/no feature ':all';/no feature;/m;
90             }
91 4         18 $res_after_first_line =~ s/^#line .+//gm;
92              
93 4         20 $res = "sub" . $res_before_first_line . $res_after_first_line;
94 4         21 $res =~ s/^\s+//gm;
95 4         62 $res =~ s/\n+//g;
96 4         16 $res =~ s/;\}\z/}/;
97 4         10 $res;
98             }
99              
100             sub _quote_key {
101 9 100 100 9   51 $_[0] =~ /\A-?[A-Za-z_][A-Za-z0-9_]*\z/ ||
102             $_[0] =~ /\A-?[1-9][0-9]{0,8}\z/ ? $_[0] : _double_quote($_[0]);
103             }
104              
105             sub _dump {
106 47     47   90 my ($val, $subscript) = @_;
107              
108 47         83 my $ref = ref($val);
109 47 100       100 if ($ref eq '') {
110 26 100 100     173 if (!defined($val)) {
    100 100        
      100        
111 1         4 return "undef";
112             } elsif (looks_like_number($val) && !$OPT_STRINGIFY_NUMBERS &&
113             # perl does several normalizations to number literal, e.g.
114             # "+1" becomes 1, 0123 is octal literal, etc. make sure we
115             # only leave out quote when the number is not normalized
116             $val eq $val+0 &&
117             # perl also doesn't recognize Inf and NaN as numeric
118             # literals (ref: perldata) so these unquoted literals will
119             # choke under 'use strict "subs"
120             $val !~ /\A-?(?:inf(?:inity)?|nan)\z/i
121             ) {
122 14         31 return $val;
123             } else {
124 11         24 return _double_quote($val);
125             }
126             }
127 21         43 my $refaddr = refaddr($val);
128 21   100     102 $_subscripts{$refaddr} //= $subscript;
129 21 100       54 if ($_seen_refaddrs{$refaddr}++) {
130             my $target = "\$var" .
131 3 50       7 ($_subscripts{$refaddr} ? "->$_subscripts{$refaddr}" : "");
132 3         8 push @_fixups, "\$var->$subscript=$target;";
133 3         8 return _single_quote($target);
134             }
135              
136 18         26 my $class;
137              
138 18 100 66     58 if ($ref eq 'Regexp' || $ref eq 'REGEXP') {
139 1         634 require Regexp::Stringify;
140 1         748 return Regexp::Stringify::stringify_regexp(
141             regexp=>$val, with_qr=>1, plver=>$OPT_PERL_VERSION);
142             }
143              
144 17 100       43 if (blessed $val) {
145 1         2 $class = $ref;
146 1         4 $ref = reftype($val);
147             }
148              
149 17         22 my $res;
150 17 100       66 if ($ref eq 'ARRAY') {
    100          
    100          
    100          
    50          
151 4         6 $res = "[";
152 4         6 my $i = 0;
153 4         9 for (@$val) {
154 8 100       18 $res .= "," if $i;
155 8         17 $res .= _dump($_, "$subscript\[$i]");
156 8         12 $i++;
157             }
158 4         6 $res .= "]";
159             } elsif ($ref eq 'HASH') {
160 5         7 $res = "{";
161 5         9 my $i = 0;
162 5         34 for (sort keys %$val) {
163 9 100       17 $res .= "," if $i++;
164 9         15 my $k = _quote_key($_);
165 9         326 my $v = _dump($val->{$_}, "$subscript\{$k}");
166 9         21 $res .= "$k=>$v";
167             }
168 5         11 $res .= "}";
169             } elsif ($ref eq 'SCALAR') {
170 2         13 $res = "\\"._dump($$val, $subscript);
171             } elsif ($ref eq 'REF') {
172 1         2 $res = "\\"._dump($$val, $subscript);
173             } elsif ($ref eq 'CODE') {
174 5 100       14 $res = $OPT_DEPARSE ? _dump_code($val) : 'sub{"DUMMY"}';
175             } else {
176 0         0 die "Sorry, I can't dump $val (ref=$ref) yet";
177             }
178              
179 17 100       37 $res = "bless($res,"._double_quote($class).")" if defined($class);
180 17         34 $res;
181             }
182              
183             our $_is_dd;
184             our $_is_ellipsis;
185             sub _dd_or_dmp {
186 27     27   45 local %_seen_refaddrs;
187 27         36 local %_subscripts;
188 27         39 local @_fixups;
189              
190 27         34 my $res;
191 27 50       70 if (@_ > 1) {
192 0         0 $res = "(" . join(",", map {_dump($_, '')} @_) . ")";
  0         0  
193             } else {
194 27         67 $res = _dump($_[0], '');
195             }
196 27 100       99 if (@_fixups) {
197 2         7 $res = "do{my\$var=$res;" . join("", @_fixups) . "\$var}";
198             }
199              
200 27 100       46 if ($_is_ellipsis) {
201 2 100       8 $res = substr($res, 0, $OPT_MAX_DUMP_LEN_BEFORE_ELLIPSIS) . '...'
202             if length($res) > $OPT_MAX_DUMP_LEN_BEFORE_ELLIPSIS;
203             }
204              
205 27 50       47 if ($_is_dd) {
206 0         0 say $res;
207 0 0 0     0 return wantarray() || @_ > 1 ? @_ : $_[0];
208             } else {
209 27         138 return $res;
210             }
211             }
212              
213 0     0 1 0 sub dd { local $_is_dd=1; _dd_or_dmp(@_) } # goto &sub doesn't work with local
  0         0  
214 25     25 1 3545 sub dmp { goto &_dd_or_dmp }
215              
216 0     0 1 0 sub dd_ellipsis { local $_is_dd=1; local $_is_ellipsis=1; _dd_or_dmp(@_) }
  0         0  
  0         0  
217 2     2 1 722 sub dmp_ellipsis { local $_is_ellipsis=1; _dd_or_dmp(@_) }
  2         5  
218              
219             1;
220             # ABSTRACT: Dump Perl data structures as Perl code
221              
222             __END__