File Coverage

blib/lib/Data/Dmp/Prune.pm
Criterion Covered Total %
statement 61 112 54.4
branch 24 56 42.8
condition 8 23 34.7
subroutine 8 13 61.5
pod 4 4 100.0
total 105 208 50.4


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