File Coverage

blib/lib/Data/Dump/HTML/Collapsible.pm
Criterion Covered Total %
statement 17 111 15.3
branch 0 50 0.0
condition 0 21 0.0
subroutine 6 14 42.8
pod 2 2 100.0
total 25 198 12.6


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