File Coverage

blib/lib/CPAN/Meta/YAML.pm
Criterion Covered Total %
statement 312 339 92.3
branch 171 202 84.6
condition 37 42 88.1
subroutine 33 34 97.0
pod 0 10 0.0
total 553 627 88.3


line stmt bran cond sub pod time code
1 12     12   2051119 use 5.008001; # sane UTF-8 support
  12         48  
2 12     12   77 use strict;
  12         48  
  12         439  
3 12     12   65 use warnings;
  12         25  
  12         1106  
4             package CPAN::Meta::YAML; # git description: v1.75-3-g85169f1
5             # XXX-INGY is 5.8.1 too old/broken for utf8?
6             # XXX-XDG Lancaster consensus was that it was sufficient until
7             # proven otherwise
8             $CPAN::Meta::YAML::VERSION = '0.020';
9             ; # original $VERSION removed by Doppelgaenger
10              
11             #####################################################################
12             # The CPAN::Meta::YAML API.
13             #
14             # These are the currently documented API functions/methods and
15             # exports:
16              
17 12     12   75 use Exporter;
  12         30  
  12         2638  
18             our @ISA = qw{ Exporter };
19             our @EXPORT = qw{ Load Dump };
20             our @EXPORT_OK = qw{ LoadFile DumpFile freeze thaw };
21              
22             ###
23             # Functional/Export API:
24              
25             sub Dump {
26 160     160 0 550299 return CPAN::Meta::YAML->new(@_)->_dump_string;
27             }
28              
29             # XXX-INGY Returning last document seems a bad behavior.
30             # XXX-XDG I think first would seem more natural, but I don't know
31             # that it's worth changing now
32             sub Load {
33 115     115 0 588079 my $self = CPAN::Meta::YAML->_load_string(@_);
34 114 100       237 if ( wantarray ) {
35 54         162 return @$self;
36             } else {
37             # To match YAML.pm, return the last document
38 60         247 return $self->[-1];
39             }
40             }
41              
42             # XXX-INGY Do we really need freeze and thaw?
43             # XXX-XDG I don't think so. I'd support deprecating them.
44             BEGIN {
45 12     12   58 *freeze = \&Dump;
46 12         11956 *thaw = \&Load;
47             }
48              
49             sub DumpFile {
50 1     1 0 2070 my $file = shift;
51 1         6 return CPAN::Meta::YAML->new(@_)->_dump_file($file);
52             }
53              
54             sub LoadFile {
55 1     1 0 459 my $file = shift;
56 1         10 my $self = CPAN::Meta::YAML->_load_file($file);
57 1 50       3 if ( wantarray ) {
58 1         5 return @$self;
59             } else {
60             # Return only the last document to match YAML.pm,
61 0         0 return $self->[-1];
62             }
63             }
64              
65              
66             ###
67             # Object Oriented API:
68              
69             # Create an empty CPAN::Meta::YAML object
70             # XXX-INGY Why do we use ARRAY object?
71             # NOTE: I get it now, but I think it's confusing and not needed.
72             # Will change it on a branch later, for review.
73             #
74             # XXX-XDG I don't support changing it yet. It's a very well-documented
75             # "API" of CPAN::Meta::YAML. I'd support deprecating it, but Adam suggested
76             # we not change it until YAML.pm's own OO API is established so that
77             # users only have one API change to digest, not two
78             sub new {
79 172     172 0 785759 my $class = shift;
80 172         670 bless [ @_ ], $class;
81             }
82              
83             # XXX-INGY It probably doesn't matter, and it's probably too late to
84             # change, but 'read/write' are the wrong names. Read and Write
85             # are actions that take data from storage to memory
86             # characters/strings. These take the data to/from storage to native
87             # Perl objects, which the terms dump and load are meant. As long as
88             # this is a legacy quirk to CPAN::Meta::YAML it's ok, but I'd prefer not
89             # to add new {read,write}_* methods to this API.
90              
91             sub read_string {
92 218     218 0 2411418 my $self = shift;
93 218         969 $self->_load_string(@_);
94             }
95              
96             sub write_string {
97 105     105 0 777742 my $self = shift;
98 105         615 $self->_dump_string(@_);
99             }
100              
101             sub read {
102 14     14 0 315387 my $self = shift;
103 14         49 $self->_load_file(@_);
104             }
105              
106             sub write {
107 4     4 0 11 my $self = shift;
108 4         20 $self->_dump_file(@_);
109             }
110              
111              
112              
113              
114             #####################################################################
115             # Constants
116              
117             # Printed form of the unprintable characters in the lowest range
118             # of ASCII characters, listed by ASCII ordinal position.
119             my @UNPRINTABLE = qw(
120             0 x01 x02 x03 x04 x05 x06 a
121             b t n v f r x0E x0F
122             x10 x11 x12 x13 x14 x15 x16 x17
123             x18 x19 x1A e x1C x1D x1E x1F
124             );
125              
126             # Printable characters for escapes
127             my %UNESCAPES = (
128             0 => "\x00", z => "\x00", N => "\x85",
129             a => "\x07", b => "\x08", t => "\x09",
130             n => "\x0a", v => "\x0b", f => "\x0c",
131             r => "\x0d", e => "\x1b", '\\' => '\\',
132             );
133              
134             # XXX-INGY
135             # I(ngy) need to decide if these values should be quoted in
136             # CPAN::Meta::YAML or not. Probably yes.
137              
138             # These 3 values have special meaning when unquoted and using the
139             # default YAML schema. They need quotes if they are strings.
140             my %QUOTE = map { $_ => 1 } qw{
141             null true false
142             };
143              
144             # The commented out form is simpler, but overloaded the Perl regex
145             # engine due to recursion and backtracking problems on strings
146             # larger than 32,000ish characters. Keep it for reference purposes.
147             # qr/\"((?:\\.|[^\"])*)\"/
148             my $re_capture_double_quoted = qr/\"([^\\"]*(?:\\.[^\\"]*)*)\"/;
149             my $re_capture_single_quoted = qr/\'([^\']*(?:\'\'[^\']*)*)\'/;
150             # unquoted re gets trailing space that needs to be stripped
151             my $re_capture_unquoted_key = qr/([^:]+(?::+\S(?:[^:]*|.*?(?=:)))*)(?=\s*\:(?:\s+|$))/;
152             my $re_trailing_comment = qr/(?:\s+\#.*)?/;
153             my $re_key_value_separator = qr/\s*:(?:\s+(?:\#.*)?|$)/;
154              
155              
156              
157              
158              
159             #####################################################################
160             # CPAN::Meta::YAML Implementation.
161             #
162             # These are the private methods that do all the work. They may change
163             # at any time.
164              
165              
166             ###
167             # Loader functions:
168              
169             # Create an object from a file
170             sub _load_file {
171 15 100   15   80 my $class = ref $_[0] ? ref shift : shift;
172              
173             # Check the file
174 15 100       51 my $file = shift or $class->_error( 'You did not specify a file name' );
175 14 100       371 $class->_error( "File '$file' does not exist" )
176             unless -e $file;
177 13 100       43 $class->_error( "'$file' is a directory, not a file" )
178             unless -f _;
179 12 50       54 $class->_error( "Insufficient permissions to read '$file'" )
180             unless -r _;
181              
182             # Open unbuffered with strict UTF-8 decoding and no translation layers
183 12         592 open( my $fh, "<:unix:encoding(UTF-8)", $file );
184 12 50       919 unless ( $fh ) {
185 0         0 $class->_error("Failed to open file '$file': $!");
186             }
187              
188             # flock if available (or warn if not possible for OS-specific reasons)
189 12 50       36 if ( _can_flock() ) {
190 12 50       141 flock( $fh, Fcntl::LOCK_SH() )
191             or warn "Couldn't lock '$file' for reading: $!";
192             }
193              
194             # slurp the contents
195 12         26 my $contents = eval {
196 12     12   105 use warnings FATAL => 'utf8';
  12         59  
  12         76158  
197 12         56 local $/;
198             <$fh>
199 12         566 };
200 12 100       222 if ( my $err = $@ ) {
201 2         19 $class->_error("Error reading from file '$file': $err");
202             }
203              
204             # close the file (release the lock)
205 10 50       172 unless ( close $fh ) {
206 0         0 $class->_error("Failed to close file '$file': $!");
207             }
208              
209 10         79 $class->_load_string( $contents );
210             }
211              
212             # Create an object from a string
213             sub _load_string {
214 343 100   343   1177 my $class = ref $_[0] ? ref shift : shift;
215 343         921 my $self = bless [], $class;
216 343         851 my $string = $_[0];
217 343         711 eval {
218 343 100       994 unless ( defined $string ) {
219 1         6 die \"Did not provide a string to load";
220             }
221              
222             # Check if Perl has it marked as characters, but it's internally
223             # inconsistent. E.g. maybe latin1 got read on a :utf8 layer
224 342 100 100     2059 if ( utf8::is_utf8($string) && ! utf8::valid($string) ) {
225 1         5 die \<<'...';
226             Read an invalid UTF-8 string (maybe mixed UTF-8 and 8-bit character set).
227             Did you decode with lax ":utf8" instead of strict ":encoding(UTF-8)"?
228             ...
229             }
230              
231             # Ensure Unicode character semantics, even for 0x80-0xff
232 341         1092 utf8::upgrade($string);
233              
234             # Check for and strip any leading UTF-8 BOM
235 341         929 $string =~ s/^\x{FEFF}//;
236              
237             # Check for some special cases
238 341 100       931 return $self unless length $string;
239              
240             # Split the file into lines
241 337         18190 my @lines = grep { ! /^\s*(?:\#.*)?\z/ }
  1456         6157  
242             split /(?:\015{1,2}\012|\015|\012)/, $string;
243              
244             # Strip the initial YAML header
245 337 100 100     1886 @lines and $lines[0] =~ /^\%YAML[: ][\d\.]+.*\z/ and shift @lines;
246              
247             # A nibbling parser
248 337         620 my $in_document = 0;
249 337         827 while ( @lines ) {
250             # Do we have a document header?
251 361 100       1751 if ( $lines[0] =~ /^---\s*(?:(.+)\s*)?\z/ ) {
252             # Handle scalar documents
253 278         505 shift @lines;
254 278 100 100     1330 if ( defined $1 and $1 !~ /^(?:\#.+|\%YAML[: ][\d\.]+)\z/ ) {
255 43         249 push @$self,
256             $self->_load_scalar( "$1", [ undef ], \@lines );
257 43         180 next;
258             }
259 235         434 $in_document = 1;
260             }
261              
262 318 100 100     5686 if ( ! @lines or $lines[0] =~ /^(?:---|\.\.\.)/ ) {
    100 100        
    100          
    50          
263             # A naked document
264 12         34 push @$self, undef;
265 12   66     59 while ( @lines and $lines[0] !~ /^---/ ) {
266 0         0 shift @lines;
267             }
268 12         36 $in_document = 0;
269              
270             # XXX The final '-+$' is to look for -- which ends up being an
271             # error later.
272             } elsif ( ! $in_document && @$self ) {
273             # only the first document can be explicit
274 2         15 die \"CPAN::Meta::YAML failed to classify the line '$lines[0]'";
275             } elsif ( $lines[0] =~ /^\s*\-(?:\s|$|-+$)/ ) {
276             # An array at the root
277 66         155 my $document = [ ];
278 66         229 push @$self, $document;
279 66         302 $self->_load_array( $document, [ 0 ], \@lines );
280              
281             } elsif ( $lines[0] =~ /^(\s*)\S/ ) {
282             # A hash at the root
283 238         424 my $document = { };
284 238         545 push @$self, $document;
285 238         1368 $self->_load_hash( $document, [ length($1) ], \@lines );
286              
287             } else {
288             # Shouldn't get here. @lines have whitespace-only lines
289             # stripped, and previous match is a line with any
290             # non-whitespace. So this clause should only be reachable via
291             # a perlbug where \s is not symmetric with \S
292              
293             # uncoverable statement
294 0         0 die \"CPAN::Meta::YAML failed to classify the line '$lines[0]'";
295             }
296             }
297             };
298 343         693 my $err = $@;
299 343 100       1259 if ( ref $err eq 'SCALAR' ) {
    50          
300 15         27 $self->_error(${$err});
  15         71  
301             } elsif ( $err ) {
302 0         0 $self->_error($err);
303             }
304              
305 328         1224 return $self;
306             }
307              
308             sub _unquote_single {
309 232     232   1943 my ($self, $string) = @_;
310 232 100       609 return '' unless length $string;
311 227         526 $string =~ s/\'\'/\'/g;
312 227         1098 return $string;
313             }
314              
315             sub _unquote_double {
316 87     87   326 my ($self, $string) = @_;
317 87 100       243 return '' unless length $string;
318 86         187 $string =~ s/\\"/"/g;
319 86         348 $string =~
320             s{\\([Nnever\\fartz0b]|x([0-9a-fA-F]{2}))}
321 74 100       348 {(length($1)>1)?pack("H2",$2):$UNESCAPES{$1}}gex;
322 86         404 return $string;
323             }
324              
325             # Load a YAML scalar string to the actual Perl scalar
326             sub _load_scalar {
327 930     930   2354 my ($self, $string, $indent, $lines) = @_;
328              
329             # Trim trailing whitespace
330 930         5728 $string =~ s/\s*\z//;
331              
332             # Explitic null/undef
333 930 100       2811 return undef if $string eq '~';
334              
335             # Single quote
336 892 100       5593 if ( $string =~ /^$re_capture_single_quoted$re_trailing_comment\z/ ) {
337 185         527 return $self->_unquote_single($1);
338             }
339              
340             # Double quote.
341 707 100       5060 if ( $string =~ /^$re_capture_double_quoted$re_trailing_comment\z/ ) {
342 58         156 return $self->_unquote_double($1);
343             }
344              
345             # Special cases
346 649 100       1853 if ( $string =~ /^[\'\"!&]/ ) {
347 2         22 die \"CPAN::Meta::YAML does not support a feature in line '$string'";
348             }
349 647 100       1689 return {} if $string =~ /^{}(?:\s+\#.*)?\z/;
350 637 100       1610 return [] if $string =~ /^\[\](?:\s+\#.*)?\z/;
351              
352             # Regular unquoted string
353 627 100       1796 if ( $string !~ /^[>|]/ ) {
354 614 100 100     3131 die \"CPAN::Meta::YAML found illegal characters in plain scalar: '$string'"
355             if $string =~ /^(?:-(?:\s|$)|[\@\%\`])/ or
356             $string =~ /:(?:\s|$)/;
357 606         1732 $string =~ s/\s+#.*\z//;
358 606         3388 return $string;
359             }
360              
361             # Error
362 13 50       38 die \"CPAN::Meta::YAML failed to find multi-line scalar content" unless @$lines;
363              
364             # Check the indent depth
365 13         38 $lines->[0] =~ /^(\s*)/;
366 13         52 $indent->[-1] = length("$1");
367 13 50 33     72 if ( defined $indent->[-2] and $indent->[-1] <= $indent->[-2] ) {
368 0         0 die \"CPAN::Meta::YAML found bad indenting in line '$lines->[0]'";
369             }
370              
371             # Pull the lines
372 13         34 my @multiline = ();
373 13         37 while ( @$lines ) {
374 35         105 $lines->[0] =~ /^(\s*)/;
375 35 100       96 last unless length($1) >= $indent->[-1];
376 28         105 push @multiline, substr(shift(@$lines), $indent->[-1]);
377             }
378              
379 13 100       51 my $j = (substr($string, 0, 1) eq '>') ? ' ' : "\n";
380 13 100       58 my $t = (substr($string, 1, 1) eq '-') ? '' : "\n";
381 13         96 return join( $j, @multiline ) . $t;
382             }
383              
384             # Load an array
385             sub _load_array {
386 110     110   335 my ($self, $array, $indent, $lines) = @_;
387              
388 110         4374 while ( @$lines ) {
389             # Check for a new document
390 287 100       1079 if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
391 15   100     119 while ( @$lines and $lines->[0] !~ /^---/ ) {
392 5         23 shift @$lines;
393             }
394 15         65 return 1;
395             }
396              
397             # Check the indent level
398 272         788 $lines->[0] =~ /^(\s*)/;
399 272 100       1125 if ( length($1) < $indent->[-1] ) {
    50          
400 24         93 return 1;
401             } elsif ( length($1) > $indent->[-1] ) {
402 0         0 die \"CPAN::Meta::YAML found bad indenting in line '$lines->[0]'";
403             }
404              
405 248 100 33     2140 if ( $lines->[0] =~ /^(\s*\-\s+)[^\'\"]\S*\s*:(?:\s+|$)/ ) {
    100          
    100          
    50          
406             # Inline nested hash
407 26         83 my $indent2 = length("$1");
408 26         124 $lines->[0] =~ s/-/ /;
409 26         77 push @$array, { };
410 26         110 $self->_load_hash( $array->[-1], [ @$indent, $indent2 ], $lines );
411              
412             } elsif ( $lines->[0] =~ /^\s*\-\s*\z/ ) {
413 38         80 shift @$lines;
414 38 100       101 unless ( @$lines ) {
415 2         7 push @$array, undef;
416 2         9 return 1;
417             }
418 36 100       171 if ( $lines->[0] =~ /^(\s*)\-/ ) {
    50          
419 4         16 my $indent2 = length("$1");
420 4 50       14 if ( $indent->[-1] == $indent2 ) {
421             # Null array entry
422 4         14 push @$array, undef;
423             } else {
424             # Naked indenter
425 0         0 push @$array, [ ];
426 0         0 $self->_load_array(
427             $array->[-1], [ @$indent, $indent2 ], $lines
428             );
429             }
430              
431             } elsif ( $lines->[0] =~ /^(\s*)\S/ ) {
432 32         91 push @$array, { };
433 32         158 $self->_load_hash(
434             $array->[-1], [ @$indent, length("$1") ], $lines
435             );
436              
437             } else {
438 0         0 die \"CPAN::Meta::YAML failed to classify line '$lines->[0]'";
439             }
440              
441             } elsif ( $lines->[0] =~ /^\s*\-(\s*)(.+?)\s*\z/ ) {
442             # Array entry with a value
443 180         326 shift @$lines;
444 180         888 push @$array, $self->_load_scalar(
445             "$2", [ @$indent, undef ], $lines
446             );
447              
448             } elsif ( defined $indent->[-2] and $indent->[-1] == $indent->[-2] ) {
449             # This is probably a structure like the following...
450             # ---
451             # foo:
452             # - list
453             # bar: value
454             #
455             # ... so lets return and let the hash parser handle it
456 4         19 return 1;
457              
458             } else {
459 0         0 die \"CPAN::Meta::YAML failed to classify line '$lines->[0]'";
460             }
461             }
462              
463 62         308 return 1;
464             }
465              
466             # Load a hash
467             sub _load_hash {
468 418     418   1067 my ($self, $hash, $indent, $lines) = @_;
469              
470 418         1036 while ( @$lines ) {
471             # Check for a new document
472 1039 100       3240 if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
473 3   100     30 while ( @$lines and $lines->[0] !~ /^---/ ) {
474 1         4 shift @$lines;
475             }
476 3         16 return 1;
477             }
478              
479             # Check the indent level
480 1036         2713 $lines->[0] =~ /^(\s*)/;
481 1036 100       4002 if ( length($1) < $indent->[-1] ) {
    50          
482 155         529 return 1;
483             } elsif ( length($1) > $indent->[-1] ) {
484 0         0 die \"CPAN::Meta::YAML found bad indenting in line '$lines->[0]'";
485             }
486              
487             # Find the key
488 881         1376 my $key;
489              
490             # Quoted keys
491 881 100       17519 if ( $lines->[0] =~
    100          
    100          
    50          
492             s/^\s*$re_capture_single_quoted$re_key_value_separator//
493             ) {
494 47         125 $key = $self->_unquote_single($1);
495             }
496             elsif ( $lines->[0] =~
497             s/^\s*$re_capture_double_quoted$re_key_value_separator//
498             ) {
499 29         68 $key = $self->_unquote_double($1);
500             }
501             elsif ( $lines->[0] =~
502             s/^\s*$re_capture_unquoted_key$re_key_value_separator//
503             ) {
504 804         2015 $key = $1;
505 804         2037 $key =~ s/\s+$//;
506             }
507             elsif ( $lines->[0] =~ /^\s*\?/ ) {
508 0         0 die \"CPAN::Meta::YAML does not support a feature in line '$lines->[0]'";
509             }
510             else {
511 1         7 die \"CPAN::Meta::YAML failed to classify line '$lines->[0]'";
512             }
513              
514 880 100       2623 if ( exists $hash->{$key} ) {
515 1         13 warn "CPAN::Meta::YAML found a duplicate key '$key' in line '$lines->[0]'";
516             }
517              
518             # Do we have a value?
519 880 100       1875 if ( length $lines->[0] ) {
520             # Yes
521 707         2522 $hash->{$key} = $self->_load_scalar(
522             shift(@$lines), [ @$indent, undef ], $lines
523             );
524             } else {
525             # An indent
526 173         316 shift @$lines;
527 173 100       477 unless ( @$lines ) {
528 1         4 $hash->{$key} = undef;
529 1         5 return 1;
530             }
531 172 100       941 if ( $lines->[0] =~ /^(\s*)-/ ) {
    50          
532 44         158 $hash->{$key} = [];
533             $self->_load_array(
534 44         254 $hash->{$key}, [ @$indent, length($1) ], $lines
535             );
536             } elsif ( $lines->[0] =~ /^(\s*)./ ) {
537 128         386 my $indent2 = length("$1");
538 128 100       310 if ( $indent->[-1] >= $indent2 ) {
539             # Null hash entry
540 6         25 $hash->{$key} = undef;
541             } else {
542 122         363 $hash->{$key} = {};
543             $self->_load_hash(
544 122         601 $hash->{$key}, [ @$indent, length($1) ], $lines
545             );
546             }
547             }
548             }
549             }
550              
551 251         1016 return 1;
552             }
553              
554              
555             ###
556             # Dumper functions:
557              
558             # Save an object to a file
559             sub _dump_file {
560 5     5   35 my $self = shift;
561              
562 5         38 require Fcntl;
563              
564             # Check the file
565 5 100       25 my $file = shift or $self->_error( 'You did not specify a file name' );
566              
567 4         3 my $fh;
568             # flock if available (or warn if not possible for OS-specific reasons)
569 4 50       17 if ( _can_flock() ) {
570             # Open without truncation (truncate comes after lock)
571 4         9 my $flags = Fcntl::O_WRONLY()|Fcntl::O_CREAT();
572 4 50       483 sysopen( $fh, $file, $flags )
573             or $self->_error("Failed to open file '$file' for writing: $!");
574              
575             # Use no translation and strict UTF-8
576 4         63 binmode( $fh, ":raw:encoding(UTF-8)");
577              
578 4 50       369 flock( $fh, Fcntl::LOCK_EX() )
579             or warn "Couldn't lock '$file' for reading: $!";
580              
581             # truncate and spew contents
582 4         138 truncate $fh, 0;
583 4         25 seek $fh, 0, 0;
584             }
585             else {
586 0         0 open $fh, ">:unix:encoding(UTF-8)", $file;
587             }
588              
589             # serialize and spew to the handle
590 4         8 print {$fh} $self->_dump_string;
  4         16  
591              
592             # close the file (release the lock)
593 4 50       765 unless ( close $fh ) {
594 0         0 $self->_error("Failed to close file '$file': $!");
595             }
596              
597 4         47 return 1;
598             }
599              
600             # Save an object to a string
601             sub _dump_string {
602 269     269   496 my $self = shift;
603 269 100 100     1582 return '' unless ref $self && @$self;
604              
605             # Iterate over the documents
606 265         422 my $indent = 0;
607 265         433 my @lines = ();
608              
609 265         481 eval {
610 265         555 foreach my $cursor ( @$self ) {
611 278         671 push @lines, '---';
612              
613             # An empty document
614 278 100       1570 if ( ! defined $cursor ) {
    100          
    100          
    50          
615             # Do nothing
616              
617             # A scalar document
618             } elsif ( ! ref $cursor ) {
619 19         67 $lines[-1] .= ' ' . $self->_dump_scalar( $cursor );
620              
621             # A list at the root
622             } elsif ( ref $cursor eq 'ARRAY' ) {
623 33 100       102 unless ( @$cursor ) {
624 1         3 $lines[-1] .= ' []';
625 1         4 next;
626             }
627 32         3719 push @lines, $self->_dump_array( $cursor, $indent, {} );
628              
629             # A hash at the root
630             } elsif ( ref $cursor eq 'HASH' ) {
631 219 100       553 unless ( %$cursor ) {
632 1         4 $lines[-1] .= ' {}';
633 1         4 next;
634             }
635 218         713 push @lines, $self->_dump_hash( $cursor, $indent, {} );
636              
637             } else {
638 0         0 die \("Cannot serialize " . ref($cursor));
639             }
640             }
641             };
642 265 100       977 if ( ref $@ eq 'SCALAR' ) {
    50          
643 1         2 $self->_error(${$@});
  1         3  
644             } elsif ( $@ ) {
645 0         0 $self->_error($@);
646             }
647              
648 264         605 join '', map { "$_\n" } @lines;
  942         4520  
649             }
650              
651             sub _has_internal_string_value {
652 1105     1105   1790 my $value = shift;
653 1105         3698 my $b_obj = B::svref_2object(\$value); # for round trip problem
654 1105         3551 return $b_obj->FLAGS & B::SVf_POK();
655             }
656              
657             sub _dump_scalar {
658 1105     1105   1863 my $string = $_[1];
659 1105         1676 my $is_key = $_[2];
660             # Check this before checking length or it winds up looking like a string!
661 1105         3671 my $has_string_flag = _has_internal_string_value($string);
662 1105 100       2625 return '~' unless defined $string;
663 1081 100       3451 return "''" unless length $string;
664 1078 100       3205 if (Scalar::Util::looks_like_number($string)) {
665             # keys and values that have been used as strings get quoted
666 90 100 100     848 if ( $is_key || $has_string_flag ) {
667 56         310 return qq['$string'];
668             }
669             else {
670 34         113 return $string;
671             }
672             }
673 988 100       2923 if ( $string =~ /[\x00-\x09\x0b-\x0d\x0e-\x1f\x7f-\x9f\'\n]/ ) {
674 98         199 $string =~ s/\\/\\\\/g;
675 98         140 $string =~ s/"/\\"/g;
676 98         164 $string =~ s/\n/\\n/g;
677 98         154 $string =~ s/[\x85]/\\N/g;
678 98         329 $string =~ s/([\x00-\x1f])/\\$UNPRINTABLE[ord($1)]/g;
679 98         211 $string =~ s/([\x7f-\x9f])/'\x' . sprintf("%X",ord($1))/ge;
  12         57  
680 98         273 return qq|"$string"|;
681             }
682 890 100 100     6422 if ( $string =~ /(?:^[~!@#%&*|>?:,'"`{}\[\]]|^-+$|\s|:\z)/ or
683             $QUOTE{$string}
684             ) {
685 193         571 return "'$string'";
686             }
687 697         2357 return $string;
688             }
689              
690             sub _dump_array {
691 55     55   160 my ($self, $array, $indent, $seen) = @_;
692 55 50       352 if ( $seen->{refaddr($array)}++ ) {
693 0         0 die \"CPAN::Meta::YAML does not support circular references";
694             }
695 55         125 my @lines = ();
696 55         187 foreach my $el ( @$array ) {
697 127         341 my $line = (' ' x $indent) . '-';
698 127         263 my $type = ref $el;
699 127 100       317 if ( ! $type ) {
    100          
    50          
700 94         231 $line .= ' ' . $self->_dump_scalar( $el );
701 94         279 push @lines, $line;
702              
703             } elsif ( $type eq 'ARRAY' ) {
704 1 50       4 if ( @$el ) {
705 0         0 push @lines, $line;
706 0         0 push @lines, $self->_dump_array( $el, $indent + 1, $seen );
707             } else {
708 1         2 $line .= ' []';
709 1         2 push @lines, $line;
710             }
711              
712             } elsif ( $type eq 'HASH' ) {
713 32 100       78 if ( keys %$el ) {
714 31         95 push @lines, $line;
715 31         95 push @lines, $self->_dump_hash( $el, $indent + 1, $seen );
716             } else {
717 1         1 $line .= ' {}';
718 1         4 push @lines, $line;
719             }
720              
721             } else {
722 0         0 die \"CPAN::Meta::YAML does not support $type references";
723             }
724             }
725              
726 53         337 @lines;
727             }
728              
729             sub _dump_hash {
730 310     310   776 my ($self, $hash, $indent, $seen) = @_;
731 310 100       1842 if ( $seen->{refaddr($hash)}++ ) {
732 1         8 die \"CPAN::Meta::YAML does not support circular references";
733             }
734 309         538 my @lines = ();
735 309         1369 foreach my $name ( sort keys %$hash ) {
736 542         1172 my $el = $hash->{$name};
737 542         1556 my $line = (' ' x $indent) . $self->_dump_scalar($name, 1) . ":";
738 542         1125 my $type = ref $el;
739 542 100       1327 if ( ! $type ) {
    100          
    50          
740 452         961 $line .= ' ' . $self->_dump_scalar( $el );
741 452         2681 push @lines, $line;
742              
743             } elsif ( $type eq 'ARRAY' ) {
744 26 100       81 if ( @$el ) {
745 23         51 push @lines, $line;
746 23         102 push @lines, $self->_dump_array( $el, $indent + 1, $seen );
747             } else {
748 3         7 $line .= ' []';
749 3         12 push @lines, $line;
750             }
751              
752             } elsif ( $type eq 'HASH' ) {
753 64 100       162 if ( keys %$el ) {
754 61         137 push @lines, $line;
755 61         210 push @lines, $self->_dump_hash( $el, $indent + 1, $seen );
756             } else {
757 3         6 $line .= ' {}';
758 3         12 push @lines, $line;
759             }
760              
761             } else {
762 0         0 die \"CPAN::Meta::YAML does not support $type references";
763             }
764             }
765              
766 308         2349 @lines;
767             }
768              
769              
770              
771             #####################################################################
772             # DEPRECATED API methods:
773              
774             # Error storage (DEPRECATED as of 1.57)
775             our $errstr = '';
776              
777             # Set error
778             sub _error {
779 22     22   187 require Carp;
780 22         53 $errstr = $_[1];
781 22         104 $errstr =~ s/ at \S+ line \d+.*//;
782 22         4613 Carp::croak( $errstr );
783             }
784              
785             # Retrieve error
786             my $errstr_warned;
787             sub errstr {
788 0     0 0 0 require Carp;
789 0 0       0 Carp::carp( "CPAN::Meta::YAML->errstr and \$CPAN::Meta::YAML::errstr is deprecated" )
790             unless $errstr_warned++;
791 0         0 $errstr;
792             }
793              
794              
795              
796              
797             #####################################################################
798             # Helper functions. Possibly not needed.
799              
800              
801             # Use to detect nv or iv
802 12     12   135 use B;
  12         26  
  12         1835  
803              
804             # XXX-INGY Is flock CPAN::Meta::YAML's responsibility?
805             # Some platforms can't flock :-(
806             # XXX-XDG I think it is. When reading and writing files, we ought
807             # to be locking whenever possible. People (foolishly) use YAML
808             # files for things like session storage, which has race issues.
809             my $HAS_FLOCK;
810             sub _can_flock {
811 16 100   16   43 if ( defined $HAS_FLOCK ) {
812 13         65 return $HAS_FLOCK;
813             }
814             else {
815 3         17 require Config;
816 3         8 my $c = \%Config::Config;
817 3         8 $HAS_FLOCK = grep { $c->{$_} } qw/d_flock d_fcntl_can_lock d_lockf/;
  9         446  
818 3 50       23 require Fcntl if $HAS_FLOCK;
819 3         13 return $HAS_FLOCK;
820             }
821             }
822              
823              
824             # XXX-INGY Is this core in 5.8.1? Can we remove this?
825             # XXX-XDG Scalar::Util 1.18 didn't land until 5.8.8, so we need this
826             #####################################################################
827             # Use Scalar::Util if possible, otherwise emulate it
828              
829 12     12   1386 use Scalar::Util ();
  12         66  
  12         1433  
830             BEGIN {
831 12     12   42 local $@;
832 12 50       54 if ( eval { Scalar::Util->VERSION(1.18); } ) {
  12         454  
833 12         1127 *refaddr = *Scalar::Util::refaddr;
834             }
835             else {
836 0         0 eval <<'END_PERL';
837             # Scalar::Util failed to load or too old
838             sub refaddr {
839             my $pkg = ref($_[0]) or return undef;
840             if ( !! UNIVERSAL::can($_[0], 'can') ) {
841             bless $_[0], 'Scalar::Util::Fake';
842             } else {
843             $pkg = undef;
844             }
845             "$_[0]" =~ /0x(\w+)/;
846             my $i = do { no warnings 'portable'; hex $1 };
847             bless $_[0], $pkg if defined $pkg;
848             $i;
849             }
850             END_PERL
851             }
852             }
853              
854             delete $CPAN::Meta::YAML::{refaddr};
855              
856             1;
857              
858             # XXX-INGY Doc notes I'm putting up here. Changing the doc when it's wrong
859             # but leaving grey area stuff up here.
860             #
861             # I would like to change Read/Write to Load/Dump below without
862             # changing the actual API names.
863             #
864             # It might be better to put Load/Dump API in the SYNOPSIS instead of the
865             # dubious OO API.
866             #
867             # null and bool explanations may be outdated.
868              
869             =pod
870              
871             =encoding UTF-8
872              
873             =head1 NAME
874              
875             CPAN::Meta::YAML - Read and write a subset of YAML for CPAN Meta files
876              
877             =head1 VERSION
878              
879             version 0.020
880              
881             =head1 SYNOPSIS
882              
883             use CPAN::Meta::YAML;
884              
885             # reading a META file
886             open $fh, "<:utf8", "META.yml";
887             $yaml_text = do { local $/; <$fh> };
888             $yaml = CPAN::Meta::YAML->read_string($yaml_text)
889             or die CPAN::Meta::YAML->errstr;
890              
891             # finding the metadata
892             $meta = $yaml->[0];
893              
894             # writing a META file
895             $yaml_text = $yaml->write_string
896             or die CPAN::Meta::YAML->errstr;
897             open $fh, ">:utf8", "META.yml";
898             print $fh $yaml_text;
899              
900             =head1 DESCRIPTION
901              
902             This module implements a subset of the YAML specification for use in reading
903             and writing CPAN metadata files like F and F. It should
904             not be used for any other general YAML parsing or generation task.
905              
906             NOTE: F (and F) files should be UTF-8 encoded. Users are
907             responsible for proper encoding and decoding. In particular, the C and
908             C methods do B support UTF-8 and should not be used.
909              
910             =head1 SUPPORT
911              
912             This module is currently derived from L by Adam Kennedy. If
913             there are bugs in how it parses a particular META.yml file, please file
914             a bug report in the YAML::Tiny bugtracker:
915             L
916              
917             =head1 SEE ALSO
918              
919             L, L, L
920              
921             =head1 AUTHORS
922              
923             =over 4
924              
925             =item *
926              
927             Adam Kennedy
928              
929             =item *
930              
931             David Golden
932              
933             =back
934              
935             =head1 CONTRIBUTOR
936              
937             =for stopwords Karen Etheridge
938              
939             Karen Etheridge
940              
941             =head1 COPYRIGHT AND LICENSE
942              
943             This software is copyright (c) 2010 by Adam Kennedy.
944              
945             This is free software; you can redistribute it and/or modify it under
946             the same terms as the Perl 5 programming language system itself.
947              
948             =cut
949              
950             __END__