File Coverage

blib/lib/YAML/Tiny.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 10 10 100.0
total 563 627 89.9


line stmt bran cond sub pod time code
1 12     12   2354535 use 5.008001; # sane UTF-8 support
  12         50  
2 12     12   73 use strict;
  12         53  
  12         367  
3 12     12   66 use warnings;
  12         77  
  12         1059  
4             package YAML::Tiny; # 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              
9             our $VERSION = '1.76';
10              
11             #####################################################################
12             # The YAML::Tiny API.
13             #
14             # These are the currently documented API functions/methods and
15             # exports:
16              
17 12     12   76 use Exporter;
  12         35  
  12         2791  
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 1 850918 return YAML::Tiny->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 1 921976 my $self = YAML::Tiny->_load_string(@_);
34 114 100       261 if ( wantarray ) {
35 54         221 return @$self;
36             } else {
37             # To match YAML.pm, return the last document
38 60         297 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   59 *freeze = \&Dump;
46 12         11561 *thaw = \&Load;
47             }
48              
49             sub DumpFile {
50 1     1 1 1637 my $file = shift;
51 1         22 return YAML::Tiny->new(@_)->_dump_file($file);
52             }
53              
54             sub LoadFile {
55 1     1 1 279 my $file = shift;
56 1         5 my $self = YAML::Tiny->_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 YAML::Tiny 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 YAML::Tiny. 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 1 1528087 my $class = shift;
80 172         737 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 YAML::Tiny 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 1 3036033 my $self = shift;
93 218         1202 $self->_load_string(@_);
94             }
95              
96             sub write_string {
97 105     105 1 959294 my $self = shift;
98 105         639 $self->_dump_string(@_);
99             }
100              
101             sub read {
102 14     14 1 772078 my $self = shift;
103 14         65 $self->_load_file(@_);
104             }
105              
106             sub write {
107 4     4 1 8 my $self = shift;
108 4         15 $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             # YAML::Tiny 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             # YAML::Tiny 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   50 my $class = ref $_[0] ? ref shift : shift;
172              
173             # Check the file
174 15 100       61 my $file = shift or $class->_error( 'You did not specify a file name' );
175 14 100       503 $class->_error( "File '$file' does not exist" )
176             unless -e $file;
177 13 100       67 $class->_error( "'$file' is a directory, not a file" )
178             unless -f _;
179 12 50       55 $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         668 open( my $fh, "<:unix:encoding(UTF-8)", $file );
184 12 50       1090 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       93 if ( _can_flock() ) {
190 12 50       154 flock( $fh, Fcntl::LOCK_SH() )
191             or warn "Couldn't lock '$file' for reading: $!";
192             }
193              
194             # slurp the contents
195 12         31 my $contents = eval {
196 12     12   104 use warnings FATAL => 'utf8';
  12         27  
  12         76211  
197 12         63 local $/;
198             <$fh>
199 12         607 };
200 12 100       301 if ( my $err = $@ ) {
201 2         21 $class->_error("Error reading from file '$file': $err");
202             }
203              
204             # close the file (release the lock)
205 10 50       220 unless ( close $fh ) {
206 0         0 $class->_error("Failed to close file '$file': $!");
207             }
208              
209 10         73 $class->_load_string( $contents );
210             }
211              
212             # Create an object from a string
213             sub _load_string {
214 343 100   343   1269 my $class = ref $_[0] ? ref shift : shift;
215 343         1008 my $self = bless [], $class;
216 343         944 my $string = $_[0];
217 343         642 eval {
218 343 100       1265 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     2400 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 341         1228 utf8::upgrade($string);
233              
234             # Check for and strip any leading UTF-8 BOM
235 341         1090 $string =~ s/^\x{FEFF}//;
236              
237             # Check for some special cases
238 341 100       1047 return $self unless length $string;
239              
240             # Split the file into lines
241 337         11445 my @lines = grep { ! /^\s*(?:\#.*)?\z/ }
  1456         5930  
242             split /(?:\015{1,2}\012|\015|\012)/, $string;
243              
244             # Strip the initial YAML header
245 337 100 100     2170 @lines and $lines[0] =~ /^\%YAML[: ][\d\.]+.*\z/ and shift @lines;
246              
247             # A nibbling parser
248 337         719 my $in_document = 0;
249 337         1008 while ( @lines ) {
250             # Do we have a document header?
251 361 100       1829 if ( $lines[0] =~ /^---\s*(?:(.+)\s*)?\z/ ) {
252             # Handle scalar documents
253 278         530 shift @lines;
254 278 100 100     1511 if ( defined $1 and $1 !~ /^(?:\#.+|\%YAML[: ][\d\.]+)\z/ ) {
255 43         259 push @$self,
256             $self->_load_scalar( "$1", [ undef ], \@lines );
257 43         173 next;
258             }
259 235         520 $in_document = 1;
260             }
261              
262 318 100 100     3883 if ( ! @lines or $lines[0] =~ /^(?:---|\.\.\.)/ ) {
    100 100        
    100          
    50          
263             # A naked document
264 12         25 push @$self, undef;
265 12   66     54 while ( @lines and $lines[0] !~ /^---/ ) {
266 0         0 shift @lines;
267             }
268 12         22 $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         18 die \"YAML::Tiny failed to classify the line '$lines[0]'";
275             } elsif ( $lines[0] =~ /^\s*\-(?:\s|$|-+$)/ ) {
276             # An array at the root
277 66         141 my $document = [ ];
278 66         152 push @$self, $document;
279 66         323 $self->_load_array( $document, [ 0 ], \@lines );
280              
281             } elsif ( $lines[0] =~ /^(\s*)\S/ ) {
282             # A hash at the root
283 238         467 my $document = { };
284 238         1769 push @$self, $document;
285 238         1604 $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 \"YAML::Tiny failed to classify the line '$lines[0]'";
295             }
296             }
297             };
298 343         727 my $err = $@;
299 343 100       1358 if ( ref $err eq 'SCALAR' ) {
    50          
300 15         29 $self->_error(${$err});
  15         67  
301             } elsif ( $err ) {
302 0         0 $self->_error($err);
303             }
304              
305 328         1205 return $self;
306             }
307              
308             sub _unquote_single {
309 232     232   785 my ($self, $string) = @_;
310 232 100       615 return '' unless length $string;
311 227         3710 $string =~ s/\'\'/\'/g;
312 227         1028 return $string;
313             }
314              
315             sub _unquote_double {
316 87     87   292 my ($self, $string) = @_;
317 87 100       246 return '' unless length $string;
318 86         191 $string =~ s/\\"/"/g;
319 86         427 $string =~
320             s{\\([Nnever\\fartz0b]|x([0-9a-fA-F]{2}))}
321 74 100       391 {(length($1)>1)?pack("H2",$2):$UNESCAPES{$1}}gex;
322 86         467 return $string;
323             }
324              
325             # Load a YAML scalar string to the actual Perl scalar
326             sub _load_scalar {
327 930     930   2329 my ($self, $string, $indent, $lines) = @_;
328              
329             # Trim trailing whitespace
330 930         5346 $string =~ s/\s*\z//;
331              
332             # Explitic null/undef
333 930 100       2880 return undef if $string eq '~';
334              
335             # Single quote
336 892 100       5944 if ( $string =~ /^$re_capture_single_quoted$re_trailing_comment\z/ ) {
337 185         582 return $self->_unquote_single($1);
338             }
339              
340             # Double quote.
341 707 100       3514 if ( $string =~ /^$re_capture_double_quoted$re_trailing_comment\z/ ) {
342 58         202 return $self->_unquote_double($1);
343             }
344              
345             # Special cases
346 649 100       3162 if ( $string =~ /^[\'\"!&]/ ) {
347 2         23 die \"YAML::Tiny does not support a feature in line '$string'";
348             }
349 647 100       1581 return {} if $string =~ /^{}(?:\s+\#.*)?\z/;
350 637 100       1667 return [] if $string =~ /^\[\](?:\s+\#.*)?\z/;
351              
352             # Regular unquoted string
353 627 100       1860 if ( $string !~ /^[>|]/ ) {
354 614 100 100     3619 die \"YAML::Tiny found illegal characters in plain scalar: '$string'"
355             if $string =~ /^(?:-(?:\s|$)|[\@\%\`])/ or
356             $string =~ /:(?:\s|$)/;
357 606         1131 $string =~ s/\s+#.*\z//;
358 606         3373 return $string;
359             }
360              
361             # Error
362 13 50       61 die \"YAML::Tiny failed to find multi-line scalar content" unless @$lines;
363              
364             # Check the indent depth
365 13         42 $lines->[0] =~ /^(\s*)/;
366 13         48 $indent->[-1] = length("$1");
367 13 50 33     69 if ( defined $indent->[-2] and $indent->[-1] <= $indent->[-2] ) {
368 0         0 die \"YAML::Tiny found bad indenting in line '$lines->[0]'";
369             }
370              
371             # Pull the lines
372 13         28 my @multiline = ();
373 13         45 while ( @$lines ) {
374 35         71 $lines->[0] =~ /^(\s*)/;
375 35 100       90 last unless length($1) >= $indent->[-1];
376 28         97 push @multiline, substr(shift(@$lines), $indent->[-1]);
377             }
378              
379 13 100       54 my $j = (substr($string, 0, 1) eq '>') ? ' ' : "\n";
380 13 100       43 my $t = (substr($string, 1, 1) eq '-') ? '' : "\n";
381 13         92 return join( $j, @multiline ) . $t;
382             }
383              
384             # Load an array
385             sub _load_array {
386 110     110   321 my ($self, $array, $indent, $lines) = @_;
387              
388 110         366 while ( @$lines ) {
389             # Check for a new document
390 287 100       960 if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
391 15   100     117 while ( @$lines and $lines->[0] !~ /^---/ ) {
392 5         16 shift @$lines;
393             }
394 15         55 return 1;
395             }
396              
397             # Check the indent level
398 272         730 $lines->[0] =~ /^(\s*)/;
399 272 100       1173 if ( length($1) < $indent->[-1] ) {
    50          
400 24         88 return 1;
401             } elsif ( length($1) > $indent->[-1] ) {
402 0         0 die \"YAML::Tiny found bad indenting in line '$lines->[0]'";
403             }
404              
405 248 100 33     2340 if ( $lines->[0] =~ /^(\s*\-\s+)[^\'\"]\S*\s*:(?:\s+|$)/ ) {
    100          
    100          
    50          
406             # Inline nested hash
407 26         83 my $indent2 = length("$1");
408 26         128 $lines->[0] =~ s/-/ /;
409 26         75 push @$array, { };
410 26         88 $self->_load_hash( $array->[-1], [ @$indent, $indent2 ], $lines );
411              
412             } elsif ( $lines->[0] =~ /^\s*\-\s*\z/ ) {
413 38         78 shift @$lines;
414 38 100       101 unless ( @$lines ) {
415 2         6 push @$array, undef;
416 2         13 return 1;
417             }
418 36 100       188 if ( $lines->[0] =~ /^(\s*)\-/ ) {
    50          
419 4         20 my $indent2 = length("$1");
420 4 50       17 if ( $indent->[-1] == $indent2 ) {
421             # Null array entry
422 4         16 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         79 push @$array, { };
433 32         178 $self->_load_hash(
434             $array->[-1], [ @$indent, length("$1") ], $lines
435             );
436              
437             } else {
438 0         0 die \"YAML::Tiny failed to classify line '$lines->[0]'";
439             }
440              
441             } elsif ( $lines->[0] =~ /^\s*\-(\s*)(.+?)\s*\z/ ) {
442             # Array entry with a value
443 180         350 shift @$lines;
444 180         897 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         15 return 1;
457              
458             } else {
459 0         0 die \"YAML::Tiny failed to classify line '$lines->[0]'";
460             }
461             }
462              
463 62         246 return 1;
464             }
465              
466             # Load a hash
467             sub _load_hash {
468 418     418   1101 my ($self, $hash, $indent, $lines) = @_;
469              
470 418         1105 while ( @$lines ) {
471             # Check for a new document
472 1039 100       3198 if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
473 3   100     24 while ( @$lines and $lines->[0] !~ /^---/ ) {
474 1         3 shift @$lines;
475             }
476 3         11 return 1;
477             }
478              
479             # Check the indent level
480 1036         2603 $lines->[0] =~ /^(\s*)/;
481 1036 100       3742 if ( length($1) < $indent->[-1] ) {
    50          
482 155         537 return 1;
483             } elsif ( length($1) > $indent->[-1] ) {
484 0         0 die \"YAML::Tiny found bad indenting in line '$lines->[0]'";
485             }
486              
487             # Find the key
488 881         1284 my $key;
489              
490             # Quoted keys
491 881 100       16197 if ( $lines->[0] =~
    100          
    100          
    50          
492             s/^\s*$re_capture_single_quoted$re_key_value_separator//
493             ) {
494 47         148 $key = $self->_unquote_single($1);
495             }
496             elsif ( $lines->[0] =~
497             s/^\s*$re_capture_double_quoted$re_key_value_separator//
498             ) {
499 29         120 $key = $self->_unquote_double($1);
500             }
501             elsif ( $lines->[0] =~
502             s/^\s*$re_capture_unquoted_key$re_key_value_separator//
503             ) {
504 804         2012 $key = $1;
505 804         1947 $key =~ s/\s+$//;
506             }
507             elsif ( $lines->[0] =~ /^\s*\?/ ) {
508 0         0 die \"YAML::Tiny does not support a feature in line '$lines->[0]'";
509             }
510             else {
511 1         5 die \"YAML::Tiny failed to classify line '$lines->[0]'";
512             }
513              
514 880 100       2513 if ( exists $hash->{$key} ) {
515 1         17 warn "YAML::Tiny found a duplicate key '$key' in line '$lines->[0]'";
516             }
517              
518             # Do we have a value?
519 880 100       1911 if ( length $lines->[0] ) {
520             # Yes
521 707         2736 $hash->{$key} = $self->_load_scalar(
522             shift(@$lines), [ @$indent, undef ], $lines
523             );
524             } else {
525             # An indent
526 173         343 shift @$lines;
527 173 100       517 unless ( @$lines ) {
528 1         3 $hash->{$key} = undef;
529 1         4 return 1;
530             }
531 172 100       993 if ( $lines->[0] =~ /^(\s*)-/ ) {
    50          
532 44         154 $hash->{$key} = [];
533             $self->_load_array(
534 44         256 $hash->{$key}, [ @$indent, length($1) ], $lines
535             );
536             } elsif ( $lines->[0] =~ /^(\s*)./ ) {
537 128         379 my $indent2 = length("$1");
538 128 100       291 if ( $indent->[-1] >= $indent2 ) {
539             # Null hash entry
540 6         21 $hash->{$key} = undef;
541             } else {
542 122         424 $hash->{$key} = {};
543             $self->_load_hash(
544 122         543 $hash->{$key}, [ @$indent, length($1) ], $lines
545             );
546             }
547             }
548             }
549             }
550              
551 251         952 return 1;
552             }
553              
554              
555             ###
556             # Dumper functions:
557              
558             # Save an object to a file
559             sub _dump_file {
560 5     5   8 my $self = shift;
561              
562 5         46 require Fcntl;
563              
564             # Check the file
565 5 100       18 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       14 if ( _can_flock() ) {
570             # Open without truncation (truncate comes after lock)
571 4         6 my $flags = Fcntl::O_WRONLY()|Fcntl::O_CREAT();
572 4 50       471 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         61 binmode( $fh, ":raw:encoding(UTF-8)");
577              
578 4 50       327 flock( $fh, Fcntl::LOCK_EX() )
579             or warn "Couldn't lock '$file' for reading: $!";
580              
581             # truncate and spew contents
582 4         140 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         9 print {$fh} $self->_dump_string;
  4         16  
591              
592             # close the file (release the lock)
593 4 50       713 unless ( close $fh ) {
594 0         0 $self->_error("Failed to close file '$file': $!");
595             }
596              
597 4         45 return 1;
598             }
599              
600             # Save an object to a string
601             sub _dump_string {
602 269     269   548 my $self = shift;
603 269 100 100     1826 return '' unless ref $self && @$self;
604              
605             # Iterate over the documents
606 265         499 my $indent = 0;
607 265         537 my @lines = ();
608              
609 265         478 eval {
610 265         697 foreach my $cursor ( @$self ) {
611 278         609 push @lines, '---';
612              
613             # An empty document
614 278 100       1673 if ( ! defined $cursor ) {
    100          
    100          
    50          
615             # Do nothing
616              
617             # A scalar document
618             } elsif ( ! ref $cursor ) {
619 19         77 $lines[-1] .= ' ' . $self->_dump_scalar( $cursor );
620              
621             # A list at the root
622             } elsif ( ref $cursor eq 'ARRAY' ) {
623 33 100       106 unless ( @$cursor ) {
624 1         4 $lines[-1] .= ' []';
625 1         4 next;
626             }
627 32         144 push @lines, $self->_dump_array( $cursor, $indent, {} );
628              
629             # A hash at the root
630             } elsif ( ref $cursor eq 'HASH' ) {
631 219 100       677 unless ( %$cursor ) {
632 1         2 $lines[-1] .= ' {}';
633 1         3 next;
634             }
635 218         822 push @lines, $self->_dump_hash( $cursor, $indent, {} );
636              
637             } else {
638 0         0 die \("Cannot serialize " . ref($cursor));
639             }
640             }
641             };
642 265 100       1068 if ( ref $@ eq 'SCALAR' ) {
    50          
643 1         4 $self->_error(${$@});
  1         6  
644             } elsif ( $@ ) {
645 0         0 $self->_error($@);
646             }
647              
648 264         740 join '', map { "$_\n" } @lines;
  942         3416  
649             }
650              
651             sub _has_internal_string_value {
652 1105     1105   1857 my $value = shift;
653 1105         3637 my $b_obj = B::svref_2object(\$value); # for round trip problem
654 1105         3680 return $b_obj->FLAGS & B::SVf_POK();
655             }
656              
657             sub _dump_scalar {
658 1105     1105   1667 my $string = $_[1];
659 1105         1569 my $is_key = $_[2];
660             # Check this before checking length or it winds up looking like a string!
661 1105         2184 my $has_string_flag = _has_internal_string_value($string);
662 1105 100       2685 return '~' unless defined $string;
663 1081 100       2326 return "''" unless length $string;
664 1078 100       3509 if (Scalar::Util::looks_like_number($string)) {
665             # keys and values that have been used as strings get quoted
666 90 100 100     373 if ( $is_key || $has_string_flag ) {
667 56         230 return qq['$string'];
668             }
669             else {
670 34         100 return $string;
671             }
672             }
673 988 100       3095 if ( $string =~ /[\x00-\x09\x0b-\x0d\x0e-\x1f\x7f-\x9f\'\n]/ ) {
674 98         219 $string =~ s/\\/\\\\/g;
675 98         167 $string =~ s/"/\\"/g;
676 98         230 $string =~ s/\n/\\n/g;
677 98         209 $string =~ s/[\x85]/\\N/g;
678 98         8727 $string =~ s/([\x00-\x1f])/\\$UNPRINTABLE[ord($1)]/g;
679 98         248 $string =~ s/([\x7f-\x9f])/'\x' . sprintf("%X",ord($1))/ge;
  12         46  
680 98         375 return qq|"$string"|;
681             }
682 890 100 100     6350 if ( $string =~ /(?:^[~!@#%&*|>?:,'"`{}\[\]]|^-+$|\s|:\z)/ or
683             $QUOTE{$string}
684             ) {
685 193         688 return "'$string'";
686             }
687 697         1890 return $string;
688             }
689              
690             sub _dump_array {
691 55     55   170 my ($self, $array, $indent, $seen) = @_;
692 55 50       402 if ( $seen->{refaddr($array)}++ ) {
693 0         0 die \"YAML::Tiny does not support circular references";
694             }
695 55         148 my @lines = ();
696 55         149 foreach my $el ( @$array ) {
697 127         314 my $line = (' ' x $indent) . '-';
698 127         301 my $type = ref $el;
699 127 100       366 if ( ! $type ) {
    100          
    50          
700 94         278 $line .= ' ' . $self->_dump_scalar( $el );
701 94         310 push @lines, $line;
702              
703             } elsif ( $type eq 'ARRAY' ) {
704 1 50       9 if ( @$el ) {
705 0         0 push @lines, $line;
706 0         0 push @lines, $self->_dump_array( $el, $indent + 1, $seen );
707             } else {
708 1         3 $line .= ' []';
709 1         3 push @lines, $line;
710             }
711              
712             } elsif ( $type eq 'HASH' ) {
713 32 100       88 if ( keys %$el ) {
714 31         72 push @lines, $line;
715 31         102 push @lines, $self->_dump_hash( $el, $indent + 1, $seen );
716             } else {
717 1         3 $line .= ' {}';
718 1         3 push @lines, $line;
719             }
720              
721             } else {
722 0         0 die \"YAML::Tiny does not support $type references";
723             }
724             }
725              
726 53         413 @lines;
727             }
728              
729             sub _dump_hash {
730 310     310   764 my ($self, $hash, $indent, $seen) = @_;
731 310 100       1931 if ( $seen->{refaddr($hash)}++ ) {
732 1         15 die \"YAML::Tiny does not support circular references";
733             }
734 309         683 my @lines = ();
735 309         1290 foreach my $name ( sort keys %$hash ) {
736 542         1135 my $el = $hash->{$name};
737 542         1571 my $line = (' ' x $indent) . $self->_dump_scalar($name, 1) . ":";
738 542         1124 my $type = ref $el;
739 542 100       1206 if ( ! $type ) {
    100          
    50          
740 452         935 $line .= ' ' . $self->_dump_scalar( $el );
741 452         1342 push @lines, $line;
742              
743             } elsif ( $type eq 'ARRAY' ) {
744 26 100       78 if ( @$el ) {
745 23         57 push @lines, $line;
746 23         172 push @lines, $self->_dump_array( $el, $indent + 1, $seen );
747             } else {
748 3         4 $line .= ' []';
749 3         8 push @lines, $line;
750             }
751              
752             } elsif ( $type eq 'HASH' ) {
753 64 100       137 if ( keys %$el ) {
754 61         125 push @lines, $line;
755 61         221 push @lines, $self->_dump_hash( $el, $indent + 1, $seen );
756             } else {
757 3         5 $line .= ' {}';
758 3         10 push @lines, $line;
759             }
760              
761             } else {
762 0         0 die \"YAML::Tiny does not support $type references";
763             }
764             }
765              
766 308         1825 @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   236 require Carp;
780 22         58 $errstr = $_[1];
781 22         125 $errstr =~ s/ at \S+ line \d+.*//;
782 22         5397 Carp::croak( $errstr );
783             }
784              
785             # Retrieve error
786             my $errstr_warned;
787             sub errstr {
788 0     0 1 0 require Carp;
789 0 0       0 Carp::carp( "YAML::Tiny->errstr and \$YAML::Tiny::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   141 use B;
  12         43  
  12         1801  
803              
804             # XXX-INGY Is flock YAML::Tiny'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   46 if ( defined $HAS_FLOCK ) {
812 13         46 return $HAS_FLOCK;
813             }
814             else {
815 3         31 require Config;
816 3         8 my $c = \%Config::Config;
817 3         9 $HAS_FLOCK = grep { $c->{$_} } qw/d_flock d_fcntl_can_lock d_lockf/;
  9         442  
818 3 50       21 require Fcntl if $HAS_FLOCK;
819 3         14 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   88 use Scalar::Util ();
  12         30  
  12         1304  
830             BEGIN {
831 12     12   50 local $@;
832 12 50       28 if ( eval { Scalar::Util->VERSION(1.18); } ) {
  12         473  
833 12         1207 *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 $YAML::Tiny::{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             __END__