Updated Module::Build to 0.35_08
[p5sagit/p5-mst-13.2.git] / cpan / Module-Build / lib / Module / Build / YAML.pm
CommitLineData
613f422f 1# Adapted from YAML::Tiny 1.40
a314697d 2package Module::Build::YAML;
3
4use strict;
613f422f 5use Carp 'croak';
a314697d 6
613f422f 7# UTF Support?
8sub HAVE_UTF8 () { $] >= 5.007003 }
9BEGIN {
10 if ( HAVE_UTF8 ) {
11 # The string eval helps hide this from Test::MinimumVersion
12 eval "require utf8;";
13 die "Failed to load UTF-8 support" if $@;
14 }
15
16 # Class structure
17 require 5.004;
18
19 $Module::Build::YAML::VERSION = '1.40';
20
21 # Error storage
22 $Module::Build::YAML::errstr = '';
23}
24
25# The character class of all characters we need to escape
26# NOTE: Inlined, since it's only used once
27# my $RE_ESCAPE = '[\\x00-\\x08\\x0b-\\x0d\\x0e-\\x1f\"\n]';
28
29# Printed form of the unprintable characters in the lowest range
30# of ASCII characters, listed by ASCII ordinal position.
31my @UNPRINTABLE = qw(
32 z x01 x02 x03 x04 x05 x06 a
33 x08 t n v f r x0e x0f
34 x10 x11 x12 x13 x14 x15 x16 x17
35 x18 x19 x1a e x1c x1d x1e x1f
36);
37
38# Printable characters for escapes
39my %UNESCAPES = (
40 z => "\x00", a => "\x07", t => "\x09",
41 n => "\x0a", v => "\x0b", f => "\x0c",
42 r => "\x0d", e => "\x1b", '\\' => '\\',
43);
44
45# Special magic boolean words
46my %QUOTE = map { $_ => 1 } qw{
47 null Null NULL
48 y Y yes Yes YES n N no No NO
49 true True TRUE false False FALSE
50 on On ON off Off OFF
51};
52
53#####################################################################
54# Implementation
55
56# Create an empty Module::Build::YAML object
a314697d 57sub new {
613f422f 58 my $class = shift;
59 bless [ @_ ], $class;
a314697d 60}
61
613f422f 62# Create an object from a file
63sub read {
64 my $class = ref $_[0] ? ref shift : shift;
65
66 # Check the file
67 my $file = shift or return $class->_error( 'You did not specify a file name' );
68 return $class->_error( "File '$file' does not exist" ) unless -e $file;
69 return $class->_error( "'$file' is a directory, not a file" ) unless -f _;
70 return $class->_error( "Insufficient permissions to read '$file'" ) unless -r _;
71
72 # Slurp in the file
73 local $/ = undef;
74 local *CFG;
75 unless ( open(CFG, $file) ) {
76 return $class->_error("Failed to open file '$file': $!");
77 }
78 my $contents = <CFG>;
79 unless ( close(CFG) ) {
80 return $class->_error("Failed to close file '$file': $!");
81 }
82
83 $class->read_string( $contents );
a314697d 84}
85
613f422f 86# Create an object from a string
87sub read_string {
88 my $class = ref $_[0] ? ref shift : shift;
89 my $self = bless [], $class;
90 my $string = $_[0];
91 unless ( defined $string ) {
92 return $self->_error("Did not provide a string to load");
93 }
94
95 # Byte order marks
96 # NOTE: Keeping this here to educate maintainers
97 # my %BOM = (
98 # "\357\273\277" => 'UTF-8',
99 # "\376\377" => 'UTF-16BE',
100 # "\377\376" => 'UTF-16LE',
101 # "\377\376\0\0" => 'UTF-32LE'
102 # "\0\0\376\377" => 'UTF-32BE',
103 # );
104 if ( $string =~ /^(?:\376\377|\377\376|\377\376\0\0|\0\0\376\377)/ ) {
105 return $self->_error("Stream has a non UTF-8 BOM");
106 } else {
107 # Strip UTF-8 bom if found, we'll just ignore it
108 $string =~ s/^\357\273\277//;
109 }
110
111 # Try to decode as utf8
112 utf8::decode($string) if HAVE_UTF8;
113
114 # Check for some special cases
115 return $self unless length $string;
116 unless ( $string =~ /[\012\015]+\z/ ) {
117 return $self->_error("Stream does not end with newline character");
118 }
119
120 # Split the file into lines
121 my @lines = grep { ! /^\s*(?:\#.*)?\z/ }
122 split /(?:\015{1,2}\012|\015|\012)/, $string;
123
124 # Strip the initial YAML header
125 @lines and $lines[0] =~ /^\%YAML[: ][\d\.]+.*\z/ and shift @lines;
126
127 # A nibbling parser
128 while ( @lines ) {
129 # Do we have a document header?
130 if ( $lines[0] =~ /^---\s*(?:(.+)\s*)?\z/ ) {
131 # Handle scalar documents
132 shift @lines;
133 if ( defined $1 and $1 !~ /^(?:\#.+|\%YAML[: ][\d\.]+)\z/ ) {
134 push @$self, $self->_read_scalar( "$1", [ undef ], \@lines );
135 next;
136 }
137 }
138
139 if ( ! @lines or $lines[0] =~ /^(?:---|\.\.\.)/ ) {
140 # A naked document
141 push @$self, undef;
142 while ( @lines and $lines[0] !~ /^---/ ) {
143 shift @lines;
144 }
145
146 } elsif ( $lines[0] =~ /^\s*\-/ ) {
147 # An array at the root
148 my $document = [ ];
149 push @$self, $document;
150 $self->_read_array( $document, [ 0 ], \@lines );
151
152 } elsif ( $lines[0] =~ /^(\s*)\S/ ) {
153 # A hash at the root
154 my $document = { };
155 push @$self, $document;
156 $self->_read_hash( $document, [ length($1) ], \@lines );
157
158 } else {
159 croak("Module::Build::YAML failed to classify the line '$lines[0]'");
160 }
161 }
162
163 $self;
a314697d 164}
165
613f422f 166# Deparse a scalar string to the actual scalar
167sub _read_scalar {
168 my ($self, $string, $indent, $lines) = @_;
169
170 # Trim trailing whitespace
171 $string =~ s/\s*\z//;
172
173 # Explitic null/undef
174 return undef if $string eq '~';
175
176 # Quotes
177 if ( $string =~ /^\'(.*?)\'\z/ ) {
178 return '' unless defined $1;
179 $string = $1;
180 $string =~ s/\'\'/\'/g;
181 return $string;
182 }
183 if ( $string =~ /^\"((?:\\.|[^\"])*)\"\z/ ) {
184 # Reusing the variable is a little ugly,
185 # but avoids a new variable and a string copy.
186 $string = $1;
187 $string =~ s/\\"/"/g;
188 $string =~ s/\\([never\\fartz]|x([0-9a-fA-F]{2}))/(length($1)>1)?pack("H2",$2):$UNESCAPES{$1}/gex;
189 return $string;
190 }
191
192 # Special cases
193 if ( $string =~ /^[\'\"!&]/ ) {
194 croak("Module::Build::YAML does not support a feature in line '$lines->[0]'");
195 }
196 return {} if $string eq '{}';
197 return [] if $string eq '[]';
198
199 # Regular unquoted string
200 return $string unless $string =~ /^[>|]/;
201
202 # Error
203 croak("Module::Build::YAML failed to find multi-line scalar content") unless @$lines;
204
205 # Check the indent depth
206 $lines->[0] =~ /^(\s*)/;
207 $indent->[-1] = length("$1");
208 if ( defined $indent->[-2] and $indent->[-1] <= $indent->[-2] ) {
209 croak("Module::Build::YAML found bad indenting in line '$lines->[0]'");
210 }
211
212 # Pull the lines
213 my @multiline = ();
214 while ( @$lines ) {
215 $lines->[0] =~ /^(\s*)/;
216 last unless length($1) >= $indent->[-1];
217 push @multiline, substr(shift(@$lines), length($1));
218 }
219
220 my $j = (substr($string, 0, 1) eq '>') ? ' ' : "\n";
221 my $t = (substr($string, 1, 1) eq '-') ? '' : "\n";
222 return join( $j, @multiline ) . $t;
a314697d 223}
224
613f422f 225# Parse an array
226sub _read_array {
227 my ($self, $array, $indent, $lines) = @_;
a314697d 228
613f422f 229 while ( @$lines ) {
230 # Check for a new document
231 if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
232 while ( @$lines and $lines->[0] !~ /^---/ ) {
233 shift @$lines;
234 }
235 return 1;
236 }
237
238 # Check the indent level
239 $lines->[0] =~ /^(\s*)/;
240 if ( length($1) < $indent->[-1] ) {
241 return 1;
242 } elsif ( length($1) > $indent->[-1] ) {
243 croak("Module::Build::YAML found bad indenting in line '$lines->[0]'");
244 }
245
246 if ( $lines->[0] =~ /^(\s*\-\s+)[^\'\"]\S*\s*:(?:\s+|$)/ ) {
247 # Inline nested hash
248 my $indent2 = length("$1");
249 $lines->[0] =~ s/-/ /;
250 push @$array, { };
251 $self->_read_hash( $array->[-1], [ @$indent, $indent2 ], $lines );
252
253 } elsif ( $lines->[0] =~ /^\s*\-(\s*)(.+?)\s*\z/ ) {
254 # Array entry with a value
255 shift @$lines;
256 push @$array, $self->_read_scalar( "$2", [ @$indent, undef ], $lines );
a314697d 257
613f422f 258 } elsif ( $lines->[0] =~ /^\s*\-\s*\z/ ) {
259 shift @$lines;
260 unless ( @$lines ) {
261 push @$array, undef;
262 return 1;
263 }
264 if ( $lines->[0] =~ /^(\s*)\-/ ) {
265 my $indent2 = length("$1");
266 if ( $indent->[-1] == $indent2 ) {
267 # Null array entry
268 push @$array, undef;
269 } else {
270 # Naked indenter
271 push @$array, [ ];
272 $self->_read_array( $array->[-1], [ @$indent, $indent2 ], $lines );
273 }
a314697d 274
613f422f 275 } elsif ( $lines->[0] =~ /^(\s*)\S/ ) {
276 push @$array, { };
277 $self->_read_hash( $array->[-1], [ @$indent, length("$1") ], $lines );
a314697d 278
613f422f 279 } else {
280 croak("Module::Build::YAML failed to classify line '$lines->[0]'");
281 }
a314697d 282
613f422f 283 } elsif ( defined $indent->[-2] and $indent->[-1] == $indent->[-2] ) {
284 # This is probably a structure like the following...
285 # ---
286 # foo:
287 # - list
288 # bar: value
289 #
290 # ... so lets return and let the hash parser handle it
291 return 1;
a314697d 292
613f422f 293 } else {
294 croak("Module::Build::YAML failed to classify line '$lines->[0]'");
295 }
296 }
a314697d 297
613f422f 298 return 1;
299}
300
301# Parse an array
302sub _read_hash {
303 my ($self, $hash, $indent, $lines) = @_;
304
305 while ( @$lines ) {
306 # Check for a new document
307 if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
308 while ( @$lines and $lines->[0] !~ /^---/ ) {
309 shift @$lines;
310 }
311 return 1;
312 }
a314697d 313
613f422f 314 # Check the indent level
315 $lines->[0] =~ /^(\s*)/;
316 if ( length($1) < $indent->[-1] ) {
317 return 1;
318 } elsif ( length($1) > $indent->[-1] ) {
319 croak("Module::Build::YAML found bad indenting in line '$lines->[0]'");
320 }
a314697d 321
613f422f 322 # Get the key
323 unless ( $lines->[0] =~ s/^\s*([^\'\" ][^\n]*?)\s*:(\s+|$)// ) {
324 if ( $lines->[0] =~ /^\s*[?\'\"]/ ) {
325 croak("Module::Build::YAML does not support a feature in line '$lines->[0]'");
326 }
327 croak("Module::Build::YAML failed to classify line '$lines->[0]'");
328 }
329 my $key = $1;
a314697d 330
613f422f 331 # Do we have a value?
332 if ( length $lines->[0] ) {
333 # Yes
334 $hash->{$key} = $self->_read_scalar( shift(@$lines), [ @$indent, undef ], $lines );
335 } else {
336 # An indent
337 shift @$lines;
338 unless ( @$lines ) {
339 $hash->{$key} = undef;
340 return 1;
341 }
342 if ( $lines->[0] =~ /^(\s*)-/ ) {
343 $hash->{$key} = [];
344 $self->_read_array( $hash->{$key}, [ @$indent, length($1) ], $lines );
345 } elsif ( $lines->[0] =~ /^(\s*)./ ) {
346 my $indent2 = length("$1");
347 if ( $indent->[-1] >= $indent2 ) {
348 # Null hash entry
349 $hash->{$key} = undef;
350 } else {
351 $hash->{$key} = {};
352 $self->_read_hash( $hash->{$key}, [ @$indent, length($1) ], $lines );
353 }
354 }
355 }
356 }
a314697d 357
613f422f 358 return 1;
359}
360
361# Save an object to a file
362sub write {
363 my $self = shift;
364 my $file = shift or return $self->_error('No file name provided');
a314697d 365
613f422f 366 # Write it to the file
367 open( CFG, '>' . $file ) or return $self->_error(
368 "Failed to open file '$file' for writing: $!"
369 );
370 print CFG $self->write_string;
371 close CFG;
a314697d 372
613f422f 373 return 1;
374}
a314697d 375
613f422f 376# Save an object to a string
377sub write_string {
378 my $self = shift;
379 return '' unless @$self;
a314697d 380
613f422f 381 # Iterate over the documents
382 my $indent = 0;
383 my @lines = ();
384 foreach my $cursor ( @$self ) {
385 push @lines, '---';
a314697d 386
613f422f 387 # An empty document
388 if ( ! defined $cursor ) {
389 # Do nothing
390
391 # A scalar document
392 } elsif ( ! ref $cursor ) {
393 $lines[-1] .= ' ' . $self->_write_scalar( $cursor, $indent );
394
395 # A list at the root
396 } elsif ( ref $cursor eq 'ARRAY' ) {
397 unless ( @$cursor ) {
398 $lines[-1] .= ' []';
399 next;
400 }
401 push @lines, $self->_write_array( $cursor, $indent, {} );
402
403 # A hash at the root
404 } elsif ( ref $cursor eq 'HASH' ) {
405 unless ( %$cursor ) {
406 $lines[-1] .= ' {}';
407 next;
408 }
409 push @lines, $self->_write_hash( $cursor, $indent, {} );
410
411 } else {
412 croak("Cannot serialize " . ref($cursor));
413 }
414 }
415
416 join '', map { "$_\n" } @lines;
417}
418
419sub _write_scalar {
420 my $string = $_[1];
421 return '~' unless defined $string;
422 return "''" unless length $string;
423 if ( $string =~ /[\x00-\x08\x0b-\x0d\x0e-\x1f\"\'\n]/ ) {
424 $string =~ s/\\/\\\\/g;
425 $string =~ s/"/\\"/g;
426 $string =~ s/\n/\\n/g;
427 $string =~ s/([\x00-\x1f])/\\$UNPRINTABLE[ord($1)]/g;
428 return qq|"$string"|;
429 }
430 if ( $string =~ /(?:^\W|\s)/ or $QUOTE{$string} ) {
431 return "'$string'";
432 }
433 return $string;
434}
435
436sub _write_array {
437 my ($self, $array, $indent, $seen) = @_;
438 if ( $seen->{refaddr($array)}++ ) {
439 die "Module::Build::YAML does not support circular references";
440 }
441 my @lines = ();
442 foreach my $el ( @$array ) {
443 my $line = (' ' x $indent) . '-';
444 my $type = ref $el;
445 if ( ! $type ) {
446 $line .= ' ' . $self->_write_scalar( $el, $indent + 1 );
447 push @lines, $line;
448
449 } elsif ( $type eq 'ARRAY' ) {
450 if ( @$el ) {
451 push @lines, $line;
452 push @lines, $self->_write_array( $el, $indent + 1, $seen );
453 } else {
454 $line .= ' []';
455 push @lines, $line;
456 }
457
458 } elsif ( $type eq 'HASH' ) {
459 if ( keys %$el ) {
460 push @lines, $line;
461 push @lines, $self->_write_hash( $el, $indent + 1, $seen );
462 } else {
463 $line .= ' {}';
464 push @lines, $line;
465 }
466
467 } else {
468 die "Module::Build::YAML does not support $type references";
469 }
470 }
471
472 @lines;
473}
474
475sub _write_hash {
476 my ($self, $hash, $indent, $seen) = @_;
477 if ( $seen->{refaddr($hash)}++ ) {
478 die "Module::Build::YAML does not support circular references";
479 }
480 my @lines = ();
481 foreach my $name ( sort keys %$hash ) {
482 my $el = $hash->{$name};
483 my $line = (' ' x $indent) . "$name:";
484 my $type = ref $el;
485 if ( ! $type ) {
486 $line .= ' ' . $self->_write_scalar( $el, $indent + 1 );
487 push @lines, $line;
488
489 } elsif ( $type eq 'ARRAY' ) {
490 if ( @$el ) {
491 push @lines, $line;
492 push @lines, $self->_write_array( $el, $indent + 1, $seen );
493 } else {
494 $line .= ' []';
495 push @lines, $line;
496 }
497
498 } elsif ( $type eq 'HASH' ) {
499 if ( keys %$el ) {
500 push @lines, $line;
501 push @lines, $self->_write_hash( $el, $indent + 1, $seen );
502 } else {
503 $line .= ' {}';
504 push @lines, $line;
505 }
506
507 } else {
508 die "Module::Build::YAML does not support $type references";
509 }
510 }
511
512 @lines;
513}
514
515# Set error
516sub _error {
517 $Module::Build::YAML::errstr = $_[1];
518 undef;
519}
520
521# Retrieve error
522sub errstr {
523 $Module::Build::YAML::errstr;
524}
525
526#####################################################################
527# YAML Compatibility
528
529sub Dump {
530 Module::Build::YAML->new(@_)->write_string;
531}
532
533sub Load {
534 my $self = Module::Build::YAML->read_string(@_);
535 unless ( $self ) {
536 croak("Failed to load YAML document from string");
537 }
538 if ( wantarray ) {
539 return @$self;
540 } else {
541 # To match YAML.pm, return the last document
542 return $self->[-1];
543 }
544}
545
546BEGIN {
547 *freeze = *Dump;
548 *thaw = *Load;
549}
550
551sub DumpFile {
552 my $file = shift;
553 Module::Build::YAML->new(@_)->write($file);
554}
555
556sub LoadFile {
557 my $self = Module::Build::YAML->read($_[0]);
558 unless ( $self ) {
559 croak("Failed to load YAML document from '" . ($_[0] || '') . "'");
560 }
561 if ( wantarray ) {
562 return @$self;
563 } else {
564 # Return only the last document to match YAML.pm,
565 return $self->[-1];
566 }
567}
568
569#####################################################################
570# Use Scalar::Util if possible, otherwise emulate it
571
572BEGIN {
573 eval {
574 require Scalar::Util;
575 };
576 if ( $@ ) {
577 # Failed to load Scalar::Util
578 eval <<'END_PERL';
579sub refaddr {
580 my $pkg = ref($_[0]) or return undef;
581 if (!!UNIVERSAL::can($_[0], 'can')) {
582 bless $_[0], 'Scalar::Util::Fake';
583 } else {
584 $pkg = undef;
585 }
586 "$_[0]" =~ /0x(\w+)/;
587 my $i = do { local $^W; hex $1 };
588 bless $_[0], $pkg if defined $pkg;
589 $i;
590}
591END_PERL
592 } else {
593 Scalar::Util->import('refaddr');
594 }
595}
596
5971;
598
599__END__
a314697d 600