Commits

Toby Inkster committed b0e058c

initial version

Comments (0)

Files changed (34)

+See https://bitbucket.org/tobyink/p5-app-cpantimes for the latest development.
+
+1.501800	2012-12-16
+	- Initial version, based on cpanminus 1.5018.
+bin/cpanm
+Changes
+Makefile.PL
+MANIFEST			This list of files
+README
+lib/App/cpantimes.pm
+t/happy_cpantesters.t
+=for developers
+
+If you're a developer, Makefile.PL will pack `cpanm` from
+script/cpanm.PL and then copy that file to `bin/cpanm`.
+
+You can pretend to be an author by running it with:
+
+  perl Makefile.PL --author
+
+You should install local::lib, Parse::CPAN::Meta, Module::Metadata,
+App::FatPacker and HTTP::Tiny before doing so.
+
+Do not commit the changes to `cpanm` standalone executable. We only
+commit changes to this file whenever we tag a new CPAN release.
+
+=cut
+
+my $author = ((grep{ $_ eq '--author' } @ARGV) or -d '.git' && `git remote -v` =~ /git\@github/);
+print STDERR "Running as an author mode!\n" if $author;
+
+# Author: perl Makefile.PL packs 'script/cpanm.PL' -> 'cpanm'
+if ($author) {
+	shift @ARGV;
+	system $^X, "script/build.PL" and die $?;
+}
+
+# perl Makefile.PL (from git repo) copies 'cpanm' -> 'bin/cpanm'
+if (-e 'cpant') {
+	print STDERR "Generating bin/cpant from cpant\n";
+	open my $in,  "<cpant"     or die $!;
+	open my $out, ">bin/cpant" or die $!;
+	while (<$in>) {
+		s|^#!/usr/bin/env perl|#!perl|; # so MakeMaker can fix it
+		print $out $_
+	}
+}
+
+use ExtUtils::MakeMaker;
+
+my %args = (
+	NAME => 'App::cpantimes',
+	VERSION_FROM => "lib/App/cpantimes.pm",
+	ABSTRACT => 'Get, unpack, build, install and report on modules from CPAN.',
+	PREREQ_PM => {
+		'Module::Build' => 0.36,
+		'ExtUtils::MakeMaker' => 6.31,
+		'ExtUtils::Install' => 1.46,
+	},
+	LICENSE => 'perl',
+	EXE_FILES => [ 'bin/cpant' ],
+);
+
+my $eummv = eval ($ExtUtils::MakeMaker::VERSION);
+if ($eummv >= 6.45) {
+    $args{META_MERGE} = {
+        resources => {
+            repository => 'https://bitbucket.org/tobyink/p5-app-cpantimes',
+            bugtracker => 'https://rt.cpan.org/Ticket/Create.html?Queue=App-cpantimes',
+        },
+    };
+}
+
+WriteMakefile(%args);
+Fork of App::cpanminus with Test::Reporter hacked in!

Empty file added.

fatlib/CPAN/DistnameInfo.pm

+
+package CPAN::DistnameInfo;
+
+$VERSION = "0.11";
+use strict;
+
+sub distname_info {
+  my $file = shift or return;
+
+  my ($dist, $version) = $file =~ /^
+    ((?:[-+.]*(?:[A-Za-z0-9]+|(?<=\D)_|_(?=\D))*
+     (?:
+	[A-Za-z](?=[^A-Za-z]|$)
+	|
+	\d(?=-)
+     )(?<![._-][vV])
+    )+)(.*)
+  $/xs or return ($file,undef,undef);
+
+  if ($dist =~ /-undef\z/ and ! length $version) {
+    $dist =~ s/-undef\z//;
+  }
+
+  # Remove potential -withoutworldwriteables suffix
+  $version =~ s/-withoutworldwriteables$//;
+
+  if ($version =~ /^(-[Vv].*)-(\d.*)/) {
+   
+    # Catch names like Unicode-Collate-Standard-V3_1_1-0.1
+    # where the V3_1_1 is part of the distname
+    $dist .= $1;
+    $version = $2;
+  }
+
+  # Normalize the Dist.pm-1.23 convention which CGI.pm and
+  # a few others use.
+  $dist =~ s{\.pm$}{};
+
+  $version = $1
+    if !length $version and $dist =~ s/-(\d+\w)$//;
+
+  $version = $1 . $version
+    if $version =~ /^\d+$/ and $dist =~ s/-(\w+)$//;
+
+  if ($version =~ /\d\.\d/) {
+    $version =~ s/^[-_.]+//;
+  }
+  else {
+    $version =~ s/^[-_]+//;
+  }
+
+  my $dev;
+  if (length $version) {
+    if ($file =~ /^perl-?\d+\.(\d+)(?:\D(\d+))?(-(?:TRIAL|RC)\d+)?$/) {
+      $dev = 1 if (($1 > 6 and $1 & 1) or ($2 and $2 >= 50)) or $3;
+    }
+    elsif ($version =~ /\d\D\d+_\d/ or $version =~ /-TRIAL/) {
+      $dev = 1;
+    }
+  }
+  else {
+    $version = undef;
+  }
+
+  ($dist, $version, $dev);
+}
+
+sub new {
+  my $class = shift;
+  my $distfile = shift;
+
+  $distfile =~ s,//+,/,g;
+
+  my %info = ( pathname => $distfile );
+
+  ($info{filename} = $distfile) =~ s,^(((.*?/)?authors/)?id/)?([A-Z])/(\4[A-Z])/(\5[-A-Z0-9]*)/,,
+    and $info{cpanid} = $6;
+
+  if ($distfile =~ m,([^/]+)\.(tar\.(?:g?z|bz2)|zip|tgz)$,i) { # support more ?
+    $info{distvname} = $1;
+    $info{extension} = $2;
+  }
+
+  @info{qw(dist version beta)} = distname_info($info{distvname});
+  $info{maturity} = delete $info{beta} ? 'developer' : 'released';
+
+  return bless \%info, $class;
+}
+
+sub dist      { shift->{dist} }
+sub version   { shift->{version} }
+sub maturity  { shift->{maturity} }
+sub filename  { shift->{filename} }
+sub cpanid    { shift->{cpanid} }
+sub distvname { shift->{distvname} }
+sub extension { shift->{extension} }
+sub pathname  { shift->{pathname} }
+
+sub properties { %{ $_[0] } }
+
+1;
+
+__END__
+

fatlib/CPAN/Meta.pm

+use 5.006;
+use strict;
+use warnings;
+package CPAN::Meta;
+BEGIN {
+  $CPAN::Meta::VERSION = '2.110930';
+}
+# ABSTRACT: the distribution metadata for a CPAN dist
+
+
+use Carp qw(carp croak);
+use CPAN::Meta::Feature;
+use CPAN::Meta::Prereqs;
+use CPAN::Meta::Converter;
+use CPAN::Meta::Validator;
+use Parse::CPAN::Meta 1.4400 ();
+
+sub _dclone {
+  my $ref = shift;
+  my $backend = Parse::CPAN::Meta->json_backend();
+  return $backend->new->decode(
+    $backend->new->convert_blessed->encode($ref)
+  );
+}
+
+
+BEGIN {
+  my @STRING_READERS = qw(
+    abstract
+    description
+    dynamic_config
+    generated_by
+    name
+    release_status
+    version
+  );
+
+  no strict 'refs';
+  for my $attr (@STRING_READERS) {
+    *$attr = sub { $_[0]{ $attr } };
+  }
+}
+
+
+BEGIN {
+  my @LIST_READERS = qw(
+    author
+    keywords
+    license
+  );
+
+  no strict 'refs';
+  for my $attr (@LIST_READERS) {
+    *$attr = sub {
+      my $value = $_[0]{ $attr };
+      croak "$attr must be called in list context"
+        unless wantarray;
+      return @{ _dclone($value) } if ref $value;
+      return $value;
+    };
+  }
+}
+
+sub authors  { $_[0]->author }
+sub licenses { $_[0]->license }
+
+
+BEGIN {
+  my @MAP_READERS = qw(
+    meta-spec
+    resources
+    provides
+    no_index
+
+    prereqs
+    optional_features
+  );
+
+  no strict 'refs';
+  for my $attr (@MAP_READERS) {
+    (my $subname = $attr) =~ s/-/_/;
+    *$subname = sub {
+      my $value = $_[0]{ $attr };
+      return _dclone($value) if $value;
+      return {};
+    };
+  }
+}
+
+
+sub custom_keys {
+  return grep { /^x_/i } keys %{$_[0]};
+}
+
+sub custom {
+  my ($self, $attr) = @_;
+  my $value = $self->{$attr};
+  return _dclone($value) if ref $value;
+  return $value;
+}
+
+
+sub _new {
+  my ($class, $struct, $options) = @_;
+  my $self;
+
+  if ( $options->{lazy_validation} ) {
+    # try to convert to a valid structure; if succeeds, then return it
+    my $cmc = CPAN::Meta::Converter->new( $struct );
+    $self = $cmc->convert( version => 2 ); # valid or dies
+    return bless $self, $class;
+  }
+  else {
+    # validate original struct
+    my $cmv = CPAN::Meta::Validator->new( $struct );
+    unless ( $cmv->is_valid) {
+      die "Invalid metadata structure. Errors: "
+        . join(", ", $cmv->errors) . "\n";
+    }
+  }
+
+  # up-convert older spec versions
+  my $version = $struct->{'meta-spec'}{version} || '1.0';
+  if ( $version == 2 ) {
+    $self = $struct;
+  }
+  else {
+    my $cmc = CPAN::Meta::Converter->new( $struct );
+    $self = $cmc->convert( version => 2 );
+  }
+
+  return bless $self, $class;
+}
+
+sub new {
+  my ($class, $struct, $options) = @_;
+  my $self = eval { $class->_new($struct, $options) };
+  croak($@) if $@;
+  return $self;
+}
+
+
+sub create {
+  my ($class, $struct, $options) = @_;
+  my $version = __PACKAGE__->VERSION || 2;
+  $struct->{generated_by} ||= __PACKAGE__ . " version $version" ;
+  $struct->{'meta-spec'}{version} ||= int($version);
+  my $self = eval { $class->_new($struct, $options) };
+  croak ($@) if $@;
+  return $self;
+}
+
+
+sub load_file {
+  my ($class, $file, $options) = @_;
+  $options->{lazy_validation} = 1 unless exists $options->{lazy_validation};
+
+  croak "load_file() requires a valid, readable filename"
+    unless -r $file;
+
+  my $self;
+  eval {
+    my $struct = Parse::CPAN::Meta->load_file( $file );
+    $self = $class->_new($struct, $options);
+  };
+  croak($@) if $@;
+  return $self;
+}
+
+
+sub load_yaml_string {
+  my ($class, $yaml, $options) = @_;
+  $options->{lazy_validation} = 1 unless exists $options->{lazy_validation};
+
+  my $self;
+  eval {
+    my ($struct) = Parse::CPAN::Meta->load_yaml_string( $yaml );
+    $self = $class->_new($struct, $options);
+  };
+  croak($@) if $@;
+  return $self;
+}
+
+
+sub load_json_string {
+  my ($class, $json, $options) = @_;
+  $options->{lazy_validation} = 1 unless exists $options->{lazy_validation};
+
+  my $self;
+  eval {
+    my $struct = Parse::CPAN::Meta->load_json_string( $json );
+    $self = $class->_new($struct, $options);
+  };
+  croak($@) if $@;
+  return $self;
+}
+
+
+sub save {
+  my ($self, $file, $options) = @_;
+
+  my $version = $options->{version} || '2';
+  my $layer = $] ge '5.008001' ? ':utf8' : '';
+
+  if ( $version ge '2' ) {
+    carp "'$file' should end in '.json'"
+      unless $file =~ m{\.json$};
+  }
+  else {
+    carp "'$file' should end in '.yml'"
+      unless $file =~ m{\.yml$};
+  }
+
+  my $data = $self->as_string( $options );
+  open my $fh, ">$layer", $file
+    or die "Error opening '$file' for writing: $!\n";
+
+  print {$fh} $data;
+  close $fh
+    or die "Error closing '$file': $!\n";
+
+  return 1;
+}
+
+
+sub meta_spec_version {
+  my ($self) = @_;
+  return $self->meta_spec->{version};
+}
+
+
+sub effective_prereqs {
+  my ($self, $features) = @_;
+  $features ||= [];
+
+  my $prereq = CPAN::Meta::Prereqs->new($self->prereqs);
+
+  return $prereq unless @$features;
+
+  my @other = map {; $self->feature($_)->prereqs } @$features;
+
+  return $prereq->with_merged_prereqs(\@other);
+}
+
+
+sub should_index_file {
+  my ($self, $filename) = @_;
+
+  for my $no_index_file (@{ $self->no_index->{file} || [] }) {
+    return if $filename eq $no_index_file;
+  }
+
+  for my $no_index_dir (@{ $self->no_index->{directory} }) {
+    $no_index_dir =~ s{$}{/} unless $no_index_dir =~ m{/\z};
+    return if index($filename, $no_index_dir) == 0;
+  }
+
+  return 1;
+}
+
+
+sub should_index_package {
+  my ($self, $package) = @_;
+
+  for my $no_index_pkg (@{ $self->no_index->{package} || [] }) {
+    return if $package eq $no_index_pkg;
+  }
+
+  for my $no_index_ns (@{ $self->no_index->{namespace} }) {
+    return if index($package, "${no_index_ns}::") == 0;
+  }
+
+  return 1;
+}
+
+
+sub features {
+  my ($self) = @_;
+
+  my $opt_f = $self->optional_features;
+  my @features = map {; CPAN::Meta::Feature->new($_ => $opt_f->{ $_ }) }
+                 keys %$opt_f;
+
+  return @features;
+}
+
+
+sub feature {
+  my ($self, $ident) = @_;
+
+  croak "no feature named $ident"
+    unless my $f = $self->optional_features->{ $ident };
+
+  return CPAN::Meta::Feature->new($ident, $f);
+}
+
+
+sub as_struct {
+  my ($self, $options) = @_;
+  my $struct = _dclone($self);
+  if ( $options->{version} ) {
+    my $cmc = CPAN::Meta::Converter->new( $struct );
+    $struct = $cmc->convert( version => $options->{version} );
+  }
+  return $struct;
+}
+
+
+sub as_string {
+  my ($self, $options) = @_;
+
+  my $version = $options->{version} || '2';
+
+  my $struct;
+  if ( $self->meta_spec_version ne $version ) {
+    my $cmc = CPAN::Meta::Converter->new( $self->as_struct );
+    $struct = $cmc->convert( version => $version );
+  }
+  else {
+    $struct = $self->as_struct;
+  }
+
+  my ($data, $backend);
+  if ( $version ge '2' ) {
+    $backend = Parse::CPAN::Meta->json_backend();
+    $data = $backend->new->pretty->canonical->encode($struct);
+  }
+  else {
+    $backend = Parse::CPAN::Meta->yaml_backend();
+    $data = eval { no strict 'refs'; &{"$backend\::Dump"}($struct) };
+    if ( $@ ) {
+      croak $backend->can('errstr') ? $backend->errstr : $@
+    }
+  }
+
+  return $data;
+}
+
+# Used by JSON::PP, etc. for "convert_blessed"
+sub TO_JSON {
+  return { %{ $_[0] } };
+}
+
+1;
+
+
+
+
+__END__
+
+

fatlib/CPAN/Meta/Converter.pm

+use 5.006;
+use strict;
+use warnings;
+package CPAN::Meta::Converter;
+BEGIN {
+  $CPAN::Meta::Converter::VERSION = '2.110930';
+}
+# ABSTRACT: Convert CPAN distribution metadata structures
+
+
+use CPAN::Meta::Validator;
+use version 0.82 ();
+use Parse::CPAN::Meta 1.4400 ();
+
+sub _dclone {
+  my $ref = shift;
+  my $backend = Parse::CPAN::Meta->json_backend();
+  return $backend->new->decode(
+    $backend->new->convert_blessed->encode($ref)
+  );
+}
+
+my %known_specs = (
+    '2'   => 'http://search.cpan.org/perldoc?CPAN::Meta::Spec',
+    '1.4' => 'http://module-build.sourceforge.net/META-spec-v1.4.html',
+    '1.3' => 'http://module-build.sourceforge.net/META-spec-v1.3.html',
+    '1.2' => 'http://module-build.sourceforge.net/META-spec-v1.2.html',
+    '1.1' => 'http://module-build.sourceforge.net/META-spec-v1.1.html',
+    '1.0' => 'http://module-build.sourceforge.net/META-spec-v1.0.html'
+);
+
+my @spec_list = sort { $a <=> $b } keys %known_specs;
+my ($LOWEST, $HIGHEST) = @spec_list[0,-1];
+
+#--------------------------------------------------------------------------#
+# converters
+#
+# called as $converter->($element, $field_name, $full_meta, $to_version)
+#
+# defined return value used for field
+# undef return value means field is skipped
+#--------------------------------------------------------------------------#
+
+sub _keep { $_[0] }
+
+sub _keep_or_one { defined($_[0]) ? $_[0] : 1 }
+
+sub _keep_or_zero { defined($_[0]) ? $_[0] : 0 }
+
+sub _keep_or_unknown { defined($_[0]) && length($_[0]) ? $_[0] : "unknown" }
+
+sub _generated_by {
+  my $gen = shift;
+  my $sig = __PACKAGE__ . " version " . (__PACKAGE__->VERSION || "<dev>");
+
+  return $sig unless defined $gen and length $gen;
+  return $gen if $gen =~ /(, )\Q$sig/;
+  return "$gen, $sig";
+}
+
+sub _listify { ! defined $_[0] ? undef : ref $_[0] eq 'ARRAY' ? $_[0] : [$_[0]] }
+
+sub _prefix_custom {
+  my $key = shift;
+  $key =~ s/^(?!x_)   # Unless it already starts with x_
+             (?:x-?)? # Remove leading x- or x (if present)
+           /x_/ix;    # and prepend x_
+  return $key;
+}
+
+sub _ucfirst_custom {
+  my $key = shift;
+  $key = ucfirst $key unless $key =~ /[A-Z]/;
+  return $key;
+}
+
+sub _change_meta_spec {
+  my ($element, undef, undef, $version) = @_;
+  $element->{version} = $version;
+  $element->{url} = $known_specs{$version};
+  return $element;
+}
+
+my @valid_licenses_1 = (
+  'perl',
+  'gpl',
+  'apache',
+  'artistic',
+  'artistic_2',
+  'lgpl',
+  'bsd',
+  'gpl',
+  'mit',
+  'mozilla',
+  'open_source',
+  'unrestricted',
+  'restrictive',
+  'unknown',
+);
+
+my %license_map_1 = (
+  ( map { $_ => $_ } @valid_licenses_1 ),
+  artistic2 => 'artistic_2',
+);
+
+sub _license_1 {
+  my ($element) = @_;
+  return 'unknown' unless defined $element;
+  if ( $license_map_1{lc $element} ) {
+    return $license_map_1{lc $element};
+  }
+  return 'unknown';
+}
+
+my @valid_licenses_2 = qw(
+  agpl_3
+  apache_1_1
+  apache_2_0
+  artistic_1
+  artistic_2
+  bsd
+  freebsd
+  gfdl_1_2
+  gfdl_1_3
+  gpl_1
+  gpl_2
+  gpl_3
+  lgpl_2_1
+  lgpl_3_0
+  mit
+  mozilla_1_0
+  mozilla_1_1
+  openssl
+  perl_5
+  qpl_1_0
+  ssleay
+  sun
+  zlib
+  open_source
+  restricted
+  unrestricted
+  unknown
+);
+
+# The "old" values were defined by Module::Build, and were often vague.  I have
+# made the decisions below based on reading Module::Build::API and how clearly
+# it specifies the version of the license.
+my %license_map_2 = (
+  (map { $_ => $_ } @valid_licenses_2),
+  apache      => 'apache_2_0',  # clearly stated as 2.0
+  artistic    => 'artistic_1',  # clearly stated as 1
+  artistic2   => 'artistic_2',  # clearly stated as 2
+  gpl         => 'open_source', # we don't know which GPL; punt
+  lgpl        => 'open_source', # we don't know which LGPL; punt
+  mozilla     => 'open_source', # we don't know which MPL; punt
+  perl        => 'perl_5',      # clearly Perl 5
+  restrictive => 'restricted',
+);
+
+sub _license_2 {
+  my ($element) = @_;
+  return [ 'unknown' ] unless defined $element;
+  $element = [ $element ] unless ref $element eq 'ARRAY';
+  my @new_list;
+  for my $lic ( @$element ) {
+    next unless defined $lic;
+    if ( my $new = $license_map_2{lc $lic} ) {
+      push @new_list, $new;
+    }
+  }
+  return @new_list ? \@new_list : [ 'unknown' ];
+}
+
+my %license_downgrade_map = qw(
+  agpl_3            open_source
+  apache_1_1        apache
+  apache_2_0        apache
+  artistic_1        artistic
+  artistic_2        artistic_2
+  bsd               bsd
+  freebsd           open_source
+  gfdl_1_2          open_source
+  gfdl_1_3          open_source
+  gpl_1             gpl
+  gpl_2             gpl
+  gpl_3             gpl
+  lgpl_2_1          lgpl
+  lgpl_3_0          lgpl
+  mit               mit
+  mozilla_1_0       mozilla
+  mozilla_1_1       mozilla
+  openssl           open_source
+  perl_5            perl
+  qpl_1_0           open_source
+  ssleay            open_source
+  sun               open_source
+  zlib              open_source
+  open_source       open_source
+  restricted        restrictive
+  unrestricted      unrestricted
+  unknown           unknown
+);
+
+sub _downgrade_license {
+  my ($element) = @_;
+  if ( ! defined $element ) {
+    return "unknown";
+  }
+  elsif( ref $element eq 'ARRAY' ) {
+    if ( @$element == 1 ) {
+      return $license_downgrade_map{$element->[0]} || "unknown";
+    }
+  }
+  elsif ( ! ref $element ) {
+    return $license_downgrade_map{$element} || "unknown";
+  }
+  return "unknown";
+}
+
+my $no_index_spec_1_2 = {
+  'file' => \&_listify,
+  'dir' => \&_listify,
+  'package' => \&_listify,
+  'namespace' => \&_listify,
+};
+
+my $no_index_spec_1_3 = {
+  'file' => \&_listify,
+  'directory' => \&_listify,
+  'package' => \&_listify,
+  'namespace' => \&_listify,
+};
+
+my $no_index_spec_2 = {
+  'file' => \&_listify,
+  'directory' => \&_listify,
+  'package' => \&_listify,
+  'namespace' => \&_listify,
+  ':custom'  => \&_prefix_custom,
+};
+
+sub _no_index_1_2 {
+  my (undef, undef, $meta) = @_;
+  my $no_index = $meta->{no_index} || $meta->{private};
+  return unless $no_index;
+
+  # cleanup wrong format
+  if ( ! ref $no_index ) {
+    my $item = $no_index;
+    $no_index = { dir => [ $item ], file => [ $item ] };
+  }
+  elsif ( ref $no_index eq 'ARRAY' ) {
+    my $list = $no_index;
+    $no_index = { dir => [ @$list ], file => [ @$list ] };
+  }
+
+  # common mistake: files -> file
+  if ( exists $no_index->{files} ) {
+    $no_index->{file} = delete $no_index->{file};
+  }
+  # common mistake: modules -> module
+  if ( exists $no_index->{modules} ) {
+    $no_index->{module} = delete $no_index->{module};
+  }
+  return _convert($no_index, $no_index_spec_1_2);
+}
+
+sub _no_index_directory {
+  my ($element, $key, $meta, $version) = @_;
+  return unless $element;
+
+  # cleanup wrong format
+  if ( ! ref $element ) {
+    my $item = $element;
+    $element = { directory => [ $item ], file => [ $item ] };
+  }
+  elsif ( ref $element eq 'ARRAY' ) {
+    my $list = $element;
+    $element = { directory => [ @$list ], file => [ @$list ] };
+  }
+
+  if ( exists $element->{dir} ) {
+    $element->{directory} = delete $element->{dir};
+  }
+  # common mistake: files -> file
+  if ( exists $element->{files} ) {
+    $element->{file} = delete $element->{file};
+  }
+  # common mistake: modules -> module
+  if ( exists $element->{modules} ) {
+    $element->{module} = delete $element->{module};
+  }
+  my $spec = $version == 2 ? $no_index_spec_2 : $no_index_spec_1_3;
+  return _convert($element, $spec);
+}
+
+sub _is_module_name {
+  my $mod = shift;
+  return unless defined $mod && length $mod;
+  return $mod =~ m{^[A-Za-z][A-Za-z0-9_]*(?:::[A-Za-z0-9_]+)*$};
+}
+
+sub _clean_version {
+  my ($element, $key, $meta, $to_version) = @_;
+  return 0 if ! defined $element;
+
+  $element =~ s{^\s*}{};
+  $element =~ s{\s*$}{};
+  $element =~ s{^\.}{0.};
+
+  return 0 if ! length $element;
+  return 0 if ( $element eq 'undef' || $element eq '<undef>' );
+
+  if ( my $v = eval { version->new($element) } ) {
+    return $v->is_qv ? $v->normal : $element;
+  }
+  else {
+    return 0;
+  }
+}
+
+sub _version_map {
+  my ($element) = @_;
+  return undef unless defined $element;
+  if ( ref $element eq 'HASH' ) {
+    my $new_map = {};
+    for my $k ( keys %$element ) {
+      next unless _is_module_name($k);
+      my $value = $element->{$k};
+      if ( ! ( defined $value && length $value ) ) {
+        $new_map->{$k} = 0;
+      }
+      elsif ( $value eq 'undef' || $value eq '<undef>' ) {
+        $new_map->{$k} = 0;
+      }
+      elsif ( _is_module_name( $value ) ) { # some weird, old META have this
+        $new_map->{$k} = 0;
+        $new_map->{$value} = 0;
+      }
+      else {
+        $new_map->{$k} = _clean_version($value);
+      }
+    }
+    return $new_map;
+  }
+  elsif ( ref $element eq 'ARRAY' ) {
+    my $hashref = { map { $_ => 0 } @$element };
+    return _version_map($hashref); # cleanup any weird stuff
+  }
+  elsif ( ref $element eq '' && length $element ) {
+    return { $element => 0 }
+  }
+  return;
+}
+
+sub _prereqs_from_1 {
+  my (undef, undef, $meta) = @_;
+  my $prereqs = {};
+  for my $phase ( qw/build configure/ ) {
+    my $key = "${phase}_requires";
+    $prereqs->{$phase}{requires} = _version_map($meta->{$key})
+      if $meta->{$key};
+  }
+  for my $rel ( qw/requires recommends conflicts/ ) {
+    $prereqs->{runtime}{$rel} = _version_map($meta->{$rel})
+      if $meta->{$rel};
+  }
+  return $prereqs;
+}
+
+my $prereqs_spec = {
+  configure => \&_prereqs_rel,
+  build     => \&_prereqs_rel,
+  test      => \&_prereqs_rel,
+  runtime   => \&_prereqs_rel,
+  develop   => \&_prereqs_rel,
+  ':custom'  => \&_prefix_custom,
+};
+
+my $relation_spec = {
+  requires   => \&_version_map,
+  recommends => \&_version_map,
+  suggests   => \&_version_map,
+  conflicts  => \&_version_map,
+  ':custom'  => \&_prefix_custom,
+};
+
+sub _cleanup_prereqs {
+  my ($prereqs, $key, $meta, $to_version) = @_;
+  return unless $prereqs && ref $prereqs eq 'HASH';
+  return _convert( $prereqs, $prereqs_spec, $to_version );
+}
+
+sub _prereqs_rel {
+  my ($relation, $key, $meta, $to_version) = @_;
+  return unless $relation && ref $relation eq 'HASH';
+  return _convert( $relation, $relation_spec, $to_version );
+}
+
+
+BEGIN {
+  my @old_prereqs = qw(
+    requires
+    configure_requires
+    recommends
+    conflicts
+  );
+
+  for ( @old_prereqs ) {
+    my $sub = "_get_$_";
+    my ($phase,$type) = split qr/_/, $_;
+    if ( ! defined $type ) {
+      $type = $phase;
+      $phase = 'runtime';
+    }
+    no strict 'refs';
+    *{$sub} = sub { _extract_prereqs($_[2]->{prereqs},$phase,$type) };
+  }
+}
+
+sub _get_build_requires {
+  my ($data, $key, $meta) = @_;
+
+  my $test_h  = _extract_prereqs($_[2]->{prereqs}, qw(test  requires)) || {};
+  my $build_h = _extract_prereqs($_[2]->{prereqs}, qw(build requires)) || {};
+
+  require Version::Requirements;
+  my $test_req  = Version::Requirements->from_string_hash($test_h);
+  my $build_req = Version::Requirements->from_string_hash($build_h);
+
+  $test_req->add_requirements($build_req)->as_string_hash;
+}
+
+sub _extract_prereqs {
+  my ($prereqs, $phase, $type) = @_;
+  return unless ref $prereqs eq 'HASH';
+  return $prereqs->{$phase}{$type};
+}
+
+sub _downgrade_optional_features {
+  my (undef, undef, $meta) = @_;
+  return undef unless exists $meta->{optional_features};
+  my $origin = $meta->{optional_features};
+  my $features = {};
+  for my $name ( keys %$origin ) {
+    $features->{$name} = {
+      description => $origin->{$name}{description},
+      requires => _extract_prereqs($origin->{$name}{prereqs},'runtime','requires'),
+      configure_requires => _extract_prereqs($origin->{$name}{prereqs},'runtime','configure_requires'),
+      build_requires => _extract_prereqs($origin->{$name}{prereqs},'runtime','build_requires'),
+      recommends => _extract_prereqs($origin->{$name}{prereqs},'runtime','recommends'),
+      conflicts => _extract_prereqs($origin->{$name}{prereqs},'runtime','conflicts'),
+    };
+    for my $k (keys %{$features->{$name}} ) {
+      delete $features->{$name}{$k} unless defined $features->{$name}{$k};
+    }
+  }
+  return $features;
+}
+
+sub _upgrade_optional_features {
+  my (undef, undef, $meta) = @_;
+  return undef unless exists $meta->{optional_features};
+  my $origin = $meta->{optional_features};
+  my $features = {};
+  for my $name ( keys %$origin ) {
+    $features->{$name} = {
+      description => $origin->{$name}{description},
+      prereqs => _prereqs_from_1(undef, undef, $origin->{$name}),
+    };
+    delete $features->{$name}{prereqs}{configure};
+  }
+  return $features;
+}
+
+my $optional_features_2_spec = {
+  description => \&_keep,
+  prereqs => \&_cleanup_prereqs,
+  ':custom'  => \&_prefix_custom,
+};
+
+sub _feature_2 {
+  my ($element, $key, $meta, $to_version) = @_;
+  return unless $element && ref $element eq 'HASH';
+  _convert( $element, $optional_features_2_spec, $to_version );
+}
+
+sub _cleanup_optional_features_2 {
+  my ($element, $key, $meta, $to_version) = @_;
+  return unless $element && ref $element eq 'HASH';
+  my $new_data = {};
+  for my $k ( keys %$element ) {
+    $new_data->{$k} = _feature_2( $element->{$k}, $k, $meta, $to_version );
+  }
+  return unless keys %$new_data;
+  return $new_data;
+}
+
+sub _optional_features_1_4 {
+  my ($element) = @_;
+  return unless $element;
+  $element = _optional_features_as_map($element);
+  for my $name ( keys %$element ) {
+    for my $drop ( qw/requires_packages requires_os excluded_os/ ) {
+      delete $element->{$name}{$drop};
+    }
+  }
+  return $element;
+}
+
+sub _optional_features_as_map {
+  my ($element) = @_;
+  return unless $element;
+  if ( ref $element eq 'ARRAY' ) {
+    my %map;
+    for my $feature ( @$element ) {
+      my (@parts) = %$feature;
+      $map{$parts[0]} = $parts[1];
+    }
+    $element = \%map;
+  }
+  return $element;
+}
+
+sub _is_urlish { defined $_[0] && $_[0] =~ m{\A[-+.a-z0-9]+:.+}i }
+
+sub _url_or_drop {
+  my ($element) = @_;
+  return $element if _is_urlish($element);
+  return;
+}
+
+sub _url_list {
+  my ($element) = @_;
+  return unless $element;
+  $element = _listify( $element );
+  $element = [ grep { _is_urlish($_) } @$element ];
+  return unless @$element;
+  return $element;
+}
+
+sub _author_list {
+  my ($element) = @_;
+  return [ 'unknown' ] unless $element;
+  $element = _listify( $element );
+  $element = [ map { defined $_ && length $_ ? $_ : 'unknown' } @$element ];
+  return [ 'unknown' ] unless @$element;
+  return $element;
+}
+
+my $resource2_upgrade = {
+  license    => sub { return _is_urlish($_[0]) ? _listify( $_[0] ) : undef },
+  homepage   => \&_url_or_drop,
+  bugtracker => sub {
+    my ($item) = @_;
+    return unless $item;
+    if ( $item =~ m{^mailto:(.*)$} ) { return { mailto => $1 } }
+    elsif( _is_urlish($item) ) { return { web => $item } }
+    else { return undef }
+  },
+  repository => sub { return _is_urlish($_[0]) ? { url => $_[0] } : undef },
+  ':custom'  => \&_prefix_custom,
+};
+
+sub _upgrade_resources_2 {
+  my (undef, undef, $meta, $version) = @_;
+  return undef unless exists $meta->{resources};
+  return _convert($meta->{resources}, $resource2_upgrade);
+}
+
+my $bugtracker2_spec = {
+  web => \&_url_or_drop,
+  mailto => \&_keep,
+  ':custom'  => \&_prefix_custom,
+};
+
+sub _repo_type {
+  my ($element, $key, $meta, $to_version) = @_;
+  return $element if defined $element;
+  return unless exists $meta->{url};
+  my $repo_url = $meta->{url};
+  for my $type ( qw/git svn/ ) {
+    return $type if $repo_url =~ m{\A$type};
+  }
+  return;
+}
+
+my $repository2_spec = {
+  web => \&_url_or_drop,
+  url => \&_url_or_drop,
+  type => \&_repo_type,
+  ':custom'  => \&_prefix_custom,
+};
+
+my $resources2_cleanup = {
+  license    => \&_url_list,
+  homepage   => \&_url_or_drop,
+  bugtracker => sub { ref $_[0] ? _convert( $_[0], $bugtracker2_spec ) : undef },
+  repository => sub { my $data = shift; ref $data ? _convert( $data, $repository2_spec ) : undef },
+  ':custom'  => \&_prefix_custom,
+};
+
+sub _cleanup_resources_2 {
+  my ($resources, $key, $meta, $to_version) = @_;
+  return undef unless $resources && ref $resources eq 'HASH';
+  return _convert($resources, $resources2_cleanup, $to_version);
+}
+
+my $resource1_spec = {
+  license    => \&_url_or_drop,
+  homepage   => \&_url_or_drop,
+  bugtracker => \&_url_or_drop,
+  repository => \&_url_or_drop,
+  ':custom'  => \&_keep,
+};
+
+sub _resources_1_3 {
+  my (undef, undef, $meta, $version) = @_;
+  return undef unless exists $meta->{resources};
+  return _convert($meta->{resources}, $resource1_spec);
+}
+
+*_resources_1_4 = *_resources_1_3;
+
+sub _resources_1_2 {
+  my (undef, undef, $meta) = @_;
+  my $resources = $meta->{resources} || {};
+  if ( $meta->{license_url} && ! $resources->{license} ) {
+    $resources->{license} = $meta->license_url
+      if _is_urlish($meta->{license_url});
+  }
+  return undef unless keys %$resources;
+  return _convert($resources, $resource1_spec);
+}
+
+my $resource_downgrade_spec = {
+  license    => sub { return ref $_[0] ? $_[0]->[0] : $_[0] },
+  homepage   => \&_url_or_drop,
+  bugtracker => sub { return $_[0]->{web} },
+  repository => sub { return $_[0]->{url} || $_[0]->{web} },
+  ':custom'  => \&_ucfirst_custom,
+};
+
+sub _downgrade_resources {
+  my (undef, undef, $meta, $version) = @_;
+  return undef unless exists $meta->{resources};
+  return _convert($meta->{resources}, $resource_downgrade_spec);
+}
+
+sub _release_status {
+  my ($element, undef, $meta) = @_;
+  return $element if $element && $element =~ m{\A(?:stable|testing|unstable)\z};
+  return _release_status_from_version(undef, undef, $meta);
+}
+
+sub _release_status_from_version {
+  my (undef, undef, $meta) = @_;
+  my $version = $meta->{version} || '';
+  return ( $version =~ /_/ ) ? 'testing' : 'stable';
+}
+
+my $provides_spec = {
+  file => \&_keep,
+  version => \&_clean_version,
+};
+
+my $provides_spec_2 = {
+  file => \&_keep,
+  version => \&_clean_version,
+  ':custom'  => \&_prefix_custom,
+};
+
+sub _provides {
+  my ($element, $key, $meta, $to_version) = @_;
+  return unless defined $element && ref $element eq 'HASH';
+  my $spec = $to_version == 2 ? $provides_spec_2 : $provides_spec;
+  my $new_data = {};
+  for my $k ( keys %$element ) {
+    $new_data->{$k} = _convert($element->{$k}, $spec, $to_version);
+  }
+  return $new_data;
+}
+
+sub _convert {
+  my ($data, $spec, $to_version) = @_;
+
+  my $new_data = {};
+  for my $key ( keys %$spec ) {
+    next if $key eq ':custom' || $key eq ':drop';
+    next unless my $fcn = $spec->{$key};
+    die "spec for '$key' is not a coderef"
+      unless ref $fcn && ref $fcn eq 'CODE';
+    my $new_value = $fcn->($data->{$key}, $key, $data, $to_version);
+    $new_data->{$key} = $new_value if defined $new_value;
+  }
+
+  my $drop_list   = $spec->{':drop'};
+  my $customizer  = $spec->{':custom'} || \&_keep;
+
+  for my $key ( keys %$data ) {
+    next if $drop_list && grep { $key eq $_ } @$drop_list;
+    next if exists $spec->{$key}; # we handled it
+    $new_data->{ $customizer->($key) } = $data->{$key};
+  }
+
+  return $new_data;
+}
+
+#--------------------------------------------------------------------------#
+# define converters for each conversion
+#--------------------------------------------------------------------------#
+
+# each converts from prior version
+# special ":custom" field is used for keys not recognized in spec
+my %up_convert = (
+  '2-from-1.4' => {
+    # PRIOR MANDATORY
+    'abstract'            => \&_keep_or_unknown,
+    'author'              => \&_author_list,
+    'generated_by'        => \&_generated_by,
+    'license'             => \&_license_2,
+    'meta-spec'           => \&_change_meta_spec,
+    'name'                => \&_keep,
+    'version'             => \&_keep,
+    # CHANGED TO MANDATORY
+    'dynamic_config'      => \&_keep_or_one,
+    # ADDED MANDATORY
+    'release_status'      => \&_release_status_from_version,
+    # PRIOR OPTIONAL
+    'keywords'            => \&_keep,
+    'no_index'            => \&_no_index_directory,
+    'optional_features'   => \&_upgrade_optional_features,
+    'provides'            => \&_provides,
+    'resources'           => \&_upgrade_resources_2,
+    # ADDED OPTIONAL
+    'description'         => \&_keep,
+    'prereqs'             => \&_prereqs_from_1,
+
+    # drop these deprecated fields, but only after we convert
+    ':drop' => [ qw(
+        build_requires
+        configure_requires
+        conflicts
+        distribution_type
+        license_url
+        private
+        recommends
+        requires
+    ) ],
+
+    # other random keys need x_ prefixing
+    ':custom'              => \&_prefix_custom,
+  },
+  '1.4-from-1.3' => {
+    # PRIOR MANDATORY
+    'abstract'            => \&_keep_or_unknown,
+    'author'              => \&_author_list,
+    'generated_by'        => \&_generated_by,
+    'license'             => \&_license_1,
+    'meta-spec'           => \&_change_meta_spec,
+    'name'                => \&_keep,
+    'version'             => \&_keep,
+    # PRIOR OPTIONAL
+    'build_requires'      => \&_version_map,
+    'conflicts'           => \&_version_map,
+    'distribution_type'   => \&_keep,
+    'dynamic_config'      => \&_keep_or_one,
+    'keywords'            => \&_keep,
+    'no_index'            => \&_no_index_directory,
+    'optional_features'   => \&_optional_features_1_4,
+    'provides'            => \&_provides,
+    'recommends'          => \&_version_map,
+    'requires'            => \&_version_map,
+    'resources'           => \&_resources_1_4,
+    # ADDED OPTIONAL
+    'configure_requires'  => \&_keep,
+
+    # drop these deprecated fields, but only after we convert
+    ':drop' => [ qw(
+      license_url
+      private
+    )],
+
+    # other random keys are OK if already valid
+    ':custom'              => \&_keep
+  },
+  '1.3-from-1.2' => {
+    # PRIOR MANDATORY
+    'abstract'            => \&_keep_or_unknown,
+    'author'              => \&_author_list,
+    'generated_by'        => \&_generated_by,
+    'license'             => \&_license_1,
+    'meta-spec'           => \&_change_meta_spec,
+    'name'                => \&_keep,
+    'version'             => \&_keep,
+    # PRIOR OPTIONAL
+    'build_requires'      => \&_version_map,
+    'conflicts'           => \&_version_map,
+    'distribution_type'   => \&_keep,
+    'dynamic_config'      => \&_keep_or_one,
+    'keywords'            => \&_keep,
+    'no_index'            => \&_no_index_directory,
+    'optional_features'   => \&_optional_features_as_map,
+    'provides'            => \&_provides,
+    'recommends'          => \&_version_map,
+    'requires'            => \&_version_map,
+    'resources'           => \&_resources_1_3,
+
+    # drop these deprecated fields, but only after we convert
+    ':drop' => [ qw(
+      license_url
+      private
+    )],
+
+    # other random keys are OK if already valid
+    ':custom'              => \&_keep
+  },
+  '1.2-from-1.1' => {
+    # PRIOR MANDATORY
+    'version'             => \&_keep,
+    # CHANGED TO MANDATORY
+    'license'             => \&_license_1,
+    'name'                => \&_keep,
+    'generated_by'        => \&_generated_by,
+    # ADDED MANDATORY
+    'abstract'            => \&_keep_or_unknown,
+    'author'              => \&_author_list,
+    'meta-spec'           => \&_change_meta_spec,
+    # PRIOR OPTIONAL
+    'build_requires'      => \&_version_map,
+    'conflicts'           => \&_version_map,
+    'distribution_type'   => \&_keep,
+    'dynamic_config'      => \&_keep_or_one,
+    'recommends'          => \&_version_map,
+    'requires'            => \&_version_map,
+    # ADDED OPTIONAL
+    'keywords'            => \&_keep,
+    'no_index'            => \&_no_index_1_2,
+    'optional_features'   => \&_optional_features_as_map,
+    'provides'            => \&_provides,
+    'resources'           => \&_resources_1_2,
+
+    # drop these deprecated fields, but only after we convert
+    ':drop' => [ qw(
+      license_url
+      private
+    )],
+
+    # other random keys are OK if already valid
+    ':custom'              => \&_keep
+  },
+  '1.1-from-1.0' => {
+    # CHANGED TO MANDATORY
+    'version'             => \&_keep,
+    # IMPLIED MANDATORY
+    'name'                => \&_keep,
+    # PRIOR OPTIONAL
+    'build_requires'      => \&_version_map,
+    'conflicts'           => \&_version_map,
+    'distribution_type'   => \&_keep,
+    'dynamic_config'      => \&_keep_or_one,
+    'generated_by'        => \&_generated_by,
+    'license'             => \&_license_1,
+    'recommends'          => \&_version_map,
+    'requires'            => \&_version_map,
+    # ADDED OPTIONAL
+    'license_url'         => \&_url_or_drop,
+    'private'             => \&_keep,
+
+    # other random keys are OK if already valid
+    ':custom'              => \&_keep
+  },
+);
+
+my %down_convert = (
+  '1.4-from-2' => {
+    # MANDATORY
+    'abstract'            => \&_keep_or_unknown,
+    'author'              => \&_author_list,
+    'generated_by'        => \&_generated_by,
+    'license'             => \&_downgrade_license,
+    'meta-spec'           => \&_change_meta_spec,
+    'name'                => \&_keep,
+    'version'             => \&_keep,
+    # OPTIONAL
+    'build_requires'      => \&_get_build_requires,
+    'configure_requires'  => \&_get_configure_requires,
+    'conflicts'           => \&_get_conflicts,
+    'distribution_type'   => \&_keep,
+    'dynamic_config'      => \&_keep_or_one,
+    'keywords'            => \&_keep,
+    'no_index'            => \&_no_index_directory,
+    'optional_features'   => \&_downgrade_optional_features,
+    'provides'            => \&_provides,
+    'recommends'          => \&_get_recommends,
+    'requires'            => \&_get_requires,
+    'resources'           => \&_downgrade_resources,
+
+    # drop these unsupported fields (after conversion)
+    ':drop' => [ qw(
+      description
+      prereqs
+      release_status
+    )],
+
+    # custom keys will be left unchanged
+    ':custom'              => \&_keep
+  },
+  '1.3-from-1.4' => {
+    # MANDATORY
+    'abstract'            => \&_keep_or_unknown,
+    'author'              => \&_author_list,
+    'generated_by'        => \&_generated_by,
+    'license'             => \&_license_1,
+    'meta-spec'           => \&_change_meta_spec,
+    'name'                => \&_keep,
+    'version'             => \&_keep,
+    # OPTIONAL
+    'build_requires'      => \&_version_map,
+    'conflicts'           => \&_version_map,
+    'distribution_type'   => \&_keep,
+    'dynamic_config'      => \&_keep_or_one,
+    'keywords'            => \&_keep,
+    'no_index'            => \&_no_index_directory,
+    'optional_features'   => \&_optional_features_as_map,
+    'provides'            => \&_provides,
+    'recommends'          => \&_version_map,
+    'requires'            => \&_version_map,
+    'resources'           => \&_resources_1_3,
+
+    # drop these unsupported fields, but only after we convert
+    ':drop' => [ qw(
+      configure_requires
+    )],
+
+    # other random keys are OK if already valid
+    ':custom'              => \&_keep,
+  },
+  '1.2-from-1.3' => {
+    # MANDATORY
+    'abstract'            => \&_keep_or_unknown,
+    'author'              => \&_author_list,
+    'generated_by'        => \&_generated_by,
+    'license'             => \&_license_1,
+    'meta-spec'           => \&_change_meta_spec,
+    'name'                => \&_keep,
+    'version'             => \&_keep,
+    # OPTIONAL
+    'build_requires'      => \&_version_map,
+    'conflicts'           => \&_version_map,
+    'distribution_type'   => \&_keep,
+    'dynamic_config'      => \&_keep_or_one,
+    'keywords'            => \&_keep,
+    'no_index'            => \&_no_index_1_2,
+    'optional_features'   => \&_optional_features_as_map,
+    'provides'            => \&_provides,
+    'recommends'          => \&_version_map,
+    'requires'            => \&_version_map,
+    'resources'           => \&_resources_1_3,
+
+    # other random keys are OK if already valid
+    ':custom'              => \&_keep,
+  },
+  '1.1-from-1.2' => {
+    # MANDATORY
+    'version'             => \&_keep,
+    # IMPLIED MANDATORY
+    'name'                => \&_keep,
+    'meta-spec'           => \&_change_meta_spec,
+    # OPTIONAL
+    'build_requires'      => \&_version_map,
+    'conflicts'           => \&_version_map,
+    'distribution_type'   => \&_keep,
+    'dynamic_config'      => \&_keep_or_one,
+    'generated_by'        => \&_generated_by,
+    'license'             => \&_license_1,
+    'private'             => \&_keep,
+    'recommends'          => \&_version_map,
+    'requires'            => \&_version_map,
+
+    # drop unsupported fields
+    ':drop' => [ qw(
+      abstract
+      author
+      provides
+      no_index
+      keywords
+      resources
+    )],
+
+    # other random keys are OK if already valid
+    ':custom'              => \&_keep,
+  },
+  '1.0-from-1.1' => {
+    # IMPLIED MANDATORY
+    'name'                => \&_keep,
+    'meta-spec'           => \&_change_meta_spec,
+    'version'             => \&_keep,
+    # PRIOR OPTIONAL
+    'build_requires'      => \&_version_map,
+    'conflicts'           => \&_version_map,
+    'distribution_type'   => \&_keep,
+    'dynamic_config'      => \&_keep_or_one,
+    'generated_by'        => \&_generated_by,
+    'license'             => \&_license_1,
+    'recommends'          => \&_version_map,
+    'requires'            => \&_version_map,
+
+    # other random keys are OK if already valid
+    ':custom'              => \&_keep,
+  },
+);
+
+my %cleanup = (
+  '2' => {
+    # PRIOR MANDATORY
+    'abstract'            => \&_keep_or_unknown,
+    'author'              => \&_author_list,
+    'generated_by'        => \&_generated_by,
+    'license'             => \&_license_2,
+    'meta-spec'           => \&_change_meta_spec,
+    'name'                => \&_keep,
+    'version'             => \&_keep,
+    # CHANGED TO MANDATORY
+    'dynamic_config'      => \&_keep_or_one,
+    # ADDED MANDATORY
+    'release_status'      => \&_release_status,
+    # PRIOR OPTIONAL
+    'keywords'            => \&_keep,
+    'no_index'            => \&_no_index_directory,
+    'optional_features'   => \&_cleanup_optional_features_2,
+    'provides'            => \&_provides,
+    'resources'           => \&_cleanup_resources_2,
+    # ADDED OPTIONAL
+    'description'         => \&_keep,
+    'prereqs'             => \&_cleanup_prereqs,
+
+    # drop these deprecated fields, but only after we convert
+    ':drop' => [ qw(
+        build_requires
+        configure_requires
+        conflicts
+        distribution_type
+        license_url
+        private
+        recommends
+        requires
+    ) ],
+
+    # other random keys need x_ prefixing
+    ':custom'              => \&_prefix_custom,
+  },
+  '1.4' => {
+    # PRIOR MANDATORY
+    'abstract'            => \&_keep_or_unknown,
+    'author'              => \&_author_list,
+    'generated_by'        => \&_generated_by,
+    'license'             => \&_license_1,
+    'meta-spec'           => \&_change_meta_spec,
+    'name'                => \&_keep,
+    'version'             => \&_keep,
+    # PRIOR OPTIONAL
+    'build_requires'      => \&_version_map,
+    'conflicts'           => \&_version_map,
+    'distribution_type'   => \&_keep,
+    'dynamic_config'      => \&_keep_or_one,
+    'keywords'            => \&_keep,
+    'no_index'            => \&_no_index_directory,
+    'optional_features'   => \&_optional_features_1_4,
+    'provides'            => \&_provides,
+    'recommends'          => \&_version_map,
+    'requires'            => \&_version_map,
+    'resources'           => \&_resources_1_4,
+    # ADDED OPTIONAL
+    'configure_requires'  => \&_keep,
+
+    # other random keys are OK if already valid
+    ':custom'             => \&_keep
+  },
+  '1.3' => {
+    # PRIOR MANDATORY
+    'abstract'            => \&_keep_or_unknown,
+    'author'              => \&_author_list,
+    'generated_by'        => \&_generated_by,
+    'license'             => \&_license_1,
+    'meta-spec'           => \&_change_meta_spec,
+    'name'                => \&_keep,
+    'version'             => \&_keep,
+    # PRIOR OPTIONAL
+    'build_requires'      => \&_version_map,
+    'conflicts'           => \&_version_map,
+    'distribution_type'   => \&_keep,
+    'dynamic_config'      => \&_keep_or_one,
+    'keywords'            => \&_keep,
+    'no_index'            => \&_no_index_directory,
+    'optional_features'   => \&_optional_features_as_map,
+    'provides'            => \&_provides,
+    'recommends'          => \&_version_map,
+    'requires'            => \&_version_map,
+    'resources'           => \&_resources_1_3,
+
+    # other random keys are OK if already valid
+    ':custom'             => \&_keep
+  },
+  '1.2' => {
+    # PRIOR MANDATORY
+    'version'             => \&_keep,
+    # CHANGED TO MANDATORY
+    'license'             => \&_license_1,
+    'name'                => \&_keep,
+    'generated_by'        => \&_generated_by,
+    # ADDED MANDATORY
+    'abstract'            => \&_keep_or_unknown,
+    'author'              => \&_author_list,
+    'meta-spec'           => \&_change_meta_spec,
+    # PRIOR OPTIONAL
+    'build_requires'      => \&_version_map,
+    'conflicts'           => \&_version_map,
+    'distribution_type'   => \&_keep,
+    'dynamic_config'      => \&_keep_or_one,
+    'recommends'          => \&_version_map,
+    'requires'            => \&_version_map,
+    # ADDED OPTIONAL
+    'keywords'            => \&_keep,
+    'no_index'            => \&_no_index_1_2,
+    'optional_features'   => \&_optional_features_as_map,
+    'provides'            => \&_provides,
+    'resources'           => \&_resources_1_2,
+
+    # other random keys are OK if already valid
+    ':custom'             => \&_keep
+  },
+  '1.1' => {
+    # CHANGED TO MANDATORY
+    'version'             => \&_keep,
+    # IMPLIED MANDATORY
+    'name'                => \&_keep,
+    'meta-spec'           => \&_change_meta_spec,
+    # PRIOR OPTIONAL
+    'build_requires'      => \&_version_map,
+    'conflicts'           => \&_version_map,
+    'distribution_type'   => \&_keep,
+    'dynamic_config'      => \&_keep_or_one,
+    'generated_by'        => \&_generated_by,
+    'license'             => \&_license_1,
+    'recommends'          => \&_version_map,
+    'requires'            => \&_version_map,
+    # ADDED OPTIONAL
+    'license_url'         => \&_url_or_drop,
+    'private'             => \&_keep,
+
+    # other random keys are OK if already valid
+    ':custom'             => \&_keep
+  },
+  '1.0' => {
+    # IMPLIED MANDATORY
+    'name'                => \&_keep,
+    'meta-spec'           => \&_change_meta_spec,
+    'version'             => \&_keep,
+    # IMPLIED OPTIONAL
+    'build_requires'      => \&_version_map,
+    'conflicts'           => \&_version_map,
+    'distribution_type'   => \&_keep,
+    'dynamic_config'      => \&_keep_or_one,
+    'generated_by'        => \&_generated_by,
+    'license'             => \&_license_1,
+    'recommends'          => \&_version_map,
+    'requires'            => \&_version_map,
+
+    # other random keys are OK if already valid
+    ':custom'             => \&_keep,
+  },
+);
+
+#--------------------------------------------------------------------------#
+# Code
+#--------------------------------------------------------------------------#
+
+
+sub new {
+  my ($class,$data) = @_;
+
+  # create an attributes hash
+  my $self = {
+    'data'    => $data,
+    'spec'    => $data->{'meta-spec'}{'version'} || "1.0",
+  };
+
+  # create the object
+  return bless $self, $class;
+}
+
+
+sub convert {
+  my ($self, %args) = @_;
+  my $args = { %args };
+
+  my $new_version = $args->{version} || $HIGHEST;
+
+  my ($old_version) = $self->{spec};
+  my $converted = _dclone($self->{data});
+
+  if ( $old_version == $new_version ) {
+    $converted = _convert( $converted, $cleanup{$old_version}, $old_version );
+    my $cmv = CPAN::Meta::Validator->new( $converted );
+    unless ( $cmv->is_valid ) {
+      my $errs = join("\n", $cmv->errors);
+      die "Failed to clean-up $old_version metadata. Errors:\n$errs\n";
+    }
+    return $converted;
+  }
+  elsif ( $old_version > $new_version )  {
+    my @vers = sort { $b <=> $a } keys %known_specs;
+    for my $i ( 0 .. $#vers-1 ) {
+      next if $vers[$i] > $old_version;
+      last if $vers[$i+1] < $new_version;
+      my $spec_string = "$vers[$i+1]-from-$vers[$i]";
+      $converted = _convert( $converted, $down_convert{$spec_string}, $vers[$i+1] );
+      my $cmv = CPAN::Meta::Validator->new( $converted );
+      unless ( $cmv->is_valid ) {
+        my $errs = join("\n", $cmv->errors);
+        die "Failed to downconvert metadata to $vers[$i+1]. Errors:\n$errs\n";
+      }
+    }
+    return $converted;
+  }
+  else {
+    my @vers = sort { $a <=> $b } keys %known_specs;
+    for my $i ( 0 .. $#vers-1 ) {
+      next if $vers[$i] < $old_version;
+      last if $vers[$i+1] > $new_version;
+      my $spec_string = "$vers[$i+1]-from-$vers[$i]";
+      $converted = _convert( $converted, $up_convert{$spec_string}, $vers[$i+1] );
+      my $cmv = CPAN::Meta::Validator->new( $converted );
+      unless ( $cmv->is_valid ) {
+        my $errs = join("\n", $cmv->errors);
+        die "Failed to upconvert metadata to $vers[$i+1]. Errors:\n$errs\n";
+      }
+    }
+    return $converted;
+  }
+}
+
+1;
+
+
+
+
+__END__
+
+

fatlib/CPAN/Meta/Feature.pm

+use 5.006;
+use strict;
+use warnings;
+package CPAN::Meta::Feature;
+BEGIN {
+  $CPAN::Meta::Feature::VERSION = '2.110930';
+}
+# ABSTRACT: an optional feature provided by a CPAN distribution
+
+use CPAN::Meta::Prereqs;
+
+
+sub new {
+  my ($class, $identifier, $spec) = @_;
+
+  my %guts = (
+    identifier  => $identifier,
+    description => $spec->{description},
+    prereqs     => CPAN::Meta::Prereqs->new($spec->{prereqs}),
+  );
+
+  bless \%guts => $class;
+}
+
+
+sub identifier  { $_[0]{identifier}  }
+
+
+sub description { $_[0]{description} }
+
+
+sub prereqs     { $_[0]{prereqs} }
+
+1;
+
+
+
+
+__END__
+
+
+

fatlib/CPAN/Meta/History.pm

+# vi:tw=72
+use 5.006;
+use strict;
+use warnings;
+package CPAN::Meta::History;
+BEGIN {
+  $CPAN::Meta::History::VERSION = '2.110930';
+}
+# ABSTRACT: history of CPAN Meta Spec changes
+1;
+
+
+
+__END__
+=pod
+

fatlib/CPAN/Meta/Prereqs.pm

+use 5.006;
+use strict;
+use warnings;
+package CPAN::Meta::Prereqs;
+BEGIN {
+  $CPAN::Meta::Prereqs::VERSION = '2.110930';
+}
+# ABSTRACT: a set of distribution prerequisites by phase and type
+
+
+use Carp qw(confess);
+use Scalar::Util qw(blessed);
+use Version::Requirements 0.101020; # finalize
+
+
+sub __legal_phases { qw(configure build test runtime develop)   }
+sub __legal_types  { qw(requires recommends suggests conflicts) }
+
+# expect a prereq spec from META.json -- rjbs, 2010-04-11
+sub new {
+  my ($class, $prereq_spec) = @_;
+  $prereq_spec ||= {};
+
+  my %is_legal_phase = map {; $_ => 1 } $class->__legal_phases;
+  my %is_legal_type  = map {; $_ => 1 } $class->__legal_types;
+
+  my %guts;
+  PHASE: for my $phase (keys %$prereq_spec) {
+    next PHASE unless $phase =~ /\Ax_/i or $is_legal_phase{$phase};
+
+    my $phase_spec = $prereq_spec->{ $phase };
+    next PHASE unless keys %$phase_spec;
+
+    TYPE: for my $type (keys %$phase_spec) {
+      next TYPE unless $type =~ /\Ax_/i or $is_legal_type{$type};
+
+      my $spec = $phase_spec->{ $type };
+
+      next TYPE unless keys %$spec;
+
+      $guts{prereqs}{$phase}{$type} = Version::Requirements->from_string_hash(
+        $spec
+      );
+    }
+  }
+
+  return bless \%guts => $class;
+}
+
+
+sub requirements_for {
+  my ($self, $phase, $type) = @_;
+
+  confess "requirements_for called without phase" unless defined $phase;
+  confess "requirements_for called without type"  unless defined $type;
+
+  unless ($phase =~ /\Ax_/i or grep { $phase eq $_ } $self->__legal_phases) {
+    confess "requested requirements for unknown phase: $phase";
+  }
+
+  unless ($type =~ /\Ax_/i or grep { $type eq $_ } $self->__legal_types) {
+    confess "requested requirements for unknown type: $type";
+  }
+
+  my $req = ($self->{prereqs}{$phase}{$type} ||= Version::Requirements->new);
+
+  $req->finalize if $self->is_finalized;
+
+  return $req;
+}
+
+
+sub with_merged_prereqs {
+  my ($self, $other) = @_;
+
+  my @other = blessed($other) ? $other : @$other;
+
+  my @prereq_objs = ($self, @other);
+
+  my %new_arg;
+
+  for my $phase ($self->__legal_phases) {
+    for my $type ($self->__legal_types) {
+      my $req = Version::Requirements->new;
+
+      for my $prereq (@prereq_objs) {
+        my $this_req = $prereq->requirements_for($phase, $type);
+        next unless $this_req->required_modules;
+
+        $req->add_requirements($this_req);
+      }
+
+      next unless $req->required_modules;
+
+      $new_arg{ $phase }{ $type } = $req->as_string_hash;
+    }
+  }
+
+  return (ref $self)->new(\%new_arg);
+}
+
+
+sub as_string_hash {
+  my ($self) = @_;
+
+  my %hash;
+
+  for my $phase ($self->__legal_phases) {
+    for my $type ($self->__legal_types) {
+      my $req = $self->requirements_for($phase, $type);
+      next unless $req->required_modules;
+
+      $hash{ $phase }{ $type } = $req->as_string_hash;
+    }
+  }
+
+  return \%hash;
+}
+
+
+sub is_finalized { $_[0]{finalized} }
+
+
+sub finalize {
+  my ($self) = @_;
+
+  $self->{finalized} = 1;
+
+  for my $phase (keys %{ $self->{prereqs} }) {
+    $_->finalize for values %{ $self->{prereqs}{$phase} };
+  }
+}
+
+
+sub clone {
+  my ($self) = @_;
+
+  my $clone = (ref $self)->new( $self->as_string_hash );
+}
+
+1;
+
+
+
+
+__END__
+
+
+

fatlib/CPAN/Meta/Spec.pm

+# vi:tw=72
+use 5.006;
+use strict;
+use warnings;
+package CPAN::Meta::Spec;
+BEGIN {
+  $CPAN::Meta::Spec::VERSION = '2.110930';
+}
+# ABSTRACT: specification for CPAN distribution metadata
+1;
+
+
+
+__END__
+=pod
+

fatlib/CPAN/Meta/Validator.pm

+use 5.006;
+use strict;
+use warnings;
+package CPAN::Meta::Validator;
+BEGIN {
+  $CPAN::Meta::Validator::VERSION = '2.110930';
+}
+# ABSTRACT: validate CPAN distribution metadata structures
+
+
+#--------------------------------------------------------------------------#
+# This code copied and adapted from Test::CPAN::Meta
+# by Barbie, <barbie@cpan.org> for Miss Barbell Productions,
+# L<http://www.missbarbell.co.uk>
+#--------------------------------------------------------------------------#
+
+#--------------------------------------------------------------------------#
+# Specification Definitions
+#--------------------------------------------------------------------------#
+
+my %known_specs = (
+    '1.4' => 'http://module-build.sourceforge.net/META-spec-v1.4.html',
+    '1.3' => 'http://module-build.sourceforge.net/META-spec-v1.3.html',
+    '1.2' => 'http://module-build.sourceforge.net/META-spec-v1.2.html',
+    '1.1' => 'http://module-build.sourceforge.net/META-spec-v1.1.html',
+    '1.0' => 'http://module-build.sourceforge.net/META-spec-v1.0.html'
+);
+my %known_urls = map {$known_specs{$_} => $_} keys %known_specs;
+
+my $module_map1 = { 'map' => { ':key' => { name => \&module, value => \&exversion } } };
+
+my $module_map2 = { 'map' => { ':key' => { name => \&module, value => \&version   } } };
+
+my $no_index_2 = {
+    'map'       => { file       => { list => { value => \&string } },
+                     directory  => { list => { value => \&string } },
+                     'package'  => { list => { value => \&string } },
+                     namespace  => { list => { value => \&string } },
+                    ':key'      => { name => \&custom_2, value => \&anything },
+    }
+};
+
+my $no_index_1_3 = {
+    'map'       => { file       => { list => { value => \&string } },
+                     directory  => { list => { value => \&string } },
+                     'package'  => { list => { value => \&string } },
+                     namespace  => { list => { value => \&string } },
+                     ':key'     => { name => \&string, value => \&anything },
+    }
+};
+
+my $no_index_1_2 = {
+    'map'       => { file       => { list => { value => \&string } },
+                     dir        => { list => { value => \&string } },
+                     'package'  => { list => { value => \&string } },
+                     namespace  => { list => { value => \&string } },
+                     ':key'     => { name => \&string, value => \&anything },
+    }
+};
+
+my $no_index_1_1 = {
+    'map'       => { ':key'     => { name => \&string, list => { value => \&string } },
+    }
+};
+
+my $prereq_map = {
+  map => {
+    ':key' => {
+      name => \&phase,
+      'map' => {
+        ':key'  => {
+          name => \&relation,
+          %$module_map1,
+        },
+      },
+    }
+  },
+};
+
+my %definitions = (
+  '2' => {
+    # REQUIRED
+    'abstract'            => { mandatory => 1, value => \&string  },
+    'author'              => { mandatory => 1, lazylist => { value => \&string } },
+    'dynamic_config'      => { mandatory => 1, value => \&boolean },
+    'generated_by'        => { mandatory => 1, value => \&string  },
+    'license'             => { mandatory => 1, lazylist => { value => \&license } },
+    'meta-spec' => {
+      mandatory => 1,
+      'map' => {
+        version => { mandatory => 1, value => \&version},
+        url     => { value => \&url },
+        ':key' => { name => \&custom_2, value => \&anything },
+      }
+    },
+    'name'                => { mandatory => 1, value => \&string  },
+    'release_status'      => { mandatory => 1, value => \&release_status },
+    'version'             => { mandatory => 1, value => \&version },
+
+    # OPTIONAL
+    'description' => { value => \&string },
+    'keywords'    => { lazylist => { value => \&string } },
+    'no_index'    => $no_index_2,
+    'optional_features'   => {
+      'map'       => {
+        ':key'  => {
+          name => \&string,
+          'map'   => {
+            description        => { value => \&string },
+            prereqs => $prereq_map,
+            ':key' => { name => \&custom_2, value => \&anything },
+          }
+        }
+      }
+    },
+    'prereqs' => $prereq_map,
+    'provides'    => {
+      'map'       => {
+        ':key' => {
+          name  => \&module,
+          'map' => {
+            file    => { mandatory => 1, value => \&file },
+            version => { value => \&version },
+            ':key' => { name => \&custom_2, value => \&anything },
+          }
+        }
+      }
+    },
+    'resources'   => {
+      'map'       => {
+        license    => { lazylist => { value => \&url } },
+        homepage   => { value => \&url },
+        bugtracker => {
+          'map' => {
+            web => { value => \&url },
+            mailto => { value => \&string},
+            ':key' => { name => \&custom_2, value => \&anything },