source: ProjectBuilder/devel/pb-modules/lib/ProjectBuilder/YAML.pm@ 2607

Last change on this file since 2607 was 2607, checked in by Bruno Cornec, 4 years ago

Fix YAML pb_Dump function, adds a test for embedded YAML, export PBCONFVER from Version and fix sles 10 sudoers

File size: 41.0 KB
Line 
1use 5.008001; # sane UTF-8 support
2use strict;
3use warnings;
4use Data::Dumper;
5
6# Original is YAML::Tiny git description: v1.72-7-g8682f63
7# We rename it here to allow embedded usage during setupv
8# when no YAML implementation is available on the target system
9# We also made modifications to avoid object usage conflicting in setupv
10# and changed pb_Dump to adapt to new parameters
11#
12package ProjectBuilder::YAML;
13# XXX-INGY is 5.8.1 too old/broken for utf8?
14# XXX-XDG Lancaster consensus was that it was sufficient until
15# proven otherwise
16
17our $VERSION = '1.73';
18
19#####################################################################
20# The ProjectBuilder::YAML API.
21#
22# These are the currently documented API functions/methods and
23# exports:
24
25use vars qw(@ISA @EXPORT);
26use Exporter;
27
28our @ISA = qw{ Exporter };
29our @EXPORT = qw{ pb_Dump pb_LoadFile };
30
31
32# The commented out form is simpler, but overloaded the Perl regex
33# engine due to recursion and backtracking problems on strings
34# larger than 32,000ish characters. Keep it for reference purposes.
35# qr/\"((?:\\.|[^\"])*)\"/
36my $re_capture_double_quoted;
37my $re_capture_single_quoted;
38# unquoted re gets trailing space that needs to be stripped
39my $re_capture_unquoted_key ;
40my $re_trailing_comment ;
41my $re_key_value_separator ;
42
43sub pb_yaml_init {
44$re_capture_double_quoted = qr/\"([^\\"]*(?:\\.[^\\"]*)*)\"/;
45$re_capture_single_quoted = qr/\'([^\']*(?:\'\'[^\']*)*)\'/;
46# unquoted re gets trailing space that needs to be stripped
47$re_capture_unquoted_key = qr/([^:]+(?::+\S(?:[^:]*|.*?(?=:)))*)(?=\s*\:(?:\s+|$))/;
48$re_trailing_comment = qr/(?:\s+\#.*)?/;
49$re_key_value_separator = qr/\s*:(?:\s+(?:\#.*)?|$)/;
50}
51
52
53###
54# Functional/Export API:
55
56sub pb_Dump {
57 pb_yaml_init();
58 return _dump_string(@_);
59}
60
61sub pb_LoadFile {
62 pb_yaml_init();
63 my $file = shift;
64 my $ret = _load_file($file);
65 if ( wantarray ) {
66 return @$ret;
67 } else {
68 # Return only the last document to match YAML.pm,
69 return $ret->[-1];
70 }
71}
72
73
74#####################################################################
75# Constants
76
77# Printed form of the unprintable characters in the lowest range
78# of ASCII characters, listed by ASCII ordinal position.
79my @UNPRINTABLE = qw(
80 0 x01 x02 x03 x04 x05 x06 a
81 b t n v f r x0E x0F
82 x10 x11 x12 x13 x14 x15 x16 x17
83 x18 x19 x1A e x1C x1D x1E x1F
84);
85
86# Printable characters for escapes
87my %UNESCAPES = (
88 0 => "\x00", z => "\x00", N => "\x85",
89 a => "\x07", b => "\x08", t => "\x09",
90 n => "\x0a", v => "\x0b", f => "\x0c",
91 r => "\x0d", e => "\x1b", '\\' => '\\',
92);
93
94# XXX-INGY
95# I(ngy) need to decide if these values should be quoted in
96# ProjectBuilder::YAML or not. Probably yes.
97
98# These 3 values have special meaning when unquoted and using the
99# default YAML schema. They need quotes if they are strings.
100my %QUOTE = map { $_ => 1 } qw{
101 null true false
102};
103
104
105
106
107#####################################################################
108# ProjectBuilder::YAML Implementation.
109#
110# These are the private methods that do all the work. They may change
111# at any time.
112
113
114###
115# Loader functions:
116
117# Create an object from a file
118sub _load_file {
119 # Check the file
120 #print("SPECIAL: loadfile ".Data::Dumper::Dumper(@_)."\n");
121 my $file = shift or _error( 'You did not specify a file name' );
122 _error( "File '$file' does not exist" )
123 unless -e $file;
124 _error( "'$file' is a directory, not a file" )
125 unless -f _;
126 _error( "Insufficient permissions to read '$file'" )
127 unless -r _;
128
129 # Open unbuffered with strict UTF-8 decoding and no translation layers
130 open( my $fh, "<:unix:encoding(UTF-8)", $file );
131 unless ( $fh ) {
132 _error("Failed to open file '$file': $!");
133 }
134
135 # flock if available (or warn if not possible for OS-specific reasons)
136 if ( _can_flock() ) {
137 flock( $fh, Fcntl::LOCK_SH() )
138 or warn "Couldn't lock '$file' for reading: $!";
139 }
140
141 # slurp the contents
142 my $contents = eval {
143 use warnings FATAL => 'utf8';
144 local $/;
145 <$fh>
146 };
147 if ( my $err = $@ ) {
148 _error("Error reading from file '$file': $err");
149 }
150
151 # close the file (release the lock)
152 unless ( close $fh ) {
153 _error("Failed to close file '$file': $!");
154 }
155
156 _load_string( $contents );
157}
158
159# Create an object from a string
160sub _load_string {
161 #print("SPECIAL: loadstring ".Data::Dumper::Dumper(@_)."\n");
162 my $self = [];
163 my $string = $_[0];
164 #print("string var: $string\n");
165 eval {
166 unless ( defined $string ) {
167 die \"Did not provide a string to load";
168 }
169
170 # Check if Perl has it marked as characters, but it's internally
171 # inconsistent. E.g. maybe latin1 got read on a :utf8 layer
172 if ( utf8::is_utf8($string) && ! utf8::valid($string) ) {
173 die \<<'...';
174Read an invalid UTF-8 string (maybe mixed UTF-8 and 8-bit character set).
175Did you decode with lax ":utf8" instead of strict ":encoding(UTF-8)"?
176...
177 }
178
179 # Ensure Unicode character semantics, even for 0x80-0xff
180 utf8::upgrade($string);
181
182 # Check for and strip any leading UTF-8 BOM
183 $string =~ s/^\x{FEFF}//;
184
185 # Check for some special cases
186 return [] unless length $string;
187
188 # Split the file into lines
189 my @lines = grep { ! /^\s*(?:\#.*)?\z/ }
190 split /(?:\015{1,2}\012|\015|\012)/, $string;
191
192 # Strip the initial YAML header
193 @lines and $lines[0] =~ /^\%YAML[: ][\d\.]+.*\z/ and shift @lines;
194
195 # A nibbling parser
196 my $in_document = 0;
197 while ( @lines ) {
198 # Do we have a document header?
199 if ( $lines[0] =~ /^---\s*(?:(.+)\s*)?\z/ ) {
200 # Handle scalar documents
201 shift @lines;
202 if ( defined $1 and $1 !~ /^(?:\#.+|\%YAML[: ][\d\.]+)\z/ ) {
203 push @$self,
204 _load_scalar( "$1", [ undef ], \@lines );
205 next;
206 }
207 $in_document = 1;
208 }
209
210 if ( ! @lines or $lines[0] =~ /^(?:---|\.\.\.)/ ) {
211 # A naked document
212 push @$self, undef;
213 while ( @lines and $lines[0] !~ /^---/ ) {
214 shift @lines;
215 }
216 $in_document = 0;
217
218 # XXX The final '-+$' is to look for -- which ends up being an
219 # error later.
220 } elsif ( ! $in_document && @$self ) {
221 # only the first document can be explicit
222 die \"ProjectBuilder::YAML failed to classify the line '$lines[0]'";
223 } elsif ( $lines[0] =~ /^\s*\-(?:\s|$|-+$)/ ) {
224 # An array at the root
225 my $document = [ ];
226 push @$self, $document;
227 _load_array( $document, [ 0 ], \@lines );
228
229 } elsif ( $lines[0] =~ /^(\s*)\S/ ) {
230 # A hash at the root
231 my $document = { };
232 push @$self, $document;
233 _load_hash( $document, [ length($1) ], \@lines );
234
235 } else {
236 # Shouldn't get here. @lines have whitespace-only lines
237 # stripped, and previous match is a line with any
238 # non-whitespace. So this clause should only be reachable via
239 # a perlbug where \s is not symmetric with \S
240
241 # uncoverable statement
242 die \"ProjectBuilder::YAML failed to classify the line '$lines[0]'";
243 }
244 }
245 };
246 my $err = $@;
247 if ( ref $err eq 'SCALAR' ) {
248 _error(${$err});
249 } elsif ( $err ) {
250 _error($err);
251 }
252
253 return $self;
254}
255
256sub _unquote_single {
257 my ($string) = @_;
258 return '' unless length $string;
259 $string =~ s/\'\'/\'/g;
260 return $string;
261}
262
263sub _unquote_double {
264 my ($string) = @_;
265 return '' unless length $string;
266 $string =~ s/\\"/"/g;
267 $string =~
268 s{\\([Nnever\\fartz0b]|x([0-9a-fA-F]{2}))}
269 {(length($1)>1)?pack("H2",$2):$UNESCAPES{$1}}gex;
270 return $string;
271}
272
273# Load a YAML scalar string to the actual Perl scalar
274sub _load_scalar {
275 #print("SPECIAL: loadscalar ".Data::Dumper::Dumper(@_)."\n");
276 my ($string, $indent, $lines) = @_;
277
278 #print("SPECIAL: string ***$string***\n");
279 # Trim trailing whitespace
280 $string =~ s/\s*\z//;
281 #print("SPECIAL: string ***$string***\n");
282
283 # Explitic null/undef
284 return undef if $string eq '~';
285
286 #print("SPECIAL: string ***$string***\n");
287 #print("SPECIAL: re_capture_double_quoted ***$re_capture_double_quoted***\n");
288 #print("SPECIAL: re_capture_single_quoted ***$re_capture_single_quoted***\n");
289 #print("SPECIAL: re_trailing_comment ***$re_trailing_comment***\n");
290 # Single quote
291 if ( $string =~ /^$re_capture_single_quoted$re_trailing_comment\z/ ) {
292 return _unquote_single($1);
293 }
294
295 #print("SPECIAL: string ***$string***\n");
296 # Double quote.
297 if ( $string =~ /^$re_capture_double_quoted$re_trailing_comment\z/ ) {
298 return _unquote_double($1);
299 }
300
301 #print("SPECIAL: string ***$string***\n");
302 # Special cases
303 if ( $string =~ /^[\'\"!&]/ ) {
304 die \"ProjectBuilder::YAML does not support a feature in line '$string'";
305 }
306 return {} if $string =~ /^{}(?:\s+\#.*)?\z/;
307 return [] if $string =~ /^\[\](?:\s+\#.*)?\z/;
308
309 #print("SPECIAL: string ***$string***\n");
310 # Regular unquoted string
311 if ( $string !~ /^[>|]/ ) {
312 die \"ProjectBuilder::YAML found illegal characters in plain scalar: '$string'"
313 if $string =~ /^(?:-(?:\s|$)|[\@\%\`])/ or
314 $string =~ /:(?:\s|$)/;
315 $string =~ s/\s+#.*\z//;
316 return $string;
317 }
318
319 #print("SPECIAL: string ***$string***\n");
320 # Error
321 die \"ProjectBuilder::YAML failed to find multi-line scalar content" unless @$lines;
322
323 # Check the indent depth
324 $lines->[0] =~ /^(\s*)/;
325 $indent->[-1] = length("$1");
326 if ( defined $indent->[-2] and $indent->[-1] <= $indent->[-2] ) {
327 die \"ProjectBuilder::YAML found bad indenting in line '$lines->[0]'";
328 }
329
330 # Pull the lines
331 my @multiline = ();
332 while ( @$lines ) {
333 $lines->[0] =~ /^(\s*)/;
334 last unless length($1) >= $indent->[-1];
335 push @multiline, substr(shift(@$lines), $indent->[-1]);
336 }
337
338 my $j = (substr($string, 0, 1) eq '>') ? ' ' : "\n";
339 my $t = (substr($string, 1, 1) eq '-') ? '' : "\n";
340 return join( $j, @multiline ) . $t;
341}
342
343# Load an array
344sub _load_array {
345 #print("SPECIAL: loadarray ".Data::Dumper::Dumper(@_)."\n");
346 my ($array, $indent, $lines) = @_;
347
348 while ( @$lines ) {
349 # Check for a new document
350 if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
351 while ( @$lines and $lines->[0] !~ /^---/ ) {
352 shift @$lines;
353 }
354 return 1;
355 }
356
357 # Check the indent level
358 $lines->[0] =~ /^(\s*)/;
359 if ( length($1) < $indent->[-1] ) {
360 return 1;
361 } elsif ( length($1) > $indent->[-1] ) {
362 die \"ProjectBuilder::YAML found bad indenting in line '$lines->[0]'";
363 }
364
365 if ( $lines->[0] =~ /^(\s*\-\s+)[^\'\"]\S*\s*:(?:\s+|$)/ ) {
366 # Inline nested hash
367 my $indent2 = length("$1");
368 $lines->[0] =~ s/-/ /;
369 push @$array, { };
370 _load_hash( $array->[-1], [ @$indent, $indent2 ], $lines );
371
372 } elsif ( $lines->[0] =~ /^\s*\-\s*\z/ ) {
373 shift @$lines;
374 unless ( @$lines ) {
375 push @$array, undef;
376 return 1;
377 }
378 if ( $lines->[0] =~ /^(\s*)\-/ ) {
379 my $indent2 = length("$1");
380 if ( $indent->[-1] == $indent2 ) {
381 # Null array entry
382 push @$array, undef;
383 } else {
384 # Naked indenter
385 push @$array, [ ];
386 _load_array(
387 $array->[-1], [ @$indent, $indent2 ], $lines
388 );
389 }
390
391 } elsif ( $lines->[0] =~ /^(\s*)\S/ ) {
392 push @$array, { };
393 _load_hash(
394 $array->[-1], [ @$indent, length("$1") ], $lines
395 );
396
397 } else {
398 die \"ProjectBuilder::YAML failed to classify line '$lines->[0]'";
399 }
400
401 } elsif ( $lines->[0] =~ /^\s*\-(\s*)(.+?)\s*\z/ ) {
402 # Array entry with a value
403 shift @$lines;
404 push @$array, _load_scalar(
405 "$2", [ @$indent, undef ], $lines
406 );
407
408 } elsif ( defined $indent->[-2] and $indent->[-1] == $indent->[-2] ) {
409 # This is probably a structure like the following...
410 # ---
411 # foo:
412 # - list
413 # bar: value
414 #
415 # ... so lets return and let the hash parser handle it
416 return 1;
417
418 } else {
419 die \"ProjectBuilder::YAML failed to classify line '$lines->[0]'";
420 }
421 }
422
423 return 1;
424}
425
426# Load a hash
427sub _load_hash {
428 #print("SPECIAL: loadhash ".Data::Dumper::Dumper(@_)."\n");
429 my ($hash, $indent, $lines) = @_;
430
431 while ( @$lines ) {
432 # Check for a new document
433 if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
434 while ( @$lines and $lines->[0] !~ /^---/ ) {
435 shift @$lines;
436 }
437 return 1;
438 }
439
440 # Check the indent level
441 $lines->[0] =~ /^(\s*)/;
442 if ( length($1) < $indent->[-1] ) {
443 return 1;
444 } elsif ( length($1) > $indent->[-1] ) {
445 die \"ProjectBuilder::YAML found bad indenting in line '$lines->[0]'";
446 }
447
448 # Find the key
449 my $key;
450
451 # Quoted keys
452 if ( $lines->[0] =~
453 s/^\s*$re_capture_single_quoted$re_key_value_separator//
454 ) {
455 $key = _unquote_single($1);
456 }
457 elsif ( $lines->[0] =~
458 s/^\s*$re_capture_double_quoted$re_key_value_separator//
459 ) {
460 $key = _unquote_double($1);
461 }
462 elsif ( $lines->[0] =~
463 s/^\s*$re_capture_unquoted_key$re_key_value_separator//
464 ) {
465 $key = $1;
466 $key =~ s/\s+$//;
467 }
468 elsif ( $lines->[0] =~ /^\s*\?/ ) {
469 die \"ProjectBuilder::YAML does not support a feature in line '$lines->[0]'";
470 }
471 else {
472 die \"ProjectBuilder::YAML failed to classify line '$lines->[0]'";
473 }
474
475 if ( exists $hash->{$key} ) {
476 warn "ProjectBuilder::YAML found a duplicate key '$key' in line '$lines->[0]'";
477 }
478
479 # Do we have a value?
480 if ( length $lines->[0] ) {
481 # Yes
482 $hash->{$key} = _load_scalar(
483 shift(@$lines), [ @$indent, undef ], $lines
484 );
485 } else {
486 # An indent
487 shift @$lines;
488 unless ( @$lines ) {
489 $hash->{$key} = undef;
490 return 1;
491 }
492 if ( $lines->[0] =~ /^(\s*)-/ ) {
493 $hash->{$key} = [];
494 _load_array(
495 $hash->{$key}, [ @$indent, length($1) ], $lines
496 );
497 } elsif ( $lines->[0] =~ /^(\s*)./ ) {
498 my $indent2 = length("$1");
499 if ( $indent->[-1] >= $indent2 ) {
500 # Null hash entry
501 $hash->{$key} = undef;
502 } else {
503 $hash->{$key} = {};
504 _load_hash(
505 $hash->{$key}, [ @$indent, length($1) ], $lines
506 );
507 }
508 }
509 }
510 }
511
512 return 1;
513}
514
515
516# Save an object to a string
517sub _dump_string {
518 my $self = $_[0];
519 return '' unless ref $self && %$self;
520
521 #print("SPECIAL: dumpstring ".Data::Dumper::Dumper($self)."\n");
522
523 # Iterate over the documents
524 my $indent = 0;
525 my @lines = ();
526 push @lines, '---';
527
528 eval {
529 foreach my $c ( sort keys %$self ) {
530 my $cursor = $self->{$c};
531 #print("SPECIAL: c, cursor ".Data::Dumper::Dumper($c,$cursor)."\n");
532 push(@lines, _dump_scalar( $c, $cursor).':');
533 $indent += 1;
534
535 # An empty document
536 if ( ! defined $cursor ) {
537 # Do nothing
538
539 # A scalar document
540 } elsif ( ! ref $cursor ) {
541 #print("SPECIAL: found scalar \n");
542 $lines[-1] .= ' ' . _dump_scalar( $c, $cursor );
543 #print("SPECIAL: dumpscalar returns".Data::Dumper::Dumper($lines[-1])."\n");
544
545 # A list at the root
546 } elsif ( ref $cursor eq 'ARRAY' ) {
547 #print("SPECIAL: found array \n");
548 unless ( @$cursor ) {
549 $lines[-1] .= ' []';
550 next;
551 }
552 push @lines, _dump_array( $c, $cursor, $indent, {} );
553
554 # A hash at the root
555 } elsif ( ref $cursor eq 'HASH' ) {
556 #print("SPECIAL: found hash \n");
557 unless ( %$cursor ) {
558 $lines[-1] .= ' {}';
559 next;
560 }
561 push @lines, _dump_hash( $c, $cursor, $indent, {} );
562
563 } else {
564 die \("Cannot serialize " . ref($cursor));
565 }
566 #print("SPECIAL: lines ".Data::Dumper::Dumper(@lines)."\n");
567 $indent -= 1;
568 }
569 };
570 if ( ref $@ eq 'SCALAR' ) {
571 _error(${$@});
572 } elsif ( $@ ) {
573 _error($@);
574 }
575
576 join '', map { "$_\n" } @lines;
577}
578
579sub _has_internal_string_value {
580 my $value = shift;
581 my $b_obj = B::svref_2object(\$value); # for round trip problem
582 return $b_obj->FLAGS & B::SVf_POK();
583}
584
585sub _dump_scalar {
586 #print("SPECIAL: dumpscalar ".Data::Dumper::Dumper(@_)."\n");
587 my $string = $_[0];
588 #print("SPECIAL: string ".Data::Dumper::Dumper($string)."\n");
589 my $is_key = $_[1];
590 # Check this before checking length or it winds up looking like a string!
591 my $has_string_flag = _has_internal_string_value($string);
592 #print("SPECIAL: hasstring ".Data::Dumper::Dumper($has_string_flag)."\n");
593 return '~' unless defined $string;
594 return "''" unless length $string;
595 if (Scalar::Util::looks_like_number($string)) {
596 # keys and values that have been used as strings get quoted
597 if ( $is_key || $has_string_flag ) {
598 return qq['$string'];
599 }
600 else {
601 return $string;
602 }
603 }
604 if ( $string =~ /[\x00-\x09\x0b-\x0d\x0e-\x1f\x7f-\x9f\'\n]/ ) {
605 $string =~ s/\\/\\\\/g;
606 $string =~ s/"/\\"/g;
607 $string =~ s/\n/\\n/g;
608 $string =~ s/[\x85]/\\N/g;
609 $string =~ s/([\x00-\x1f])/\\$UNPRINTABLE[ord($1)]/g;
610 $string =~ s/([\x7f-\x9f])/'\x' . sprintf("%X",ord($1))/ge;
611 return qq|"$string"|;
612 }
613 if ( $string =~ /(?:^[~!@#%&*|>?:,'"`{}\[\]]|^-+$|\s|:\z)/ or
614 $QUOTE{$string}
615 ) {
616 return "'$string'";
617 }
618 return $string;
619}
620
621sub _dump_array {
622 #print("SPECIAL: dumparray ".Data::Dumper::Dumper(@_)."\n");
623 my ($array, $indent, $seen) = @_;
624 if ( $seen->{refaddr($array)}++ ) {
625 die \"ProjectBuilder::YAML does not support circular references";
626 }
627 my @lines = ();
628 foreach my $el ( @$array ) {
629 my $line = (' ' x $indent) . '-';
630 my $type = ref $el;
631 if ( ! $type ) {
632 $line .= ' ' . _dump_scalar( $el );
633 push @lines, $line;
634
635 } elsif ( $type eq 'ARRAY' ) {
636 if ( @$el ) {
637 push @lines, $line;
638 push @lines, _dump_array( $el, $indent + 1, $seen );
639 } else {
640 $line .= ' []';
641 push @lines, $line;
642 }
643
644 } elsif ( $type eq 'HASH' ) {
645 if ( keys %$el ) {
646 push @lines, $line;
647 push @lines, _dump_hash( $el, $indent + 1, $seen );
648 } else {
649 $line .= ' {}';
650 push @lines, $line;
651 }
652
653 } else {
654 die \"ProjectBuilder::YAML does not support $type references";
655 }
656 }
657
658 @lines;
659}
660
661sub _dump_hash {
662 #print("SPECIAL: dumphash ".Data::Dumper::Dumper(@_)."\n");
663 my ($c, $hash, $indent, $seen) = @_;
664 if ( $seen->{refaddr($hash)}++ ) {
665 die \"ProjectBuilder::YAML does not support circular references";
666 }
667 my @lines = ();
668 foreach my $name ( sort keys %$hash ) {
669 my $el = $hash->{$name};
670 my $line = (' ' x $indent) . _dump_scalar($name, 1) . ":";
671 my $type = ref $el;
672 if ( ! $type ) {
673 $line .= ' ' . _dump_scalar( $el );
674 push @lines, $line;
675
676 } elsif ( $type eq 'ARRAY' ) {
677 if ( @$el ) {
678 push @lines, $line;
679 push @lines, _dump_array( $el, $indent + 1, $seen );
680 } else {
681 $line .= ' []';
682 push @lines, $line;
683 }
684
685 } elsif ( $type eq 'HASH' ) {
686 if ( keys %$el ) {
687 push @lines, $line;
688 push @lines, _dump_hash( $el, $indent + 1, $seen );
689 } else {
690 $line .= ' {}';
691 push @lines, $line;
692 }
693
694 } else {
695 die \"ProjectBuilder::YAML does not support $type references";
696 }
697 #print("SPECIAL: lines ".Data::Dumper::Dumper(@lines)."\n");
698 }
699
700 @lines;
701}
702
703
704
705#####################################################################
706# DEPRECATED API methods:
707
708# Error storage (DEPRECATED as of 1.57)
709our $errstr = '';
710
711# Set error
712sub _error {
713 require Carp;
714 $errstr = $_[0];
715 #print("SPECIAL: error: ".Data::Dumper::Dumper(@_)."\n");
716 #$errstr =~ s/ at \S+ line \d+.*//;
717 Carp::croak( $errstr );
718}
719
720# Retrieve error
721my $errstr_warned;
722sub errstr {
723 require Carp;
724 Carp::carp( "ProjectBuilder::YAML->errstr and \$ProjectBuilder::YAML::errstr is deprecated" )
725 unless $errstr_warned++;
726 $errstr;
727}
728
729
730
731
732#####################################################################
733# Helper functions. Possibly not needed.
734
735
736# Use to detect nv or iv
737use B;
738
739# XXX-INGY Is flock ProjectBuilder::YAML's responsibility?
740# Some platforms can't flock :-(
741# XXX-XDG I think it is. When reading and writing files, we ought
742# to be locking whenever possible. People (foolishly) use YAML
743# files for things like session storage, which has race issues.
744my $HAS_FLOCK;
745sub _can_flock {
746 if ( defined $HAS_FLOCK ) {
747 return $HAS_FLOCK;
748 }
749 else {
750 require Config;
751 my $c = \%Config::Config;
752 $HAS_FLOCK = grep { $c->{$_} } qw/d_flock d_fcntl_can_lock d_lockf/;
753 require Fcntl if $HAS_FLOCK;
754 return $HAS_FLOCK;
755 }
756}
757
758
759# XXX-INGY Is this core in 5.8.1? Can we remove this?
760# XXX-XDG Scalar::Util 1.18 didn't land until 5.8.8, so we need this
761#####################################################################
762# Use Scalar::Util if possible, otherwise emulate it
763
764use Scalar::Util ();
765BEGIN {
766 local $@;
767 if ( eval { Scalar::Util->VERSION(1.18); } ) {
768 *refaddr = *Scalar::Util::refaddr;
769 }
770 else {
771 eval <<'END_PERL';
772# Scalar::Util failed to load or too old
773sub refaddr {
774 my $pkg = ref($_[0]) or return undef;
775 if ( !! UNIVERSAL::can($_[0], 'can') ) {
776 bless $_[0], 'Scalar::Util::Fake';
777 } else {
778 $pkg = undef;
779 }
780 "$_[0]" =~ /0x(\w+)/;
781 my $i = do { no warnings 'portable'; hex $1 };
782 bless $_[0], $pkg if defined $pkg;
783 $i;
784}
785END_PERL
786 }
787}
788
789delete $ProjectBuilder::YAML::{refaddr};
790
7911;
792
793# XXX-INGY Doc notes I'm putting up here. Changing the doc when it's wrong
794# but leaving grey area stuff up here.
795#
796# I would like to change Read/Write to Load/Dump below without
797# changing the actual API names.
798#
799# It might be better to put Load/Dump API in the SYNOPSIS instead of the
800# dubious OO API.
801#
802# null and bool explanations may be outdated.
803
804__END__
805
806=pod
807
808=head1 NAME
809
810ProjectBuilder::YAML - Read/Write YAML files with as little code as possible
811
812=head1 VERSION
813
814version 1.73
815
816=head1 PREAMBLE
817
818The YAML specification is huge. Really, B<really> huge. It contains all the
819functionality of XML, except with flexibility and choice, which makes it
820easier to read, but with a formal specification that is more complex than
821XML.
822
823The original pure-Perl implementation L<YAML> costs just over 4 megabytes
824of memory to load. Just like with Windows F<.ini> files (3 meg to load) and
825CSS (3.5 meg to load) the situation is just asking for a B<ProjectBuilder::YAML>
826module, an incomplete but correct and usable subset of the functionality,
827in as little code as possible.
828
829Like the other C<::Tiny> modules, ProjectBuilder::YAML has no non-core dependencies,
830does not require a compiler to install, is back-compatible to Perl v5.8.1,
831and can be inlined into other modules if needed.
832
833In exchange for this adding this extreme flexibility, it provides support
834for only a limited subset of YAML. But the subset supported contains most
835of the features for the more common uses of YAML.
836
837=head1 SYNOPSIS
838
839Assuming F<file.yml> like this:
840
841 ---
842 rootproperty: blah
843 section:
844 one: two
845 three: four
846 Foo: Bar
847 empty: ~
848
849
850Read and write F<file.yml> like this:
851
852 use ProjectBuilder::YAML;
853
854 # Open the config
855 my $yaml = ProjectBuilder::YAML->read( 'file.yml' );
856
857 # Get a reference to the first document
858 my $config = $yaml->[0];
859
860 # Or read properties directly
861 my $root = $yaml->[0]->{rootproperty};
862 my $one = $yaml->[0]->{section}->{one};
863 my $Foo = $yaml->[0]->{section}->{Foo};
864
865 # Change data directly
866 $yaml->[0]->{newsection} = { this => 'that' }; # Add a section
867 $yaml->[0]->{section}->{Foo} = 'Not Bar!'; # Change a value
868 delete $yaml->[0]->{section}; # Delete a value
869
870 # Save the document back to the file
871 $yaml->write( 'file.yml' );
872
873To create a new YAML file from scratch:
874
875 # Create a new object with a single hashref document
876 my $yaml = ProjectBuilder::YAML->new( { wibble => "wobble" } );
877
878 # Add an arrayref document
879 push @$yaml, [ 'foo', 'bar', 'baz' ];
880
881 # Save both documents to a file
882 $yaml->write( 'data.yml' );
883
884Then F<data.yml> will contain:
885
886 ---
887 wibble: wobble
888 ---
889 - foo
890 - bar
891 - baz
892
893=head1 DESCRIPTION
894
895B<ProjectBuilder::YAML> is a perl class for reading and writing YAML-style files,
896written with as little code as possible, reducing load time and memory
897overhead.
898
899Most of the time it is accepted that Perl applications use a lot
900of memory and modules. The B<::Tiny> family of modules is specifically
901intended to provide an ultralight and zero-dependency alternative to
902many more-thorough standard modules.
903
904This module is primarily for reading human-written files (like simple
905config files) and generating very simple human-readable files. Note that
906I said B<human-readable> and not B<geek-readable>. The sort of files that
907your average manager or secretary should be able to look at and make
908sense of.
909
910=for stopwords normalise
911
912L<ProjectBuilder::YAML> does not generate comments, it won't necessarily preserve the
913order of your hashes, and it will normalise if reading in and writing out
914again.
915
916It only supports a very basic subset of the full YAML specification.
917
918=for stopwords embeddable
919
920Usage is targeted at files like Perl's META.yml, for which a small and
921easily-embeddable module is extremely attractive.
922
923Features will only be added if they are human readable, and can be written
924in a few lines of code. Please don't be offended if your request is
925refused. Someone has to draw the line, and for ProjectBuilder::YAML that someone
926is me.
927
928If you need something with more power move up to L<YAML> (7 megabytes of
929memory overhead) or L<YAML::XS> (6 megabytes memory overhead and requires
930a C compiler).
931
932To restate, L<ProjectBuilder::YAML> does B<not> preserve your comments, whitespace,
933or the order of your YAML data. But it should round-trip from Perl
934structure to file and back again just fine.
935
936=head1 METHODS
937
938=for Pod::Coverage HAVE_UTF8 refaddr
939
940=head2 new
941
942The constructor C<new> creates a C<ProjectBuilder::YAML> object as a blessed array
943reference. Any arguments provided are taken as separate documents
944to be serialized.
945
946=head2 read $filename
947
948The C<read> constructor reads a YAML file from a file name,
949and returns a new C<ProjectBuilder::YAML> object containing the parsed content.
950
951Returns the object on success or throws an error on failure.
952
953=head2 read_string $string;
954
955The C<read_string> constructor reads YAML data from a character string, and
956returns a new C<ProjectBuilder::YAML> object containing the parsed content. If you have
957read the string from a file yourself, be sure that you have correctly decoded
958it into characters first.
959
960Returns the object on success or throws an error on failure.
961
962=head2 write $filename
963
964The C<write> method generates the file content for the properties, and
965writes it to disk using UTF-8 encoding to the filename specified.
966
967Returns true on success or throws an error on failure.
968
969=head2 write_string
970
971Generates the file content for the object and returns it as a character
972string. This may contain non-ASCII characters and should be encoded
973before writing it to a file.
974
975Returns true on success or throws an error on failure.
976
977=for stopwords errstr
978
979=head2 errstr (DEPRECATED)
980
981Prior to version 1.57, some errors were fatal and others were available only
982via the C<$ProjectBuilder::YAML::errstr> variable, which could be accessed via the
983C<errstr()> method.
984
985Starting with version 1.57, all errors are fatal and throw exceptions.
986
987The C<$errstr> variable is still set when exceptions are thrown, but
988C<$errstr> and the C<errstr()> method are deprecated and may be removed in a
989future release. The first use of C<errstr()> will issue a deprecation
990warning.
991
992=head1 FUNCTIONS
993
994ProjectBuilder::YAML implements a number of functions to add compatibility with
995the L<YAML> API. These should be a drop-in replacement.
996
997=head2 pb_Dump
998
999 my $string = pb_Dump(list-of-Perl-data-structures);
1000
1001Turn Perl data into YAML. This function works very much like
1002Data::Dumper::Dumper().
1003
1004It takes a list of Perl data structures and dumps them into a serialized
1005form.
1006
1007It returns a character string containing the YAML stream. Be sure to encode
1008it as UTF-8 before serializing to a file or socket.
1009
1010The structures can be references or plain scalars.
1011
1012Dies on any error.
1013
1014=head2 pb_Load
1015
1016 my @data_structures = pb_Load(string-containing-a-YAML-stream);
1017
1018Turn YAML into Perl data. This is the opposite of Dump.
1019
1020Just like L<Storable>'s thaw() function or the eval() function in relation
1021to L<Data::Dumper>.
1022
1023It parses a character string containing a valid YAML stream into a list of
1024Perl data structures representing the individual YAML documents. Be sure to
1025decode the character string correctly if the string came from a file or
1026socket.
1027
1028 my $last_data_structure = pb_Load(string-containing-a-YAML-stream);
1029
1030For consistency with YAML.pm, when Load is called in scalar context, it
1031returns the data structure corresponding to the last of the YAML documents
1032found in the input stream.
1033
1034Dies on any error.
1035
1036=head2 freeze() and thaw()
1037
1038Aliases to pb_Dump() and pb_Load() for L<Storable> fans. This will also allow
1039ProjectBuilder::YAML to be plugged directly into modules like POE.pm, that use the
1040freeze/thaw API for internal serialization.
1041
1042=head2 pb_DumpFile(filepath, list)
1043
1044Writes the YAML stream to a file with UTF-8 encoding instead of just
1045returning a string.
1046
1047Dies on any error.
1048
1049=head2 LoadFile(filepath)
1050
1051Reads the YAML stream from a UTF-8 encoded file instead of a string.
1052
1053Dies on any error.
1054
1055=head1 YAML TINY SPECIFICATION
1056
1057This section of the documentation provides a specification for "YAML Tiny",
1058a subset of the YAML specification.
1059
1060It is based on and described comparatively to the YAML 1.1 Working Draft
10612004-12-28 specification, located at L<http://yaml.org/spec/current.html>.
1062
1063Terminology and chapter numbers are based on that specification.
1064
1065=head2 1. Introduction and Goals
1066
1067The purpose of the YAML Tiny specification is to describe a useful subset
1068of the YAML specification that can be used for typical document-oriented
1069use cases such as configuration files and simple data structure dumps.
1070
1071=for stopwords extensibility
1072
1073Many specification elements that add flexibility or extensibility are
1074intentionally removed, as is support for complex data structures, class
1075and object-orientation.
1076
1077In general, the YAML Tiny language targets only those data structures
1078available in JSON, with the additional limitation that only simple keys
1079are supported.
1080
1081As a result, all possible YAML Tiny documents should be able to be
1082transformed into an equivalent JSON document, although the reverse is
1083not necessarily true (but will be true in simple cases).
1084
1085=for stopwords PCRE
1086
1087As a result of these simplifications the YAML Tiny specification should
1088be implementable in a (relatively) small amount of code in any language
1089that supports Perl Compatible Regular Expressions (PCRE).
1090
1091=head2 2. Introduction
1092
1093YAML Tiny supports three data structures. These are scalars (in a variety
1094of forms), block-form sequences and block-form mappings. Flow-style
1095sequences and mappings are not supported, with some minor exceptions
1096detailed later.
1097
1098The use of three dashes "---" to indicate the start of a new document is
1099supported, and multiple documents per file/stream is allowed.
1100
1101Both line and inline comments are supported.
1102
1103Scalars are supported via the plain style, single quote and double quote,
1104as well as literal-style and folded-style multi-line scalars.
1105
1106The use of explicit tags is not supported.
1107
1108The use of "null" type scalars is supported via the ~ character.
1109
1110The use of "bool" type scalars is not supported.
1111
1112=for stopwords serializer
1113
1114However, serializer implementations should take care to explicitly escape
1115strings that match a "bool" keyword in the following set to prevent other
1116implementations that do support "bool" accidentally reading a string as a
1117boolean
1118
1119 y|Y|yes|Yes|YES|n|N|no|No|NO
1120 |true|True|TRUE|false|False|FALSE
1121 |on|On|ON|off|Off|OFF
1122
1123The use of anchors and aliases is not supported.
1124
1125The use of directives is supported only for the %YAML directive.
1126
1127=head2 3. Processing YAML Tiny Information
1128
1129B<Processes>
1130
1131=for stopwords deserialization
1132
1133The YAML specification dictates three-phase serialization and three-phase
1134deserialization.
1135
1136The YAML Tiny specification does not mandate any particular methodology
1137or mechanism for parsing.
1138
1139Any compliant parser is only required to parse a single document at a
1140time. The ability to support streaming documents is optional and most
1141likely non-typical.
1142
1143=for stopwords acyclic
1144
1145Because anchors and aliases are not supported, the resulting representation
1146graph is thus directed but (unlike the main YAML specification) B<acyclic>.
1147
1148Circular references/pointers are not possible, and any YAML Tiny serializer
1149detecting a circular reference should error with an appropriate message.
1150
1151B<Presentation Stream>
1152
1153=for stopwords unicode
1154
1155YAML Tiny reads and write UTF-8 encoded files. Operations on strings expect
1156or produce Unicode characters not UTF-8 encoded bytes.
1157
1158B<Loading Failure Points>
1159
1160=for stopwords modality
1161
1162=for stopwords parsers
1163
1164YAML Tiny parsers and emitters are not expected to recover from, or
1165adapt to, errors. The specific error modality of any implementation is
1166not dictated (return codes, exceptions, etc.) but is expected to be
1167consistent.
1168
1169=head2 4. Syntax
1170
1171B<Character Set>
1172
1173YAML Tiny streams are processed in memory as Unicode characters and
1174read/written with UTF-8 encoding.
1175
1176The escaping and unescaping of the 8-bit YAML escapes is required.
1177
1178The escaping and unescaping of 16-bit and 32-bit YAML escapes is not
1179required.
1180
1181B<Indicator Characters>
1182
1183Support for the "~" null/undefined indicator is required.
1184
1185Implementations may represent this as appropriate for the underlying
1186language.
1187
1188Support for the "-" block sequence indicator is required.
1189
1190Support for the "?" mapping key indicator is B<not> required.
1191
1192Support for the ":" mapping value indicator is required.
1193
1194Support for the "," flow collection indicator is B<not> required.
1195
1196Support for the "[" flow sequence indicator is B<not> required, with
1197one exception (detailed below).
1198
1199Support for the "]" flow sequence indicator is B<not> required, with
1200one exception (detailed below).
1201
1202Support for the "{" flow mapping indicator is B<not> required, with
1203one exception (detailed below).
1204
1205Support for the "}" flow mapping indicator is B<not> required, with
1206one exception (detailed below).
1207
1208Support for the "#" comment indicator is required.
1209
1210Support for the "&" anchor indicator is B<not> required.
1211
1212Support for the "*" alias indicator is B<not> required.
1213
1214Support for the "!" tag indicator is B<not> required.
1215
1216Support for the "|" literal block indicator is required.
1217
1218Support for the ">" folded block indicator is required.
1219
1220Support for the "'" single quote indicator is required.
1221
1222Support for the """ double quote indicator is required.
1223
1224Support for the "%" directive indicator is required, but only
1225for the special case of a %YAML version directive before the
1226"---" document header, or on the same line as the document header.
1227
1228For example:
1229
1230 %YAML 1.1
1231 ---
1232 - A sequence with a single element
1233
1234Special Exception:
1235
1236To provide the ability to support empty sequences
1237and mappings, support for the constructs [] (empty sequence) and {}
1238(empty mapping) are required.
1239
1240For example,
1241
1242 %YAML 1.1
1243 # A document consisting of only an empty mapping
1244 --- {}
1245 # A document consisting of only an empty sequence
1246 --- []
1247 # A document consisting of an empty mapping within a sequence
1248 - foo
1249 - {}
1250 - bar
1251
1252B<Syntax Primitives>
1253
1254Other than the empty sequence and mapping cases described above, YAML Tiny
1255supports only the indentation-based block-style group of contexts.
1256
1257All five scalar contexts are supported.
1258
1259Indentation spaces work as per the YAML specification in all cases.
1260
1261Comments work as per the YAML specification in all simple cases.
1262Support for indented multi-line comments is B<not> required.
1263
1264Separation spaces work as per the YAML specification in all cases.
1265
1266B<YAML Tiny Character Stream>
1267
1268The only directive supported by the YAML Tiny specification is the
1269%YAML language/version identifier. Although detected, this directive
1270will have no control over the parsing itself.
1271
1272=for stopwords recognise
1273
1274The parser must recognise both the YAML 1.0 and YAML 1.1+ formatting
1275of this directive (as well as the commented form, although no explicit
1276code should be needed to deal with this case, being a comment anyway)
1277
1278That is, all of the following should be supported.
1279
1280 --- #YAML:1.0
1281 - foo
1282
1283 %YAML:1.0
1284 ---
1285 - foo
1286
1287 % YAML 1.1
1288 ---
1289 - foo
1290
1291Support for the %TAG directive is B<not> required.
1292
1293Support for additional directives is B<not> required.
1294
1295Support for the document boundary marker "---" is required.
1296
1297Support for the document boundary market "..." is B<not> required.
1298
1299If necessary, a document boundary should simply by indicated with a
1300"---" marker, with not preceding "..." marker.
1301
1302Support for empty streams (containing no documents) is required.
1303
1304Support for implicit document starts is required.
1305
1306That is, the following must be equivalent.
1307
1308 # Full form
1309 %YAML 1.1
1310 ---
1311 foo: bar
1312
1313 # Implicit form
1314 foo: bar
1315
1316B<Nodes>
1317
1318Support for nodes optional anchor and tag properties is B<not> required.
1319
1320Support for node anchors is B<not> required.
1321
1322Support for node tags is B<not> required.
1323
1324Support for alias nodes is B<not> required.
1325
1326Support for flow nodes is B<not> required.
1327
1328Support for block nodes is required.
1329
1330B<Scalar Styles>
1331
1332Support for all five scalar styles is required as per the YAML
1333specification, although support for quoted scalars spanning more
1334than one line is B<not> required.
1335
1336Support for multi-line scalar documents starting on the header
1337is not required.
1338
1339Support for the chomping indicators on multi-line scalar styles
1340is required.
1341
1342B<Collection Styles>
1343
1344Support for block-style sequences is required.
1345
1346Support for flow-style sequences is B<not> required.
1347
1348Support for block-style mappings is required.
1349
1350Support for flow-style mappings is B<not> required.
1351
1352Both sequences and mappings should be able to be arbitrarily
1353nested.
1354
1355Support for plain-style mapping keys is required.
1356
1357Support for quoted keys in mappings is B<not> required.
1358
1359Support for "?"-indicated explicit keys is B<not> required.
1360
1361=for stopwords endeth
1362
1363Here endeth the specification.
1364
1365=head2 Additional Perl-Specific Notes
1366
1367For some Perl applications, it's important to know if you really have a
1368number and not a string.
1369
1370That is, in some contexts is important that 3 the number is distinctive
1371from "3" the string.
1372
1373Because even Perl itself is not trivially able to understand the difference
1374(certainly without XS-based modules) Perl implementations of the YAML Tiny
1375specification are not required to retain the distinctiveness of 3 vs "3".
1376
1377=head1 SUPPORT
1378
1379Bugs should be reported via the CPAN bug tracker at
1380
1381L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=YAML-Tiny>
1382
1383=begin html
1384
1385For other issues, or commercial enhancement or support, please contact
1386<a href="http://ali.as/">Adam Kennedy</a> directly.
1387
1388=end html
1389
1390=head1 AUTHOR
1391
1392Adam Kennedy E<lt>adamk@cpan.orgE<gt>
1393
1394=head1 SEE ALSO
1395
1396=over 4
1397
1398=item * L<YAML>
1399
1400=item * L<YAML::Syck>
1401
1402=item * L<Config::Tiny>
1403
1404=item * L<CSS::Tiny>
1405
1406=item * L<http://use.perl.org/use.perl.org/_Alias/journal/29427.html>
1407
1408=item * L<http://ali.as/>
1409
1410=back
1411
1412=head1 COPYRIGHT
1413
1414Copyright 2006 - 2013 Adam Kennedy.
1415
1416This program is free software; you can redistribute
1417it and/or modify it under the same terms as Perl itself.
1418
1419The full text of the license can be found in the
1420LICENSE file included with this module.
1421
1422=cut
Note: See TracBrowser for help on using the repository browser.