1 package Parse::CPAN::Meta;
8 $Parse::CPAN::Meta::VERSION = '0.04_01';
9 @Parse::CPAN::Meta::ISA = qw{ Exporter };
10 @Parse::CPAN::Meta::EXPORT_OK = qw{ Load LoadFile };
20 # Printable characters for escapes
22 z => "\x00", a => "\x07", t => "\x09",
23 n => "\x0a", v => "\x0b", f => "\x0c",
24 r => "\x0d", e => "\x1b", '\\' => '\\',
29 "\357\273\277" => 'UTF-8',
30 "\376\377" => 'UTF-16BE',
31 "\377\376" => 'UTF-16LE',
32 "\0\0\376\377" => 'UTF-32BE',
33 "\377\376\0\0" => 'UTF-32LE'
36 sub BOM_MIN_LENGTH () { 2 }
37 sub BOM_MAX_LENGTH () { 4 }
38 sub HAVE_UTF8 () { $] >= 5.007003 }
40 BEGIN { require utf8 if HAVE_UTF8 }
43 #####################################################################
46 # Create an object from a file
50 croak('You did not specify a file name') unless $file;
51 croak( "File '$file' does not exist" ) unless -e $file;
52 croak( "'$file' is a directory, not a file" ) unless -f _;
53 croak( "Insufficient permissions to read '$file'" ) unless -r _;
57 open( CFG, $file ) or croak("Failed to open file '$file': $!");
59 close CFG or croak("Failed to close file '$file': $!");
61 # Hand off to the actual parser
65 # Parse a document from a string.
66 # Doing checks on $_[0] prevents us having to do a string copy.
71 # Handle special cases
72 foreach my $length ( BOM_MIN_LENGTH .. BOM_MAX_LENGTH ) {
73 if ( my $enc = $BOM{substr($str, 0, $length)} ) {
74 croak("Stream has a non UTF-8 BOM") unless $enc eq 'UTF-8';
75 substr($str, 0, $length) = ''; # strip UTF-8 bom if found, we'll just ignore it
80 utf8::decode($str); # try to decode as utf8
83 unless ( defined $str ) {
84 croak("Did not provide a string to Load");
86 return() unless length $str;
87 unless ( $str =~ /[\012\015]+$/ ) {
88 croak("Stream does not end with newline character");
91 # Split the file into lines
92 my @lines = grep { ! /^\s*(?:\#.*)?$/ }
93 split /(?:\015{1,2}\012|\015|\012)/, $str;
98 # Do we have a document header?
99 if ( $lines[0] =~ /^---\s*(?:(.+)\s*)?$/ ) {
100 # Handle scalar documents
102 if ( defined $1 and $1 !~ /^(?:\#.+|\%YAML:[\d\.]+)$/ ) {
103 push @documents, _scalar( "$1", [ undef ], \@lines );
108 if ( ! @lines or $lines[0] =~ /^---\s*(?:(.+)\s*)?$/ ) {
110 push @documents, undef;
112 } elsif ( $lines[0] =~ /^\s*\-/ ) {
113 # An array at the root
115 push @documents, $document;
116 _array( $document, [ 0 ], \@lines );
118 } elsif ( $lines[0] =~ /^(\s*)\w/ ) {
121 push @documents, $document;
122 _hash( $document, [ length($1) ], \@lines );
125 croak("Parse::CPAN::Meta does not support the line '$lines[0]'");
132 return $documents[-1];
136 # Deparse a scalar string to the actual scalar
142 # Trim trailing whitespace
145 # Explitic null/undef
146 return undef if $string eq '~';
149 if ( $string =~ /^\'(.*?)\'$/ ) {
150 return '' unless defined $1;
155 if ( $string =~ /^\"((?:\\.|[^\"])*)\"$/ ) {
158 $str =~ s/\\([never\\fartz]|x([0-9a-fA-F]{2}))/(length($1)>1)?pack("H2",$2):$UNESCAPES{$1}/gex;
161 if ( $string =~ /^[\'\"]/ ) {
162 # A quote with folding... we don't support that
163 croak("Parse::CPAN::Meta does not support multi-line quoted scalars");
166 # Null hash and array
167 if ( $string eq '{}' ) {
171 if ( $string eq '[]' ) {
176 # Regular unquoted string
177 return $string unless $string =~ /^[>|]/;
180 croak("Multi-line scalar content missing") unless @$lines;
182 # Check the indent depth
183 $lines->[0] =~ /^(\s*)/;
184 $indent->[-1] = length("$1");
185 if ( defined $indent->[-2] and $indent->[-1] <= $indent->[-2] ) {
186 croak("Illegal line indenting");
192 $lines->[0] =~ /^(\s*)/;
193 last unless length($1) >= $indent->[-1];
194 push @multiline, substr(shift(@$lines), length($1));
197 my $j = (substr($string, 0, 1) eq '>') ? ' ' : "\n";
198 my $t = (substr($string, 1, 1) eq '-') ? '' : "\n";
199 return join( $j, @multiline ) . $t;
209 # Check for a new document
210 return 1 if $lines->[0] =~ /^---\s*(?:(.+)\s*)?$/;
212 # Check the indent level
213 $lines->[0] =~ /^(\s*)/;
214 if ( length($1) < $indent->[-1] ) {
216 } elsif ( length($1) > $indent->[-1] ) {
217 croak("Hash line over-indented");
220 if ( $lines->[0] =~ /^(\s*\-\s+)[^\'\"]\S*\s*:(?:\s+|$)/ ) {
222 my $indent2 = length("$1");
223 $lines->[0] =~ s/-/ /;
225 _hash( $array->[-1], [ @$indent, $indent2 ], $lines );
227 } elsif ( $lines->[0] =~ /^\s*\-(\s*)(.+?)\s*$/ ) {
228 # Array entry with a value
230 push @$array, _scalar( "$2", [ @$indent, undef ], $lines );
232 } elsif ( $lines->[0] =~ /^\s*\-\s*$/ ) {
238 if ( $lines->[0] =~ /^(\s*)\-/ ) {
239 my $indent2 = length("$1");
240 if ( $indent->[-1] == $indent2 ) {
246 _array( $array->[-1], [ @$indent, $indent2 ], $lines );
249 } elsif ( $lines->[0] =~ /^(\s*)\w/ ) {
251 _hash( $array->[-1], [ @$indent, length("$1") ], $lines );
254 croak("Parse::CPAN::Meta does not support the line '$lines->[0]'");
257 } elsif ( defined $indent->[-2] and $indent->[-1] == $indent->[-2] ) {
258 # This is probably a structure like the following...
264 # ... so lets return and let the hash parser handle it
268 croak("Parse::CPAN::Meta does not support the line '$lines->[0]'");
282 # Check for a new document
283 return 1 if $lines->[0] =~ /^---\s*(?:(.+)\s*)?$/;
285 # Check the indent level
286 $lines->[0] =~/^(\s*)/;
287 if ( length($1) < $indent->[-1] ) {
289 } elsif ( length($1) > $indent->[-1] ) {
290 croak("Hash line over-indented");
294 unless ( $lines->[0] =~ s/^\s*([^\'\"][^\n]*?)\s*:(\s+|$)// ) {
295 croak("Bad hash line");
299 # Do we have a value?
300 if ( length $lines->[0] ) {
302 $hash->{$key} = _scalar( shift(@$lines), [ @$indent, undef ], $lines );
309 $hash->{$key} = undef;
312 if ( $lines->[0] =~ /^(\s*)-/ ) {
314 _array( $hash->{$key}, [ @$indent, length($1) ], $lines );
315 } elsif ( $lines->[0] =~ /^(\s*)./ ) {
316 my $indent2 = length("$1");
317 if ( $indent->[-1] >= $indent2 ) {
319 $hash->{$key} = undef;
322 _hash( $hash->{$key}, [ @$indent, length($1) ], $lines );
338 Parse::CPAN::Meta - Parse META.yml and other similar CPAN metadata files
342 #############################################
355 #############################################
358 use Parse::CPAN::Meta;
361 my @yaml = Parse::CPAN::Meta::LoadFile( 'Meta.yml' );
364 my $root = $yaml[0]->{rootproperty};
365 my $one = $yaml[0]->{section}->{one};
366 my $Foo = $yaml[0]->{section}->{Foo};
370 B<Parse::CPAN::Meta> is a parser for META.yml files, based on the
371 parser half of L<YAML::Tiny>.
373 It supports a basic subset of the full YAML specification, enough to
374 implement parsing of typical META.yml files, and other similarly simple
377 If you need something with more power, move up to a full YAML parser such
378 as L<YAML>, L<YAML::Syck> or L<YAML::LibYAML>.
380 Parse::CPAN::Meta provides a very simply API of only two functions, based
381 on the YAML functions of the same name. Wherever possible, identical
382 calling semantics are used.
384 All error reporting is done with exceptions (dieing).
388 For maintenance clarity, no functions are exported.
390 =head2 Load( $string )
392 my @documents = Load( $string );
394 Parses a string containing a valid YAML stream into a list of Perl data
397 =head2 LoadFile( $file_name )
399 Reads the YAML stream from a file instead of a string.
403 Bugs should be reported via the CPAN bug tracker at
405 L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Parse-CPAN-Meta>
409 Adam Kennedy E<lt>adamk@cpan.orgE<gt>
413 L<YAML::Tiny>, L<YAML>, L<YAML::Syck>
417 Copyright 2006 - 2009 Adam Kennedy.
419 This program is free software; you can redistribute
420 it and/or modify it under the same terms as Perl itself.
422 The full text of the license can be found in the
423 LICENSE file included with this module.