Update CPAN-Meta to CPAN version 2.113640
authorChris 'BinGOs' Williams <chris@bingosnet.co.uk>
Fri, 30 Dec 2011 22:37:19 +0000 (22:37 +0000)
committerChris 'BinGOs' Williams <chris@bingosnet.co.uk>
Fri, 30 Dec 2011 23:44:10 +0000 (23:44 +0000)
  [DELTA]

  2.113640  2011-12-30 15:19:46 America/New_York

  - Version::Requirements has now been merged as CPAN::Meta::Requirements,
    rendering Version::Requirements itself redundant

17 files changed:
MANIFEST
Porting/Maintainers.pl
cpan/CPAN-Meta/Changes
cpan/CPAN-Meta/lib/CPAN/Meta.pm
cpan/CPAN-Meta/lib/CPAN/Meta/Converter.pm
cpan/CPAN-Meta/lib/CPAN/Meta/Feature.pm
cpan/CPAN-Meta/lib/CPAN/Meta/History.pm
cpan/CPAN-Meta/lib/CPAN/Meta/Prereqs.pm
cpan/CPAN-Meta/lib/CPAN/Meta/Requirements.pm [new file with mode: 0644]
cpan/CPAN-Meta/lib/CPAN/Meta/Spec.pm
cpan/CPAN-Meta/lib/CPAN/Meta/Validator.pm
cpan/CPAN-Meta/t/accepts.t [new file with mode: 0644]
cpan/CPAN-Meta/t/basic.t [new file with mode: 0644]
cpan/CPAN-Meta/t/finalize.t [new file with mode: 0644]
cpan/CPAN-Meta/t/from-hash.t [new file with mode: 0644]
cpan/CPAN-Meta/t/merge.t [new file with mode: 0644]
pod/perldelta.pod

index d338e44..99eb9ef 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -320,8 +320,11 @@ cpan/CPAN-Meta/lib/CPAN/Meta/Feature.pm
 cpan/CPAN-Meta/lib/CPAN/Meta/History.pm
 cpan/CPAN-Meta/lib/CPAN/Meta.pm
 cpan/CPAN-Meta/lib/CPAN/Meta/Prereqs.pm
+cpan/CPAN-Meta/lib/CPAN/Meta/Requirements.pm
 cpan/CPAN-Meta/lib/CPAN/Meta/Spec.pm
 cpan/CPAN-Meta/lib/CPAN/Meta/Validator.pm
+cpan/CPAN-Meta/t/accepts.t
+cpan/CPAN-Meta/t/basic.t
 cpan/CPAN-Meta/t/converter-bad.t
 cpan/CPAN-Meta/t/converter-fail.t
 cpan/CPAN-Meta/t/converter.t
@@ -362,7 +365,10 @@ cpan/CPAN-Meta/t/data/resources.yml
 cpan/CPAN-Meta/t/data/restricted-2.json
 cpan/CPAN-Meta/t/data/restrictive-1_4.yml
 cpan/CPAN-Meta/t/data/unicode.yml
+cpan/CPAN-Meta/t/finalize.t
+cpan/CPAN-Meta/t/from-hash.t
 cpan/CPAN-Meta/t/load-bad.t
+cpan/CPAN-Meta/t/merge.t
 cpan/CPAN-Meta/t/meta-obj.t
 cpan/CPAN-Meta/t/no-index.t
 cpan/CPAN-Meta/t/prereqs-finalize.t
index b4ca87b..192f1ad 100755 (executable)
@@ -480,7 +480,7 @@ use File::Glob qw(:case);
 
     'CPAN::Meta' => {
         'MAINTAINER'   => 'dagolden',
-        'DISTRIBUTION' => 'DAGOLDEN/CPAN-Meta-2.112621.tar.gz',
+        'DISTRIBUTION' => 'RJBS/CPAN-Meta-2.113640.tar.gz',
         'FILES'        => q[cpan/CPAN-Meta],
         'EXCLUDED'     => [
             qw(t/00-compile.t),
index 873d41a..eaf4098 100644 (file)
@@ -1,5 +1,10 @@
 Revision history for CPAN-Meta
 
+2.113640  2011-12-30 15:19:46 America/New_York
+
+  - Version::Requirements has now been merged as CPAN::Meta::Requirements,
+    rendering Version::Requirements itself redundant
+
 2.112621  2011-09-19 12:15:16 America/New_York
 
   [BUGFIX]
index cfb1b25..eee0e6c 100644 (file)
@@ -2,7 +2,7 @@ use 5.006;
 use strict;
 use warnings;
 package CPAN::Meta;
-our $VERSION = '2.112621'; # VERSION
+our $VERSION = '2.113640'; # VERSION
 
 
 use Carp qw(carp croak);
@@ -346,7 +346,7 @@ CPAN::Meta - the distribution metadata for a CPAN dist
 
 =head1 VERSION
 
-version 2.112621
+version 2.113640
 
 =head1 SYNOPSIS
 
@@ -674,9 +674,9 @@ L<CPAN::Meta::Validator>
 
 =head2 Bugs / Feature Requests
 
-Please report any bugs or feature requests by email to C<bug-cpan-meta at rt.cpan.org>, or through
-the web interface at L<http://rt.cpan.org/Public/Dist/Display.html?Name=CPAN-Meta>. You will be automatically notified of any
-progress on the request by the system.
+Please report any bugs or feature requests through the issue tracker
+at L<http://rt.cpan.org/Public/Dist/Display.html?Name=CPAN-Meta>.
+You will be notified automatically of any progress on your issue.
 
 =head2 Source Code
 
index e468720..09fe335 100644 (file)
@@ -2,7 +2,7 @@ use 5.006;
 use strict;
 use warnings;
 package CPAN::Meta::Converter;
-our $VERSION = '2.112621'; # VERSION
+our $VERSION = '2.113640'; # VERSION
 
 
 use CPAN::Meta::Validator;
@@ -429,9 +429,9 @@ sub _get_build_requires {
   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);
+  require CPAN::Meta::Requirements;
+  my $test_req  = CPAN::Meta::Requirements->from_string_hash($test_h);
+  my $build_req = CPAN::Meta::Requirements->from_string_hash($build_h);
 
   $test_req->add_requirements($build_req)->as_string_hash;
 }
@@ -1260,7 +1260,7 @@ CPAN::Meta::Converter - Convert CPAN distribution metadata structures
 
 =head1 VERSION
 
-version 2.112621
+version 2.113640
 
 =head1 SYNOPSIS
 
index 4002b74..7914d9a 100644 (file)
@@ -2,7 +2,7 @@ use 5.006;
 use strict;
 use warnings;
 package CPAN::Meta::Feature;
-our $VERSION = '2.112621'; # VERSION
+our $VERSION = '2.113640'; # VERSION
 
 use CPAN::Meta::Prereqs;
 
@@ -42,7 +42,7 @@ CPAN::Meta::Feature - an optional feature provided by a CPAN distribution
 
 =head1 VERSION
 
-version 2.112621
+version 2.113640
 
 =head1 DESCRIPTION
 
index 1f2e9ae..cd3b9dd 100644 (file)
@@ -3,7 +3,7 @@ use 5.006;
 use strict;
 use warnings;
 package CPAN::Meta::History;
-our $VERSION = '2.112621'; # VERSION
+our $VERSION = '2.113640'; # VERSION
 
 1;
 
@@ -20,7 +20,7 @@ CPAN::Meta::History - history of CPAN Meta Spec changes
 
 =head1 VERSION
 
-version 2.112621
+version 2.113640
 
 =head1 DESCRIPTION
 
index 04c5cf7..1b97804 100644 (file)
@@ -2,12 +2,12 @@ use 5.006;
 use strict;
 use warnings;
 package CPAN::Meta::Prereqs;
-our $VERSION = '2.112621'; # VERSION
+our $VERSION = '2.113640'; # VERSION
 
 
 use Carp qw(confess);
 use Scalar::Util qw(blessed);
-use Version::Requirements 0.101020; # finalize
+use CPAN::Meta::Requirements;
 
 
 sub __legal_phases { qw(configure build test runtime develop)   }
@@ -35,7 +35,7 @@ sub new {
 
       next TYPE unless keys %$spec;
 
-      $guts{prereqs}{$phase}{$type} = Version::Requirements->from_string_hash(
+      $guts{prereqs}{$phase}{$type} = CPAN::Meta::Requirements->from_string_hash(
         $spec
       );
     }
@@ -59,7 +59,7 @@ sub requirements_for {
     confess "requested requirements for unknown type: $type";
   }
 
-  my $req = ($self->{prereqs}{$phase}{$type} ||= Version::Requirements->new);
+  my $req = ($self->{prereqs}{$phase}{$type} ||= CPAN::Meta::Requirements->new);
 
   $req->finalize if $self->is_finalized;
 
@@ -78,7 +78,7 @@ sub with_merged_prereqs {
 
   for my $phase ($self->__legal_phases) {
     for my $type ($self->__legal_types) {
-      my $req = Version::Requirements->new;
+      my $req = CPAN::Meta::Requirements->new;
 
       for my $prereq (@prereq_objs) {
         my $this_req = $prereq->requirements_for($phase, $type);
@@ -149,7 +149,7 @@ CPAN::Meta::Prereqs - a set of distribution prerequisites by phase and type
 
 =head1 VERSION
 
-version 2.112621
+version 2.113640
 
 =head1 DESCRIPTION
 
@@ -189,10 +189,10 @@ dumping the whole set into a structure or string.
 
   my $requirements = $prereqs->requirements_for( $phase, $type );
 
-This method returns a L<Version::Requirements> object for the given phase/type
-combination.  If no prerequisites are registered for that combination, a new
-Version::Requirements object will be returned, and it may be added to as
-needed.
+This method returns a L<CPAN::Meta::Requirements> object for the given
+phase/type combination.  If no prerequisites are registered for that
+combination, a new CPAN::Meta::Requirements object will be returned, and it may
+be added to as needed.
 
 If C<$phase> or C<$type> are undefined or otherwise invalid, an exception will
 be raised.
@@ -215,7 +215,7 @@ will not alter them.
 
 This method returns a hashref containing structures suitable for dumping into a
 distmeta data structure.  It is made up of hashes and strings, only; there will
-be no Prereqs, Version::Requirements, or C<version> objects inside it.
+be no Prereqs, CPAN::Meta::Requirements, or C<version> objects inside it.
 
 =head2 is_finalized
 
diff --git a/cpan/CPAN-Meta/lib/CPAN/Meta/Requirements.pm b/cpan/CPAN-Meta/lib/CPAN/Meta/Requirements.pm
new file mode 100644 (file)
index 0000000..dd2861e
--- /dev/null
@@ -0,0 +1,598 @@
+use strict;
+use warnings;
+package CPAN::Meta::Requirements;
+our $VERSION = '2.113640'; # VERSION
+# ABSTRACT: a set of version requirements for a CPAN dist
+
+
+use Carp ();
+use Scalar::Util ();
+use version 0.77 (); # the ->parse method
+
+
+sub new {
+  my ($class) = @_;
+  return bless {} => $class;
+}
+
+sub _version_object {
+  my ($self, $version) = @_;
+
+  $version = (! defined $version)                ? version->parse(0)
+           : (! Scalar::Util::blessed($version)) ? version->parse($version)
+           :                                       $version;
+
+  return $version;
+}
+
+
+BEGIN {
+  for my $type (qw(minimum maximum exclusion exact_version)) {
+    my $method = "with_$type";
+    my $to_add = $type eq 'exact_version' ? $type : "add_$type";
+
+    my $code = sub {
+      my ($self, $name, $version) = @_;
+
+      $version = $self->_version_object( $version );
+
+      $self->__modify_entry_for($name, $method, $version);
+
+      return $self;
+    };
+    
+    no strict 'refs';
+    *$to_add = $code;
+  }
+}
+
+
+sub add_requirements {
+  my ($self, $req) = @_;
+
+  for my $module ($req->required_modules) {
+    my $modifiers = $req->__entry_for($module)->as_modifiers;
+    for my $modifier (@$modifiers) {
+      my ($method, @args) = @$modifier;
+      $self->$method($module => @args);
+    };
+  }
+
+  return $self;
+}
+
+
+sub accepts_module {
+  my ($self, $module, $version) = @_;
+
+  $version = $self->_version_object( $version );
+
+  return 1 unless my $range = $self->__entry_for($module);
+  return $range->_accepts($version);
+}
+
+
+sub clear_requirement {
+  my ($self, $module) = @_;
+
+  return $self unless $self->__entry_for($module);
+
+  Carp::confess("can't clear requirements on finalized requirements")
+    if $self->is_finalized;
+
+  delete $self->{requirements}{ $module };
+
+  return $self;
+}
+
+
+sub required_modules { keys %{ $_[0]{requirements} } }
+
+
+sub clone {
+  my ($self) = @_;
+  my $new = (ref $self)->new;
+
+  return $new->add_requirements($self);
+}
+
+sub __entry_for     { $_[0]{requirements}{ $_[1] } }
+
+sub __modify_entry_for {
+  my ($self, $name, $method, $version) = @_;
+
+  my $fin = $self->is_finalized;
+  my $old = $self->__entry_for($name);
+
+  Carp::confess("can't add new requirements to finalized requirements")
+    if $fin and not $old;
+
+  my $new = ($old || 'CPAN::Meta::Requirements::_Range::Range')
+          ->$method($version);
+
+  Carp::confess("can't modify finalized requirements")
+    if $fin and $old->as_string ne $new->as_string;
+
+  $self->{requirements}{ $name } = $new;
+}
+
+
+sub is_simple {
+  my ($self) = @_;
+  for my $module ($self->required_modules) {
+    # XXX: This is a complete hack, but also entirely correct.
+    return if $self->__entry_for($module)->as_string =~ /\s/;
+  }
+
+  return 1;
+}
+
+
+sub is_finalized { $_[0]{finalized} }
+
+
+sub finalize { $_[0]{finalized} = 1 }
+
+
+sub as_string_hash {
+  my ($self) = @_;
+
+  my %hash = map {; $_ => $self->{requirements}{$_}->as_string }
+             $self->required_modules;
+
+  return \%hash;
+}
+
+
+my %methods_for_op = (
+  '==' => [ qw(exact_version) ],
+  '!=' => [ qw(add_exclusion) ],
+  '>=' => [ qw(add_minimum)   ],
+  '<=' => [ qw(add_maximum)   ],
+  '>'  => [ qw(add_minimum add_exclusion) ],
+  '<'  => [ qw(add_maximum add_exclusion) ],
+);
+
+sub from_string_hash {
+  my ($class, $hash) = @_;
+
+  my $self = $class->new;
+
+  for my $module (keys %$hash) {
+    my @parts = split qr{\s*,\s*}, $hash->{ $module };
+    for my $part (@parts) {
+      my ($op, $ver) = split /\s+/, $part, 2;
+
+      if (! defined $ver) {
+        $self->add_minimum($module => $op);
+      } else {
+        Carp::confess("illegal requirement string: $hash->{ $module }")
+          unless my $methods = $methods_for_op{ $op };
+
+        $self->$_($module => $ver) for @$methods;
+      }
+    }
+  }
+
+  return $self;
+}
+
+##############################################################
+
+{
+  package
+    CPAN::Meta::Requirements::_Range::Exact;
+  sub _new     { bless { version => $_[1] } => $_[0] }
+
+  sub _accepts { return $_[0]{version} == $_[1] }
+
+  sub as_string { return "== $_[0]{version}" }
+
+  sub as_modifiers { return [ [ exact_version => $_[0]{version} ] ] }
+
+  sub _clone {
+    (ref $_[0])->_new( version->new( $_[0]{version} ) )
+  }
+
+  sub with_exact_version {
+    my ($self, $version) = @_;
+
+    return $self->_clone if $self->_accepts($version);
+
+    Carp::confess("illegal requirements: unequal exact version specified");
+  }
+
+  sub with_minimum {
+    my ($self, $minimum) = @_;
+    return $self->_clone if $self->{version} >= $minimum;
+    Carp::confess("illegal requirements: minimum above exact specification");
+  }
+
+  sub with_maximum {
+    my ($self, $maximum) = @_;
+    return $self->_clone if $self->{version} <= $maximum;
+    Carp::confess("illegal requirements: maximum below exact specification");
+  }
+
+  sub with_exclusion {
+    my ($self, $exclusion) = @_;
+    return $self->_clone unless $exclusion == $self->{version};
+    Carp::confess("illegal requirements: excluded exact specification");
+  }
+}
+
+##############################################################
+
+{
+  package
+    CPAN::Meta::Requirements::_Range::Range;
+
+  sub _self { ref($_[0]) ? $_[0] : (bless { } => $_[0]) }
+
+  sub _clone {
+    return (bless { } => $_[0]) unless ref $_[0];
+
+    my ($s) = @_;
+    my %guts = (
+      (exists $s->{minimum} ? (minimum => version->new($s->{minimum})) : ()),
+      (exists $s->{maximum} ? (maximum => version->new($s->{maximum})) : ()),
+
+      (exists $s->{exclusions}
+        ? (exclusions => [ map { version->new($_) } @{ $s->{exclusions} } ])
+        : ()),
+    );
+
+    bless \%guts => ref($s);
+  }
+
+  sub as_modifiers {
+    my ($self) = @_;
+    my @mods;
+    push @mods, [ add_minimum => $self->{minimum} ] if exists $self->{minimum};
+    push @mods, [ add_maximum => $self->{maximum} ] if exists $self->{maximum};
+    push @mods, map {; [ add_exclusion => $_ ] } @{$self->{exclusions} || []};
+    return \@mods;
+  }
+
+  sub as_string {
+    my ($self) = @_;
+
+    return 0 if ! keys %$self;
+
+    return "$self->{minimum}" if (keys %$self) == 1 and exists $self->{minimum};
+
+    my @exclusions = @{ $self->{exclusions} || [] };
+
+    my @parts;
+
+    for my $pair (
+      [ qw( >= > minimum ) ],
+      [ qw( <= < maximum ) ],
+    ) {
+      my ($op, $e_op, $k) = @$pair;
+      if (exists $self->{$k}) {
+        my @new_exclusions = grep { $_ != $self->{ $k } } @exclusions;
+        if (@new_exclusions == @exclusions) {
+          push @parts, "$op $self->{ $k }";
+        } else {
+          push @parts, "$e_op $self->{ $k }";
+          @exclusions = @new_exclusions;
+        }
+      }
+    }
+
+    push @parts, map {; "!= $_" } @exclusions;
+
+    return join q{, }, @parts;
+  }
+
+  sub with_exact_version {
+    my ($self, $version) = @_;
+    $self = $self->_clone;
+
+    Carp::confess("illegal requirements: exact specification outside of range")
+      unless $self->_accepts($version);
+
+    return CPAN::Meta::Requirements::_Range::Exact->_new($version);
+  }
+
+  sub _simplify {
+    my ($self) = @_;
+
+    if (defined $self->{minimum} and defined $self->{maximum}) {
+      if ($self->{minimum} == $self->{maximum}) {
+        Carp::confess("illegal requirements: excluded all values")
+          if grep { $_ == $self->{minimum} } @{ $self->{exclusions} || [] };
+
+        return CPAN::Meta::Requirements::_Range::Exact->_new($self->{minimum})
+      }
+
+      Carp::confess("illegal requirements: minimum exceeds maximum")
+        if $self->{minimum} > $self->{maximum};
+    }
+
+    # eliminate irrelevant exclusions
+    if ($self->{exclusions}) {
+      my %seen;
+      @{ $self->{exclusions} } = grep {
+        (! defined $self->{minimum} or $_ >= $self->{minimum})
+        and
+        (! defined $self->{maximum} or $_ <= $self->{maximum})
+        and
+        ! $seen{$_}++
+      } @{ $self->{exclusions} };
+    }
+
+    return $self;
+  }
+
+  sub with_minimum {
+    my ($self, $minimum) = @_;
+    $self = $self->_clone;
+
+    if (defined (my $old_min = $self->{minimum})) {
+      $self->{minimum} = (sort { $b cmp $a } ($minimum, $old_min))[0];
+    } else {
+      $self->{minimum} = $minimum;
+    }
+
+    return $self->_simplify;
+  }
+
+  sub with_maximum {
+    my ($self, $maximum) = @_;
+    $self = $self->_clone;
+
+    if (defined (my $old_max = $self->{maximum})) {
+      $self->{maximum} = (sort { $a cmp $b } ($maximum, $old_max))[0];
+    } else {
+      $self->{maximum} = $maximum;
+    }
+
+    return $self->_simplify;
+  }
+
+  sub with_exclusion {
+    my ($self, $exclusion) = @_;
+    $self = $self->_clone;
+
+    push @{ $self->{exclusions} ||= [] }, $exclusion;
+
+    return $self->_simplify;
+  }
+
+  sub _accepts {
+    my ($self, $version) = @_;
+
+    return if defined $self->{minimum} and $version < $self->{minimum};
+    return if defined $self->{maximum} and $version > $self->{maximum};
+    return if defined $self->{exclusions}
+          and grep { $version == $_ } @{ $self->{exclusions} };
+
+    return 1;
+  }
+}
+
+1;
+
+__END__
+=pod
+
+=head1 NAME
+
+CPAN::Meta::Requirements - a set of version requirements for a CPAN dist
+
+=head1 VERSION
+
+version 2.113640
+
+=head1 SYNOPSIS
+
+  use CPAN::Meta::Requirements;
+
+  my $build_requires = CPAN::Meta::Requirements->new;
+
+  $build_requires->add_minimum('Library::Foo' => 1.208);
+
+  $build_requires->add_minimum('Library::Foo' => 2.602);
+
+  $build_requires->add_minimum('Module::Bar'  => 'v1.2.3');
+
+  $METAyml->{build_requires} = $build_requires->as_string_hash;
+
+=head1 DESCRIPTION
+
+A CPAN::Meta::Requirements object models a set of version constraints like
+those specified in the F<META.yml> or F<META.json> files in CPAN distributions.
+It can be built up by adding more and more constraints, and it will reduce them
+to the simplest representation.
+
+Logically impossible constraints will be identified immediately by thrown
+exceptions.
+
+=head1 METHODS
+
+=head2 new
+
+  my $req = CPAN::Meta::Requirements->new;
+
+This returns a new CPAN::Meta::Requirements object.  It ignores any arguments
+given.
+
+=head2 add_minimum
+
+  $req->add_minimum( $module => $version );
+
+This adds a new minimum version requirement.  If the new requirement is
+redundant to the existing specification, this has no effect.
+
+Minimum requirements are inclusive.  C<$version> is required, along with any
+greater version number.
+
+This method returns the requirements object.
+
+=head2 add_maximum
+
+  $req->add_maximum( $module => $version );
+
+This adds a new maximum version requirement.  If the new requirement is
+redundant to the existing specification, this has no effect.
+
+Maximum requirements are inclusive.  No version strictly greater than the given
+version is allowed.
+
+This method returns the requirements object.
+
+=head2 add_exclusion
+
+  $req->add_exclusion( $module => $version );
+
+This adds a new excluded version.  For example, you might use these three
+method calls:
+
+  $req->add_minimum( $module => '1.00' );
+  $req->add_maximum( $module => '1.82' );
+
+  $req->add_exclusion( $module => '1.75' );
+
+Any version between 1.00 and 1.82 inclusive would be acceptable, except for
+1.75.
+
+This method returns the requirements object.
+
+=head2 exact_version
+
+  $req->exact_version( $module => $version );
+
+This sets the version required for the given module to I<exactly> the given
+version.  No other version would be considered acceptable.
+
+This method returns the requirements object.
+
+=head2 add_requirements
+
+  $req->add_requirements( $another_req_object );
+
+This method adds all the requirements in the given CPAN::Meta::Requirements object
+to the requirements object on which it was called.  If there are any conflicts,
+an exception is thrown.
+
+This method returns the requirements object.
+
+=head2 accepts_module
+
+  my $bool = $req->accepts_modules($module => $version);
+
+Given an module and version, this method returns true if the version
+specification for the module accepts the provided version.  In other words,
+given:
+
+  Module => '>= 1.00, < 2.00'
+
+We will accept 1.00 and 1.75 but not 0.50 or 2.00.
+
+For modules that do not appear in the requirements, this method will return
+true.
+
+=head2 clear_requirement
+
+  $req->clear_requirement( $module );
+
+This removes the requirement for a given module from the object.
+
+This method returns the requirements object.
+
+=head2 required_modules
+
+This method returns a list of all the modules for which requirements have been
+specified.
+
+=head2 clone
+
+  $req->clone;
+
+This method returns a clone of the invocant.  The clone and the original object
+can then be changed independent of one another.
+
+=head2 is_simple
+
+This method returns true if and only if all requirements are inclusive minimums
+-- that is, if their string expression is just the version number.
+
+=head2 is_finalized
+
+This method returns true if the requirements have been finalized by having the
+C<finalize> method called on them.
+
+=head2 finalize
+
+This method marks the requirements finalized.  Subsequent attempts to change
+the requirements will be fatal, I<if> they would result in a change.  If they
+would not alter the requirements, they have no effect.
+
+If a finalized set of requirements is cloned, the cloned requirements are not
+also finalized.
+
+=head2 as_string_hash
+
+This returns a reference to a hash describing the requirements using the
+strings in the F<META.yml> specification.
+
+For example after the following program:
+
+  my $req = CPAN::Meta::Requirements->new;
+
+  $req->add_minimum('CPAN::Meta::Requirements' => 0.102);
+
+  $req->add_minimum('Library::Foo' => 1.208);
+
+  $req->add_maximum('Library::Foo' => 2.602);
+
+  $req->add_minimum('Module::Bar'  => 'v1.2.3');
+
+  $req->add_exclusion('Module::Bar'  => 'v1.2.8');
+
+  $req->exact_version('Xyzzy'  => '6.01');
+
+  my $hashref = $req->as_string_hash;
+
+C<$hashref> would contain:
+
+  {
+    'CPAN::Meta::Requirements' => '0.102',
+    'Library::Foo' => '>= 1.208, <= 2.206',
+    'Module::Bar'  => '>= v1.2.3, != v1.2.8',
+    'Xyzzy'        => '== 6.01',
+  }
+
+=head2 from_string_hash
+
+  my $req = CPAN::Meta::Requirements->from_string_hash( \%hash );
+
+This is an alternate constructor for a CPAN::Meta::Requirements object.  It takes
+a hash of module names and version requirement strings and returns a new
+CPAN::Meta::Requirements object.
+
+=head1 AUTHORS
+
+=over 4
+
+=item *
+
+David Golden <dagolden@cpan.org>
+
+=item *
+
+Ricardo Signes <rjbs@cpan.org>
+
+=back
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is copyright (c) 2010 by David Golden and Ricardo Signes.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=cut
+
index 09bfc23..d846c89 100644 (file)
@@ -3,7 +3,7 @@ use 5.006;
 use strict;
 use warnings;
 package CPAN::Meta::Spec;
-our $VERSION = '2.112621'; # VERSION
+our $VERSION = '2.113640'; # VERSION
 
 1;
 
@@ -20,7 +20,7 @@ CPAN::Meta::Spec - specification for CPAN distribution metadata
 
 =head1 VERSION
 
-version 2.112621
+version 2.113640
 
 =head1 SYNOPSIS
 
index c3d6906..4b811e4 100644 (file)
@@ -2,7 +2,7 @@ use 5.006;
 use strict;
 use warnings;
 package CPAN::Meta::Validator;
-our $VERSION = '2.112621'; # VERSION
+our $VERSION = '2.113640'; # VERSION
 
 
 #--------------------------------------------------------------------------#
@@ -838,7 +838,7 @@ CPAN::Meta::Validator - validate CPAN distribution metadata structures
 
 =head1 VERSION
 
-version 2.112621
+version 2.113640
 
 =head1 SYNOPSIS
 
diff --git a/cpan/CPAN-Meta/t/accepts.t b/cpan/CPAN-Meta/t/accepts.t
new file mode 100644 (file)
index 0000000..3456394
--- /dev/null
@@ -0,0 +1,29 @@
+use strict;
+use warnings;
+
+use CPAN::Meta::Requirements;
+
+use Test::More 0.88;
+
+{
+  my $req = CPAN::Meta::Requirements->new->add_minimum(Foo => 1);
+
+  ok(  $req->accepts_module(Foo => 1));
+  ok(! $req->accepts_module(Foo => 0));
+}
+
+{
+  my $req = CPAN::Meta::Requirements->new->add_maximum(Foo => 1);
+
+  ok(  $req->accepts_module(Foo => 1));
+  ok(! $req->accepts_module(Foo => 2));
+}
+
+{
+  my $req = CPAN::Meta::Requirements->new->add_exclusion(Foo => 1);
+
+  ok(  $req->accepts_module(Foo => 0));
+  ok(! $req->accepts_module(Foo => 1));
+}
+
+done_testing;
diff --git a/cpan/CPAN-Meta/t/basic.t b/cpan/CPAN-Meta/t/basic.t
new file mode 100644 (file)
index 0000000..81061b5
--- /dev/null
@@ -0,0 +1,224 @@
+use strict;
+use warnings;
+
+use CPAN::Meta::Requirements;
+
+use Test::More 0.88;
+
+sub dies_ok (&@) {
+  my ($code, $qr, $comment) = @_;
+
+  my $lived = eval { $code->(); 1 };
+
+  if ($lived) {
+    fail("$comment: did not die");
+  } else {
+    like($@, $qr, $comment);
+  }
+}
+
+{
+  my $req = CPAN::Meta::Requirements->new;
+
+  $req->add_minimum('Foo::Bar' => 10);
+  $req->add_minimum('Foo::Bar' => 0);
+  $req->add_minimum('Foo::Bar' => 2);
+
+  $req->add_minimum('Foo::Baz' => version->declare('v1.2.3'));
+
+  $req->add_minimum('Foo::Undef' => undef);
+
+  is_deeply(
+    $req->as_string_hash,
+    {
+      'Foo::Bar'   => 10,
+      'Foo::Baz'   => 'v1.2.3',
+      'Foo::Undef' => 0,
+    },
+    "some basic minimums",
+  );
+
+  ok($req->is_simple, "just minimums? simple");
+}
+
+{
+  my $req = CPAN::Meta::Requirements->new;
+  $req->add_maximum(Foo => 1);
+  is_deeply($req->as_string_hash, { Foo => '<= 1' }, "max only");
+
+  ok(! $req->is_simple, "maximums? not simple");
+}
+
+{
+  my $req = CPAN::Meta::Requirements->new;
+  $req->add_exclusion(Foo => 1);
+  $req->add_exclusion(Foo => 2);
+
+  # Why would you ever do this?? -- rjbs, 2010-02-20
+  is_deeply($req->as_string_hash, { Foo => '!= 1, != 2' }, "excl only");
+}
+
+{
+  my $req = CPAN::Meta::Requirements->new;
+
+  $req->add_minimum(Foo => 1);
+  $req->add_maximum(Foo => 2);
+
+  is_deeply(
+    $req->as_string_hash,
+    {
+      Foo => '>= 1, <= 2',
+    },
+    "min and max",
+  );
+
+  $req->add_maximum(Foo => 3);
+
+  is_deeply(
+    $req->as_string_hash,
+    {
+      Foo => '>= 1, <= 2',
+    },
+    "exclusions already outside range do not matter",
+  );
+
+  $req->add_exclusion(Foo => 1.5);
+
+  is_deeply(
+    $req->as_string_hash,
+    {
+      Foo => '>= 1, <= 2, != 1.5',
+    },
+    "exclusions",
+  );
+
+  $req->add_minimum(Foo => 1.6);
+
+  is_deeply(
+    $req->as_string_hash,
+    {
+      Foo => '>= 1.6, <= 2',
+    },
+    "exclusions go away when made irrelevant",
+  );
+}
+
+{
+  my $req = CPAN::Meta::Requirements->new;
+
+  $req->add_minimum(Foo => 1);
+  $req->add_exclusion(Foo => 1);
+  $req->add_maximum(Foo => 2);
+
+  is_deeply(
+    $req->as_string_hash,
+    {
+      Foo => '> 1, <= 2',
+    },
+    "we can exclude an endpoint",
+  );
+}
+
+{
+  my $req = CPAN::Meta::Requirements->new;
+  $req->add_minimum(Foo => 1);
+
+  $req->add_exclusion(Foo => 1);
+
+  dies_ok { $req->add_maximum(Foo => 1); }
+    qr/excluded all/,
+    "can't exclude all values" ;
+}
+
+{
+  my $req = CPAN::Meta::Requirements->new;
+  $req->add_minimum(Foo => 1);
+  dies_ok {$req->exact_version(Foo => 0.5); }
+    qr/outside of range/,
+    "can't add outside-range exact spec to range";
+}
+
+{
+  my $req = CPAN::Meta::Requirements->new;
+  $req->add_minimum(Foo => 1);
+  dies_ok { $req->add_maximum(Foo => 0.5); }
+    qr/minimum exceeds maximum/,
+    "maximum must exceed (or equal) minimum";
+
+  $req = CPAN::Meta::Requirements->new;
+  $req->add_maximum(Foo => 0.5);
+  dies_ok { $req->add_minimum(Foo => 1); }
+    qr/minimum exceeds maximum/,
+    "maximum must exceed (or equal) minimum";
+}
+
+{
+  my $req = CPAN::Meta::Requirements->new;
+
+  $req->add_minimum(Foo => 1);
+  $req->add_maximum(Foo => 1);
+
+  $req->add_maximum(Foo => 2); # ignored
+  $req->add_minimum(Foo => 0); # ignored
+  $req->add_exclusion(Foo => .5); # ignored
+
+  is_deeply(
+    $req->as_string_hash,
+    {
+      'Foo' => '== 1',
+    },
+    "if min==max, becomes exact requirement",
+  );
+}
+
+{
+  my $req = CPAN::Meta::Requirements->new;
+  $req->add_minimum(Foo => 1);
+  $req->add_exclusion(Foo => 0);
+  $req->add_maximum(Foo => 3);
+  $req->add_exclusion(Foo => 4);
+
+  $req->add_exclusion(Foo => 2);
+  $req->add_exclusion(Foo => 2);
+
+  is_deeply(
+    $req->as_string_hash,
+    {
+      Foo => '>= 1, <= 3, != 2',
+    },
+    'test exclusion-skipping',
+  );
+}
+
+sub foo_1 {
+  my $req = CPAN::Meta::Requirements->new;
+  $req->exact_version(Foo => 1);
+  return $req;
+}
+
+{
+  my $req = foo_1;
+
+  $req->exact_version(Foo => 1); # ignored
+
+  is_deeply($req->as_string_hash, { Foo => '== 1' }, "exact requirement");
+
+  dies_ok { $req->exact_version(Foo => 2); }
+    qr/unequal/,
+    "can't exactly specify differing versions" ;
+
+  $req = foo_1;
+  $req->add_minimum(Foo => 0); # ignored
+  $req->add_maximum(Foo => 2); # ignored
+
+  dies_ok { $req->add_maximum(Foo => 0); } qr/maximum below/, "max < fixed";
+
+  $req = foo_1;
+  dies_ok { $req->add_minimum(Foo => 2); } qr/minimum above/, "min > fixed";
+
+  $req = foo_1;
+  $req->add_exclusion(Foo => 8); # ignored
+  dies_ok { $req->add_exclusion(Foo => 1); } qr/excluded exact/, "!= && ==";
+}
+
+done_testing;
diff --git a/cpan/CPAN-Meta/t/finalize.t b/cpan/CPAN-Meta/t/finalize.t
new file mode 100644 (file)
index 0000000..58048b5
--- /dev/null
@@ -0,0 +1,91 @@
+use strict;
+use warnings;
+
+use CPAN::Meta::Requirements;
+
+use Test::More 0.88;
+
+sub dies_ok (&@) {
+  my ($code, $qr, $comment) = @_;
+
+  my $lived = eval { $code->(); 1 };
+
+  if ($lived) {
+    fail("$comment: did not die");
+  } else {
+    like($@, $qr, $comment);
+  }
+}
+
+{
+  my $req = CPAN::Meta::Requirements->new;
+
+  $req->add_minimum('Foo::Bar' => 10);
+  $req->add_minimum('Foo::Bar' => 0);
+  $req->add_minimum('Foo::Bar' => 2);
+
+  $req->add_minimum('Foo::Baz' => version->declare('v1.2.3'));
+
+  $req->add_minimum('Foo::Undef' => undef);
+
+  my $want = {
+    'Foo::Bar'   => 10,
+    'Foo::Baz'   => 'v1.2.3',
+    'Foo::Undef' => 0,
+  };
+
+  is_deeply(
+    $req->as_string_hash,
+    $want,
+    "some basic minimums",
+  );
+
+  $req->finalize;
+
+  $req->add_minimum('Foo::Bar', 2);
+
+  pass('we can add a Foo::Bar requirement with no effect post finalization');
+
+  dies_ok { $req->add_minimum('Foo::Bar', 12) }
+    qr{finalized req},
+    "can't add a higher Foo::Bar after finalization";
+
+  dies_ok { $req->add_minimum('Foo::New', 0) }
+    qr{finalized req},
+    "can't add a new module prereq after finalization";
+
+  dies_ok { $req->clear_requirement('Foo::Bar') }
+    qr{finalized req},
+    "can't clear an existing prereq after finalization";
+
+  $req->clear_requirement('Bogus::Req');
+
+  pass('we can clear a prereq that was not set to begin with');
+
+  is_deeply(
+    $req->as_string_hash,
+    $want,
+    "none of our attempts to alter the object post-finalization worked",
+  );
+
+  my $cloned = $req->clone;
+
+  $cloned->add_minimum('Foo::Bar', 12);
+
+  is_deeply(
+    $cloned->as_string_hash,
+    {
+      %$want,
+      'Foo::Bar' => 12,
+    },
+    "we can alter a cloned V:R (finalization does not survive cloning)",
+  );
+
+  is_deeply(
+    $req->as_string_hash,
+    $want,
+    "...and original requirements are untouched",
+  );
+}
+
+done_testing;
diff --git a/cpan/CPAN-Meta/t/from-hash.t b/cpan/CPAN-Meta/t/from-hash.t
new file mode 100644 (file)
index 0000000..369c45f
--- /dev/null
@@ -0,0 +1,48 @@
+use strict;
+use warnings;
+
+use CPAN::Meta::Requirements;
+
+use Test::More 0.88;
+
+sub dies_ok (&@) {
+  my ($code, $qr, $comment) = @_;
+
+  my $lived = eval { $code->(); 1 };
+
+  if ($lived) {
+    fail("$comment: did not die");
+  } else {
+    like($@, $qr, $comment);
+  }
+}
+
+{
+  my $string_hash = {
+    Left   => 10,
+    Shared => '>= 2, <= 9, != 7',
+    Right  => 18,
+  };
+
+  my $req = CPAN::Meta::Requirements->from_string_hash($string_hash);
+
+  is_deeply(
+    $req->as_string_hash,
+    $string_hash,
+    "we can load from a string hash",
+  );
+}
+
+{
+  my $string_hash = {
+    Left   => 10,
+    Shared => '= 2',
+    Right  => 18,
+  };
+
+  dies_ok { CPAN::Meta::Requirements->from_string_hash($string_hash) }
+    qr/illegal/,
+    "we die when we can't understand a version spec";
+}
+
+done_testing;
diff --git a/cpan/CPAN-Meta/t/merge.t b/cpan/CPAN-Meta/t/merge.t
new file mode 100644 (file)
index 0000000..a051356
--- /dev/null
@@ -0,0 +1,136 @@
+use strict;
+use warnings;
+
+use CPAN::Meta::Requirements;
+
+use Test::More 0.88;
+
+sub dies_ok (&@) {
+  my ($code, $qr, $comment) = @_;
+
+  my $lived = eval { $code->(); 1 };
+
+  if ($lived) {
+    fail("$comment: did not die");
+  } else {
+    like($@, $qr, $comment);
+  }
+}
+
+{
+  my $req_1 = CPAN::Meta::Requirements->new;
+  $req_1->add_minimum(Left   => 10);
+  $req_1->add_minimum(Shared => 2);
+  $req_1->add_exclusion(Shared => 7);
+
+  my $req_2 = CPAN::Meta::Requirements->new;
+  $req_2->add_minimum(Shared => 1);
+  $req_2->add_maximum(Shared => 9);
+  $req_2->add_minimum(Right  => 18);
+
+  $req_1->add_requirements($req_2);
+
+  is_deeply(
+    $req_1->as_string_hash,
+    {
+      Left   => 10,
+      Shared => '>= 2, <= 9, != 7',
+      Right  => 18,
+    },
+    "add requirements to an existing set of requirements",
+  );
+}
+
+{
+  my $req_1 = CPAN::Meta::Requirements->new;
+  $req_1->add_minimum(Left   => 10);
+  $req_1->add_minimum(Shared => 2);
+  $req_1->add_exclusion(Shared => 7);
+  $req_1->exact_version(Exact => 8);
+
+  my $req_2 = CPAN::Meta::Requirements->new;
+  $req_2->add_minimum(Shared => 1);
+  $req_2->add_maximum(Shared => 9);
+  $req_2->add_minimum(Right  => 18);
+  $req_2->exact_version(Exact => 8);
+
+  my $clone = $req_1->clone->add_requirements($req_2);
+
+  is_deeply(
+    $req_1->as_string_hash,
+    {
+      Left   => 10,
+      Shared => '>= 2, != 7',
+      Exact  => '== 8',
+    },
+    "clone/add_requirements does not affect lhs",
+  );
+
+  is_deeply(
+    $req_2->as_string_hash,
+    {
+      Shared => '>= 1, <= 9',
+      Right  => 18,
+      Exact  => '== 8',
+    },
+    "clone/add_requirements does not affect rhs",
+  );
+
+  is_deeply(
+    $clone->as_string_hash,
+    {
+      Left   => 10,
+      Shared => '>= 2, <= 9, != 7',
+      Right  => 18,
+      Exact  => '== 8',
+    },
+    "clone and add_requirements",
+  );
+
+  $clone->clear_requirement('Shared');
+
+  is_deeply(
+    $clone->as_string_hash,
+    {
+      Left   => 10,
+      Right  => 18,
+      Exact  => '== 8',
+    },
+    "cleared the shared requirement",
+  );
+}
+
+{
+  my $req_1 = CPAN::Meta::Requirements->new;
+  $req_1->add_maximum(Foo => 1);
+
+  my $req_2 = $req_1->clone;
+
+  is_deeply(
+    $req_2->as_string_hash,
+    {
+      'Foo' => '<= 1',
+    },
+    'clone with only max',
+  );
+}
+
+{
+  my $left = CPAN::Meta::Requirements->new;
+  $left->add_minimum(Foo => 0);
+  $left->add_minimum(Bar => 1);
+
+  my $right = CPAN::Meta::Requirements->new;
+  $right->add_requirements($left);
+
+  is_deeply(
+    $right->as_string_hash,
+    {
+      Foo => 0,
+      Bar => 1,
+    },
+    "we do not lose 0-min reqs on merge",
+  );
+}
+
+done_testing;
index 2eb3222..838f53b 100644 (file)
@@ -112,6 +112,12 @@ behavior may have been expected by some command-line uses of CGI.pm.
 
 =item *
 
+L<CPAN::Meta> has been upgraded from version 2.112621 to version 2.113640.
+
+Version::Requirements has now been merged as CPAN::Meta::Requirements.
+
+=item *
+
 L<CPANPLUS> has been upgraded from version 0.9113 to version 0.9115.
 
 =item *