File Coverage

blib/lib/YAML/As/Parsed.pm
Criterion Covered Total %
statement 314 348 90.5
branch 168 202 83.1
condition 37 42 88.1
subroutine 34 37 91.8
pod 0 11 0.0
total 553 640 86.5


line stmt bran cond sub pod time code
1 11     11   1289483 use 5.008001; # sane UTF-8 support
  11         48  
2 11     11   89 use strict;
  11         71  
  11         378  
3 11     11   65 use warnings;
  11         29  
  11         941  
4             package YAML::As::Parsed; # git description: v1.72-7-g8682f63
5              
6             our $VERSION = '0.08';
7              
8 11     11   6000 use Tie::IxHash;
  11         58127  
  11         915  
9              
10             sub ordered_hash {
11 424     424 0 851 my %hash = ();
12 424         2248 tie(%hash, 'Tie::IxHash');
13 424         8444 return \%hash;
14             }
15              
16 11     11   89 use Exporter;
  11         22  
  11         2150  
17             our @ISA = qw{ Exporter };
18             our @EXPORT = qw{ Load Dump };
19             our @EXPORT_OK = qw{ LoadFile DumpFile freeze thaw };
20              
21             sub Dump {
22 159     159 0 318612 return __PACKAGE__->new(@_)->_dump_string;
23             }
24              
25             sub Load {
26 112     112 0 358815 my $self = __PACKAGE__->_load_string(@_);
27 112 100       223 if ( wantarray ) {
28 53         174 return @$self;
29             } else {
30             # To match YAML.pm, return the last document
31 59         225 return $self->[-1];
32             }
33             }
34              
35             BEGIN {
36 11     11   48 *freeze = \&Dump;
37 11         10775 *thaw = \&Load;
38             }
39              
40             sub DumpFile {
41 0     0 0 0 my $file = shift;
42 0         0 return __PACKAGE__->new(@_)->_dump_file($file);
43             }
44              
45             sub LoadFile {
46 0     0 0 0 my $file = shift;
47 0         0 my $self = __PACKAGE__->_load_file($file);
48 0 0       0 if ( wantarray ) {
49 0         0 return @$self;
50             } else {
51             # Return only the last document to match YAML.pm,
52 0         0 return $self->[-1];
53             }
54             }
55              
56             sub new {
57 170     170 0 725606 my $class = shift;
58 170         795 bless [ @_ ], $class;
59             }
60              
61              
62             sub read_string {
63 221     221 0 2221951 my $self = shift;
64 221         883 $self->_load_string(@_);
65             }
66              
67             sub write_string {
68 105     105 0 627480 my $self = shift;
69 105         568 $self->_dump_string(@_);
70             }
71              
72             sub read {
73 14     14 0 186025 my $self = shift;
74 14         62 $self->_load_file(@_);
75             }
76              
77             sub write {
78 4     4 0 12 my $self = shift;
79 4         20 $self->_dump_file(@_);
80             }
81              
82             my @UNPRINTABLE = qw(
83             0 x01 x02 x03 x04 x05 x06 a
84             b t n v f r x0E x0F
85             x10 x11 x12 x13 x14 x15 x16 x17
86             x18 x19 x1A e x1C x1D x1E x1F
87             );
88              
89             my %UNESCAPES = (
90             0 => "\x00", z => "\x00", N => "\x85",
91             a => "\x07", b => "\x08", t => "\x09",
92             n => "\x0a", v => "\x0b", f => "\x0c",
93             r => "\x0d", e => "\x1b", '\\' => '\\',
94             );
95              
96              
97             my %QUOTE = map { $_ => 1 } qw{
98             null true false
99             };
100              
101             my $re_capture_double_quoted = qr/\"([^\\"]*(?:\\.[^\\"]*)*)\"/;
102             my $re_capture_single_quoted = qr/\'([^\']*(?:\'\'[^\']*)*)\'/;
103             my $re_capture_unquoted_key = qr/([^:]+(?::+\S(?:[^:]*|.*?(?=:)))*)(?=\s*\:(?:\s+|$))/;
104             my $re_trailing_comment = qr/(?:\s+\#.*)?/;
105             my $re_key_value_separator = qr/\s*:(?:\s+(?:\#.*)?|$)/;
106              
107             sub _load_file {
108 14 100   14   38 my $class = ref $_[0] ? ref shift : shift;
109              
110             # Check the file
111 14 100       38 my $file = shift or $class->_error( 'You did not specify a file name' );
112 13 100       308 $class->_error( "File '$file' does not exist" )
113             unless -e $file;
114 12 100       58 $class->_error( "'$file' is a directory, not a file" )
115             unless -f _;
116 11 50       51 $class->_error( "Insufficient permissions to read '$file'" )
117             unless -r _;
118              
119             # Open unbuffered with strict UTF-8 decoding and no translation layers
120 11     2   511 open( my $fh, "<:unix:encoding(UTF-8)", $file );
  2         5073  
  2         36  
  2         12  
121 11 50       1676 unless ( $fh ) {
122 0         0 $class->_error("Failed to open file '$file': $!");
123             }
124              
125             # flock if available (or warn if not possible for OS-specific reasons)
126 11 50       36 if ( _can_flock() ) {
127 11 50       100 flock( $fh, Fcntl::LOCK_SH() )
128             or warn "Couldn't lock '$file' for reading: $!";
129             }
130              
131             # slurp the contents
132 11         20 my $contents = eval {
133 11     11   96 use warnings FATAL => 'utf8';
  11         20  
  11         69531  
134 11         44 local $/;
135             <$fh>
136 11         642 };
137 11 100       207 if ( my $err = $@ ) {
138 2         17 $class->_error("Error reading from file '$file': $err");
139             }
140              
141             # close the file (release the lock)
142 9 50       168 unless ( close $fh ) {
143 0         0 $class->_error("Failed to close file '$file': $!");
144             }
145              
146 9         63 $class->_load_string( $contents );
147             }
148              
149             sub _load_string {
150 342 100   342   1005 my $class = ref $_[0] ? ref shift : shift;
151 342         966 my $self = bless [], $class;
152 342         665 my $string = $_[0];
153 342         559 eval {
154 342 100       985 unless ( defined $string ) {
155 1         7 die \"Did not provide a string to load";
156             }
157              
158             # Check if Perl has it marked as characters, but it's internally
159             # inconsistent. E.g. maybe latin1 got read on a :utf8 layer
160 341 100 100     1912 if ( utf8::is_utf8($string) && ! utf8::valid($string) ) {
161 1         6 die \<<'...';
162             Read an invalid UTF-8 string (maybe mixed UTF-8 and 8-bit character set).
163             Did you decode with lax ":utf8" instead of strict ":encoding(UTF-8)"?
164             ...
165             }
166              
167             # Ensure Unicode character semantics, even for 0x80-0xff
168 340         941 utf8::upgrade($string);
169              
170             # Check for and strip any leading UTF-8 BOM
171 340         901 $string =~ s/^\x{FEFF}//;
172              
173             # Check for some special cases
174 340 100       845 return $self unless length $string;
175              
176             # Split the file into lines
177 336         11899 my @lines = grep { ! /^\s*(?:\#.*)?\z/ }
  1469         6236  
178             split /(?:\015{1,2}\012|\015|\012)/, $string;
179              
180             # Strip the initial YAML header
181 336 100 100     1949 @lines and $lines[0] =~ /^\%YAML[: ][\d\.]+.*\z/ and shift @lines;
182              
183             # A nibbling parser
184 336         600 my $in_document = 0;
185 336         887 while ( @lines ) {
186             # Do we have a document header?
187 356 100       1584 if ( $lines[0] =~ /^---\s*(?:(.+)\s*)?\z/ ) {
188             # Handle scalar documents
189 275         484 shift @lines;
190 275 100 100     1171 if ( defined $1 and $1 !~ /^(?:\#.+|\%YAML[: ][\d\.]+)\z/ ) {
191 41         231 push @$self,
192             $self->_load_scalar( "$1", [ undef ], \@lines );
193 41         187 next;
194             }
195 234         400 $in_document = 1;
196             }
197              
198 315 100 100     3056 if ( ! @lines or $lines[0] =~ /^(?:---|\.\.\.)/ ) {
    100 100        
    100          
    50          
199             # A naked document
200 12         27 push @$self, undef;
201 12   66     45 while ( @lines and $lines[0] !~ /^---/ ) {
202 0         0 shift @lines;
203             }
204 12         28 $in_document = 0;
205              
206             # XXX The final '-+$' is to look for -- which ends up being an
207             # error later.
208             } elsif ( ! $in_document && @$self ) {
209             # only the first document can be explicit
210 2         15 die \"YAML::As::Parsed failed to classify the line '$lines[0]'";
211             } elsif ( $lines[0] =~ /^\s*\-(?:\s|$|-+$)/ ) {
212             # An array at the root
213 65         116 my $document = [ ];
214 65         151 push @$self, $document;
215 65         270 $self->_load_array( $document, [ 0 ], \@lines );
216              
217             } elsif ( $lines[0] =~ /^(\s*)\S/ ) {
218             # A hash at the root
219 236         685 my $document = ordered_hash;
220 236         540 push @$self, $document;
221 236         1210 $self->_load_hash( $document, [ length($1) ], \@lines );
222              
223             } else {
224             # Shouldn't get here. @lines have whitespace-only lines
225             # stripped, and previous match is a line with any
226             # non-whitespace. So this clause should only be reachable via
227             # a perlbug where \s is not symmetric with \S
228              
229             # uncoverable statement
230 0         0 die \"YAML::As::Parsed failed to classify the line '$lines[0]'";
231             }
232             }
233             };
234 342         760 my $err = $@;
235 342 100       1121 if ( ref $err eq 'SCALAR' ) {
    50          
236 14         34 $self->_error(${$err});
  14         66  
237             } elsif ( $err ) {
238 0         0 $self->_error($err);
239             }
240              
241 328         1175 return $self;
242             }
243              
244             sub _unquote_single {
245 230     230   774 my ($self, $string) = @_;
246 230 100       575 return '' unless length $string;
247 225         477 $string =~ s/\'\'/\'/g;
248 225         1125 return $string;
249             }
250              
251             sub _unquote_double {
252 87     87   318 my ($self, $string) = @_;
253 87 100       267 return '' unless length $string;
254 86         210 $string =~ s/\\"/"/g;
255 86         463 $string =~
256             s{\\([Nnever\\fartz0b]|x([0-9a-fA-F]{2}))}
257 74 100       449 {(length($1)>1)?pack("H2",$2):$UNESCAPES{$1}}gex;
258 86         495 return $string;
259             }
260              
261             sub _load_scalar {
262 943     943   2237 my ($self, $string, $indent, $lines) = @_;
263              
264             # Trim trailing whitespace
265 943         5608 $string =~ s/\s*\z//;
266              
267             # Explitic null/undef
268 943 100       2658 return undef if $string eq '~';
269              
270             # Single quote
271 898 100       5826 if ( $string =~ /^$re_capture_single_quoted$re_trailing_comment\z/ ) {
272 183         486 return $self->_unquote_single($1);
273             }
274              
275             # Double quote.
276 715 100       3620 if ( $string =~ /^$re_capture_double_quoted$re_trailing_comment\z/ ) {
277 58         173 return $self->_unquote_double($1);
278             }
279              
280             # Special cases
281 657 100       1856 if ( $string =~ /^[\'\"!&]/ ) {
282 2         21 die \"YAML::As::Parsed does not support a feature in line '$string'";
283             }
284 655 100       1604 return {} if $string =~ /^{}(?:\s+\#.*)?\z/;
285 645 100       1508 return [] if $string =~ /^\[\](?:\s+\#.*)?\z/;
286              
287             # Regular unquoted string
288 635 100       1603 if ( $string !~ /^[>|]/ ) {
289 623 100 100     3623 die \"YAML::As::Parsed found illegal characters in plain scalar: '$string'"
290             if $string =~ /^(?:-(?:\s|$)|[\@\%\`])/ or
291             $string =~ /:(?:\s|$)/;
292 615         1134 $string =~ s/\s+#.*\z//;
293 615         3433 return $string;
294             }
295              
296             # Error
297 12 50       44 die \"YAML::As::Parsed failed to find multi-line scalar content" unless @$lines;
298              
299             # Check the indent depth
300 12         44 $lines->[0] =~ /^(\s*)/;
301 12         54 $indent->[-1] = length("$1");
302 12 50 33     77 if ( defined $indent->[-2] and $indent->[-1] <= $indent->[-2] ) {
303 0         0 die \"YAML::As::Parsed found bad indenting in line '$lines->[0]'";
304             }
305              
306             # Pull the lines
307 12         30 my @multiline = ();
308 12         56 while ( @$lines ) {
309 34         103 $lines->[0] =~ /^(\s*)/;
310 34 100       108 last unless length($1) >= $indent->[-1];
311 27         119 push @multiline, substr(shift(@$lines), $indent->[-1]);
312             }
313              
314 12 100       59 my $j = (substr($string, 0, 1) eq '>') ? ' ' : "\n";
315 12 100       51 my $t = (substr($string, 1, 1) eq '-') ? '' : "\n";
316 12         108 return join( $j, @multiline ) . $t;
317             }
318              
319             sub _load_array {
320 111     111   358 my ($self, $array, $indent, $lines) = @_;
321              
322 111         616 while ( @$lines ) {
323             # Check for a new document
324 282 100       949 if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
325 13   100     68 while ( @$lines and $lines->[0] !~ /^---/ ) {
326 5         19 shift @$lines;
327             }
328 13         41 return 1;
329             }
330              
331             # Check the indent level
332 269         785 $lines->[0] =~ /^(\s*)/;
333 269 100       1188 if ( length($1) < $indent->[-1] ) {
    50          
334 26         163 return 1;
335             } elsif ( length($1) > $indent->[-1] ) {
336 0         0 die \"YAML::As::Parsed found bad indenting in line '$lines->[0]'";
337             }
338              
339 243 100 33     2252 if ( $lines->[0] =~ /^(\s*\-\s+)[^\'\"]\S*\s*:(?:\s+|$)/ ) {
    100          
    100          
    50          
340             # Inline nested hash
341 32         93 my $indent2 = length("$1");
342 32         146 $lines->[0] =~ s/-/ /;
343 32         115 push @$array, ordered_hash;
344 32         158 $self->_load_hash( $array->[-1], [ @$indent, $indent2 ], $lines );
345              
346             } elsif ( $lines->[0] =~ /^\s*\-\s*\z/ ) {
347 38         77 shift @$lines;
348 38 100       121 unless ( @$lines ) {
349 2         5 push @$array, undef;
350 2         14 return 1;
351             }
352 36 100       169 if ( $lines->[0] =~ /^(\s*)\-/ ) {
    50          
353 4         16 my $indent2 = length("$1");
354 4 50       18 if ( $indent->[-1] == $indent2 ) {
355             # Null array entry
356 4         15 push @$array, undef;
357             } else {
358             # Naked indenter
359 0         0 push @$array, [ ];
360 0         0 $self->_load_array(
361             $array->[-1], [ @$indent, $indent2 ], $lines
362             );
363             }
364              
365             } elsif ( $lines->[0] =~ /^(\s*)\S/ ) {
366 32         86 push @$array, ordered_hash;
367 32         163 $self->_load_hash(
368             $array->[-1], [ @$indent, length("$1") ], $lines
369             );
370              
371             } else {
372 0         0 die \"YAML::As::Parsed failed to classify line '$lines->[0]'";
373             }
374              
375             } elsif ( $lines->[0] =~ /^\s*\-(\s*)(.+?)\s*\z/ ) {
376             # Array entry with a value
377 169         305 shift @$lines;
378 169         802 push @$array, $self->_load_scalar(
379             "$2", [ @$indent, undef ], $lines
380             );
381              
382             } elsif ( defined $indent->[-2] and $indent->[-1] == $indent->[-2] ) {
383             # This is probably a structure like the following...
384             # ---
385             # foo:
386             # - list
387             # bar: value
388             #
389             # ... so lets return and let the hash parser handle it
390 4         19 return 1;
391              
392             } else {
393 0         0 die \"YAML::As::Parsed failed to classify line '$lines->[0]'";
394             }
395             }
396              
397 63         254 return 1;
398             }
399              
400             sub _load_hash {
401 424     424   1207 my ($self, $hash, $indent, $lines) = @_;
402              
403 424         1992 while ( @$lines ) {
404             # Check for a new document
405 1072 100       12178 if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
406 3   100     17 while ( @$lines and $lines->[0] !~ /^---/ ) {
407 1         4 shift @$lines;
408             }
409 3         13 return 1;
410             }
411              
412             # Check the indent level
413 1069         2799 $lines->[0] =~ /^(\s*)/;
414 1069 100       4269 if ( length($1) < $indent->[-1] ) {
    50          
415 159         688 return 1;
416             } elsif ( length($1) > $indent->[-1] ) {
417 0         0 die \"YAML::As::Parsed found bad indenting in line '$lines->[0]'";
418             }
419              
420             # Find the key
421 910         1434 my $key;
422              
423             # Quoted keys
424 910 100       16812 if ( $lines->[0] =~
    100          
    50          
    0          
425             s/^\s*$re_capture_single_quoted$re_key_value_separator//
426             ) {
427 47         132 $key = $self->_unquote_single($1);
428             }
429             elsif ( $lines->[0] =~
430             s/^\s*$re_capture_double_quoted$re_key_value_separator//
431             ) {
432 29         103 $key = $self->_unquote_double($1);
433             }
434             elsif ( $lines->[0] =~
435             s/^\s*$re_capture_unquoted_key$re_key_value_separator//
436             ) {
437 834         2169 $key = $1;
438 834         2180 $key =~ s/\s+$//;
439             }
440             elsif ( $lines->[0] =~ /^\s*\?/ ) {
441 0         0 die \"YAML::As::Parsed does not support a feature in line '$lines->[0]'";
442             }
443             else {
444 0         0 die \"YAML::As::Parsed failed to classify line '$lines->[0]'";
445             }
446              
447 910 100       3758 if ( exists $hash->{$key} ) {
448 1         22 warn "YAML::As::Parsed found a duplicate key '$key' in line '$lines->[0]'";
449             }
450              
451             # Do we have a value?
452 910 100       5727 if ( length $lines->[0] ) {
453             # Yes
454 733         2520 $hash->{$key} = $self->_load_scalar(
455             shift(@$lines), [ @$indent, undef ], $lines
456             );
457             } else {
458             # An indent
459 177         313 shift @$lines;
460 177 100       482 unless ( @$lines ) {
461 1         3 $hash->{$key} = undef;
462 1         12 return 1;
463             }
464 176 100       903 if ( $lines->[0] =~ /^(\s*)-/ ) {
    50          
465 46         220 $hash->{$key} = [];
466             $self->_load_array(
467 46         1072 $hash->{$key}, [ @$indent, length($1) ], $lines
468             );
469             } elsif ( $lines->[0] =~ /^(\s*)./ ) {
470 130         374 my $indent2 = length("$1");
471 130 100       347 if ( $indent->[-1] >= $indent2 ) {
472             # Null hash entry
473 6         20 $hash->{$key} = undef;
474             } else {
475 124         272 $hash->{$key} = ordered_hash;
476             $self->_load_hash(
477 124         2824 $hash->{$key}, [ @$indent, length($1) ], $lines
478             );
479             }
480             }
481             }
482             }
483              
484 254         4979 return 1;
485             }
486              
487              
488              
489             sub _dump_file {
490 4     4   18 my $self = shift;
491              
492 4         35 require Fcntl;
493              
494             # Check the file
495 4 100       22 my $file = shift or $self->_error( 'You did not specify a file name' );
496              
497 3         6 my $fh;
498             # flock if available (or warn if not possible for OS-specific reasons)
499 3 50       13 if ( _can_flock() ) {
500             # Open without truncation (truncate comes after lock)
501 3         7 my $flags = Fcntl::O_WRONLY()|Fcntl::O_CREAT();
502 3 50       505 sysopen( $fh, $file, $flags )
503             or $self->_error("Failed to open file '$file' for writing: $!");
504              
505             # Use no translation and strict UTF-8
506 3         82 binmode( $fh, ":raw:encoding(UTF-8)");
507              
508 3 50       1469 flock( $fh, Fcntl::LOCK_EX() )
509             or warn "Couldn't lock '$file' for reading: $!";
510              
511             # truncate and spew contents
512 3         175 truncate $fh, 0;
513 3         29 seek $fh, 0, 0;
514             }
515             else {
516 0         0 open $fh, ">:unix:encoding(UTF-8)", $file;
517             }
518              
519             # serialize and spew to the handle
520 3         9 print {$fh} $self->_dump_string;
  3         20  
521              
522             # close the file (release the lock)
523 3 50       756 unless ( close $fh ) {
524 0         0 $self->_error("Failed to close file '$file': $!");
525             }
526              
527 3         50 return 1;
528             }
529              
530             sub _dump_string {
531 267     267   535 my $self = shift;
532 267 100 100     1647 return '' unless ref $self && @$self;
533              
534             # Iterate over the documents
535 263         478 my $indent = 0;
536 263         508 my @lines = ();
537              
538 263         499 eval {
539 263         677 foreach my $cursor ( @$self ) {
540 272         619 push @lines, '---';
541              
542             # An empty document
543 272 100       1574 if ( ! defined $cursor ) {
    100          
    100          
    50          
544             # Do nothing
545              
546             # A scalar document
547             } elsif ( ! ref $cursor ) {
548 17         89 $lines[-1] .= ' ' . $self->_dump_scalar( $cursor );
549              
550             # A list at the root
551             } elsif ( ref $cursor eq 'ARRAY' ) {
552 31 100       100 unless ( @$cursor ) {
553 1         2 $lines[-1] .= ' []';
554 1         3 next;
555             }
556 30         139 push @lines, $self->_dump_array( $cursor, $indent, {} );
557              
558             # A hash at the root
559             } elsif ( ref $cursor eq 'HASH' ) {
560 217 100       611 unless ( %$cursor ) {
561 1         3 $lines[-1] .= ' {}';
562 1         2 next;
563             }
564 216         1257 push @lines, $self->_dump_hash( $cursor, $indent, {} );
565              
566             } else {
567 0         0 die \("Cannot serialize " . ref($cursor));
568             }
569             }
570             };
571 263 100       1045 if ( ref $@ eq 'SCALAR' ) {
    50          
572 1         2 $self->_error(${$@});
  1         6  
573             } elsif ( $@ ) {
574 0         0 $self->_error($@);
575             }
576              
577 262         718 join '', map { "$_\n" } @lines;
  922         3220  
578             }
579              
580             sub _has_internal_string_value {
581 1085     1085   1816 my $value = shift;
582 1085         3534 my $b_obj = B::svref_2object(\$value); # for round trip problem
583 1085         3703 return $b_obj->FLAGS & B::SVf_POK();
584             }
585              
586             sub _dump_scalar {
587 1085     1085   1732 my $string = $_[1];
588 1085         1726 my $is_key = $_[2];
589             # Check this before checking length or it winds up looking like a string!
590 1085         2109 my $has_string_flag = _has_internal_string_value($string);
591 1085 100       2517 return '~' unless defined $string;
592 1061 100       2330 return "''" unless length $string;
593 1058 100       3216 if (Scalar::Util::looks_like_number($string)) {
594             # keys and values that have been used as strings get quoted
595 80 100 100     323 if ( $is_key || $has_string_flag ) {
596 56         175 return qq['$string'];
597             }
598             else {
599 24         75 return $string;
600             }
601             }
602 978 100       2986 if ( $string =~ /[\x00-\x09\x0b-\x0d\x0e-\x1f\x7f-\x9f\'\n]/ ) {
603 98         251 $string =~ s/\\/\\\\/g;
604 98         180 $string =~ s/"/\\"/g;
605 98         219 $string =~ s/\n/\\n/g;
606 98         187 $string =~ s/[\x85]/\\N/g;
607 98         525 $string =~ s/([\x00-\x1f])/\\$UNPRINTABLE[ord($1)]/g;
608 98         260 $string =~ s/([\x7f-\x9f])/'\x' . sprintf("%X",ord($1))/ge;
  12         66  
609 98         388 return qq|"$string"|;
610             }
611 880 100 100     6358 if ( $string =~ /(?:^[~!@#%&*|>?:,'"`{}\[\]]|^-+$|\s|:\z)/ or
612             $QUOTE{$string}
613             ) {
614 191         626 return "'$string'";
615             }
616 689         1813 return $string;
617             }
618              
619             sub _dump_array {
620 53     53   182 my ($self, $array, $indent, $seen) = @_;
621 53 50       365 if ( $seen->{refaddr($array)}++ ) {
622 0         0 die \"YAML::As::Parsed does not support circular references";
623             }
624 53         134 my @lines = ();
625 53         188 foreach my $el ( @$array ) {
626 117         278 my $line = (' ' x $indent) . '-';
627 117         217 my $type = ref $el;
628 117 100       315 if ( ! $type ) {
    100          
    50          
629 84         219 $line .= ' ' . $self->_dump_scalar( $el );
630 84         248 push @lines, $line;
631              
632             } elsif ( $type eq 'ARRAY' ) {
633 1 50       6 if ( @$el ) {
634 0         0 push @lines, $line;
635 0         0 push @lines, $self->_dump_array( $el, $indent + 1, $seen );
636             } else {
637 1         2 $line .= ' []';
638 1         3 push @lines, $line;
639             }
640              
641             } elsif ( $type eq 'HASH' ) {
642 32 100       84 if ( keys %$el ) {
643 31         70 push @lines, $line;
644 31         93 push @lines, $self->_dump_hash( $el, $indent + 1, $seen );
645             } else {
646 1         4 $line .= ' {}';
647 1         3 push @lines, $line;
648             }
649              
650             } else {
651 0         0 die \"YAML::As::Parsed does not support $type references";
652             }
653             }
654              
655 51         368 @lines;
656             }
657              
658             sub _dump_hash {
659 308     308   852 my ($self, $hash, $indent, $seen) = @_;
660 308 100       1757 if ( $seen->{refaddr($hash)}++ ) {
661 1         11 die \"YAML::As::Parsed does not support circular references";
662             }
663 307         532 my @lines = ();
664 307         1210 foreach my $name ( sort keys %$hash ) {
665 538         1833 my $el = $hash->{$name};
666 538         1820 my $line = (' ' x $indent) . $self->_dump_scalar($name, 1) . ":";
667 538         1143 my $type = ref $el;
668 538 100       1267 if ( ! $type ) {
    100          
    50          
669 448         963 $line .= ' ' . $self->_dump_scalar( $el );
670 448         1330 push @lines, $line;
671              
672             } elsif ( $type eq 'ARRAY' ) {
673 26 100       93 if ( @$el ) {
674 23         60 push @lines, $line;
675 23         103 push @lines, $self->_dump_array( $el, $indent + 1, $seen );
676             } else {
677 3         8 $line .= ' []';
678 3         10 push @lines, $line;
679             }
680              
681             } elsif ( $type eq 'HASH' ) {
682 64 100       158 if ( keys %$el ) {
683 61         151 push @lines, $line;
684 61         220 push @lines, $self->_dump_hash( $el, $indent + 1, $seen );
685             } else {
686 3         6 $line .= ' {}';
687 3         11 push @lines, $line;
688             }
689              
690             } else {
691 0         0 die \"YAML::As::Parsed does not support $type references";
692             }
693             }
694              
695 306         1765 @lines;
696             }
697              
698             our $errstr = '';
699              
700             sub _error {
701 21     21   213 require Carp;
702 21         49 $errstr = $_[1];
703 21         98 $errstr =~ s/ at \S+ line \d+.*//;
704 21         4383 Carp::croak( $errstr );
705             }
706              
707             my $errstr_warned;
708             sub errstr {
709 0     0 0 0 require Carp;
710 0 0       0 Carp::carp( "YAML::As::Parsed->errstr and \$YAML::As::Parsed::errstr is deprecated" )
711             unless $errstr_warned++;
712 0         0 $errstr;
713             }
714              
715 11     11   168 use B;
  11         65  
  11         1693  
716              
717             my $HAS_FLOCK;
718             sub _can_flock {
719 14 100   14   42 if ( defined $HAS_FLOCK ) {
720 12         33 return $HAS_FLOCK;
721             }
722             else {
723 2         10 require Config;
724 2         5 my $c = \%Config::Config;
725 2         5 $HAS_FLOCK = grep { $c->{$_} } qw/d_flock d_fcntl_can_lock d_lockf/;
  6         355  
726 2 50       16 require Fcntl if $HAS_FLOCK;
727 2         9 return $HAS_FLOCK;
728             }
729             }
730              
731 11     11   122 use Scalar::Util ();
  11         21  
  11         1071  
732             BEGIN {
733 11     11   38 local $@;
734 11 50       31 if ( eval { Scalar::Util->VERSION(1.18); } ) {
  11         375  
735 11         862 *refaddr = *Scalar::Util::refaddr;
736             }
737             else {
738 0         0 eval <<'END_PERL';
739             sub refaddr {
740             my $pkg = ref($_[0]) or return undef;
741             if ( !! UNIVERSAL::can($_[0], 'can') ) {
742             bless $_[0], 'Scalar::Util::Fake';
743             } else {
744             $pkg = undef;
745             }
746             "$_[0]" =~ /0x(\w+)/;
747             my $i = do { no warnings 'portable'; hex $1 };
748             bless $_[0], $pkg if defined $pkg;
749             $i;
750             }
751             END_PERL
752             }
753             }
754              
755             delete $YAML::As::Parsed::{refaddr};
756              
757             1;
758              
759             __END__