File Coverage

blib/lib/CPAN/Meta/YAML.pm
Criterion Covered Total %
statement 316 344 92.1
branch 171 202 84.6
condition 36 42 85.7
subroutine 34 35 97.1
pod 0 10 0.0
total 557 633 88.1


line stmt bran cond sub pod time code
1 12     12   271014 use 5.008001; # sane UTF-8 support
  12         42  
2 12     12   59 use strict;
  12         19  
  12         326  
3 12     12   45 use warnings;
  12         20  
  12         659  
4             package CPAN::Meta::YAML; # git description: v1.68-2-gcc5324e
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.017'; # TRIAL
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   61 use Exporter;
  12         20  
  12         2470  
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 130181 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 112     112 0 47603 my $self = CPAN::Meta::YAML->_load_string(@_);
34 111 100       204 if ( wantarray ) {
35 54         149 return @$self;
36             } else {
37             # To match YAML.pm, return the last document
38 57         174 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   38 *freeze = \&Dump;
46 12         8023 *thaw = \&Load;
47             }
48              
49             sub DumpFile {
50 1     1 0 2043 my $file = shift;
51 1         7 return CPAN::Meta::YAML->new(@_)->_dump_file($file);
52             }
53              
54             sub LoadFile {
55 1     1 0 657 my $file = shift;
56 1         9 my $self = CPAN::Meta::YAML->_load_file($file);
57 1 50       4 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 41611 my $class = shift;
80 172         631 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 596326 my $self = shift;
93 218         700 $self->_load_string(@_);
94             }
95              
96             sub write_string {
97 105     105 0 265768 my $self = shift;
98 105         422 $self->_dump_string(@_);
99             }
100              
101             sub read {
102 14     14 0 27875 my $self = shift;
103 14         53 $self->_load_file(@_);
104             }
105              
106             sub write {
107 4     4 0 33 my $self = shift;
108 4         12 $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   52 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       341 $class->_error( "File '$file' does not exist" )
176             unless -e $file;
177 13 100       47 $class->_error( "'$file' is a directory, not a file" )
178             unless -f _;
179 12 50       42 $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     3   466 open( my $fh, "<:unix:encoding(UTF-8)", $file );
  3         21  
  3         3  
  3         28  
184 12 50       16855 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       102 flock( $fh, Fcntl::LOCK_SH() )
191             or warn "Couldn't lock '$file' for reading: $!";
192             }
193              
194             # slurp the contents
195 12         27 my $contents = eval {
196 12     12   69 use warnings FATAL => 'utf8';
  12         18  
  12         50934  
197 12         52 local $/;
198             <$fh>
199 12         403 };
200 12 100       267 if ( my $err = $@ ) {
201 2         23 $class->_error("Error reading from file '$file': $err");
202             }
203              
204             # close the file (release the lock)
205 10 50       153 unless ( close $fh ) {
206 0         0 $class->_error("Failed to close file '$file': $!");
207             }
208              
209 10         78 $class->_load_string( $contents );
210             }
211              
212             # Create an object from a string
213             sub _load_string {
214 340 100   340   792 my $class = ref $_[0] ? ref shift : shift;
215 340         719 my $self = bless [], $class;
216 340         558 my $string = $_[0];
217 340         394 eval {
218 340 100       778 unless ( defined $string ) {
219 1         5 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 339 100 100     1716 if ( utf8::is_utf8($string) && ! utf8::valid($string) ) {
225 1         4 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 338         657 utf8::upgrade($string);
233              
234             # Check for and strip any leading UTF-8 BOM
235 338         738 $string =~ s/^\x{FEFF}//;
236              
237             # Check for some special cases
238 338 100       1056 return $self unless length $string;
239              
240             # Split the file into lines
241 334         6947 my @lines = grep { ! /^\s*(?:\#.*)?\z/ }
  1441         5352  
242             split /(?:\015{1,2}\012|\015|\012)/, $string;
243              
244             # Strip the initial YAML header
245 334 100 100     1789 @lines and $lines[0] =~ /^\%YAML[: ][\d\.]+.*\z/ and shift @lines;
246              
247             # A nibbling parser
248 334         439 my $in_document = 0;
249 334         728 while ( @lines ) {
250             # Do we have a document header?
251 358 100       1486 if ( $lines[0] =~ /^---\s*(?:(.+)\s*)?\z/ ) {
252             # Handle scalar documents
253 278         355 shift @lines;
254 278 100 100     1203 if ( defined $1 and $1 !~ /^(?:\#.+|\%YAML[: ][\d\.]+)\z/ ) {
255 43         201 push @$self,
256             $self->_load_scalar( "$1", [ undef ], \@lines );
257 43         146 next;
258             }
259 235         315 $in_document = 1;
260             }
261              
262 315 100 100     3373 if ( ! @lines or $lines[0] =~ /^(?:---|\.\.\.)/ ) {
    100 100        
    100          
    50          
263             # A naked document
264 12         23 push @$self, undef;
265 12   66     69 while ( @lines and $lines[0] !~ /^---/ ) {
266 0         0 shift @lines;
267             }
268 12         39 $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         13 die \"CPAN::Meta::YAML failed to classify the line '$lines[0]'";
275             } elsif ( $lines[0] =~ /^\s*\-(?:\s|$|-+$)/ ) {
276             # An array at the root
277 65         109 my $document = [ ];
278 65         122 push @$self, $document;
279 65         238 $self->_load_array( $document, [ 0 ], \@lines );
280              
281             } elsif ( $lines[0] =~ /^(\s*)\S/ ) {
282             # A hash at the root
283 236         353 my $document = { };
284 236         460 push @$self, $document;
285 236         1069 $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 340         443 my $err = $@;
299 340 100       908 if ( ref $err eq 'SCALAR' ) {
    50          
300 15         18 $self->_error(${$err});
  15         50  
301             } elsif ( $err ) {
302 0         0 $self->_error($err);
303             }
304              
305 325         971 return $self;
306             }
307              
308             sub _unquote_single {
309 232     232   428 my ($self, $string) = @_;
310 232 100       558 return '' unless length $string;
311 227         309 $string =~ s/\'\'/\'/g;
312 227         1194 return $string;
313             }
314              
315             sub _unquote_double {
316 87     87   169 my ($self, $string) = @_;
317 87 100       219 return '' unless length $string;
318 86         139 $string =~ s/\\"/"/g;
319 86         257 $string =~
320             s{\\([Nnever\\fartz0b]|x([0-9a-fA-F]{2}))}
321 74 100       317 {(length($1)>1)?pack("H2",$2):$UNESCAPES{$1}}gex;
322 86         450 return $string;
323             }
324              
325             # Load a YAML scalar string to the actual Perl scalar
326             sub _load_scalar {
327 926     926   1392 my ($self, $string, $indent, $lines) = @_;
328              
329             # Trim trailing whitespace
330 926         4220 $string =~ s/\s*\z//;
331              
332             # Explitic null/undef
333 926 100       2206 return undef if $string eq '~';
334              
335             # Single quote
336 888 100       4580 if ( $string =~ /^$re_capture_single_quoted$re_trailing_comment\z/ ) {
337 185         425 return $self->_unquote_single($1);
338             }
339              
340             # Double quote.
341 703 100       2669 if ( $string =~ /^$re_capture_double_quoted$re_trailing_comment\z/ ) {
342 58         152 return $self->_unquote_double($1);
343             }
344              
345             # Special cases
346 645 100       1509 if ( $string =~ /^[\'\"!&]/ ) {
347 2         22 die \"CPAN::Meta::YAML does not support a feature in line '$string'";
348             }
349 643 100       1244 return {} if $string =~ /^{}(?:\s+\#.*)?\z/;
350 633 100       1265 return [] if $string =~ /^\[\](?:\s+\#.*)?\z/;
351              
352             # Regular unquoted string
353 623 100       1462 if ( $string !~ /^[>|]/ ) {
354 614 100 100     2995 die \"CPAN::Meta::YAML found illegal characters in plain scalar: '$string'"
355             if $string =~ /^(?:-(?:\s|$)|[\@\%\`])/ or
356             $string =~ /:(?:\s|$)/;
357 606         845 $string =~ s/\s+#.*\z//;
358 606         3551 return $string;
359             }
360              
361             # Error
362 9 50       28 die \"CPAN::Meta::YAML failed to find multi-line scalar content" unless @$lines;
363              
364             # Check the indent depth
365 9         26 $lines->[0] =~ /^(\s*)/;
366 9         30 $indent->[-1] = length("$1");
367 9 50 33     63 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 9         20 my @multiline = ();
373 9         28 while ( @$lines ) {
374 23         54 $lines->[0] =~ /^(\s*)/;
375 23 100       64 last unless length($1) >= $indent->[-1];
376 17         102 push @multiline, substr(shift(@$lines), length($1));
377             }
378              
379 9 100       40 my $j = (substr($string, 0, 1) eq '>') ? ' ' : "\n";
380 9 100       25 my $t = (substr($string, 1, 1) eq '-') ? '' : "\n";
381 9         73 return join( $j, @multiline ) . $t;
382             }
383              
384             # Load an array
385             sub _load_array {
386 109     109   174 my ($self, $array, $indent, $lines) = @_;
387              
388 109         276 while ( @$lines ) {
389             # Check for a new document
390 285 100       848 if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
391 15   100     107 while ( @$lines and $lines->[0] !~ /^---/ ) {
392 5         22 shift @$lines;
393             }
394 15         61 return 1;
395             }
396              
397             # Check the indent level
398 270         560 $lines->[0] =~ /^(\s*)/;
399 270 100       1009 if ( length($1) < $indent->[-1] ) {
    50          
400 24         85 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 246 100 33     1885 if ( $lines->[0] =~ /^(\s*\-\s+)[^\'\"]\S*\s*:(?:\s+|$)/ ) {
    100          
    100          
    50          
406             # Inline nested hash
407 26         64 my $indent2 = length("$1");
408 26         115 $lines->[0] =~ s/-/ /;
409 26         63 push @$array, { };
410 26         103 $self->_load_hash( $array->[-1], [ @$indent, $indent2 ], $lines );
411              
412             } elsif ( $lines->[0] =~ /^\s*\-\s*\z/ ) {
413 38         47 shift @$lines;
414 38 100       88 unless ( @$lines ) {
415 2         3 push @$array, undef;
416 2         7 return 1;
417             }
418 36 100       148 if ( $lines->[0] =~ /^(\s*)\-/ ) {
    50          
419 4         10 my $indent2 = length("$1");
420 4 50       9 if ( $indent->[-1] == $indent2 ) {
421             # Null array entry
422 4         11 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         57 push @$array, { };
433 32         141 $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 178         237 shift @$lines;
444 178         812 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         20 return 1;
457              
458             } else {
459 0         0 die \"CPAN::Meta::YAML failed to classify line '$lines->[0]'";
460             }
461             }
462              
463 61         232 return 1;
464             }
465              
466             # Load a hash
467             sub _load_hash {
468 416     416   622 my ($self, $hash, $indent, $lines) = @_;
469              
470 416         831 while ( @$lines ) {
471             # Check for a new document
472 1037 100       2700 if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
473 3   100     26 while ( @$lines and $lines->[0] !~ /^---/ ) {
474 1         4 shift @$lines;
475             }
476 3         13 return 1;
477             }
478              
479             # Check the indent level
480 1034         2046 $lines->[0] =~ /^(\s*)/;
481 1034 100       3535 if ( length($1) < $indent->[-1] ) {
    50          
482 155         539 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 879         822 my $key;
489              
490             # Quoted keys
491 879 100       13057 if ( $lines->[0] =~
    100          
    100          
    50          
492             s/^\s*$re_capture_single_quoted$re_key_value_separator//
493             ) {
494 47         99 $key = $self->_unquote_single($1);
495             }
496             elsif ( $lines->[0] =~
497             s/^\s*$re_capture_double_quoted$re_key_value_separator//
498             ) {
499 29         64 $key = $self->_unquote_double($1);
500             }
501             elsif ( $lines->[0] =~
502             s/^\s*$re_capture_unquoted_key$re_key_value_separator//
503             ) {
504 802         1263 $key = $1;
505 802         1529 $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         9 die \"CPAN::Meta::YAML failed to classify line '$lines->[0]'";
512             }
513              
514 878 100       2231 if ( exists $hash->{$key} ) {
515 1         23 warn "CPAN::Meta::YAML found a duplicate key '$key' in line '$lines->[0]'";
516             }
517              
518             # Do we have a value?
519 878 100       2155 if ( length $lines->[0] ) {
520             # Yes
521 705         2332 $hash->{$key} = $self->_load_scalar(
522             shift(@$lines), [ @$indent, undef ], $lines
523             );
524             } else {
525             # An indent
526 173         225 shift @$lines;
527 173 100       868 unless ( @$lines ) {
528 1         3 $hash->{$key} = undef;
529 1         6 return 1;
530             }
531 172 100       881 if ( $lines->[0] =~ /^(\s*)-/ ) {
    50          
532 44         143 $hash->{$key} = [];
533             $self->_load_array(
534 44         253 $hash->{$key}, [ @$indent, length($1) ], $lines
535             );
536             } elsif ( $lines->[0] =~ /^(\s*)./ ) {
537 128         297 my $indent2 = length("$1");
538 128 100       257 if ( $indent->[-1] >= $indent2 ) {
539             # Null hash entry
540 6         29 $hash->{$key} = undef;
541             } else {
542 122         320 $hash->{$key} = {};
543             $self->_load_hash(
544 122         634 $hash->{$key}, [ @$indent, length($1) ], $lines
545             );
546             }
547             }
548             }
549             }
550              
551 249         920 return 1;
552             }
553              
554              
555             ###
556             # Dumper functions:
557              
558             # Save an object to a file
559             sub _dump_file {
560 5     5   7 my $self = shift;
561              
562 5         39 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         6 my $fh;
568             # flock if available (or warn if not possible for OS-specific reasons)
569 4 50       11 if ( _can_flock() ) {
570             # Open without truncation (truncate comes after lock)
571 4         37 my $flags = Fcntl::O_WRONLY()|Fcntl::O_CREAT();
572 4         246 sysopen( $fh, $file, $flags );
573 4 50       19 unless ( $fh ) {
574 0         0 $self->_error("Failed to open file '$file' for writing: $!");
575             }
576              
577             # Use no translation and strict UTF-8
578 4         114 binmode( $fh, ":raw:encoding(UTF-8)");
579              
580 4 50       19779 flock( $fh, Fcntl::LOCK_EX() )
581             or warn "Couldn't lock '$file' for reading: $!";
582              
583             # truncate and spew contents
584 4         129 truncate $fh, 0;
585 4         17 seek $fh, 0, 0;
586             }
587             else {
588 0         0 open $fh, ">:unix:encoding(UTF-8)", $file;
589             }
590              
591             # serialize and spew to the handle
592 4         10 print {$fh} $self->_dump_string;
  4         17  
593              
594             # close the file (release the lock)
595 4 50       225 unless ( close $fh ) {
596 0         0 $self->_error("Failed to close file '$file': $!");
597             }
598              
599 4         34 return 1;
600             }
601              
602             # Save an object to a string
603             sub _dump_string {
604 269     269   289 my $self = shift;
605 269 100 100     1490 return '' unless ref $self && @$self;
606              
607             # Iterate over the documents
608 265         293 my $indent = 0;
609 265         717 my @lines = ();
610              
611 265         350 eval {
612 265         738 foreach my $cursor ( @$self ) {
613 278         550 push @lines, '---';
614              
615             # An empty document
616 278 100       1337 if ( ! defined $cursor ) {
    100          
    100          
    50          
617             # Do nothing
618              
619             # A scalar document
620             } elsif ( ! ref $cursor ) {
621 19         47 $lines[-1] .= ' ' . $self->_dump_scalar( $cursor );
622              
623             # A list at the root
624             } elsif ( ref $cursor eq 'ARRAY' ) {
625 33 100       77 unless ( @$cursor ) {
626 1         3 $lines[-1] .= ' []';
627 1         2 next;
628             }
629 32         134 push @lines, $self->_dump_array( $cursor, $indent, {} );
630              
631             # A hash at the root
632             } elsif ( ref $cursor eq 'HASH' ) {
633 219 100       971 unless ( %$cursor ) {
634 1         4 $lines[-1] .= ' {}';
635 1         3 next;
636             }
637 218         718 push @lines, $self->_dump_hash( $cursor, $indent, {} );
638              
639             } else {
640 0         0 die \("Cannot serialize " . ref($cursor));
641             }
642             }
643             };
644 265 100       812 if ( ref $@ eq 'SCALAR' ) {
    50          
645 1         2 $self->_error(${$@});
  1         5  
646             } elsif ( $@ ) {
647 0         0 $self->_error($@);
648             }
649              
650 264         417 join '', map { "$_\n" } @lines;
  942         2313  
651             }
652              
653             sub _has_internal_string_value {
654 1105     1105   1191 my $value = shift;
655 1105         2569 my $b_obj = B::svref_2object(\$value); # for round trip problem
656 1105         3068 return $b_obj->FLAGS & B::SVf_POK();
657             }
658              
659             sub _dump_scalar {
660 1105     1105   1045 my $string = $_[1];
661 1105         979 my $is_key = $_[2];
662             # Check this before checking length or it winds up looking like a string!
663 1105         1432 my $has_string_flag = _has_internal_string_value($string);
664 1105 100       1898 return '~' unless defined $string;
665 1081 100       1802 return "''" unless length $string;
666 1078 100       2684 if (Scalar::Util::looks_like_number($string)) {
667             # keys and values that have been used as strings get quoted
668 90 100 100     312 if ( $is_key || $has_string_flag ) {
669 56         165 return qq['$string'];
670             }
671             else {
672 34         83 return $string;
673             }
674             }
675 988 100       2382 if ( $string =~ /[\x00-\x09\x0b-\x0d\x0e-\x1f\x7f-\x9f\'\n]/ ) {
676 98         138 $string =~ s/\\/\\\\/g;
677 98         163 $string =~ s/"/\\"/g;
678 98         197 $string =~ s/\n/\\n/g;
679 98         110 $string =~ s/[\x85]/\\N/g;
680 98         300 $string =~ s/([\x00-\x1f])/\\$UNPRINTABLE[ord($1)]/g;
681 98         158 $string =~ s/([\x7f-\x9f])/'\x' . sprintf("%X",ord($1))/ge;
  12         62  
682 98         298 return qq|"$string"|;
683             }
684 890 100 66     4679 if ( $string =~ /(?:^[~!@#%&*|>?:,'"`{}\[\]]|^-+$|\s|:\z)/ or
685             $QUOTE{$string}
686             ) {
687 193         616 return "'$string'";
688             }
689 697         1542 return $string;
690             }
691              
692             sub _dump_array {
693 55     55   106 my ($self, $array, $indent, $seen) = @_;
694 55 50       343 if ( $seen->{refaddr($array)}++ ) {
695 0         0 die \"CPAN::Meta::YAML does not support circular references";
696             }
697 55         92 my @lines = ();
698 55         104 foreach my $el ( @$array ) {
699 127         226 my $line = (' ' x $indent) . '-';
700 127         153 my $type = ref $el;
701 127 100       259 if ( ! $type ) {
    100          
    50          
702 94         178 $line .= ' ' . $self->_dump_scalar( $el );
703 94         209 push @lines, $line;
704              
705             } elsif ( $type eq 'ARRAY' ) {
706 1 50       5 if ( @$el ) {
707 0         0 push @lines, $line;
708 0         0 push @lines, $self->_dump_array( $el, $indent + 1, $seen );
709             } else {
710 1         2 $line .= ' []';
711 1         3 push @lines, $line;
712             }
713              
714             } elsif ( $type eq 'HASH' ) {
715 32 100       67 if ( keys %$el ) {
716 31         54 push @lines, $line;
717 31         77 push @lines, $self->_dump_hash( $el, $indent + 1, $seen );
718             } else {
719 1         2 $line .= ' {}';
720 1         3 push @lines, $line;
721             }
722              
723             } else {
724 0         0 die \"CPAN::Meta::YAML does not support $type references";
725             }
726             }
727              
728 53         273 @lines;
729             }
730              
731             sub _dump_hash {
732 310     310   518 my ($self, $hash, $indent, $seen) = @_;
733 310 100       1544 if ( $seen->{refaddr($hash)}++ ) {
734 1         9 die \"CPAN::Meta::YAML does not support circular references";
735             }
736 309         390 my @lines = ();
737 309         1039 foreach my $name ( sort keys %$hash ) {
738 542         730 my $el = $hash->{$name};
739 542         1228 my $line = (' ' x $indent) . $self->_dump_scalar($name, 1) . ":";
740 542         697 my $type = ref $el;
741 542 100       1018 if ( ! $type ) {
    100          
    50          
742 452         832 $line .= ' ' . $self->_dump_scalar( $el );
743 452         996 push @lines, $line;
744              
745             } elsif ( $type eq 'ARRAY' ) {
746 26 100       61 if ( @$el ) {
747 23         47 push @lines, $line;
748 23         105 push @lines, $self->_dump_array( $el, $indent + 1, $seen );
749             } else {
750 3         7 $line .= ' []';
751 3         38 push @lines, $line;
752             }
753              
754             } elsif ( $type eq 'HASH' ) {
755 64 100       128 if ( keys %$el ) {
756 61         122 push @lines, $line;
757 61         183 push @lines, $self->_dump_hash( $el, $indent + 1, $seen );
758             } else {
759 3         7 $line .= ' {}';
760 3         8 push @lines, $line;
761             }
762              
763             } else {
764 0         0 die \"CPAN::Meta::YAML does not support $type references";
765             }
766             }
767              
768 308         1271 @lines;
769             }
770              
771              
772              
773             #####################################################################
774             # DEPRECATED API methods:
775              
776             # Error storage (DEPRECATED as of 1.57)
777             our $errstr = '';
778              
779             # Set error
780             sub _error {
781 22     22   136 require Carp;
782 22         47 $errstr = $_[1];
783 22         95 $errstr =~ s/ at \S+ line \d+.*//;
784 22         4467 Carp::croak( $errstr );
785             }
786              
787             # Retrieve error
788             my $errstr_warned;
789             sub errstr {
790 0     0 0 0 require Carp;
791 0 0       0 Carp::carp( "CPAN::Meta::YAML->errstr and \$CPAN::Meta::YAML::errstr is deprecated" )
792             unless $errstr_warned++;
793 0         0 $errstr;
794             }
795              
796              
797              
798              
799             #####################################################################
800             # Helper functions. Possibly not needed.
801              
802              
803             # Use to detect nv or iv
804 12     12   104 use B;
  12         20  
  12         1822  
805              
806             # XXX-INGY Is flock CPAN::Meta::YAML's responsibility?
807             # Some platforms can't flock :-(
808             # XXX-XDG I think it is. When reading and writing files, we ought
809             # to be locking whenever possible. People (foolishly) use YAML
810             # files for things like session storage, which has race issues.
811             my $HAS_FLOCK;
812             sub _can_flock {
813 16 100   16   50 if ( defined $HAS_FLOCK ) {
814 13         42 return $HAS_FLOCK;
815             }
816             else {
817 3         68 require Config;
818 3         9 my $c = \%Config::Config;
819 3         10 $HAS_FLOCK = grep { $c->{$_} } qw/d_flock d_fcntl_can_lock d_lockf/;
  9         354  
820 3 50       23 require Fcntl if $HAS_FLOCK;
821 3         12 return $HAS_FLOCK;
822             }
823             }
824              
825              
826             # XXX-INGY Is this core in 5.8.1? Can we remove this?
827             # XXX-XDG Scalar::Util 1.18 didn't land until 5.8.8, so we need this
828             #####################################################################
829             # Use Scalar::Util if possible, otherwise emulate it
830              
831 12     12   68 use Scalar::Util ();
  12         72  
  12         886  
832             BEGIN {
833 12     12   25 local $@;
834 12 50       23 if ( eval { Scalar::Util->VERSION(1.18); } ) {
  12         425  
835 12         916 *refaddr = *Scalar::Util::refaddr;
836             }
837             else {
838 0         0 eval <<'END_PERL';
839             # Scalar::Util failed to load or too old
840             sub refaddr {
841             my $pkg = ref($_[0]) or return undef;
842             if ( !! UNIVERSAL::can($_[0], 'can') ) {
843             bless $_[0], 'Scalar::Util::Fake';
844             } else {
845             $pkg = undef;
846             }
847             "$_[0]" =~ /0x(\w+)/;
848             my $i = do { no warnings 'portable'; hex $1 };
849             bless $_[0], $pkg if defined $pkg;
850             $i;
851             }
852             END_PERL
853             }
854             }
855              
856             delete $CPAN::Meta::YAML::{refaddr};
857              
858             1;
859              
860             # XXX-INGY Doc notes I'm putting up here. Changing the doc when it's wrong
861             # but leaving grey area stuff up here.
862             #
863             # I would like to change Read/Write to Load/Dump below without
864             # changing the actual API names.
865             #
866             # It might be better to put Load/Dump API in the SYNOPSIS instead of the
867             # dubious OO API.
868             #
869             # null and bool explanations may be outdated.
870              
871             =pod
872              
873             =encoding UTF-8
874              
875             =head1 NAME
876              
877             CPAN::Meta::YAML - Read and write a subset of YAML for CPAN Meta files
878              
879             =head1 VERSION
880              
881             version 0.017
882              
883             =head1 SYNOPSIS
884              
885             use CPAN::Meta::YAML;
886              
887             # reading a META file
888             open $fh, "<:utf8", "META.yml";
889             $yaml_text = do { local $/; <$fh> };
890             $yaml = CPAN::Meta::YAML->read_string($yaml_text)
891             or die CPAN::Meta::YAML->errstr;
892              
893             # finding the metadata
894             $meta = $yaml->[0];
895              
896             # writing a META file
897             $yaml_text = $yaml->write_string
898             or die CPAN::Meta::YAML->errstr;
899             open $fh, ">:utf8", "META.yml";
900             print $fh $yaml_text;
901              
902             =head1 DESCRIPTION
903              
904             This module implements a subset of the YAML specification for use in reading
905             and writing CPAN metadata files like F and F. It should
906             not be used for any other general YAML parsing or generation task.
907              
908             NOTE: F (and F) files should be UTF-8 encoded. Users are
909             responsible for proper encoding and decoding. In particular, the C and
910             C methods do B support UTF-8 and should not be used.
911              
912             =head1 SUPPORT
913              
914             This module is currently derived from L by Adam Kennedy. If
915             there are bugs in how it parses a particular META.yml file, please file
916             a bug report in the YAML::Tiny bugtracker:
917             L
918              
919             =head1 SEE ALSO
920              
921             L, L, L
922              
923             =head1 AUTHORS
924              
925             =over 4
926              
927             =item *
928              
929             Adam Kennedy
930              
931             =item *
932              
933             David Golden
934              
935             =back
936              
937             =head1 COPYRIGHT AND LICENSE
938              
939             This software is copyright (c) 2010 by Adam Kennedy.
940              
941             This is free software; you can redistribute it and/or modify it under
942             the same terms as the Perl 5 programming language system itself.
943              
944             =cut
945              
946             __END__