File Coverage

blib/lib/YAML/Tiny.pm
Criterion Covered Total %
statement 315 342 92.4
branch 171 202 84.6
condition 36 42 85.7
subroutine 34 35 97.1
pod 10 10 100.0
total 566 631 89.8


line stmt bran cond sub pod time code
1 12     12   205620 use 5.008001; # sane UTF-8 support
  12         44  
2 12     12   46 use strict;
  12         12  
  12         215  
3 12     12   37 use warnings;
  12         13  
  12         510  
4             package YAML::Tiny; # git description: v1.71-3-g016bc8a
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.72'; # TRIAL
10              
11             #####################################################################
12             # The YAML::Tiny API.
13             #
14             # These are the currently documented API functions/methods and
15             # exports:
16              
17 12     12   41 use Exporter;
  12         12  
  12         1858  
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 89153 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 43094 my $self = YAML::Tiny->_load_string(@_);
34 114 100       136 if ( wantarray ) {
35 54         103 return @$self;
36             } else {
37             # To match YAML.pm, return the last document
38 60         162 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   34 *freeze = \&Dump;
46 12         6079 *thaw = \&Load;
47             }
48              
49             sub DumpFile {
50 1     1 1 1412 my $file = shift;
51 1         4 return YAML::Tiny->new(@_)->_dump_file($file);
52             }
53              
54             sub LoadFile {
55 1     1 1 526 my $file = shift;
56 1         6 my $self = YAML::Tiny->_load_file($file);
57 1 50       2 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 25227 my $class = shift;
80 172         440 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 334266 my $self = shift;
93 218         464 $self->_load_string(@_);
94             }
95              
96             sub write_string {
97 105     105 1 143924 my $self = shift;
98 105         259 $self->_dump_string(@_);
99             }
100              
101             sub read {
102 14     14 1 16164 my $self = shift;
103 14         36 $self->_load_file(@_);
104             }
105              
106             sub write {
107 4     4 1 7 my $self = shift;
108 4         11 $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   34 my $class = ref $_[0] ? ref shift : shift;
172              
173             # Check the file
174 15 100       31 my $file = shift or $class->_error( 'You did not specify a file name' );
175 14 100       209 $class->_error( "File '$file' does not exist" )
176             unless -e $file;
177 13 100       33 $class->_error( "'$file' is a directory, not a file" )
178             unless -f _;
179 12 50       44 $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   303 open( my $fh, "<:unix:encoding(UTF-8)", $file );
  3         15  
  3         3  
  3         18  
184 12 50       8521 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       23 if ( _can_flock() ) {
190 12 50       59 flock( $fh, Fcntl::LOCK_SH() )
191             or warn "Couldn't lock '$file' for reading: $!";
192             }
193              
194             # slurp the contents
195 12         14 my $contents = eval {
196 12     12   57 use warnings FATAL => 'utf8';
  12         12  
  12         37456  
197 12         29 local $/;
198             <$fh>
199 12         202 };
200 12 100       145 if ( my $err = $@ ) {
201 2         15 $class->_error("Error reading from file '$file': $err");
202             }
203              
204             # close the file (release the lock)
205 10 50       82 unless ( close $fh ) {
206 0         0 $class->_error("Failed to close file '$file': $!");
207             }
208              
209 10         39 $class->_load_string( $contents );
210             }
211              
212             # Create an object from a string
213             sub _load_string {
214 343 100   343   635 my $class = ref $_[0] ? ref shift : shift;
215 343         526 my $self = bless [], $class;
216 343         348 my $string = $_[0];
217 343         296 eval {
218 343 100       571 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     1285 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         471 utf8::upgrade($string);
233              
234             # Check for and strip any leading UTF-8 BOM
235 341         602 $string =~ s/^\x{FEFF}//;
236              
237             # Check for some special cases
238 341 100       792 return $self unless length $string;
239              
240             # Split the file into lines
241 337         4320 my @lines = grep { ! /^\s*(?:\#.*)?\z/ }
  1456         3152  
242             split /(?:\015{1,2}\012|\015|\012)/, $string;
243              
244             # Strip the initial YAML header
245 337 100 100     1300 @lines and $lines[0] =~ /^\%YAML[: ][\d\.]+.*\z/ and shift @lines;
246              
247             # A nibbling parser
248 337         356 my $in_document = 0;
249 337         557 while ( @lines ) {
250             # Do we have a document header?
251 361 100       947 if ( $lines[0] =~ /^---\s*(?:(.+)\s*)?\z/ ) {
252             # Handle scalar documents
253 278         236 shift @lines;
254 278 100 100     804 if ( defined $1 and $1 !~ /^(?:\#.+|\%YAML[: ][\d\.]+)\z/ ) {
255 43         142 push @$self,
256             $self->_load_scalar( "$1", [ undef ], \@lines );
257 43         110 next;
258             }
259 235         243 $in_document = 1;
260             }
261              
262 318 100 100     2195 if ( ! @lines or $lines[0] =~ /^(?:---|\.\.\.)/ ) {
    100 100        
    100          
    50          
263             # A naked document
264 12         15 push @$self, undef;
265 12   66     54 while ( @lines and $lines[0] !~ /^---/ ) {
266 0         0 shift @lines;
267             }
268 12         21 $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         11 die \"YAML::Tiny failed to classify the line '$lines[0]'";
275             } elsif ( $lines[0] =~ /^\s*\-(?:\s|$|-+$)/ ) {
276             # An array at the root
277 66         81 my $document = [ ];
278 66         112 push @$self, $document;
279 66         173 $self->_load_array( $document, [ 0 ], \@lines );
280              
281             } elsif ( $lines[0] =~ /^(\s*)\S/ ) {
282             # A hash at the root
283 238         280 my $document = { };
284 238         288 push @$self, $document;
285 238         717 $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         338 my $err = $@;
299 343 100       617 if ( ref $err eq 'SCALAR' ) {
    50          
300 15         15 $self->_error(${$err});
  15         40  
301             } elsif ( $err ) {
302 0         0 $self->_error($err);
303             }
304              
305 328         670 return $self;
306             }
307              
308             sub _unquote_single {
309 232     232   283 my ($self, $string) = @_;
310 232 100       385 return '' unless length $string;
311 227         208 $string =~ s/\'\'/\'/g;
312 227         788 return $string;
313             }
314              
315             sub _unquote_double {
316 87     87   115 my ($self, $string) = @_;
317 87 100       145 return '' unless length $string;
318 86         98 $string =~ s/\\"/"/g;
319 86         197 $string =~
320             s{\\([Nnever\\fartz0b]|x([0-9a-fA-F]{2}))}
321 74 100       242 {(length($1)>1)?pack("H2",$2):$UNESCAPES{$1}}gex;
322 86         339 return $string;
323             }
324              
325             # Load a YAML scalar string to the actual Perl scalar
326             sub _load_scalar {
327 930     930   955 my ($self, $string, $indent, $lines) = @_;
328              
329             # Trim trailing whitespace
330 930         2682 $string =~ s/\s*\z//;
331              
332             # Explitic null/undef
333 930 100       1547 return undef if $string eq '~';
334              
335             # Single quote
336 892 100       2765 if ( $string =~ /^$re_capture_single_quoted$re_trailing_comment\z/ ) {
337 185         258 return $self->_unquote_single($1);
338             }
339              
340             # Double quote.
341 707 100       1695 if ( $string =~ /^$re_capture_double_quoted$re_trailing_comment\z/ ) {
342 58         89 return $self->_unquote_double($1);
343             }
344              
345             # Special cases
346 649 100       983 if ( $string =~ /^[\'\"!&]/ ) {
347 2         12 die \"YAML::Tiny does not support a feature in line '$string'";
348             }
349 647 100       892 return {} if $string =~ /^{}(?:\s+\#.*)?\z/;
350 637 100       785 return [] if $string =~ /^\[\](?:\s+\#.*)?\z/;
351              
352             # Regular unquoted string
353 627 100       946 if ( $string !~ /^[>|]/ ) {
354 614 100 100     2103 die \"YAML::Tiny found illegal characters in plain scalar: '$string'"
355             if $string =~ /^(?:-(?:\s|$)|[\@\%\`])/ or
356             $string =~ /:(?:\s|$)/;
357 606         545 $string =~ s/\s+#.*\z//;
358 606         2321 return $string;
359             }
360              
361             # Error
362 13 50       28 die \"YAML::Tiny failed to find multi-line scalar content" unless @$lines;
363              
364             # Check the indent depth
365 13         25 $lines->[0] =~ /^(\s*)/;
366 13         30 $indent->[-1] = length("$1");
367 13 50 33     63 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         22 my @multiline = ();
373 13         25 while ( @$lines ) {
374 35         58 $lines->[0] =~ /^(\s*)/;
375 35 100       67 last unless length($1) >= $indent->[-1];
376 28         98 push @multiline, substr(shift(@$lines), $indent->[-1]);
377             }
378              
379 13 100       40 my $j = (substr($string, 0, 1) eq '>') ? ' ' : "\n";
380 13 100       29 my $t = (substr($string, 1, 1) eq '-') ? '' : "\n";
381 13         72 return join( $j, @multiline ) . $t;
382             }
383              
384             # Load an array
385             sub _load_array {
386 110     110   135 my ($self, $array, $indent, $lines) = @_;
387              
388 110         201 while ( @$lines ) {
389             # Check for a new document
390 287 100       585 if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
391 15   100     65 while ( @$lines and $lines->[0] !~ /^---/ ) {
392 5         13 shift @$lines;
393             }
394 15         43 return 1;
395             }
396              
397             # Check the indent level
398 272         362 $lines->[0] =~ /^(\s*)/;
399 272 100       677 if ( length($1) < $indent->[-1] ) {
    50          
400 24         57 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     1162 if ( $lines->[0] =~ /^(\s*\-\s+)[^\'\"]\S*\s*:(?:\s+|$)/ ) {
    100          
    100          
    50          
406             # Inline nested hash
407 26         39 my $indent2 = length("$1");
408 26         69 $lines->[0] =~ s/-/ /;
409 26         35 push @$array, { };
410 26         65 $self->_load_hash( $array->[-1], [ @$indent, $indent2 ], $lines );
411              
412             } elsif ( $lines->[0] =~ /^\s*\-\s*\z/ ) {
413 38         34 shift @$lines;
414 38 100       61 unless ( @$lines ) {
415 2         3 push @$array, undef;
416 2         7 return 1;
417             }
418 36 100       95 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         8 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         39 push @$array, { };
433 32         84 $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         148 shift @$lines;
444 180         519 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         11 return 1;
457              
458             } else {
459 0         0 die \"YAML::Tiny failed to classify line '$lines->[0]'";
460             }
461             }
462              
463 62         159 return 1;
464             }
465              
466             # Load a hash
467             sub _load_hash {
468 418     418   399 my ($self, $hash, $indent, $lines) = @_;
469              
470 418         590 while ( @$lines ) {
471             # Check for a new document
472 1039 100       1742 if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
473 3   100     18 while ( @$lines and $lines->[0] !~ /^---/ ) {
474 1         3 shift @$lines;
475             }
476 3         8 return 1;
477             }
478              
479             # Check the indent level
480 1036         1178 $lines->[0] =~ /^(\s*)/;
481 1036 100       2193 if ( length($1) < $indent->[-1] ) {
    50          
482 155         318 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         546 my $key;
489              
490             # Quoted keys
491 881 100       7594 if ( $lines->[0] =~
    100          
    100          
    50          
492             s/^\s*$re_capture_single_quoted$re_key_value_separator//
493             ) {
494 47         68 $key = $self->_unquote_single($1);
495             }
496             elsif ( $lines->[0] =~
497             s/^\s*$re_capture_double_quoted$re_key_value_separator//
498             ) {
499 29         51 $key = $self->_unquote_double($1);
500             }
501             elsif ( $lines->[0] =~
502             s/^\s*$re_capture_unquoted_key$re_key_value_separator//
503             ) {
504 804         871 $key = $1;
505 804         962 $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         10 die \"YAML::Tiny failed to classify line '$lines->[0]'";
512             }
513              
514 880 100       1481 if ( exists $hash->{$key} ) {
515 1         15 warn "YAML::Tiny found a duplicate key '$key' in line '$lines->[0]'";
516             }
517              
518             # Do we have a value?
519 880 100       1318 if ( length $lines->[0] ) {
520             # Yes
521 707         1383 $hash->{$key} = $self->_load_scalar(
522             shift(@$lines), [ @$indent, undef ], $lines
523             );
524             } else {
525             # An indent
526 173         133 shift @$lines;
527 173 100       353 unless ( @$lines ) {
528 1         2 $hash->{$key} = undef;
529 1         5 return 1;
530             }
531 172 100       475 if ( $lines->[0] =~ /^(\s*)-/ ) {
    50          
532 44         93 $hash->{$key} = [];
533             $self->_load_array(
534 44         153 $hash->{$key}, [ @$indent, length($1) ], $lines
535             );
536             } elsif ( $lines->[0] =~ /^(\s*)./ ) {
537 128         173 my $indent2 = length("$1");
538 128 100       140 if ( $indent->[-1] >= $indent2 ) {
539             # Null hash entry
540 6         30 $hash->{$key} = undef;
541             } else {
542 122         176 $hash->{$key} = {};
543             $self->_load_hash(
544 122         301 $hash->{$key}, [ @$indent, length($1) ], $lines
545             );
546             }
547             }
548             }
549             }
550              
551 251         656 return 1;
552             }
553              
554              
555             ###
556             # Dumper functions:
557              
558             # Save an object to a file
559             sub _dump_file {
560 5     5   4 my $self = shift;
561              
562 5         28 require Fcntl;
563              
564             # Check the file
565 5 100       16 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       8 if ( _can_flock() ) {
570             # Open without truncation (truncate comes after lock)
571 4         23 my $flags = Fcntl::O_WRONLY()|Fcntl::O_CREAT();
572 4 50       232 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         79 binmode( $fh, ":raw:encoding(UTF-8)");
577              
578 4 50       16709 flock( $fh, Fcntl::LOCK_EX() )
579             or warn "Couldn't lock '$file' for reading: $!";
580              
581             # truncate and spew contents
582 4         106 truncate $fh, 0;
583 4         13 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         6 print {$fh} $self->_dump_string;
  4         15  
591              
592             # close the file (release the lock)
593 4 50       192 unless ( close $fh ) {
594 0         0 $self->_error("Failed to close file '$file': $!");
595             }
596              
597 4         31 return 1;
598             }
599              
600             # Save an object to a string
601             sub _dump_string {
602 269     269   230 my $self = shift;
603 269 100 100     1149 return '' unless ref $self && @$self;
604              
605             # Iterate over the documents
606 265         219 my $indent = 0;
607 265         294 my @lines = ();
608              
609 265         248 eval {
610 265         371 foreach my $cursor ( @$self ) {
611 278         265 push @lines, '---';
612              
613             # An empty document
614 278 100       886 if ( ! defined $cursor ) {
    100          
    100          
    50          
615             # Do nothing
616              
617             # A scalar document
618             } elsif ( ! ref $cursor ) {
619 19         38 $lines[-1] .= ' ' . $self->_dump_scalar( $cursor );
620              
621             # A list at the root
622             } elsif ( ref $cursor eq 'ARRAY' ) {
623 33 100       60 unless ( @$cursor ) {
624 1         3 $lines[-1] .= ' []';
625 1         2 next;
626             }
627 32         81 push @lines, $self->_dump_array( $cursor, $indent, {} );
628              
629             # A hash at the root
630             } elsif ( ref $cursor eq 'HASH' ) {
631 219 100       692 unless ( %$cursor ) {
632 1         3 $lines[-1] .= ' {}';
633 1         3 next;
634             }
635 218         437 push @lines, $self->_dump_hash( $cursor, $indent, {} );
636              
637             } else {
638 0         0 die \("Cannot serialize " . ref($cursor));
639             }
640             }
641             };
642 265 100       546 if ( ref $@ eq 'SCALAR' ) {
    50          
643 1         2 $self->_error(${$@});
  1         4  
644             } elsif ( $@ ) {
645 0         0 $self->_error($@);
646             }
647              
648 264         288 join '', map { "$_\n" } @lines;
  942         1553  
649             }
650              
651             sub _has_internal_string_value {
652 1105     1105   742 my $value = shift;
653 1105         1770 my $b_obj = B::svref_2object(\$value); # for round trip problem
654 1105         1904 return $b_obj->FLAGS & B::SVf_POK();
655             }
656              
657             sub _dump_scalar {
658 1105     1105   788 my $string = $_[1];
659 1105         724 my $is_key = $_[2];
660             # Check this before checking length or it winds up looking like a string!
661 1105         1080 my $has_string_flag = _has_internal_string_value($string);
662 1105 100       1448 return '~' unless defined $string;
663 1081 100       1244 return "''" unless length $string;
664 1078 100       1882 if (Scalar::Util::looks_like_number($string)) {
665             # keys and values that have been used as strings get quoted
666 90 100 100     236 if ( $is_key || $has_string_flag ) {
667 56         109 return qq['$string'];
668             }
669             else {
670 34         60 return $string;
671             }
672             }
673 988 100       1705 if ( $string =~ /[\x00-\x09\x0b-\x0d\x0e-\x1f\x7f-\x9f\'\n]/ ) {
674 98         101 $string =~ s/\\/\\\\/g;
675 98         71 $string =~ s/"/\\"/g;
676 98         93 $string =~ s/\n/\\n/g;
677 98         73 $string =~ s/[\x85]/\\N/g;
678 98         226 $string =~ s/([\x00-\x1f])/\\$UNPRINTABLE[ord($1)]/g;
679 98         110 $string =~ s/([\x7f-\x9f])/'\x' . sprintf("%X",ord($1))/ge;
  12         34  
680 98         211 return qq|"$string"|;
681             }
682 890 100 66     3214 if ( $string =~ /(?:^[~!@#%&*|>?:,'"`{}\[\]]|^-+$|\s|:\z)/ or
683             $QUOTE{$string}
684             ) {
685 193         416 return "'$string'";
686             }
687 697         1001 return $string;
688             }
689              
690             sub _dump_array {
691 55     55   67 my ($self, $array, $indent, $seen) = @_;
692 55 50       216 if ( $seen->{refaddr($array)}++ ) {
693 0         0 die \"YAML::Tiny does not support circular references";
694             }
695 55         63 my @lines = ();
696 55         71 foreach my $el ( @$array ) {
697 127         154 my $line = (' ' x $indent) . '-';
698 127         105 my $type = ref $el;
699 127 100       195 if ( ! $type ) {
    100          
    50          
700 94         119 $line .= ' ' . $self->_dump_scalar( $el );
701 94         149 push @lines, $line;
702              
703             } elsif ( $type eq 'ARRAY' ) {
704 1 50       3 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       58 if ( keys %$el ) {
714 31         32 push @lines, $line;
715 31         56 push @lines, $self->_dump_hash( $el, $indent + 1, $seen );
716             } else {
717 1         1 $line .= ' {}';
718 1         2 push @lines, $line;
719             }
720              
721             } else {
722 0         0 die \"YAML::Tiny does not support $type references";
723             }
724             }
725              
726 53         171 @lines;
727             }
728              
729             sub _dump_hash {
730 310     310   303 my ($self, $hash, $indent, $seen) = @_;
731 310 100       1081 if ( $seen->{refaddr($hash)}++ ) {
732 1         6 die \"YAML::Tiny does not support circular references";
733             }
734 309         315 my @lines = ();
735 309         752 foreach my $name ( sort keys %$hash ) {
736 542         497 my $el = $hash->{$name};
737 542         778 my $line = (' ' x $indent) . $self->_dump_scalar($name, 1) . ":";
738 542         499 my $type = ref $el;
739 542 100       691 if ( ! $type ) {
    100          
    50          
740 452         504 $line .= ' ' . $self->_dump_scalar( $el );
741 452         701 push @lines, $line;
742              
743             } elsif ( $type eq 'ARRAY' ) {
744 26 100       41 if ( @$el ) {
745 23         26 push @lines, $line;
746 23         60 push @lines, $self->_dump_array( $el, $indent + 1, $seen );
747             } else {
748 3         3 $line .= ' []';
749 3         29 push @lines, $line;
750             }
751              
752             } elsif ( $type eq 'HASH' ) {
753 64 100       85 if ( keys %$el ) {
754 61         52 push @lines, $line;
755 61         108 push @lines, $self->_dump_hash( $el, $indent + 1, $seen );
756             } else {
757 3         3 $line .= ' {}';
758 3         5 push @lines, $line;
759             }
760              
761             } else {
762 0         0 die \"YAML::Tiny does not support $type references";
763             }
764             }
765              
766 308         801 @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   120 require Carp;
780 22         28 $errstr = $_[1];
781 22         72 $errstr =~ s/ at \S+ line \d+.*//;
782 22         2875 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   85 use B;
  12         17  
  12         1339  
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   31 if ( defined $HAS_FLOCK ) {
812 13         27 return $HAS_FLOCK;
813             }
814             else {
815 3         45 require Config;
816 3         6 my $c = \%Config::Config;
817 3         7 $HAS_FLOCK = grep { $c->{$_} } qw/d_flock d_fcntl_can_lock d_lockf/;
  9         286  
818 3 50       18 require Fcntl if $HAS_FLOCK;
819 3         9 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   57 use Scalar::Util ();
  12         17  
  12         584  
830             BEGIN {
831 12     12   23 local $@;
832 12 50       17 if ( eval { Scalar::Util->VERSION(1.18); } ) {
  12         344  
833 12         652 *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__