File Coverage

blib/lib/YAML/Tiny.pm
Criterion Covered Total %
statement 315 342 92.4
branch 171 202 84.6
condition 37 42 88.1
subroutine 34 35 97.1
pod 10 10 100.0
total 567 631 90.0


line stmt bran cond sub pod time code
1 12     12   913369 use 5.008001; # sane UTF-8 support
  12         198  
2 12     12   73 use strict;
  12         58  
  12         307  
3 12     12   65 use warnings;
  12         52  
  12         735  
4             package YAML::Tiny; # git description: v1.72-7-g8682f63
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.73';
10              
11             #####################################################################
12             # The YAML::Tiny API.
13             #
14             # These are the currently documented API functions/methods and
15             # exports:
16              
17 12     12   93 use Exporter;
  12         36  
  12         2591  
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 170886 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 84286 my $self = YAML::Tiny->_load_string(@_);
34 114 100       199 if ( wantarray ) {
35 54         148 return @$self;
36             } else {
37             # To match YAML.pm, return the last document
38 60         185 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         8920 *thaw = \&Load;
47             }
48              
49             sub DumpFile {
50 1     1 1 2142 my $file = shift;
51 1         5 return YAML::Tiny->new(@_)->_dump_file($file);
52             }
53              
54             sub LoadFile {
55 1     1 1 607 my $file = shift;
56 1         12 my $self = YAML::Tiny->_load_file($file);
57 1 50       4 if ( wantarray ) {
58 1         7 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 54767 my $class = shift;
80 172         558 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 765478 my $self = shift;
93 218         799 $self->_load_string(@_);
94             }
95              
96             sub write_string {
97 105     105 1 281269 my $self = shift;
98 105         469 $self->_dump_string(@_);
99             }
100              
101             sub read {
102 14     14 1 28360 my $self = shift;
103 14         56 $self->_load_file(@_);
104             }
105              
106             sub write {
107 4     4 1 17 my $self = shift;
108 4         25 $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   53 my $class = ref $_[0] ? ref shift : shift;
172              
173             # Check the file
174 15 100       56 my $file = shift or $class->_error( 'You did not specify a file name' );
175 14 100       304 $class->_error( "File '$file' does not exist" )
176             unless -e $file;
177 13 100       61 $class->_error( "'$file' is a directory, not a file" )
178             unless -f _;
179 12 50       122 $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   461 open( my $fh, "<:unix:encoding(UTF-8)", $file );
  3         18  
  3         5  
  3         27  
184 12 50       10613 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       40 if ( _can_flock() ) {
190 12 50       165 flock( $fh, Fcntl::LOCK_SH() )
191             or warn "Couldn't lock '$file' for reading: $!";
192             }
193              
194             # slurp the contents
195 12         40 my $contents = eval {
196 12     12   102 use warnings FATAL => 'utf8';
  12         24  
  12         54226  
197 12         60 local $/;
198             <$fh>
199 12         498 };
200 12 100       290 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       175 unless ( close $fh ) {
206 0         0 $class->_error("Failed to close file '$file': $!");
207             }
208              
209 10         76 $class->_load_string( $contents );
210             }
211              
212             # Create an object from a string
213             sub _load_string {
214 343 100   343   947 my $class = ref $_[0] ? ref shift : shift;
215 343         695 my $self = bless [], $class;
216 343         660 my $string = $_[0];
217 343         510 eval {
218 343 100       844 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     1967 if ( utf8::is_utf8($string) && ! utf8::valid($string) ) {
225 1         7 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         916 utf8::upgrade($string);
233              
234             # Check for and strip any leading UTF-8 BOM
235 341         985 $string =~ s/^\x{FEFF}//;
236              
237             # Check for some special cases
238 341 100       1054 return $self unless length $string;
239              
240             # Split the file into lines
241 337         5423 my @lines = grep { ! /^\s*(?:\#.*)?\z/ }
  1456         5302  
242             split /(?:\015{1,2}\012|\015|\012)/, $string;
243              
244             # Strip the initial YAML header
245 337 100 100     1770 @lines and $lines[0] =~ /^\%YAML[: ][\d\.]+.*\z/ and shift @lines;
246              
247             # A nibbling parser
248 337         621 my $in_document = 0;
249 337         798 while ( @lines ) {
250             # Do we have a document header?
251 361 100       1613 if ( $lines[0] =~ /^---\s*(?:(.+)\s*)?\z/ ) {
252             # Handle scalar documents
253 278         475 shift @lines;
254 278 100 100     1159 if ( defined $1 and $1 !~ /^(?:\#.+|\%YAML[: ][\d\.]+)\z/ ) {
255 43         310 push @$self,
256             $self->_load_scalar( "$1", [ undef ], \@lines );
257 43         174 next;
258             }
259 235         436 $in_document = 1;
260             }
261              
262 318 100 100     2893 if ( ! @lines or $lines[0] =~ /^(?:---|\.\.\.)/ ) {
    100 100        
    100          
    50          
263             # A naked document
264 12         27 push @$self, undef;
265 12   66     46 while ( @lines and $lines[0] !~ /^---/ ) {
266 0         0 shift @lines;
267             }
268 12         30 $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         176 my $document = [ ];
278 66         179 push @$self, $document;
279 66         332 $self->_load_array( $document, [ 0 ], \@lines );
280              
281             } elsif ( $lines[0] =~ /^(\s*)\S/ ) {
282             # A hash at the root
283 238         497 my $document = { };
284 238         508 push @$self, $document;
285 238         1161 $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         744 my $err = $@;
299 343 100       1083 if ( ref $err eq 'SCALAR' ) {
    50          
300 15         25 $self->_error(${$err});
  15         58  
301             } elsif ( $err ) {
302 0         0 $self->_error($err);
303             }
304              
305 328         1121 return $self;
306             }
307              
308             sub _unquote_single {
309 232     232   566 my ($self, $string) = @_;
310 232 100       535 return '' unless length $string;
311 227         380 $string =~ s/\'\'/\'/g;
312 227         876 return $string;
313             }
314              
315             sub _unquote_double {
316 87     87   299 my ($self, $string) = @_;
317 87 100       276 return '' unless length $string;
318 86         185 $string =~ s/\\"/"/g;
319 86         369 $string =~
320 74 100       383 s{\\([Nnever\\fartz0b]|x([0-9a-fA-F]{2}))}
321 86         446 {(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   1848 sub _load_scalar {
327             my ($self, $string, $indent, $lines) = @_;
328              
329 930         3936 # Trim trailing whitespace
330             $string =~ s/\s*\z//;
331              
332 930 100       2369 # Explitic null/undef
333             return undef if $string eq '~';
334              
335 892 100       3815 # Single quote
336 185         406 if ( $string =~ /^$re_capture_single_quoted$re_trailing_comment\z/ ) {
337             return $self->_unquote_single($1);
338             }
339              
340 707 100       2601 # Double quote.
341 58         204 if ( $string =~ /^$re_capture_double_quoted$re_trailing_comment\z/ ) {
342             return $self->_unquote_double($1);
343             }
344              
345 649 100       1497 # Special cases
346 2         16 if ( $string =~ /^[\'\"!&]/ ) {
347             die \"YAML::Tiny does not support a feature in line '$string'";
348 647 100       1279 }
349 637 100       1160 return {} if $string =~ /^{}(?:\s+\#.*)?\z/;
350             return [] if $string =~ /^\[\](?:\s+\#.*)?\z/;
351              
352 627 100       1348 # Regular unquoted string
353 614 100 100     2399 if ( $string !~ /^[>|]/ ) {
354             die \"YAML::Tiny found illegal characters in plain scalar: '$string'"
355             if $string =~ /^(?:-(?:\s|$)|[\@\%\`])/ or
356 606         1005 $string =~ /:(?:\s|$)/;
357 606         2858 $string =~ s/\s+#.*\z//;
358             return $string;
359             }
360              
361 13 50       47 # Error
362             die \"YAML::Tiny failed to find multi-line scalar content" unless @$lines;
363              
364 13         52 # Check the indent depth
365 13         70 $lines->[0] =~ /^(\s*)/;
366 13 50 33     86 $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         38 # Pull the lines
372 13         46 my @multiline = ();
373 35         119 while ( @$lines ) {
374 35 100       125 $lines->[0] =~ /^(\s*)/;
375 28         139 last unless length($1) >= $indent->[-1];
376             push @multiline, substr(shift(@$lines), $indent->[-1]);
377             }
378 13 100       69  
379 13 100       62 my $j = (substr($string, 0, 1) eq '>') ? ' ' : "\n";
380 13         114 my $t = (substr($string, 1, 1) eq '-') ? '' : "\n";
381             return join( $j, @multiline ) . $t;
382             }
383              
384             # Load an array
385 110     110   307 sub _load_array {
386             my ($self, $array, $indent, $lines) = @_;
387 110         307  
388             while ( @$lines ) {
389 287 100       889 # Check for a new document
390 15   100     113 if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
391 5         27 while ( @$lines and $lines->[0] !~ /^---/ ) {
392             shift @$lines;
393 15         70 }
394             return 1;
395             }
396              
397 272         629 # Check the indent level
398 272 100       998 $lines->[0] =~ /^(\s*)/;
    50          
399 24         78 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     1855  
    100          
    100          
    50          
405             if ( $lines->[0] =~ /^(\s*\-\s+)[^\'\"]\S*\s*:(?:\s+|$)/ ) {
406 26         52 # Inline nested hash
407 26         73 my $indent2 = length("$1");
408 26         52 $lines->[0] =~ s/-/ /;
409 26         63 push @$array, { };
410             $self->_load_hash( $array->[-1], [ @$indent, $indent2 ], $lines );
411              
412 38         61 } elsif ( $lines->[0] =~ /^\s*\-\s*\z/ ) {
413 38 100       74 shift @$lines;
414 2         5 unless ( @$lines ) {
415 2         9 push @$array, undef;
416             return 1;
417 36 100       117 }
    50          
418 4         14 if ( $lines->[0] =~ /^(\s*)\-/ ) {
419 4 50       13 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         58 } 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         326 # Array entry with a value
443 180         812 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         16 # ... 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         244  
463             return 1;
464             }
465              
466             # Load a hash
467 418     418   824 sub _load_hash {
468             my ($self, $hash, $indent, $lines) = @_;
469 418         787  
470             while ( @$lines ) {
471 1039 100       2454 # Check for a new document
472 3   100     63 if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
473 1         6 while ( @$lines and $lines->[0] !~ /^---/ ) {
474             shift @$lines;
475 3         18 }
476             return 1;
477             }
478              
479 1036         1908 # Check the indent level
480 1036 100       2706 $lines->[0] =~ /^(\s*)/;
    50          
481 155         334 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         1000 # Find the key
488             my $key;
489              
490 881 100       9578 # Quoted keys
    100          
    100          
    50          
491             if ( $lines->[0] =~
492             s/^\s*$re_capture_single_quoted$re_key_value_separator//
493 47         123 ) {
494             $key = $self->_unquote_single($1);
495             }
496             elsif ( $lines->[0] =~
497             s/^\s*$re_capture_double_quoted$re_key_value_separator//
498 29         80 ) {
499             $key = $self->_unquote_double($1);
500             }
501             elsif ( $lines->[0] =~
502             s/^\s*$re_capture_unquoted_key$re_key_value_separator//
503 804         1619 ) {
504 804         1456 $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         9 else {
511             die \"YAML::Tiny failed to classify line '$lines->[0]'";
512             }
513 880 100       1994  
514 1         20 if ( exists $hash->{$key} ) {
515             warn "YAML::Tiny found a duplicate key '$key' in line '$lines->[0]'";
516             }
517              
518 880 100       1787 # Do we have a value?
519             if ( length $lines->[0] ) {
520 707         1833 # Yes
521             $hash->{$key} = $self->_load_scalar(
522             shift(@$lines), [ @$indent, undef ], $lines
523             );
524             } else {
525 173         218 # An indent
526 173 100       313 shift @$lines;
527 1         4 unless ( @$lines ) {
528 1         4 $hash->{$key} = undef;
529             return 1;
530 172 100       573 }
    50          
531 44         112 if ( $lines->[0] =~ /^(\s*)-/ ) {
532             $hash->{$key} = [];
533 44         176 $self->_load_array(
534             $hash->{$key}, [ @$indent, length($1) ], $lines
535             );
536 128         238 } elsif ( $lines->[0] =~ /^(\s*)./ ) {
537 128 100       207 my $indent2 = length("$1");
538             if ( $indent->[-1] >= $indent2 ) {
539 6         19 # Null hash entry
540             $hash->{$key} = undef;
541 122         248 } else {
542             $hash->{$key} = {};
543 122         395 $self->_load_hash(
544             $hash->{$key}, [ @$indent, length($1) ], $lines
545             );
546             }
547             }
548             }
549             }
550 251         838  
551             return 1;
552             }
553              
554              
555             ###
556             # Dumper functions:
557              
558             # Save an object to a file
559 5     5   14 sub _dump_file {
560             my $self = shift;
561 5         54  
562             require Fcntl;
563              
564 5 100       31 # 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       19 # flock if available (or warn if not possible for OS-specific reasons)
569             if ( _can_flock() ) {
570 4         13 # Open without truncation (truncate comes after lock)
571 4 50       362 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         137 # Use no translation and strict UTF-8
576             binmode( $fh, ":raw:encoding(UTF-8)");
577 4 50       27634  
578             flock( $fh, Fcntl::LOCK_EX() )
579             or warn "Couldn't lock '$file' for reading: $!";
580              
581 4         196 # truncate and spew contents
582 4         70 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         18 # serialize and spew to the handle
  4         34  
590             print {$fh} $self->_dump_string;
591              
592 4 50       604 # close the file (release the lock)
593 0         0 unless ( close $fh ) {
594             $self->_error("Failed to close file '$file': $!");
595             }
596 4         81  
597             return 1;
598             }
599              
600             # Save an object to a string
601 269     269   446 sub _dump_string {
602 269 100 100     1412 my $self = shift;
603             return '' unless ref $self && @$self;
604              
605 265         436 # Iterate over the documents
606 265         540 my $indent = 0;
607             my @lines = ();
608 265         375  
609 265         503 eval {
610 278         539 foreach my $cursor ( @$self ) {
611             push @lines, '---';
612              
613 278 100       1235 # An empty document
    100          
    100          
    50          
614             if ( ! defined $cursor ) {
615             # Do nothing
616              
617             # A scalar document
618 19         106 } elsif ( ! ref $cursor ) {
619             $lines[-1] .= ' ' . $self->_dump_scalar( $cursor );
620              
621             # A list at the root
622 33 100       122 } elsif ( ref $cursor eq 'ARRAY' ) {
623 1         3 unless ( @$cursor ) {
624 1         4 $lines[-1] .= ' []';
625             next;
626 32         142 }
627             push @lines, $self->_dump_array( $cursor, $indent, {} );
628              
629             # A hash at the root
630 219 100       468 } elsif ( ref $cursor eq 'HASH' ) {
631 1         2 unless ( %$cursor ) {
632 1         4 $lines[-1] .= ' {}';
633             next;
634 218         599 }
635             push @lines, $self->_dump_hash( $cursor, $indent, {} );
636              
637 0         0 } else {
638             die \("Cannot serialize " . ref($cursor));
639             }
640             }
641 265 100       848 };
    50          
642 1         1 if ( ref $@ eq 'SCALAR' ) {
  1         4  
643             $self->_error(${$@});
644 0         0 } elsif ( $@ ) {
645             $self->_error($@);
646             }
647 264         521  
  942         2524  
648             join '', map { "$_\n" } @lines;
649             }
650              
651 1105     1105   1309 sub _has_internal_string_value {
652 1105         2590 my $value = shift;
653 1105         2660 my $b_obj = B::svref_2object(\$value); # for round trip problem
654             return $b_obj->FLAGS & B::SVf_POK();
655             }
656              
657 1105     1105   1397 sub _dump_scalar {
658 1105         1254 my $string = $_[1];
659             my $is_key = $_[2];
660 1105         1508 # Check this before checking length or it winds up looking like a string!
661 1105 100       2081 my $has_string_flag = _has_internal_string_value($string);
662 1081 100       1811 return '~' unless defined $string;
663 1078 100       2384 return "''" unless length $string;
664             if (Scalar::Util::looks_like_number($string)) {
665 90 100 100     300 # keys and values that have been used as strings get quoted
666 56         131 if ( $is_key || $has_string_flag ) {
667             return qq['$string'];
668             }
669 34         95 else {
670             return $string;
671             }
672 988 100       2288 }
673 98         213 if ( $string =~ /[\x00-\x09\x0b-\x0d\x0e-\x1f\x7f-\x9f\'\n]/ ) {
674 98         149 $string =~ s/\\/\\\\/g;
675 98         175 $string =~ s/"/\\"/g;
676 98         161 $string =~ s/\n/\\n/g;
677 98         344 $string =~ s/[\x85]/\\N/g;
678 98         190 $string =~ s/([\x00-\x1f])/\\$UNPRINTABLE[ord($1)]/g;
  12         45  
679 98         341 $string =~ s/([\x7f-\x9f])/'\x' . sprintf("%X",ord($1))/ge;
680             return qq|"$string"|;
681 890 100 100     4490 }
682             if ( $string =~ /(?:^[~!@#%&*|>?:,'"`{}\[\]]|^-+$|\s|:\z)/ or
683             $QUOTE{$string}
684 193         585 ) {
685             return "'$string'";
686 697         1590 }
687             return $string;
688             }
689              
690 55     55   181 sub _dump_array {
691 55 50       294 my ($self, $array, $indent, $seen) = @_;
692 0         0 if ( $seen->{refaddr($array)}++ ) {
693             die \"YAML::Tiny does not support circular references";
694 55         110 }
695 55         164 my @lines = ();
696 127         272 foreach my $el ( @$array ) {
697 127         190 my $line = (' ' x $indent) . '-';
698 127 100       292 my $type = ref $el;
    100          
    50          
699 94         228 if ( ! $type ) {
700 94         244 $line .= ' ' . $self->_dump_scalar( $el );
701             push @lines, $line;
702              
703 1 50       3 } 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       57 } elsif ( $type eq 'HASH' ) {
713 31         40 if ( keys %$el ) {
714 31         71 push @lines, $line;
715             push @lines, $self->_dump_hash( $el, $indent + 1, $seen );
716 1         2 } else {
717 1         1 $line .= ' {}';
718             push @lines, $line;
719             }
720              
721 0         0 } else {
722             die \"YAML::Tiny does not support $type references";
723             }
724             }
725 53         275  
726             @lines;
727             }
728              
729 310     310   603 sub _dump_hash {
730 310 100       1323 my ($self, $hash, $indent, $seen) = @_;
731 1         5 if ( $seen->{refaddr($hash)}++ ) {
732             die \"YAML::Tiny does not support circular references";
733 309         465 }
734 309         1022 my @lines = ();
735 542         864 foreach my $name ( sort keys %$hash ) {
736 542         1095 my $el = $hash->{$name};
737 542         816 my $line = (' ' x $indent) . $self->_dump_scalar($name, 1) . ":";
738 542 100       944 my $type = ref $el;
    100          
    50          
739 452         754 if ( ! $type ) {
740 452         1018 $line .= ' ' . $self->_dump_scalar( $el );
741             push @lines, $line;
742              
743 26 100       61 } elsif ( $type eq 'ARRAY' ) {
744 23         51 if ( @$el ) {
745 23         65 push @lines, $line;
746             push @lines, $self->_dump_array( $el, $indent + 1, $seen );
747 3         6 } else {
748 3         42 $line .= ' []';
749             push @lines, $line;
750             }
751              
752 64 100       138 } elsif ( $type eq 'HASH' ) {
753 61         93 if ( keys %$el ) {
754 61         131 push @lines, $line;
755             push @lines, $self->_dump_hash( $el, $indent + 1, $seen );
756 3         7 } else {
757 3         13 $line .= ' {}';
758             push @lines, $line;
759             }
760              
761 0         0 } else {
762             die \"YAML::Tiny does not support $type references";
763             }
764             }
765 308         1109  
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   143 sub _error {
779 22         53 require Carp;
780 22         101 $errstr = $_[1];
781 22         3928 $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   129 # Use to detect nv or iv
  12         31  
  12         1775  
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   57 sub _can_flock {
811 13         51 if ( defined $HAS_FLOCK ) {
812             return $HAS_FLOCK;
813             }
814 3         16 else {
815 3         9 require Config;
816 3         10 my $c = \%Config::Config;
  9         391  
817 3 50       27 $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   114  
  12         38  
  12         873  
829             use Scalar::Util ();
830 12     12   62 BEGIN {
831 12 50       45 local $@;
  12         422  
832 12         871 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__