Author: dagolden
Date: Thu Sep 10 22:47:17 2009
New Revision: 13301

Added:
   Module-Build/trunk/devtools/update_yamltiny.pl   (contents, props changed)
Removed:
   Module-Build/trunk/t/mbyaml.t
Modified:
   Module-Build/trunk/Build.PL
   Module-Build/trunk/Changes
   Module-Build/trunk/lib/Module/Build.pm
   Module-Build/trunk/lib/Module/Build/API.pod
   Module-Build/trunk/lib/Module/Build/Base.pm
   Module-Build/trunk/lib/Module/Build/YAML.pm
   Module-Build/trunk/t/extend.t

Log:
Replace YAML.pm with YAML::Tiny

We now use YAML::Tiny in place of YAML.pm.  We lose ordered META.yml
files, but YAML::Tiny is more robust and better maintained. Also,
Parse::CPAN::Meta is based on YAML::Tiny, so now we are writing in the
same YAML dialect it expects.  Module::Build::YAML has been updated
with the guts of YAML::Tiny and there is a nifty developer tool to 
suck in the latest guts of YAML::Tiny, stop it from exporting things,
and s/YAML::Tiny/Module::Build::YAML/.



Modified: Module-Build/trunk/Build.PL
==============================================================================
--- Module-Build/trunk/Build.PL (original)
+++ Module-Build/trunk/Build.PL Thu Sep 10 22:47:17 2009
@@ -71,8 +71,8 @@
 
   auto_features => {
     YAML_support => {
-      description => "Use YAML.pm to write META.yml files",
-      requires    => {YAML => ' >= 0.35, != 0.49_01 '},
+      description => "Use YAML::Tiny to write META.yml files",
+      requires    => {'YAML::Tiny' => 1.38},
     },
     C_support => {
       description => "Compile/link C & XS code",

Modified: Module-Build/trunk/Changes
==============================================================================
--- Module-Build/trunk/Changes  (original)
+++ Module-Build/trunk/Changes  Thu Sep 10 22:47:17 2009
@@ -2,6 +2,15 @@
 
 0.35_03 - 
 
+ *** API CHANGE *** 
+ - The prepare_metadata() method used to take a YAML::Node object as an
+   argument for modification.  The method now takes no arguments and just
+   returns a hash reference of metadata. [David Golden]
+
+ Other:
+ - Replaced use of YAML.pm with YAML::Tiny; Module::Build::YAML is now
+   based on YAML::Tiny as well [David Golden]
+
 0.35_02 - Mon Sep  7 22:37:42 EDT 2009
 
  Enhancements:

Added: Module-Build/trunk/devtools/update_yamltiny.pl
==============================================================================
--- (empty file)
+++ Module-Build/trunk/devtools/update_yamltiny.pl      Thu Sep 10 22:47:17 2009
@@ -0,0 +1,37 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use Path::Class;
+require YAML::Tiny;
+require PPI;
+require PPI::Dumper;
+
+my $Doc = PPI::Document->new($INC{'YAML/Tiny.pm'});
+$Doc->prune('PPI::Token::Pod');
+$Doc->prune( sub {
+        $_[1]->isa('PPI::Statement') 
+    &&  $_[1]->first_element->isa('PPI::Token::Symbol')
+    &&  $_[1]->first_element->symbol =~ /EXPORT|ISA/
+  }
+);
+$Doc->prune( sub {
+        $_[1]->isa('PPI::Statement::Include') 
+    &&  $_[1]->child(2)->isa('PPI::Token::Word')
+    &&  $_[1]->child(2)->content eq 'Exporter'
+  }
+);
+
+#my $Dumper = PPI::Dumper->new( $Doc );
+#$Dumper->print;
+
+my $content = $Doc->serialize;
+$content =~ s{YAML::Tiny}{Module::Build::YAML}g;
+$content = "# Adapted from YAML::Tiny " . YAML::Tiny->VERSION . "\n$content";
+$content =~ s{^\s+\n(\s+\n)+}{\n}gms;
+
+my $mby = file(qw/lib Module Build YAML.pm/);
+die "Can't find $mby" unless -e $mby;
+my $fh = $mby->openw;
+print {$fh} $content;
+

Modified: Module-Build/trunk/lib/Module/Build.pm
==============================================================================
--- Module-Build/trunk/lib/Module/Build.pm      (original)
+++ Module-Build/trunk/lib/Module/Build.pm      Thu Sep 10 22:47:17 2009
@@ -337,8 +337,8 @@
 distribution.  The metadata includes the distribution name, version,
 abstract, prerequisites, license, and various other data about the
 distribution.  This file is created as F<META.yml> in YAML format.
-It is recommended that the C<YAML> module be installed to create it.
-If the C<YAML> module is not installed, an internal module supplied
+It is recommended that the C<YAML::Tiny> module be installed to create it.
+If the C<YAML::Tiny> module is not installed, an internal module supplied
 with Module::Build will be used to write the META.yml file, and this
 will most likely be fine.
 
@@ -1097,7 +1097,7 @@
 =head1 SEE ALSO
 
 perl(1), L<Module::Build::Cookbook>, L<Module::Build::Authoring>,
-L<Module::Build::API>, L<ExtUtils::MakeMaker>, L<YAML>
+L<Module::Build::API>, L<ExtUtils::MakeMaker>, L<YAML::Tiny>
 
 F<META.yml> Specification:
 L<http://module-build.sourceforge.net/META-spec-current.html>

Modified: Module-Build/trunk/lib/Module/Build/API.pod
==============================================================================
--- Module-Build/trunk/lib/Module/Build/API.pod (original)
+++ Module-Build/trunk/lib/Module/Build/API.pod Thu Sep 10 22:47:17 2009
@@ -1566,22 +1566,25 @@
 
 =item prepare_metadata()
 
-[version 0.28]
+[version 0.36]
 
-This method is provided for authors to override to customize the
-fields of F<META.yml>.  It is passed a YAML::Node node object which can
-be modified as desired and then returned.  E.g.
+This method returns a hash reference of metadata that can be used to create a
+YAML datastream. It is provided for authors to override or customize the fields
+of F<META.yml>.   E.g.
 
   package My::Builder;
   use base 'Module::Build';
 
   sub prepare_metadata {
     my $self = shift;
-    my $node = $self->SUPER::prepare_metadata( shift );
-    $node->{custom_field} = 'foo';
-    return $node;
+    my $data = $self->SUPER::prepare_metadata();
+    $data->{custom_field} = 'foo';
+    return $data;
   }
 
+Prior to version 0.36, this method took a YAML::Node as an argument to hold
+assembled metadata.
+
 =item prereq_failures()
 
 [version 0.11]
@@ -1847,7 +1850,7 @@
 =head1 SEE ALSO
 
 perl(1), L<Module::Build>(3), L<Module::Build::Authoring>(3),
-L<Module::Build::Cookbook>(3), L<ExtUtils::MakeMaker>(3), L<YAML>(3)
+L<Module::Build::Cookbook>(3), L<ExtUtils::MakeMaker>(3), L<YAML::Tiny>(3)
 
 F<META.yml> Specification:
 L<http://module-build.sourceforge.net/META-spec-current.html>

Modified: Module-Build/trunk/lib/Module/Build/Base.pm
==============================================================================
--- Module-Build/trunk/lib/Module/Build/Base.pm (original)
+++ Module-Build/trunk/lib/Module/Build/Base.pm Thu Sep 10 22:47:17 2009
@@ -1601,7 +1601,7 @@
     $self->log_info("Removed previous '$mymetafile'\n");
   }
   $self->log_info("Creating new '$mymetafile' with configuration results\n");
-  if ( $self->write_metafile( $mymetafile, $self->generate_metadata ) ) {
+  if ( $self->write_metafile( $mymetafile, $self->prepare_metadata ) ) {
     $self->add_to_cleanup( $mymetafile );
   }
   
@@ -3907,7 +3907,7 @@
     push @INC, File::Spec->catdir($self->blib, 'lib');
   }
 
-  if ( $self->write_metafile( $self->metafile, $self->generate_metadata ) ) {
+  if ( $self->write_metafile( $self->metafile, $self->prepare_metadata ) ) {
     $self->{wrote_metadata} = 1;
     $self->_add_to_manifest('MANIFEST', $metafile);
   }
@@ -3915,42 +3915,22 @@
   return 1;
 }
 
-sub generate_metadata {
-  my $self = shift;
-  my $node = {};
-
-  if ($self->_mb_feature('YAML_support')) {
-    require YAML;
-    require YAML::Node;
-    # We use YAML::Node to get the order nice in the YAML file.
-    $self->prepare_metadata( $node = YAML::Node->new({}) );
-  } else {
-    require Module::Build::YAML;
-    my @order_keys;
-    $self->prepare_metadata($node, \...@order_keys);
-    $node->{_order} = \...@order_keys;
-  }
-  return $node;
-}
-
 sub write_metafile {
   my $self = shift;
   my ($metafile, $node) = @_;
+  my $yaml;
 
   if ($self->_mb_feature('YAML_support')) {
     # XXX this is probably redundant, but stick with it
-    require YAML;
-    require YAML::Node;
-    delete $node->{_order}; # XXX also probably redundant, but for safety
-    # YAML API changed after version 0.30
-    my $yaml_sub = $YAML::VERSION le '0.30' ? \&YAML::StoreFile : 
\&YAML::DumpFile;
-    $yaml_sub->( $metafile, $node );
+    require YAML::Tiny;
+    $yaml = YAML::Tiny->new($node);
   } else {
-    # XXX probably redundant
     require Module::Build::YAML;
-    &Module::Build::YAML::DumpFile($metafile, $node);
+    $yaml = Module::Build::YAML->new($node);
   }
-  return 1;
+  my $result = $yaml->write($metafile) 
+    or $self->log_warn( "Error writing '$metafile': " . $yaml->errstr . "\n");
+  return $result;
 }
 
 sub normalize_version {
@@ -3973,14 +3953,14 @@
 }
 
 sub prepare_metadata {
-  my ($self, $node, $keys) = @_;
+  my ($self) = @_;
   my $p = $self->{properties};
+  my $node = {};
 
   # A little helper sub
   my $add_node = sub {
     my ($name, $val) = @_;
     $node->{$name} = $val;
-    push @$keys, $name if $keys;
   };
 
   foreach (qw(dist_name dist_version dist_author dist_abstract license)) {

Modified: Module-Build/trunk/lib/Module/Build/YAML.pm
==============================================================================
--- Module-Build/trunk/lib/Module/Build/YAML.pm (original)
+++ Module-Build/trunk/lib/Module/Build/YAML.pm Thu Sep 10 22:47:17 2009
@@ -1,162 +1,600 @@
+# Adapted from YAML::Tiny 1.40
 package Module::Build::YAML;
 
 use strict;
-use Config;
-use vars qw($VERSION @EXPORT @EXPORT_OK);
-$VERSION = "0.50";
-...@export = ();
-...@export_ok = qw(Dump Load DumpFile LoadFile);
+use Carp 'croak';
 
+# UTF Support?
+sub HAVE_UTF8 () { $] >= 5.007003 }
+BEGIN {
+       if ( HAVE_UTF8 ) {
+               # The string eval helps hide this from Test::MinimumVersion
+               eval "require utf8;";
+               die "Failed to load UTF-8 support" if $@;
+       }
+
+       # Class structure
+       require 5.004;
+       
+       $Module::Build::YAML::VERSION   = '1.40';
+
+       # Error storage
+       $Module::Build::YAML::errstr    = '';
+}
+
+# The character class of all characters we need to escape
+# NOTE: Inlined, since it's only used once
+# my $RE_ESCAPE   = '[\\x00-\\x08\\x0b-\\x0d\\x0e-\\x1f\"\n]';
+
+# Printed form of the unprintable characters in the lowest range
+# of ASCII characters, listed by ASCII ordinal position.
+my @UNPRINTABLE = qw(
+       z    x01  x02  x03  x04  x05  x06  a
+       x08  t    n    v    f    r    x0e  x0f
+       x10  x11  x12  x13  x14  x15  x16  x17
+       x18  x19  x1a  e    x1c  x1d  x1e  x1f
+);
+
+# Printable characters for escapes
+my %UNESCAPES = (
+       z => "\x00", a => "\x07", t    => "\x09",
+       n => "\x0a", v => "\x0b", f    => "\x0c",
+       r => "\x0d", e => "\x1b", '\\' => '\\',
+);
+
+# Special magic boolean words
+my %QUOTE = map { $_ => 1 } qw{
+       null Null NULL
+       y Y yes Yes YES n N no No NO
+       true True TRUE false False FALSE
+       on On ON off Off OFF
+};
+
+#####################################################################
+# Implementation
+
+# Create an empty Module::Build::YAML object
 sub new {
-    my $this = shift;
-    my $class = ref($this) || $this;
-    my $self = {};
-    bless $self, $class;
-    return($self);
+       my $class = shift;
+       bless [ @_ ], $class;
 }
 
-sub Dump {
-    shift if ($_[0] eq __PACKAGE__ || ref($_[0]) eq __PACKAGE__);
-    my $yaml = "";
-    foreach my $item (@_) {
-        $yaml .= "---\n";
-        $yaml .= &_yaml_chunk("", $item);
-    }
-    return $yaml;
+# Create an object from a file
+sub read {
+       my $class = ref $_[0] ? ref shift : shift;
+
+       # Check the file
+       my $file = shift or return $class->_error( 'You did not specify a file 
name' );
+       return $class->_error( "File '$file' does not exist" )              
unless -e $file;
+       return $class->_error( "'$file' is a directory, not a file" )       
unless -f _;
+       return $class->_error( "Insufficient permissions to read '$file'" ) 
unless -r _;
+
+       # Slurp in the file
+       local $/ = undef;
+       local *CFG;
+       unless ( open(CFG, $file) ) {
+               return $class->_error("Failed to open file '$file': $!");
+       }
+       my $contents = <CFG>;
+       unless ( close(CFG) ) {
+               return $class->_error("Failed to close file '$file': $!");
+       }
+
+       $class->read_string( $contents );
 }
 
-sub Load {
-    shift if ($_[0] eq __PACKAGE__ || ref($_[0]) eq __PACKAGE__);
-    die "not yet implemented";
+# Create an object from a string
+sub read_string {
+       my $class  = ref $_[0] ? ref shift : shift;
+       my $self   = bless [], $class;
+       my $string = $_[0];
+       unless ( defined $string ) {
+               return $self->_error("Did not provide a string to load");
+       }
+
+       # Byte order marks
+       # NOTE: Keeping this here to educate maintainers
+       # my %BOM = (
+       #     "\357\273\277" => 'UTF-8',
+       #     "\376\377"     => 'UTF-16BE',
+       #     "\377\376"     => 'UTF-16LE',
+       #     "\377\376\0\0" => 'UTF-32LE'
+       #     "\0\0\376\377" => 'UTF-32BE',
+       # );
+       if ( $string =~ /^(?:\376\377|\377\376|\377\376\0\0|\0\0\376\377)/ ) {
+               return $self->_error("Stream has a non UTF-8 BOM");
+       } else {
+               # Strip UTF-8 bom if found, we'll just ignore it
+               $string =~ s/^\357\273\277//;
+       }
+
+       # Try to decode as utf8
+       utf8::decode($string) if HAVE_UTF8;
+
+       # Check for some special cases
+       return $self unless length $string;
+       unless ( $string =~ /[\012\015]+\z/ ) {
+               return $self->_error("Stream does not end with newline 
character");
+       }
+
+       # Split the file into lines
+       my @lines = grep { ! /^\s*(?:\#.*)?\z/ }
+                   split /(?:\015{1,2}\012|\015|\012)/, $string;
+
+       # Strip the initial YAML header
+       @lines and $lines[0] =~ /^\%YAML[: ][\d\.]+.*\z/ and shift @lines;
+
+       # A nibbling parser
+       while ( @lines ) {
+               # Do we have a document header?
+               if ( $lines[0] =~ /^---\s*(?:(.+)\s*)?\z/ ) {
+                       # Handle scalar documents
+                       shift @lines;
+                       if ( defined $1 and $1 !~ /^(?:\#.+|\%YAML[: 
][\d\.]+)\z/ ) {
+                               push @$self, $self->_read_scalar( "$1", [ undef 
], \...@lines );
+                               next;
+                       }
+               }
+
+               if ( ! @lines or $lines[0] =~ /^(?:---|\.\.\.)/ ) {
+                       # A naked document
+                       push @$self, undef;
+                       while ( @lines and $lines[0] !~ /^---/ ) {
+                               shift @lines;
+                       }
+
+               } elsif ( $lines[0] =~ /^\s*\-/ ) {
+                       # An array at the root
+                       my $document = [ ];
+                       push @$self, $document;
+                       $self->_read_array( $document, [ 0 ], \...@lines );
+
+               } elsif ( $lines[0] =~ /^(\s*)\S/ ) {
+                       # A hash at the root
+                       my $document = { };
+                       push @$self, $document;
+                       $self->_read_hash( $document, [ length($1) ], 
\...@lines );
+
+               } else {
+                       croak("Module::Build::YAML failed to classify the line 
'$lines[0]'");
+               }
+       }
+
+       $self;
 }
 
-# This is basically copied out of YAML.pm and simplified a little.
-sub DumpFile {
-    shift if ($_[0] eq __PACKAGE__ || ref($_[0]) eq __PACKAGE__);
-    my $filename = shift;
-    local $/ = "\n"; # reset special to "sane"
-    my $mode = '>';
-    if ($filename =~ /^\s*(>{1,2})\s*(.*)$/) {
-        ($mode, $filename) = ($1, $2);
-    }
-    open my $OUT, "$mode $filename"
-      or die "Can't open $filename for writing: $!";
-    binmode($OUT, ':utf8') if $] >= 5.008 && $Config{useperlio};
-    print $OUT Dump(@_);
-    close $OUT;
+# Deparse a scalar string to the actual scalar
+sub _read_scalar {
+       my ($self, $string, $indent, $lines) = @_;
+
+       # Trim trailing whitespace
+       $string =~ s/\s*\z//;
+
+       # Explitic null/undef
+       return undef if $string eq '~';
+
+       # Quotes
+       if ( $string =~ /^\'(.*?)\'\z/ ) {
+               return '' unless defined $1;
+               $string = $1;
+               $string =~ s/\'\'/\'/g;
+               return $string;
+       }
+       if ( $string =~ /^\"((?:\\.|[^\"])*)\"\z/ ) {
+               # Reusing the variable is a little ugly,
+               # but avoids a new variable and a string copy.
+               $string = $1;
+               $string =~ s/\\"/"/g;
+               $string =~ 
s/\\([never\\fartz]|x([0-9a-fA-F]{2}))/(length($1)>1)?pack("H2",$2):$UNESCAPES{$1}/gex;
+               return $string;
+       }
+
+       # Special cases
+       if ( $string =~ /^[\'\"!&]/ ) {
+               croak("Module::Build::YAML does not support a feature in line 
'$lines->[0]'");
+       }
+       return {} if $string eq '{}';
+       return [] if $string eq '[]';
+
+       # Regular unquoted string
+       return $string unless $string =~ /^[>|]/;
+
+       # Error
+       croak("Module::Build::YAML failed to find multi-line scalar content") 
unless @$lines;
+
+       # Check the indent depth
+       $lines->[0]   =~ /^(\s*)/;
+       $indent->[-1] = length("$1");
+       if ( defined $indent->[-2] and $indent->[-1] <= $indent->[-2] ) {
+               croak("Module::Build::YAML found bad indenting in line 
'$lines->[0]'");
+       }
+
+       # Pull the lines
+       my @multiline = ();
+       while ( @$lines ) {
+               $lines->[0] =~ /^(\s*)/;
+               last unless length($1) >= $indent->[-1];
+               push @multiline, substr(shift(@$lines), length($1));
+       }
+
+       my $j = (substr($string, 0, 1) eq '>') ? ' ' : "\n";
+       my $t = (substr($string, 1, 1) eq '-') ? ''  : "\n";
+       return join( $j, @multiline ) . $t;
 }
 
-# This is basically copied out of YAML.pm and simplified a little.
-sub LoadFile {
-    shift if ($_[0] eq __PACKAGE__ || ref($_[0]) eq __PACKAGE__);
-    my $filename = shift;
-    open my $IN, $filename
-      or die "Can't open $filename for reading: $!";
-    binmode($IN, ':utf8') if $] >= 5.008 && $Config{useperlio};
-    return Load(do { local $/; <$IN> });
-    close $IN;
-}
-
-sub _yaml_chunk {
-  my ($indent, $values) = @_;
-  my $yaml_chunk = "";
-  my $ref = ref($values);
-  my ($value, @allkeys, %keyseen);
-  if (!$ref) {  # a scalar
-    $yaml_chunk .= &_yaml_value($values) . "\n";
-  }
-  elsif ($ref eq "ARRAY") {
-    foreach $value (@$values) {
-      $yaml_chunk .= "$indent-";
-      $ref = ref($value);
-      if (!$ref) {
-        $yaml_chunk .= " " . &_yaml_value($value) . "\n";
-      }
-      else {
-        $yaml_chunk .= "\n";
-        $yaml_chunk .= &_yaml_chunk("$indent  ", $value);
-      }
-    }
-  }
-  else { # assume "HASH"
-    if ($values->{_order} && ref($values->{_order}) eq "ARRAY") {
-        @allkeys = @{$values->{_order}};
-        $values = { %$values };
-        delete $values->{_order};
-    }
-    push(@allkeys, sort keys %$values);
-    foreach my $key (@allkeys) {
-      next if (!defined $key || $key eq "" || $keyseen{$key});
-      $keyseen{$key} = 1;
-      $yaml_chunk .= "$indent$key:";
-      $value = $values->{$key};
-      $ref = ref($value);
-      if (!$ref) {
-        $yaml_chunk .= " " . &_yaml_value($value) . "\n";
-      }
-      else {
-        $yaml_chunk .= "\n";
-        $yaml_chunk .= &_yaml_chunk("$indent  ", $value);
-      }
-    }
-  }
-  return($yaml_chunk);
-}
-
-sub _yaml_value {
-  my ($value) = @_;
-  # undefs become ~
-  return '~' if not defined $value;
-
-  # empty strings will become empty strings
-  return '""' if $value eq '';
-
-  # allow simple scalars (without embedded quote chars) to be unquoted
-  # (includes $%_+=-\;:,./)
-  return $value if $value !~ /["'`~\...@\#^\&\*\(\)\{\}\[\]\|<>\?]/;
-
-  # quote and escape strings with special values
-  return "'$value'"
-    if $value !~ /['`~\n!\#^\&\*\(\)\{\}\[\]\|\?]/;  # nothing but " or @ or < 
or > (email addresses)
-
-  $value =~ s/\n/\\n/g;    # handle embedded newlines
-  $value =~ s/"/\\"/g;     # handle embedded quotes
-  return qq{"$value"};
+# Parse an array
+sub _read_array {
+       my ($self, $array, $indent, $lines) = @_;
+
+       while ( @$lines ) {
+               # Check for a new document
+               if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
+                       while ( @$lines and $lines->[0] !~ /^---/ ) {
+                               shift @$lines;
+                       }
+                       return 1;
+               }
+
+               # Check the indent level
+               $lines->[0] =~ /^(\s*)/;
+               if ( length($1) < $indent->[-1] ) {
+                       return 1;
+               } elsif ( length($1) > $indent->[-1] ) {
+                       croak("Module::Build::YAML found bad indenting in line 
'$lines->[0]'");
+               }
+
+               if ( $lines->[0] =~ /^(\s*\-\s+)[^\'\"]\S*\s*:(?:\s+|$)/ ) {
+                       # Inline nested hash
+                       my $indent2 = length("$1");
+                       $lines->[0] =~ s/-/ /;
+                       push @$array, { };
+                       $self->_read_hash( $array->[-1], [ @$indent, $indent2 
], $lines );
+
+               } elsif ( $lines->[0] =~ /^\s*\-(\s*)(.+?)\s*\z/ ) {
+                       # Array entry with a value
+                       shift @$lines;
+                       push @$array, $self->_read_scalar( "$2", [ @$indent, 
undef ], $lines );
+
+               } elsif ( $lines->[0] =~ /^\s*\-\s*\z/ ) {
+                       shift @$lines;
+                       unless ( @$lines ) {
+                               push @$array, undef;
+                               return 1;
+                       }
+                       if ( $lines->[0] =~ /^(\s*)\-/ ) {
+                               my $indent2 = length("$1");
+                               if ( $indent->[-1] == $indent2 ) {
+                                       # Null array entry
+                                       push @$array, undef;
+                               } else {
+                                       # Naked indenter
+                                       push @$array, [ ];
+                                       $self->_read_array( $array->[-1], [ 
@$indent, $indent2 ], $lines );
+                               }
+
+                       } elsif ( $lines->[0] =~ /^(\s*)\S/ ) {
+                               push @$array, { };
+                               $self->_read_hash( $array->[-1], [ @$indent, 
length("$1") ], $lines );
+
+                       } else {
+                               croak("Module::Build::YAML failed to classify 
line '$lines->[0]'");
+                       }
+
+               } elsif ( defined $indent->[-2] and $indent->[-1] == 
$indent->[-2] ) {
+                       # This is probably a structure like the following...
+                       # ---
+                       # foo:
+                       # - list
+                       # bar: value
+                       #
+                       # ... so lets return and let the hash parser handle it
+                       return 1;
+
+               } else {
+                       croak("Module::Build::YAML failed to classify line 
'$lines->[0]'");
+               }
+       }
+
+       return 1;
 }
 
-1;
+# Parse an array
+sub _read_hash {
+       my ($self, $hash, $indent, $lines) = @_;
+
+       while ( @$lines ) {
+               # Check for a new document
+               if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
+                       while ( @$lines and $lines->[0] !~ /^---/ ) {
+                               shift @$lines;
+                       }
+                       return 1;
+               }
+
+               # Check the indent level
+               $lines->[0] =~ /^(\s*)/;
+               if ( length($1) < $indent->[-1] ) {
+                       return 1;
+               } elsif ( length($1) > $indent->[-1] ) {
+                       croak("Module::Build::YAML found bad indenting in line 
'$lines->[0]'");
+               }
+
+               # Get the key
+               unless ( $lines->[0] =~ s/^\s*([^\'\" ][^\n]*?)\s*:(\s+|$)// ) {
+                       if ( $lines->[0] =~ /^\s*[?\'\"]/ ) {
+                               croak("Module::Build::YAML does not support a 
feature in line '$lines->[0]'");
+                       }
+                       croak("Module::Build::YAML failed to classify line 
'$lines->[0]'");
+               }
+               my $key = $1;
+
+               # Do we have a value?
+               if ( length $lines->[0] ) {
+                       # Yes
+                       $hash->{$key} = $self->_read_scalar( shift(@$lines), [ 
@$indent, undef ], $lines );
+               } else {
+                       # An indent
+                       shift @$lines;
+                       unless ( @$lines ) {
+                               $hash->{$key} = undef;
+                               return 1;
+                       }
+                       if ( $lines->[0] =~ /^(\s*)-/ ) {
+                               $hash->{$key} = [];
+                               $self->_read_array( $hash->{$key}, [ @$indent, 
length($1) ], $lines );
+                       } elsif ( $lines->[0] =~ /^(\s*)./ ) {
+                               my $indent2 = length("$1");
+                               if ( $indent->[-1] >= $indent2 ) {
+                                       # Null hash entry
+                                       $hash->{$key} = undef;
+                               } else {
+                                       $hash->{$key} = {};
+                                       $self->_read_hash( $hash->{$key}, [ 
@$indent, length($1) ], $lines );
+                               }
+                       }
+               }
+       }
 
-__END__
+       return 1;
+}
 
-=head1 NAME
+# Save an object to a file
+sub write {
+       my $self = shift;
+       my $file = shift or return $self->_error('No file name provided');
+
+       # Write it to the file
+       open( CFG, '>' . $file ) or return $self->_error(
+               "Failed to open file '$file' for writing: $!"
+               );
+       print CFG $self->write_string;
+       close CFG;
 
-Module::Build::YAML - Provides just enough YAML support so that Module::Build 
works even if YAML.pm is not installed
+       return 1;
+}
 
-=head1 SYNOPSIS
+# Save an object to a string
+sub write_string {
+       my $self = shift;
+       return '' unless @$self;
+
+       # Iterate over the documents
+       my $indent = 0;
+       my @lines  = ();
+       foreach my $cursor ( @$self ) {
+               push @lines, '---';
+
+               # An empty document
+               if ( ! defined $cursor ) {
+                       # Do nothing
+
+               # A scalar document
+               } elsif ( ! ref $cursor ) {
+                       $lines[-1] .= ' ' . $self->_write_scalar( $cursor, 
$indent );
+
+               # A list at the root
+               } elsif ( ref $cursor eq 'ARRAY' ) {
+                       unless ( @$cursor ) {
+                               $lines[-1] .= ' []';
+                               next;
+                       }
+                       push @lines, $self->_write_array( $cursor, $indent, {} 
);
+
+               # A hash at the root
+               } elsif ( ref $cursor eq 'HASH' ) {
+                       unless ( %$cursor ) {
+                               $lines[-1] .= ' {}';
+                               next;
+                       }
+                       push @lines, $self->_write_hash( $cursor, $indent, {} );
+
+               } else {
+                       croak("Cannot serialize " . ref($cursor));
+               }
+       }
 
-    use Module::Build::YAML;
+       join '', map { "$_\n" } @lines;
+}
 
-    ...
+sub _write_scalar {
+       my $string = $_[1];
+       return '~'  unless defined $string;
+       return "''" unless length  $string;
+       if ( $string =~ /[\x00-\x08\x0b-\x0d\x0e-\x1f\"\'\n]/ ) {
+               $string =~ s/\\/\\\\/g;
+               $string =~ s/"/\\"/g;
+               $string =~ s/\n/\\n/g;
+               $string =~ s/([\x00-\x1f])/\\$UNPRINTABLE[ord($1)]/g;
+               return qq|"$string"|;
+       }
+       if ( $string =~ /(?:^\W|\s)/ or $QUOTE{$string} ) {
+               return "'$string'";
+       }
+       return $string;
+}
 
-=head1 DESCRIPTION
+sub _write_array {
+       my ($self, $array, $indent, $seen) = @_;
+       if ( $seen->{refaddr($array)}++ ) {
+               die "Module::Build::YAML does not support circular references";
+       }
+       my @lines  = ();
+       foreach my $el ( @$array ) {
+               my $line = ('  ' x $indent) . '-';
+               my $type = ref $el;
+               if ( ! $type ) {
+                       $line .= ' ' . $self->_write_scalar( $el, $indent + 1 );
+                       push @lines, $line;
+
+               } elsif ( $type eq 'ARRAY' ) {
+                       if ( @$el ) {
+                               push @lines, $line;
+                               push @lines, $self->_write_array( $el, $indent 
+ 1, $seen );
+                       } else {
+                               $line .= ' []';
+                               push @lines, $line;
+                       }
+
+               } elsif ( $type eq 'HASH' ) {
+                       if ( keys %$el ) {
+                               push @lines, $line;
+                               push @lines, $self->_write_hash( $el, $indent + 
1, $seen );
+                       } else {
+                               $line .= ' {}';
+                               push @lines, $line;
+                       }
+
+               } else {
+                       die "Module::Build::YAML does not support $type 
references";
+               }
+       }
 
-Provides just enough YAML support so that Module::Build works even if YAML.pm 
is not installed.
+       @lines;
+}
 
-Currently, this amounts to the ability to write META.yml files when C<perl 
Build distmeta>
-is executed via the Dump() and DumpFile() functions/methods.
+sub _write_hash {
+       my ($self, $hash, $indent, $seen) = @_;
+       if ( $seen->{refaddr($hash)}++ ) {
+               die "Module::Build::YAML does not support circular references";
+       }
+       my @lines  = ();
+       foreach my $name ( sort keys %$hash ) {
+               my $el   = $hash->{$name};
+               my $line = ('  ' x $indent) . "$name:";
+               my $type = ref $el;
+               if ( ! $type ) {
+                       $line .= ' ' . $self->_write_scalar( $el, $indent + 1 );
+                       push @lines, $line;
+
+               } elsif ( $type eq 'ARRAY' ) {
+                       if ( @$el ) {
+                               push @lines, $line;
+                               push @lines, $self->_write_array( $el, $indent 
+ 1, $seen );
+                       } else {
+                               $line .= ' []';
+                               push @lines, $line;
+                       }
+
+               } elsif ( $type eq 'HASH' ) {
+                       if ( keys %$el ) {
+                               push @lines, $line;
+                               push @lines, $self->_write_hash( $el, $indent + 
1, $seen );
+                       } else {
+                               $line .= ' {}';
+                               push @lines, $line;
+                       }
+
+               } else {
+                       die "Module::Build::YAML does not support $type 
references";
+               }
+       }
 
-=head1 AUTHOR
+       @lines;
+}
 
-Stephen Adkins <[email protected]>
+# Set error
+sub _error {
+       $Module::Build::YAML::errstr = $_[1];
+       undef;
+}
 
-=head1 COPYRIGHT
+# Retrieve error
+sub errstr {
+       $Module::Build::YAML::errstr;
+}
+
+#####################################################################
+# YAML Compatibility
+
+sub Dump {
+       Module::Build::YAML->new(@_)->write_string;
+}
+
+sub Load {
+       my $self = Module::Build::YAML->read_string(@_);
+       unless ( $self ) {
+               croak("Failed to load YAML document from string");
+       }
+       if ( wantarray ) {
+               return @$self;
+       } else {
+               # To match YAML.pm, return the last document
+               return $self->[-1];
+       }
+}
+
+BEGIN {
+       *freeze = *Dump;
+       *thaw   = *Load;
+}
 
-Copyright (c) 2006. Stephen Adkins. All rights reserved.
+sub DumpFile {
+       my $file = shift;
+       Module::Build::YAML->new(@_)->write($file);
+}
+
+sub LoadFile {
+       my $self = Module::Build::YAML->read($_[0]);
+       unless ( $self ) {
+               croak("Failed to load YAML document from '" . ($_[0] || '') . 
"'");
+       }
+       if ( wantarray ) {
+               return @$self;
+       } else {
+               # Return only the last document to match YAML.pm, 
+               return $self->[-1];
+       }
+}
 
-This program is free software; you can redistribute it and/or modify it
-under the same terms as Perl itself.
+#####################################################################
+# Use Scalar::Util if possible, otherwise emulate it
 
-See L<http://www.perl.com/perl/misc/Artistic.html>
+BEGIN {
+       eval {
+               require Scalar::Util;
+       };
+       if ( $@ ) {
+               # Failed to load Scalar::Util
+               eval <<'END_PERL';
+sub refaddr {
+       my $pkg = ref($_[0]) or return undef;
+       if (!!UNIVERSAL::can($_[0], 'can')) {
+               bless $_[0], 'Scalar::Util::Fake';
+       } else {
+               $pkg = undef;
+       }
+       "$_[0]" =~ /0x(\w+)/;
+       my $i = do { local $^W; hex $1 };
+       bless $_[0], $pkg if defined $pkg;
+       $i;
+}
+END_PERL
+       } else {
+               Scalar::Util->import('refaddr');
+       }
+}
 
-=cut
+1;
+
+__END__
 

Modified: Module-Build/trunk/t/extend.t
==============================================================================
--- Module-Build/trunk/t/extend.t       (original)
+++ Module-Build/trunk/t/extend.t       Thu Sep 10 22:47:17 2009
@@ -186,21 +186,20 @@
                                  meta_add => {foo => 'bar'},
                                  conflicts => {'Foo::Barxx' => 0},
                                );
-  my %data;
-  $mb->prepare_metadata( \%data );
-  is $data{foo}, 'bar';
+  my $data = $mb->prepare_metadata;
+  is $data->{foo}, 'bar';
 
   $mb->meta_merge(foo => 'baz');
-  $mb->prepare_metadata( \%data );
-  is $data{foo}, 'baz';
+  $data = $mb->prepare_metadata;
+  is $data->{foo}, 'baz';
 
   $mb->meta_merge(conflicts => {'Foo::Fooxx' => 0});
-  $mb->prepare_metadata( \%data );
-  is_deeply $data{conflicts}, {'Foo::Barxx' => 0, 'Foo::Fooxx' => 0};
+  $data = $mb->prepare_metadata;
+  is_deeply $data->{conflicts}, {'Foo::Barxx' => 0, 'Foo::Fooxx' => 0};
 
   $mb->meta_add(conflicts => {'Foo::Bazxx' => 0});
-  $mb->prepare_metadata( \%data );
-  is_deeply $data{conflicts}, {'Foo::Bazxx' => 0, 'Foo::Fooxx' => 0};
+  $data = $mb->prepare_metadata;
+  is_deeply $data->{conflicts}, {'Foo::Bazxx' => 0, 'Foo::Fooxx' => 0};
 }
 
 {

Reply via email to