File Coverage

blib/lib/Data/Denter.pm
Criterion Covered Total %
statement 287 331 86.7
branch 106 148 71.6
condition 49 88 55.6
subroutine 29 40 72.5
pod 2 16 12.5
total 473 623 75.9


line stmt bran cond sub pod time code
1             package Data::Denter;
2              
3 1     1   241811 use strict;
  1         3  
  1         49  
4 1     1   78 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  1         2  
  1         101  
5 1     1   5 use vars qw($Width $Comma $Level $TabWidth $Sort $MaxLines $HashMode);
  1         8  
  1         373  
6             require Exporter;
7             @ISA = qw(Exporter);
8             @EXPORT = qw(Indent Undent Denter);
9             @EXPORT_OK = qw(Dumper);
10             %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
11             $VERSION = '0.15';
12 1     1   7 use Carp;
  1         2  
  1         5556  
13              
14             sub Indent {
15 10 100   10 1 1769 $Sort = 1 unless defined $Sort;
16 10   50     175 Data::Denter->new(width => $Width || 4,
      50        
      50        
      50        
      100        
17             level => $Level || 0,
18             comma => $Comma || " => ",
19             sort => $Sort,
20             maxlines => $MaxLines || 0,
21             hashmode => $HashMode || 0,
22             )->indent(@_);
23             };
24             *Denter = \&Indent;
25             *Dumper = \&Indent;
26              
27             sub Undent {
28 10   50 10 1 216 Data::Denter->new(width => $Width || 4,
      50        
      50        
      100        
29             tabwidth => $TabWidth || 8,
30             comma => $Comma || " => ",
31             hashmode => $HashMode || 0,
32             )->undent(@_);
33             };
34              
35             # General error messages
36             sub invalid_usage {
37 0     0 0 0 "Invalid usage of the $_[0] method\n";
38             }
39              
40             # Indent error messages
41             sub invalid_name_level {
42 0     0 0 0 "Can't indent a typeglob name at indentation level $_[0]\n";
43             }
44              
45             sub invalid_hashmode_key {
46 0     0 0 0 my $key = shift;
47 0         0 <
48             You are using Data::Denter and you have specified a "key" that is invalid:
49             "$key"
50             The keys must be string values containing only word characters.
51             END
52             }
53              
54             # Undent error messages
55             sub invalid_indent_width {
56 0     0 0 0 my $o = shift;
57 0         0 "Invalid indent width detected at line $o->{line}\n";
58             }
59              
60             sub no_key_end_marker {
61 0     0 0 0 my ($marker, $line) = @_;
62 0         0 "No terminating marker '$marker' found for key at line $line\n";
63             }
64              
65             sub no_value_end_marker {
66 0     0 0 0 my ($marker, $line) = @_;
67 0         0 "No terminating marker '$marker' found for value at line $line\n";
68             }
69              
70             sub mismatched_quotes {
71 0     0 0 0 my $o = shift;
72 0         0 "Mismatched double quotes for value at line $o->{line}\n";
73             }
74              
75             sub invalid_key_value {
76 0     0 0 0 my $o = shift;
77 0         0 "Missing or invalid hash key/value pair at $o->{line}\n";
78             }
79              
80             sub invalid_indent_level {
81 0     0 0 0 my $o = shift;
82 0         0 "Invalid indentation level at $o->{line}\n";
83             }
84              
85             sub invalid_scalar_value {
86 0     0 0 0 my $o = shift;
87 0         0 "Invalid value for scalar ref context at $o->{line}\n";
88             }
89              
90             sub no_such_ref {
91 0     0 0 0 my $ref = shift;
92 0         0 "Cannot dereference '$ref'. Not previously defined\n";
93             }
94              
95             sub new {
96 20     20 0 34 my $class = shift;
97 20         82 my %args = @_;
98 20 100       59 $args{sort} = 1 unless defined $args{sort};
99 20   50     431 bless {__DATA__DENTER__ => 1,
      50        
      50        
      100        
      50        
      100        
100             width => $args{width} || 4,
101             comma => $args{comma} || " => ",
102             level => $args{level} || 0,
103             tabwidth => $args{tabwidth} || 8,
104             sort => $args{sort},
105             maxlines => $args{maxlines} || 0,
106             hashmode => $args{hashmode} || 0,
107             }, $class;
108             }
109              
110             sub indent {
111 10     10 0 13 my $o = shift;
112 10 50       31 croak invalid_usage('indent') unless $o->{__DATA__DENTER__};
113 10         20 my $package = caller;
114 10 50       33 $package = caller(1) if $package eq 'Data::Denter';
115 10         17 my $stream = '';
116 10         20 $o->{key} = '';
117 10         28 while (@_) {
118 11         16 $_ = shift;
119 11 100       26 if ($o->{hashmode}) {
120 2 50 33     19 croak invalid_hashmode_key($_)
121             if (ref or not /^\w+$/);
122 2         14 $stream .= $o->_indent_name("*${package}::$_", shift);
123 2         6 next;
124             }
125 9 100       83 $stream .= $o->_indent_name($_, shift), next
126             if (/^\*$package\::\w+$/);
127 8         21 $stream .= $o->_indent_data($_);
128             }
129 10         96 $o->_resolve(\$stream);
130 10         43 return $stream;
131             }
132              
133             sub _indent_data {
134 35     35   55 my $o = shift;
135 35         52 $_ = shift;
136 35 100       72 return $o->_indent_undef($_)
137             if not defined;
138 34 100       93 return $o->_indent_value($_)
139             if (not ref);
140 24 100 66     189 return $o->_indent_hash($_)
      100        
141             if (ref eq 'HASH' and not /=/ or /=HASH/);
142 21 100 66     149 return $o->_indent_array($_)
      66        
143             if (ref eq 'ARRAY' and not /=/ or /=ARRAY/);
144 16 100 66     166 return $o->_indent_ref($_, $1)
145             if (ref eq 'REF' and /^(SCALAR|REF)\(/);
146 2 50 33     25 return $o->_indent_scalar($_)
      33        
147             if (ref eq 'SCALAR' and not /=/ or /=SCALAR/);
148 0         0 return "$_\n";
149             }
150              
151             sub _indent_value {
152 10     10   17 my ($o, $data) = @_;
153 10         13 my $stream;
154 10 100 33     341 if ($data =~ /\n/) {
    100 66        
      66        
155 1         3 my $marker = 'EOV';
156 1         19 $marker++ while $data =~ /^$marker$/m;
157 1 50       9 my $chomp = ($data =~ s/\n\Z//) ? '' : '-';
158 1         4 $stream = "<<$marker$chomp\n";
159 1 50       5 $stream .= $o->{key}, $o->{key} = '' if $o->{key};
160 1         6 my @data = split /\n/, $data, -1;
161 1         3 $data = '';
162 1 50 33     7 if ($o->{maxlines} and @data > $o->{maxlines}) {
163 0         0 my $notshown = @data - $o->{maxlines};
164 0         0 $#data = $o->{maxlines} - 1;
165 0         0 push @data, "*** $notshown lines not displayed ***";
166             }
167 1         2 for (@data) {
168 3         10 s/([\x00-\x08\x0b-\x1f%\x7f-\xff])/'%'.sprintf('%02x',ord($1))/eg;
  2         12  
169 3         13 $data .= "$_\n";
170             }
171 1         3 chomp $data;
172 1         5 $stream .= "$data\n$marker\n";
173             }
174             elsif ($data =~ /^[\s\%\@\$\\?\"]|\s$/ or
175             $data =~ /\Q$o->{comma}\E/ or
176             $data =~ /([\x00-\x1f\x7f-\xff])/ or
177             $data eq '') {
178 1         8 $data =~ s/([\x00-\x1f%\x7f-\xff])/'%'.sprintf('%02x',ord($1))/eg;
  3         17  
179 1         4 $stream = qq{"$data"\n};
180 1 50       6 $stream .= $o->{key}, $o->{key} = '' if $o->{key};
181             }
182             else {
183 8         10 $stream = "$data\n";
184 8 50       24 $stream .= $o->{key}, $o->{key} = '' if $o->{key};
185             }
186 10         36 return $stream;
187             }
188              
189             sub _indent_hash {
190 3     3   5 my ($o, $data) = @_;
191 3         12 my $stream = $o->_print_ref($data, '%', 'HASH');
192 3 50       10 return $$stream if ref $stream;
193 3         11 my $indent = ++$o->{level} * $o->{width};
194 3 50       16 for my $key ($o->{sort} ?
195             (sort keys %$data) :
196             (keys %$data)
197             ) {
198 2         4 my $key_out = $key;
199 2 50 33     52 if ($key =~ /\n/ or
    50 33        
200             $key =~ /\Q$o->{comma}\E/) {
201 0         0 my $marker = 'EOK';
202 0         0 $marker++ while $key =~ /^$marker$/m;
203 0 0       0 my $chomp = (($o->{key} = $key) =~ s/\n\Z//m) ? '' : '-';
204 0         0 $o->{key} .= "\n$marker\n";
205 0         0 $key_out = "<<$marker$chomp";
206             }
207             elsif ($key =~ /^[\s\%\@\$\\?\"]|\s$/
208             or $key eq '') {
209 0         0 $key_out = qq{"$key"};
210             }
211 2         10 $stream .= ' ' x $indent . $key_out . $o->{comma};
212 2         13 $stream .= $o->_indent_data($data->{$key});
213             }
214 3         6 $o->{level}--;
215 3         17 return $stream;
216             }
217              
218             sub _indent_array {
219 5     5   9 my ($o, $data) = @_;
220 5         16 my $stream = $o->_print_ref($data, '@', 'ARRAY');
221 5 100       18 return $$stream if ref $stream;
222 4         12 my $indent = ++$o->{level} * $o->{width};
223 4         9 for my $datum (@$data) {
224 10         25 $stream .= ' ' x $indent;
225 10         26 $stream .= $o->_indent_data($datum);
226             }
227 4         9 $o->{level}--;
228 4         26 return $stream;
229             }
230              
231             sub _indent_scalar {
232 2     2   6 my ($o, $data) = @_;
233 2         10 my $stream = $o->_print_ref($data, q{$}, 'SCALAR');
234 2 50       8 return $$stream if ref $stream;
235 2         9 my $indent = ($o->{level} + 1) * $o->{width};
236 2         6 $stream .= ' ' x $indent;
237 2         11 $stream .= $o->_indent_data($$data);
238 2         18 return $stream;
239             }
240              
241             sub _indent_ref {
242 14     14   34 my ($o, $data, $type) = @_;
243 14         34 my $stream = $o->_print_ref($data, '\\', $type);
244 14 100       54 return $$stream if ref $stream;
245 10         16 chomp $stream;
246 10         33 return $stream . $o->_indent_data($$data);
247             }
248              
249             sub _indent_undef {
250 1     1   3 my ($o, $data) = @_;
251 1         3 my $stream = "?\n";
252 1 50       4 $stream .= $o->{key}, $o->{key} = '' if $o->{key};
253 1         5 return $stream;
254             }
255              
256             sub _indent_name {
257 3     3   7 my ($o, $name, $value) = @_;
258 3         23 $name =~ s/^.*:://;
259 3 50       13 croak invalid_name_level($o->{level}) if $o->{level} != 0;
260 3         7 my $stream = $name . $o->{comma};
261 3         11 $stream .= $o->_indent_data($value);
262 3         14 return $stream;
263             }
264              
265             sub _print_ref {
266 24     24   52 my ($o, $data, $symbol, $type) = @_;
267 24 50       4147 $data =~ /^(([\w:]+)=)?$type\(0x([0-9a-f]+)\)$/
268             or croak "Invalid reference: $data, for type $type\n";
269 24         167 my $stream = $symbol;
270 24 100       70 $stream .= $2 if defined $2;
271 24         92 $o->{xref}{$3}++;
272 24 100       83 if ($o->{xref}{$3} > 1) {
273 5         16 $stream .= "(*$3)\n";
274 5 50       17 $stream .= $o->{key}, $o->{key} = '' if $o->{key};
275 5         13 return \$stream;
276             }
277 19         21 push @{$o->{refs}}, $3;
  19         64  
278 19         98 $stream .= "($3)\n";
279 19 50       48 $stream .= $o->{key}, $o->{key} = '' if $o->{key};
280 19         82 return $stream;
281             }
282              
283             sub _resolve {
284 10     10   19 my ($o, $stream_ref) = @_;
285 10         15 my $ref_label = 'REF00000';
286 10         31 local $^W;
287 10         22 for my $ref (@{$o->{refs}}) {
  10         28  
288 19 100       59 if ($o->{xref}{$ref} == 1) {
289 14         509 $$stream_ref =~ s/(?:(\\)\($ref\)([\\\%\@\$])|\($ref\)\s*$)/$1$2/m;
290             }
291             else {
292 5         10 $ref_label++;
293 5         11 local $^W;
294 5         162 $$stream_ref =~
295             s/(?:(\\)\($ref\)([\\\%\@\$])|\($ref\)\s*$)/$1($ref_label)$2/m;
296 5         13 my $i = 0;
297 5         73 $$stream_ref =~
298 5         31 s/\(\*$ref\)$/ "(*$ref_label" . '-' . ++$i . ')' /gem;
299            
300             }
301             }
302 10 100       65 $$stream_ref .= "\n" unless $$stream_ref =~ /\n\Z/;
303             }
304              
305             sub undent {
306 10     10 0 41 local $/ = "\n";
307 10         19 my ($o, $text) = @_;
308 10         19 my ($comma) = $o->{comma};
309 10 50       24 croak invalid_usage('undent') unless $o->{__DATA__DENTER__};
310 10         20 my $package = caller;
311 10 50       33 $package = caller(1) if $package eq 'Data::Denter';
312 10         13 %{$o->{xref}} = ();
  10         29  
313 10         13 @{$o->{objects}} = ();
  10         23  
314 10         13 @{$o->{context}} = ();
  10         23  
315 10         15 my $glob = '';
316 10         19 chomp $text;
317 10         47 @{$o->{lines}} = split $/, $text;
  10         33  
318 10         20 $o->{level} = 0;
319 10   50     50 $o->{line} ||= 1;
320 10         27 $o->_setup_line;
321 10         27 while (not $o->{done}) {
322 11 100 66     110 if ($o->{level} == 0 and
323             $o->{content} =~ /^(.+?)\s*$comma\s*(.*)$/) {
324 3         8 $o->{content} = $2;
325 1     1   12 no strict 'refs';
  1         2  
  1         5341  
326 3         13 push @{$o->{objects}},
  1         8  
327 3 100       5 $o->{hashmode} ? $1 : *{"${package}::$1"};
328             }
329 11         15 push @{$o->{objects}}, $o->_undent_data;
  11         38  
330             }
331 10 100       22 return wantarray ? @{$o->{objects}} : ${$o->{objects}}[-1];
  9         71  
  1         8  
332             }
333              
334             sub _undent_data {
335 23     23   29 my $o = shift;
336 23         35 my ($obj, $class) = ('god', '');
337 23         25 my @refs;
338             my %refs;
339 23         57 local $^W;
340 23         108 while ($o->{content} =~ s/^\\(?:\((\w+)\))?((\%|\@|\$|\\\(\*|\\).*)/$2/) {
341 10         24 push @refs, $1;
342 10         24 $refs{$1} = scalar @refs;
343 10 100       58 last if $3 eq '\\(*';
344             }
345 23 100       130 if ($o->{content} =~ /^([\%\@\$])
    100          
    50          
346             (\w(?:\w|::)*)?
347             (?:\((\*)?(\w+)(?:-\d+)?\))?
348             \s*$/x
349             ) {
350 10         13 my $foo;
351 10 100       41 $obj = ($1 eq '%') ? {} : ($1 eq '@') ? [] : \$foo;
    100          
352 10   100     47 $class = $2 || '';
353 10 100       25 if ($3) {
354 1 50       5 croak no_such_ref($4) unless defined $o->{xref}{$4};
355 1         3 $obj = $o->{xref}{$4};
356 1         5 $o->_next_line;
357 1         12 $o->_setup_line;
358             }
359             else {
360 9         29 $o->{xref}{$4} = $obj;
361 9 100       32 if ($1 eq '%') {
    100          
362 3         16 %$obj = $o->_undent_hash;
363             }
364             elsif ($1 eq '@') {
365 4         15 @$obj = $o->_undent_array;
366             }
367             else {
368 2         10 $$obj = $o->_undent_scalar;
369             }
370 9 100       32 bless $obj, $class if length $class;
371             }
372             }
373             elsif ($o->{content} =~ /^\\\(\*(\w+)-\d+\)\s*$/
374             ) {
375 4         6 my $refs = @refs;
376 4         13 while (@refs) {
377 4         7 my $ref = pop @refs;
378 4         5 my $copy = $obj;
379 4         7 $obj = \ $copy;
380 4 100       19 $o->{xref}{$ref} = $obj if $ref;
381             }
382 4 50       15 croak no_such_ref($1) unless defined $o->{xref}{$1};
383 4         276 eval("\$" x $refs . '$obj = $o->{xref}{$1}');
384 4         19 $o->_next_line;
385 4         10 $o->_setup_line;
386             }
387             elsif ($o->{content} =~ /^\?\s*$/) {
388 0         0 $obj = $o->_undent_undef;
389             }
390             else {
391 9         21 $obj = $o->_undent_value;
392             }
393 23         55 while (@refs) {
394 6         10 my $ref = pop @refs;
395 6         9 my $copy = $obj;
396 6         9 $obj = \ $copy;
397 6 100       22 $o->{xref}{$ref} = $obj if $ref;
398             }
399 23         127 return $obj;
400             }
401              
402             sub _undent_value {
403 10     10   15 my $o = shift;
404 10         15 my $value = '';
405 10 100       43 if ($o->{content} =~ /^\<\<(\w+)(\-?)\s*$/) {
    100          
406 1         4 my ($marker, $chomp) = ($1, $2);
407 1         3 my $line = $o->{line};
408 1         5 $o->_next_line;
409 1   66     11 while (not $o->{done} and
410             $o->{lines}[0] ne $marker) {
411 3         8 $value .= $o->{lines}[0] . "\n";
412 3         9 $o->_next_line;
413             }
414 1 50       5 croak no_value_end_marker($marker, $line) if $o->{done};
415 1         6 $value =~ s/(%([0-9a-fA-F]{2}))/pack("H2","$2")/eg;
  2         11  
416 1 50       6 chomp $value if $chomp;
417             }
418             elsif ($o->{content} =~ /^\"/) {
419 1 50       10 croak $o->mismatched_quotes unless $o->{content} =~ /^\".*\"\s*$/;
420 1         10 ($value = $o->{content}) =~ s/^\"|\"\s*$//g;
421 1         7 $value =~ s/(%([0-9a-fA-F]{2}))/pack("H2","$2")/eg;
  3         19  
422             }
423             else {
424 8         17 $value = $o->{content};
425             }
426 10         23 $o->_next_line;
427 10         21 $o->_setup_line;
428 10         28 return $value;
429             }
430              
431             sub _undent_hash {
432 3     3   6 my @values;
433 3         5 my $o = shift;
434 3         7 my $level = $o->{level} + 1;
435 3         11 $o->_next_line;
436 3         9 $o->_setup_line;
437 3         12 while ($o->{level} == $level) {
438 2         21 my ($key, $value) = split $o->{comma}, $o->{content};
439 2 50 33     14 croak $o->invalid_key_value unless (defined $key and defined $value);
440 2         4 $o->{content} = $value;
441 2         11 push @values, $o->_get_key($key), $o->_undent_data;;
442             }
443 3 50       9 croak $o->invalid_indent_level if $o->{level} > $level;
444 3         11 return @values;
445             }
446              
447             sub _get_key {
448 2     2   5 my ($o, $key) = @_;
449 2         6 $key =~ s/^"(.*)"$/$1/;
450 2 50       18 return $key unless $key =~ /^\<\<(\w+)(\-?)/;
451 0         0 my ($marker, $chomp) = ($1, $2);
452 0         0 $key = '';
453 0         0 my $line = $o->{line};
454 0         0 $o->_next_line;
455 0   0     0 while (not $o->{done} and
456             $o->{lines}[0] ne $marker) {
457 0         0 $key .= $o->{lines}[0] . "\n";
458 0         0 $o->_next_line;
459             }
460 0 0       0 croak no_key_end_marker($marker, $line) if $o->{done};
461 0 0       0 chomp $key if $chomp;
462 0         0 $o->_next_line;
463 0         0 $o->_setup_line;
464 0         0 return $key;
465             }
466              
467             sub _undent_array {
468 4     4   5 my @values;
469 4         7 my $o = shift;
470 4         11 my $level = $o->{level} + 1;
471 4         12 $o->_next_line;
472 4         10 $o->_setup_line;
473 4         41 while ($o->{level} == $level) {
474 10         26 push @values, $o->_undent_data;
475             }
476 4 50       15 croak $o->invalid_indent_level if $o->{level} > $level;
477 4         16 return @values;
478             }
479              
480             sub _undent_scalar {
481 2     2   3 my $values;
482 2         5 my $o = shift;
483 2         5 my $level = $o->{level} + 1;
484 2         6 $o->_next_line;
485 2         7 $o->_setup_line;
486 2 50       9 croak $o->invalid_indent_level if $o->{level} != $level;
487 2 50       9 croak $o->invalid_scalar_value if $o->{content} =~ /^[\%\@\$\\]/;
488 2 100       16 return $o->_undent_undef if $o->{content} =~ /^\?/;
489 1         4 return $o->_undent_value;
490             }
491              
492             sub _undent_undef {
493 1     1   3 my $o = shift;
494 1         4 $o->_next_line;
495 1         4 $o->_setup_line;
496 1         3 return undef;
497             }
498              
499             sub _next_line {
500 29     29   38 my $o = shift;
501 29 50       35 $o->{done}++, $o->{level} = -1, return unless @{$o->{lines}};
  29         78  
502 29         35 $_ = shift @{$o->{lines}};
  29         57  
503 29         72 $o->{line}++;
504             }
505              
506             sub _setup_line {
507 35     35   44 my $o = shift;
508 35 100       40 $o->{done}++, $o->{level} = -1, return unless @{$o->{lines}};
  35         145  
509 25         32 my ($width, $tabwidth) = @{$o}{qw(width tabwidth)};
  25         60  
510 25         31 while (1) {
511 25         47 $_ = $o->{lines}[0];
512             # expand tabs in leading whitespace;
513 25 50       87 $o->_next_line, next if /^(\s*$|\#)/; # skip comments and blank lines
514 25         68 while (s{^( *)(\t+)}
515 0         0 {' ' x (length($1) + length($2) * $tabwidth -
516             length($1) % $tabwidth)}e){}
517 25 50       231 croak $o->invalid_indent_width unless /^(( {$width})*)(\S.*)$/;
518 25         80 $o->{level} = length($1) / $width;
519 25         56 $o->{content} = $3;
520 25         49 last;
521             }
522             }
523              
524             1;
525              
526             __END__