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   927914 use 5.008001; # sane UTF-8 support
  12         180  
2 12     12   64 use strict;
  12         21  
  12         257  
3 12     12   68 use warnings;
  12         23  
  12         902  
4             package YAML::Tiny; # git description: v1.73-12-ge02f827
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.74';
10              
11             #####################################################################
12             # The YAML::Tiny API.
13             #
14             # These are the currently documented API functions/methods and
15             # exports:
16              
17 12     12   77 use Exporter;
  12         36  
  12         2473  
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 191797 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 77786 my $self = YAML::Tiny->_load_string(@_);
34 114 100       212 if ( wantarray ) {
35 54         159 return @$self;
36             } else {
37             # To match YAML.pm, return the last document
38 60         200 return $self->[-1];
39             }
40             }
41              
42             # XXX-INGY Do we really need freeze and thaw?
43             # XXX-XDG I don't think so. I'd support deprecating them.
44             BEGIN {
45 12     12   58 *freeze = \&Dump;
46 12         8893 *thaw = \&Load;
47             }
48              
49             sub DumpFile {
50 1     1 1 1787 my $file = shift;
51 1         4 return YAML::Tiny->new(@_)->_dump_file($file);
52             }
53              
54             sub LoadFile {
55 1     1 1 319 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 49259 my $class = shift;
80 172         568 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 838424 my $self = shift;
93 218         665 $self->_load_string(@_);
94             }
95              
96             sub write_string {
97 105     105 1 322668 my $self = shift;
98 105         328 $self->_dump_string(@_);
99             }
100              
101             sub read {
102 14     14 1 25050 my $self = shift;
103 14         40 $self->_load_file(@_);
104             }
105              
106             sub write {
107 4     4 1 12 my $self = shift;
108 4         9 $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   44 my $class = ref $_[0] ? ref shift : shift;
172              
173             # Check the file
174 15 100       42 my $file = shift or $class->_error( 'You did not specify a file name' );
175 14 100       257 $class->_error( "File '$file' does not exist" )
176             unless -e $file;
177 13 100       51 $class->_error( "'$file' is a directory, not a file" )
178             unless -f _;
179 12 50       69 $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         468 open( my $fh, "<:unix:encoding(UTF-8)", $file );
184 12 50       845 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       35 if ( _can_flock() ) {
190 12 50       135 flock( $fh, Fcntl::LOCK_SH() )
191             or warn "Couldn't lock '$file' for reading: $!";
192             }
193              
194             # slurp the contents
195 12         28 my $contents = eval {
196 12     12   104 use warnings FATAL => 'utf8';
  12         23  
  12         49758  
197 12         51 local $/;
198             <$fh>
199 12         459 };
200 12 100       250 if ( my $err = $@ ) {
201 2         18 $class->_error("Error reading from file '$file': $err");
202             }
203              
204             # close the file (release the lock)
205 10 50       159 unless ( close $fh ) {
206 0         0 $class->_error("Failed to close file '$file': $!");
207             }
208              
209 10         58 $class->_load_string( $contents );
210             }
211              
212             # Create an object from a string
213             sub _load_string {
214 343 100   343   862 my $class = ref $_[0] ? ref shift : shift;
215 343         720 my $self = bless [], $class;
216 343         621 my $string = $_[0];
217 343         478 eval {
218 343 100       783 unless ( defined $string ) {
219 1         7 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     1554 if ( utf8::is_utf8($string) && ! utf8::valid($string) ) {
225 1         5 die \<<'...';
226             Read an invalid UTF-8 string (maybe mixed UTF-8 and 8-bit character set).
227             Did you decode with lax ":utf8" instead of strict ":encoding(UTF-8)"?
228             ...
229             }
230              
231             # Ensure Unicode character semantics, even for 0x80-0xff
232 341         869 utf8::upgrade($string);
233              
234             # Check for and strip any leading UTF-8 BOM
235 341         933 $string =~ s/^\x{FEFF}//;
236              
237             # Check for some special cases
238 341 100       1034 return $self unless length $string;
239              
240             # Split the file into lines
241 337         5929 my @lines = grep { ! /^\s*(?:\#.*)?\z/ }
  1456         5323  
242             split /(?:\015{1,2}\012|\015|\012)/, $string;
243              
244             # Strip the initial YAML header
245 337 100 100     1519 @lines and $lines[0] =~ /^\%YAML[: ][\d\.]+.*\z/ and shift @lines;
246              
247             # A nibbling parser
248 337         571 my $in_document = 0;
249 337         677 while ( @lines ) {
250             # Do we have a document header?
251 361 100       1416 if ( $lines[0] =~ /^---\s*(?:(.+)\s*)?\z/ ) {
252             # Handle scalar documents
253 278         458 shift @lines;
254 278 100 100     1086 if ( defined $1 and $1 !~ /^(?:\#.+|\%YAML[: ][\d\.]+)\z/ ) {
255 43         191 push @$self,
256             $self->_load_scalar( "$1", [ undef ], \@lines );
257 43         146 next;
258             }
259 235         375 $in_document = 1;
260             }
261              
262 318 100 100     2570 if ( ! @lines or $lines[0] =~ /^(?:---|\.\.\.)/ ) {
    100 100        
    100          
    50          
263             # A naked document
264 12         29 push @$self, undef;
265 12   66     43 while ( @lines and $lines[0] !~ /^---/ ) {
266 0         0 shift @lines;
267             }
268 12         33 $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         14 die \"YAML::Tiny failed to classify the line '$lines[0]'";
275             } elsif ( $lines[0] =~ /^\s*\-(?:\s|$|-+$)/ ) {
276             # An array at the root
277 66         146 my $document = [ ];
278 66         167 push @$self, $document;
279 66         214 $self->_load_array( $document, [ 0 ], \@lines );
280              
281             } elsif ( $lines[0] =~ /^(\s*)\S/ ) {
282             # A hash at the root
283 238         472 my $document = { };
284 238         501 push @$self, $document;
285 238         934 $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         628 my $err = $@;
299 343 100       900 if ( ref $err eq 'SCALAR' ) {
    50          
300 15         24 $self->_error(${$err});
  15         45  
301             } elsif ( $err ) {
302 0         0 $self->_error($err);
303             }
304              
305 328         976 return $self;
306             }
307              
308             sub _unquote_single {
309 232     232   633 my ($self, $string) = @_;
310 232 100       591 return '' unless length $string;
311 227         382 $string =~ s/\'\'/\'/g;
312 227         1402 return $string;
313             }
314              
315             sub _unquote_double {
316 87     87   282 my ($self, $string) = @_;
317 87 100       246 return '' unless length $string;
318 86         171 $string =~ s/\\"/"/g;
319 86         315 $string =~
320 74 100       356 s{\\([Nnever\\fartz0b]|x([0-9a-fA-F]{2}))}
321 86         427 {(length($1)>1)?pack("H2",$2):$UNESCAPES{$1}}gex;
322             return $string;
323             }
324              
325             # Load a YAML scalar string to the actual Perl scalar
326 930     930   1892 sub _load_scalar {
327             my ($self, $string, $indent, $lines) = @_;
328              
329 930         4091 # Trim trailing whitespace
330             $string =~ s/\s*\z//;
331              
332 930 100       2224 # Explitic null/undef
333             return undef if $string eq '~';
334              
335 892 100       3814 # Single quote
336 185         455 if ( $string =~ /^$re_capture_single_quoted$re_trailing_comment\z/ ) {
337             return $self->_unquote_single($1);
338             }
339              
340 707 100       2430 # Double quote.
341 58         159 if ( $string =~ /^$re_capture_double_quoted$re_trailing_comment\z/ ) {
342             return $self->_unquote_double($1);
343             }
344              
345 649 100       1456 # Special cases
346 2         16 if ( $string =~ /^[\'\"!&]/ ) {
347             die \"YAML::Tiny does not support a feature in line '$string'";
348 647 100       1194 }
349 637 100       1170 return {} if $string =~ /^{}(?:\s+\#.*)?\z/;
350             return [] if $string =~ /^\[\](?:\s+\#.*)?\z/;
351              
352 627 100       1296 # Regular unquoted string
353 614 100 100     2369 if ( $string !~ /^[>|]/ ) {
354             die \"YAML::Tiny found illegal characters in plain scalar: '$string'"
355             if $string =~ /^(?:-(?:\s|$)|[\@\%\`])/ or
356 606         990 $string =~ /:(?:\s|$)/;
357 606         2988 $string =~ s/\s+#.*\z//;
358             return $string;
359             }
360              
361 13 50       36 # Error
362             die \"YAML::Tiny failed to find multi-line scalar content" unless @$lines;
363              
364 13         38 # Check the indent depth
365 13         48 $lines->[0] =~ /^(\s*)/;
366 13 50 33     65 $indent->[-1] = length("$1");
367 0         0 if ( defined $indent->[-2] and $indent->[-1] <= $indent->[-2] ) {
368             die \"YAML::Tiny found bad indenting in line '$lines->[0]'";
369             }
370              
371 13         30 # Pull the lines
372 13         35 my @multiline = ();
373 35         86 while ( @$lines ) {
374 35 100       91 $lines->[0] =~ /^(\s*)/;
375 28         109 last unless length($1) >= $indent->[-1];
376             push @multiline, substr(shift(@$lines), $indent->[-1]);
377             }
378 13 100       50  
379 13 100       40 my $j = (substr($string, 0, 1) eq '>') ? ' ' : "\n";
380 13         93 my $t = (substr($string, 1, 1) eq '-') ? '' : "\n";
381             return join( $j, @multiline ) . $t;
382             }
383              
384             # Load an array
385 110     110   249 sub _load_array {
386             my ($self, $array, $indent, $lines) = @_;
387 110         267  
388             while ( @$lines ) {
389 287 100       779 # Check for a new document
390 15   100     79 if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
391 5         16 while ( @$lines and $lines->[0] !~ /^---/ ) {
392             shift @$lines;
393 15         54 }
394             return 1;
395             }
396              
397 272         628 # Check the indent level
398 272 100       876 $lines->[0] =~ /^(\s*)/;
    50          
399 24         99 if ( length($1) < $indent->[-1] ) {
400             return 1;
401 0         0 } elsif ( length($1) > $indent->[-1] ) {
402             die \"YAML::Tiny found bad indenting in line '$lines->[0]'";
403             }
404 248 100 33     1980  
    100          
    100          
    50          
405             if ( $lines->[0] =~ /^(\s*\-\s+)[^\'\"]\S*\s*:(?:\s+|$)/ ) {
406 26         64 # Inline nested hash
407 26         92 my $indent2 = length("$1");
408 26         64 $lines->[0] =~ s/-/ /;
409 26         77 push @$array, { };
410             $self->_load_hash( $array->[-1], [ @$indent, $indent2 ], $lines );
411              
412 38         74 } elsif ( $lines->[0] =~ /^\s*\-\s*\z/ ) {
413 38 100       82 shift @$lines;
414 2         3 unless ( @$lines ) {
415 2         11 push @$array, undef;
416             return 1;
417 36 100       136 }
    50          
418 4         15 if ( $lines->[0] =~ /^(\s*)\-/ ) {
419 4 50       15 my $indent2 = length("$1");
420             if ( $indent->[-1] == $indent2 ) {
421 4         14 # Null array entry
422             push @$array, undef;
423             } else {
424 0         0 # Naked indenter
425 0         0 push @$array, [ ];
426             $self->_load_array(
427             $array->[-1], [ @$indent, $indent2 ], $lines
428             );
429             }
430              
431 32         73 } elsif ( $lines->[0] =~ /^(\s*)\S/ ) {
432 32         114 push @$array, { };
433             $self->_load_hash(
434             $array->[-1], [ @$indent, length("$1") ], $lines
435             );
436              
437 0         0 } else {
438             die \"YAML::Tiny failed to classify line '$lines->[0]'";
439             }
440              
441             } elsif ( $lines->[0] =~ /^\s*\-(\s*)(.+?)\s*\z/ ) {
442 180         303 # Array entry with a value
443 180         728 shift @$lines;
444             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 4         15 # ... so lets return and let the hash parser handle it
456             return 1;
457              
458 0         0 } else {
459             die \"YAML::Tiny failed to classify line '$lines->[0]'";
460             }
461             }
462 62         212  
463             return 1;
464             }
465              
466             # Load a hash
467 418     418   857 sub _load_hash {
468             my ($self, $hash, $indent, $lines) = @_;
469 418         862  
470             while ( @$lines ) {
471 1039 100       2585 # Check for a new document
472 3   100     27 if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
473 1         4 while ( @$lines and $lines->[0] !~ /^---/ ) {
474             shift @$lines;
475 3         12 }
476             return 1;
477             }
478              
479 1036         2079 # Check the indent level
480 1036 100       3014 $lines->[0] =~ /^(\s*)/;
    50          
481 155         453 if ( length($1) < $indent->[-1] ) {
482             return 1;
483 0         0 } elsif ( length($1) > $indent->[-1] ) {
484             die \"YAML::Tiny found bad indenting in line '$lines->[0]'";
485             }
486              
487 881         1187 # Find the key
488             my $key;
489              
490 881 100       10287 # Quoted keys
    100          
    100          
    50          
491             if ( $lines->[0] =~
492             s/^\s*$re_capture_single_quoted$re_key_value_separator//
493 47         121 ) {
494             $key = $self->_unquote_single($1);
495             }
496             elsif ( $lines->[0] =~
497             s/^\s*$re_capture_double_quoted$re_key_value_separator//
498 29         85 ) {
499             $key = $self->_unquote_double($1);
500             }
501             elsif ( $lines->[0] =~
502             s/^\s*$re_capture_unquoted_key$re_key_value_separator//
503 804         1810 ) {
504 804         1621 $key = $1;
505             $key =~ s/\s+$//;
506             }
507 0         0 elsif ( $lines->[0] =~ /^\s*\?/ ) {
508             die \"YAML::Tiny does not support a feature in line '$lines->[0]'";
509             }
510 1         8 else {
511             die \"YAML::Tiny failed to classify line '$lines->[0]'";
512             }
513 880 100       2260  
514 1         16 if ( exists $hash->{$key} ) {
515             warn "YAML::Tiny found a duplicate key '$key' in line '$lines->[0]'";
516             }
517              
518 880 100       2040 # Do we have a value?
519             if ( length $lines->[0] ) {
520 707         2010 # Yes
521             $hash->{$key} = $self->_load_scalar(
522             shift(@$lines), [ @$indent, undef ], $lines
523             );
524             } else {
525 173         247 # An indent
526 173 100       374 shift @$lines;
527 1         3 unless ( @$lines ) {
528 1         5 $hash->{$key} = undef;
529             return 1;
530 172 100       666 }
    50          
531 44         137 if ( $lines->[0] =~ /^(\s*)-/ ) {
532             $hash->{$key} = [];
533 44         188 $self->_load_array(
534             $hash->{$key}, [ @$indent, length($1) ], $lines
535             );
536 128         300 } elsif ( $lines->[0] =~ /^(\s*)./ ) {
537 128 100       234 my $indent2 = length("$1");
538             if ( $indent->[-1] >= $indent2 ) {
539 6         23 # Null hash entry
540             $hash->{$key} = undef;
541 122         306 } else {
542             $hash->{$key} = {};
543 122         419 $self->_load_hash(
544             $hash->{$key}, [ @$indent, length($1) ], $lines
545             );
546             }
547             }
548             }
549             }
550 251         908  
551             return 1;
552             }
553              
554              
555             ###
556             # Dumper functions:
557              
558             # Save an object to a file
559 5     5   7 sub _dump_file {
560             my $self = shift;
561 5         27  
562             require Fcntl;
563              
564 5 100       21 # Check the file
565             my $file = shift or $self->_error( 'You did not specify a file name' );
566 4         10  
567             my $fh;
568 4 50       11 # flock if available (or warn if not possible for OS-specific reasons)
569             if ( _can_flock() ) {
570 4         7 # Open without truncation (truncate comes after lock)
571 4 50       297 my $flags = Fcntl::O_WRONLY()|Fcntl::O_CREAT();
572             sysopen( $fh, $file, $flags )
573             or $self->_error("Failed to open file '$file' for writing: $!");
574              
575 4         72 # Use no translation and strict UTF-8
576             binmode( $fh, ":raw:encoding(UTF-8)");
577 4 50       318  
578             flock( $fh, Fcntl::LOCK_EX() )
579             or warn "Couldn't lock '$file' for reading: $!";
580              
581 4         103 # truncate and spew contents
582 4         40 truncate $fh, 0;
583             seek $fh, 0, 0;
584             }
585 0         0 else {
586             open $fh, ">:unix:encoding(UTF-8)", $file;
587             }
588              
589 4         11 # serialize and spew to the handle
  4         15  
590             print {$fh} $self->_dump_string;
591              
592 4 50       505 # close the file (release the lock)
593 0         0 unless ( close $fh ) {
594             $self->_error("Failed to close file '$file': $!");
595             }
596 4         48  
597             return 1;
598             }
599              
600             # Save an object to a string
601 269     269   412 sub _dump_string {
602 269 100 100     1275 my $self = shift;
603             return '' unless ref $self && @$self;
604              
605 265         438 # Iterate over the documents
606 265         474 my $indent = 0;
607             my @lines = ();
608 265         364  
609 265         504 eval {
610 278         464 foreach my $cursor ( @$self ) {
611             push @lines, '---';
612              
613 278 100       1159 # An empty document
    100          
    100          
    50          
614             if ( ! defined $cursor ) {
615             # Do nothing
616              
617             # A scalar document
618 19         55 } elsif ( ! ref $cursor ) {
619             $lines[-1] .= ' ' . $self->_dump_scalar( $cursor );
620              
621             # A list at the root
622 33 100       74 } elsif ( ref $cursor eq 'ARRAY' ) {
623 1         3 unless ( @$cursor ) {
624 1         5 $lines[-1] .= ' []';
625             next;
626 32         94 }
627             push @lines, $self->_dump_array( $cursor, $indent, {} );
628              
629             # A hash at the root
630 219 100       481 } elsif ( ref $cursor eq 'HASH' ) {
631 1         4 unless ( %$cursor ) {
632 1         3 $lines[-1] .= ' {}';
633             next;
634 218         570 }
635             push @lines, $self->_dump_hash( $cursor, $indent, {} );
636              
637 0         0 } else {
638             die \("Cannot serialize " . ref($cursor));
639             }
640             }
641 265 100       752 };
    50          
642 1         1 if ( ref $@ eq 'SCALAR' ) {
  1         5  
643             $self->_error(${$@});
644 0         0 } elsif ( $@ ) {
645             $self->_error($@);
646             }
647 264         529  
  942         2646  
648             join '', map { "$_\n" } @lines;
649             }
650              
651 1105     1105   1483 sub _has_internal_string_value {
652 1105         2634 my $value = shift;
653 1105         2826 my $b_obj = B::svref_2object(\$value); # for round trip problem
654             return $b_obj->FLAGS & B::SVf_POK();
655             }
656              
657 1105     1105   1535 sub _dump_scalar {
658 1105         1410 my $string = $_[1];
659             my $is_key = $_[2];
660 1105         1647 # Check this before checking length or it winds up looking like a string!
661 1105 100       2132 my $has_string_flag = _has_internal_string_value($string);
662 1081 100       1846 return '~' unless defined $string;
663 1078 100       2428 return "''" unless length $string;
664             if (Scalar::Util::looks_like_number($string)) {
665 90 100 100     274 # keys and values that have been used as strings get quoted
666 56         152 if ( $is_key || $has_string_flag ) {
667             return qq['$string'];
668             }
669 34         82 else {
670             return $string;
671             }
672 988 100       2407 }
673 98         198 if ( $string =~ /[\x00-\x09\x0b-\x0d\x0e-\x1f\x7f-\x9f\'\n]/ ) {
674 98         140 $string =~ s/\\/\\\\/g;
675 98         165 $string =~ s/"/\\"/g;
676 98         176 $string =~ s/\n/\\n/g;
677 98         406 $string =~ s/[\x85]/\\N/g;
678 98         213 $string =~ s/([\x00-\x1f])/\\$UNPRINTABLE[ord($1)]/g;
  12         54  
679 98         331 $string =~ s/([\x7f-\x9f])/'\x' . sprintf("%X",ord($1))/ge;
680             return qq|"$string"|;
681 890 100 100     4632 }
682             if ( $string =~ /(?:^[~!@#%&*|>?:,'"`{}\[\]]|^-+$|\s|:\z)/ or
683             $QUOTE{$string}
684 193         617 ) {
685             return "'$string'";
686 697         1633 }
687             return $string;
688             }
689              
690 55     55   132 sub _dump_array {
691 55 50       258 my ($self, $array, $indent, $seen) = @_;
692 0         0 if ( $seen->{refaddr($array)}++ ) {
693             die \"YAML::Tiny does not support circular references";
694 55         105 }
695 55         102 my @lines = ();
696 127         265 foreach my $el ( @$array ) {
697 127         179 my $line = (' ' x $indent) . '-';
698 127 100       266 my $type = ref $el;
    100          
    50          
699 94         171 if ( ! $type ) {
700 94         223 $line .= ' ' . $self->_dump_scalar( $el );
701             push @lines, $line;
702              
703 1 50       5 } elsif ( $type eq 'ARRAY' ) {
704 0         0 if ( @$el ) {
705 0         0 push @lines, $line;
706             push @lines, $self->_dump_array( $el, $indent + 1, $seen );
707 1         3 } else {
708 1         2 $line .= ' []';
709             push @lines, $line;
710             }
711              
712 32 100       65 } elsif ( $type eq 'HASH' ) {
713 31         59 if ( keys %$el ) {
714 31         73 push @lines, $line;
715             push @lines, $self->_dump_hash( $el, $indent + 1, $seen );
716 1         3 } else {
717 1         3 $line .= ' {}';
718             push @lines, $line;
719             }
720              
721 0         0 } else {
722             die \"YAML::Tiny does not support $type references";
723             }
724             }
725 53         236  
726             @lines;
727             }
728              
729 310     310   594 sub _dump_hash {
730 310 100       1388 my ($self, $hash, $indent, $seen) = @_;
731 1         7 if ( $seen->{refaddr($hash)}++ ) {
732             die \"YAML::Tiny does not support circular references";
733 309         469 }
734 309         1101 my @lines = ();
735 542         964 foreach my $name ( sort keys %$hash ) {
736 542         1235 my $el = $hash->{$name};
737 542         885 my $line = (' ' x $indent) . $self->_dump_scalar($name, 1) . ":";
738 542 100       1006 my $type = ref $el;
    100          
    50          
739 452         787 if ( ! $type ) {
740 452         1110 $line .= ' ' . $self->_dump_scalar( $el );
741             push @lines, $line;
742              
743 26 100       85 } elsif ( $type eq 'ARRAY' ) {
744 23         43 if ( @$el ) {
745 23         84 push @lines, $line;
746             push @lines, $self->_dump_array( $el, $indent + 1, $seen );
747 3         7 } else {
748 3         8 $line .= ' []';
749             push @lines, $line;
750             }
751              
752 64 100       139 } elsif ( $type eq 'HASH' ) {
753 61         107 if ( keys %$el ) {
754 61         141 push @lines, $line;
755             push @lines, $self->_dump_hash( $el, $indent + 1, $seen );
756 3         4 } else {
757 3         9 $line .= ' {}';
758             push @lines, $line;
759             }
760              
761 0         0 } else {
762             die \"YAML::Tiny does not support $type references";
763             }
764             }
765 308         1160  
766             @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 22     22   127 sub _error {
779 22         47 require Carp;
780 22         80 $errstr = $_[1];
781 22         3396 $errstr =~ s/ at \S+ line \d+.*//;
782             Carp::croak( $errstr );
783             }
784              
785             # Retrieve error
786             my $errstr_warned;
787 0     0 1 0 sub errstr {
788 0 0       0 require Carp;
789             Carp::carp( "YAML::Tiny->errstr and \$YAML::Tiny::errstr is deprecated" )
790 0         0 unless $errstr_warned++;
791             $errstr;
792             }
793              
794              
795              
796              
797             #####################################################################
798             # Helper functions. Possibly not needed.
799              
800              
801 12     12   103 # Use to detect nv or iv
  12         27  
  12         1724  
802             use B;
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 16 100   16   41 sub _can_flock {
811 13         35 if ( defined $HAS_FLOCK ) {
812             return $HAS_FLOCK;
813             }
814 3         14 else {
815 3         10 require Config;
816 3         8 my $c = \%Config::Config;
  9         352  
817 3 50       24 $HAS_FLOCK = grep { $c->{$_} } qw/d_flock d_fcntl_can_lock d_lockf/;
818 3         15 require Fcntl if $HAS_FLOCK;
819             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 12     12   98  
  12         25  
  12         801  
829             use Scalar::Util ();
830 12     12   40 BEGIN {
831 12 50       42 local $@;
  12         424  
832 12         831 if ( eval { Scalar::Util->VERSION(1.18); } ) {
833             *refaddr = *Scalar::Util::refaddr;
834             }
835 0         0 else {
836             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__