1use strict; 2use warnings; 3package CPAN::Meta::Requirements; 4our $VERSION = '2.125'; # VERSION 5# ABSTRACT: a set of version requirements for a CPAN dist 6 7 8use Carp (); 9use Scalar::Util (); 10use version 0.77 (); # the ->parse method 11 12 13my @valid_options = qw( bad_version_hook ); 14 15sub new { 16 my ($class, $options) = @_; 17 $options ||= {}; 18 Carp::croak "Argument to $class\->new() must be a hash reference" 19 unless ref $options eq 'HASH'; 20 my %self = map {; $_ => $options->{$_}} @valid_options; 21 22 return bless \%self => $class; 23} 24 25sub _version_object { 26 my ($self, $version) = @_; 27 28 my $vobj; 29 30 eval { 31 $vobj = (! defined $version) ? version->parse(0) 32 : (! Scalar::Util::blessed($version)) ? version->parse($version) 33 : $version; 34 }; 35 36 if ( my $err = $@ ) { 37 my $hook = $self->{bad_version_hook}; 38 $vobj = eval { $hook->($version) } 39 if ref $hook eq 'CODE'; 40 unless (Scalar::Util::blessed($vobj) && $vobj->isa("version")) { 41 $err =~ s{ at .* line \d+.*$}{}; 42 die "Can't convert '$version': $err"; 43 } 44 } 45 46 # ensure no leading '.' 47 if ( $vobj =~ m{\A\.} ) { 48 $vobj = version->parse("0$vobj"); 49 } 50 51 # ensure normal v-string form 52 if ( $vobj->is_qv ) { 53 $vobj = version->parse($vobj->normal); 54 } 55 56 return $vobj; 57} 58 59 60BEGIN { 61 for my $type (qw(minimum maximum exclusion exact_version)) { 62 my $method = "with_$type"; 63 my $to_add = $type eq 'exact_version' ? $type : "add_$type"; 64 65 my $code = sub { 66 my ($self, $name, $version) = @_; 67 68 $version = $self->_version_object( $version ); 69 70 $self->__modify_entry_for($name, $method, $version); 71 72 return $self; 73 }; 74 75 no strict 'refs'; 76 *$to_add = $code; 77 } 78} 79 80 81sub add_requirements { 82 my ($self, $req) = @_; 83 84 for my $module ($req->required_modules) { 85 my $modifiers = $req->__entry_for($module)->as_modifiers; 86 for my $modifier (@$modifiers) { 87 my ($method, @args) = @$modifier; 88 $self->$method($module => @args); 89 }; 90 } 91 92 return $self; 93} 94 95 96sub accepts_module { 97 my ($self, $module, $version) = @_; 98 99 $version = $self->_version_object( $version ); 100 101 return 1 unless my $range = $self->__entry_for($module); 102 return $range->_accepts($version); 103} 104 105 106sub clear_requirement { 107 my ($self, $module) = @_; 108 109 return $self unless $self->__entry_for($module); 110 111 Carp::confess("can't clear requirements on finalized requirements") 112 if $self->is_finalized; 113 114 delete $self->{requirements}{ $module }; 115 116 return $self; 117} 118 119 120sub requirements_for_module { 121 my ($self, $module) = @_; 122 my $entry = $self->__entry_for($module); 123 return unless $entry; 124 return $entry->as_string; 125} 126 127 128sub required_modules { keys %{ $_[0]{requirements} } } 129 130 131sub clone { 132 my ($self) = @_; 133 my $new = (ref $self)->new; 134 135 return $new->add_requirements($self); 136} 137 138sub __entry_for { $_[0]{requirements}{ $_[1] } } 139 140sub __modify_entry_for { 141 my ($self, $name, $method, $version) = @_; 142 143 my $fin = $self->is_finalized; 144 my $old = $self->__entry_for($name); 145 146 Carp::confess("can't add new requirements to finalized requirements") 147 if $fin and not $old; 148 149 my $new = ($old || 'CPAN::Meta::Requirements::_Range::Range') 150 ->$method($version); 151 152 Carp::confess("can't modify finalized requirements") 153 if $fin and $old->as_string ne $new->as_string; 154 155 $self->{requirements}{ $name } = $new; 156} 157 158 159sub is_simple { 160 my ($self) = @_; 161 for my $module ($self->required_modules) { 162 # XXX: This is a complete hack, but also entirely correct. 163 return if $self->__entry_for($module)->as_string =~ /\s/; 164 } 165 166 return 1; 167} 168 169 170sub is_finalized { $_[0]{finalized} } 171 172 173sub finalize { $_[0]{finalized} = 1 } 174 175 176sub as_string_hash { 177 my ($self) = @_; 178 179 my %hash = map {; $_ => $self->{requirements}{$_}->as_string } 180 $self->required_modules; 181 182 return \%hash; 183} 184 185 186my %methods_for_op = ( 187 '==' => [ qw(exact_version) ], 188 '!=' => [ qw(add_exclusion) ], 189 '>=' => [ qw(add_minimum) ], 190 '<=' => [ qw(add_maximum) ], 191 '>' => [ qw(add_minimum add_exclusion) ], 192 '<' => [ qw(add_maximum add_exclusion) ], 193); 194 195sub add_string_requirement { 196 my ($self, $module, $req) = @_; 197 198 Carp::confess("No requirement string provided for $module") 199 unless defined $req && length $req; 200 201 my @parts = split qr{\s*,\s*}, $req; 202 203 204 for my $part (@parts) { 205 my ($op, $ver) = $part =~ m{\A\s*(==|>=|>|<=|<|!=)\s*(.*)\z}; 206 207 if (! defined $op) { 208 $self->add_minimum($module => $part); 209 } else { 210 Carp::confess("illegal requirement string: $req") 211 unless my $methods = $methods_for_op{ $op }; 212 213 $self->$_($module => $ver) for @$methods; 214 } 215 } 216} 217 218 219sub from_string_hash { 220 my ($class, $hash) = @_; 221 222 my $self = $class->new; 223 224 for my $module (keys %$hash) { 225 my $req = $hash->{$module}; 226 unless ( defined $req && length $req ) { 227 $req = 0; 228 Carp::carp("Undefined requirement for $module treated as '0'"); 229 } 230 $self->add_string_requirement($module, $req); 231 } 232 233 return $self; 234} 235 236############################################################## 237 238{ 239 package 240 CPAN::Meta::Requirements::_Range::Exact; 241 sub _new { bless { version => $_[1] } => $_[0] } 242 243 sub _accepts { return $_[0]{version} == $_[1] } 244 245 sub as_string { return "== $_[0]{version}" } 246 247 sub as_modifiers { return [ [ exact_version => $_[0]{version} ] ] } 248 249 sub _clone { 250 (ref $_[0])->_new( version->new( $_[0]{version} ) ) 251 } 252 253 sub with_exact_version { 254 my ($self, $version) = @_; 255 256 return $self->_clone if $self->_accepts($version); 257 258 Carp::confess("illegal requirements: unequal exact version specified"); 259 } 260 261 sub with_minimum { 262 my ($self, $minimum) = @_; 263 return $self->_clone if $self->{version} >= $minimum; 264 Carp::confess("illegal requirements: minimum above exact specification"); 265 } 266 267 sub with_maximum { 268 my ($self, $maximum) = @_; 269 return $self->_clone if $self->{version} <= $maximum; 270 Carp::confess("illegal requirements: maximum below exact specification"); 271 } 272 273 sub with_exclusion { 274 my ($self, $exclusion) = @_; 275 return $self->_clone unless $exclusion == $self->{version}; 276 Carp::confess("illegal requirements: excluded exact specification"); 277 } 278} 279 280############################################################## 281 282{ 283 package 284 CPAN::Meta::Requirements::_Range::Range; 285 286 sub _self { ref($_[0]) ? $_[0] : (bless { } => $_[0]) } 287 288 sub _clone { 289 return (bless { } => $_[0]) unless ref $_[0]; 290 291 my ($s) = @_; 292 my %guts = ( 293 (exists $s->{minimum} ? (minimum => version->new($s->{minimum})) : ()), 294 (exists $s->{maximum} ? (maximum => version->new($s->{maximum})) : ()), 295 296 (exists $s->{exclusions} 297 ? (exclusions => [ map { version->new($_) } @{ $s->{exclusions} } ]) 298 : ()), 299 ); 300 301 bless \%guts => ref($s); 302 } 303 304 sub as_modifiers { 305 my ($self) = @_; 306 my @mods; 307 push @mods, [ add_minimum => $self->{minimum} ] if exists $self->{minimum}; 308 push @mods, [ add_maximum => $self->{maximum} ] if exists $self->{maximum}; 309 push @mods, map {; [ add_exclusion => $_ ] } @{$self->{exclusions} || []}; 310 return \@mods; 311 } 312 313 sub as_string { 314 my ($self) = @_; 315 316 return 0 if ! keys %$self; 317 318 return "$self->{minimum}" if (keys %$self) == 1 and exists $self->{minimum}; 319 320 my @exclusions = @{ $self->{exclusions} || [] }; 321 322 my @parts; 323 324 for my $pair ( 325 [ qw( >= > minimum ) ], 326 [ qw( <= < maximum ) ], 327 ) { 328 my ($op, $e_op, $k) = @$pair; 329 if (exists $self->{$k}) { 330 my @new_exclusions = grep { $_ != $self->{ $k } } @exclusions; 331 if (@new_exclusions == @exclusions) { 332 push @parts, "$op $self->{ $k }"; 333 } else { 334 push @parts, "$e_op $self->{ $k }"; 335 @exclusions = @new_exclusions; 336 } 337 } 338 } 339 340 push @parts, map {; "!= $_" } @exclusions; 341 342 return join q{, }, @parts; 343 } 344 345 sub with_exact_version { 346 my ($self, $version) = @_; 347 $self = $self->_clone; 348 349 Carp::confess("illegal requirements: exact specification outside of range") 350 unless $self->_accepts($version); 351 352 return CPAN::Meta::Requirements::_Range::Exact->_new($version); 353 } 354 355 sub _simplify { 356 my ($self) = @_; 357 358 if (defined $self->{minimum} and defined $self->{maximum}) { 359 if ($self->{minimum} == $self->{maximum}) { 360 Carp::confess("illegal requirements: excluded all values") 361 if grep { $_ == $self->{minimum} } @{ $self->{exclusions} || [] }; 362 363 return CPAN::Meta::Requirements::_Range::Exact->_new($self->{minimum}) 364 } 365 366 Carp::confess("illegal requirements: minimum exceeds maximum") 367 if $self->{minimum} > $self->{maximum}; 368 } 369 370 # eliminate irrelevant exclusions 371 if ($self->{exclusions}) { 372 my %seen; 373 @{ $self->{exclusions} } = grep { 374 (! defined $self->{minimum} or $_ >= $self->{minimum}) 375 and 376 (! defined $self->{maximum} or $_ <= $self->{maximum}) 377 and 378 ! $seen{$_}++ 379 } @{ $self->{exclusions} }; 380 } 381 382 return $self; 383 } 384 385 sub with_minimum { 386 my ($self, $minimum) = @_; 387 $self = $self->_clone; 388 389 if (defined (my $old_min = $self->{minimum})) { 390 $self->{minimum} = (sort { $b cmp $a } ($minimum, $old_min))[0]; 391 } else { 392 $self->{minimum} = $minimum; 393 } 394 395 return $self->_simplify; 396 } 397 398 sub with_maximum { 399 my ($self, $maximum) = @_; 400 $self = $self->_clone; 401 402 if (defined (my $old_max = $self->{maximum})) { 403 $self->{maximum} = (sort { $a cmp $b } ($maximum, $old_max))[0]; 404 } else { 405 $self->{maximum} = $maximum; 406 } 407 408 return $self->_simplify; 409 } 410 411 sub with_exclusion { 412 my ($self, $exclusion) = @_; 413 $self = $self->_clone; 414 415 push @{ $self->{exclusions} ||= [] }, $exclusion; 416 417 return $self->_simplify; 418 } 419 420 sub _accepts { 421 my ($self, $version) = @_; 422 423 return if defined $self->{minimum} and $version < $self->{minimum}; 424 return if defined $self->{maximum} and $version > $self->{maximum}; 425 return if defined $self->{exclusions} 426 and grep { $version == $_ } @{ $self->{exclusions} }; 427 428 return 1; 429 } 430} 431 4321; 433# vim: ts=2 sts=2 sw=2 et: 434 435__END__ 436 437=pod 438 439=encoding utf-8 440 441=head1 NAME 442 443CPAN::Meta::Requirements - a set of version requirements for a CPAN dist 444 445=head1 VERSION 446 447version 2.125 448 449=head1 SYNOPSIS 450 451 use CPAN::Meta::Requirements; 452 453 my $build_requires = CPAN::Meta::Requirements->new; 454 455 $build_requires->add_minimum('Library::Foo' => 1.208); 456 457 $build_requires->add_minimum('Library::Foo' => 2.602); 458 459 $build_requires->add_minimum('Module::Bar' => 'v1.2.3'); 460 461 $METAyml->{build_requires} = $build_requires->as_string_hash; 462 463=head1 DESCRIPTION 464 465A CPAN::Meta::Requirements object models a set of version constraints like 466those specified in the F<META.yml> or F<META.json> files in CPAN distributions. 467It can be built up by adding more and more constraints, and it will reduce them 468to the simplest representation. 469 470Logically impossible constraints will be identified immediately by thrown 471exceptions. 472 473=head1 METHODS 474 475=head2 new 476 477 my $req = CPAN::Meta::Requirements->new; 478 479This returns a new CPAN::Meta::Requirements object. It takes an optional 480hash reference argument. The following keys are supported: 481 482=over 4 483 484=item * 485 486<bad_version_hook> -- if provided, when a version cannot be parsed into 487 488a version object, this code reference will be called with the invalid version 489string as an argument. It must return a valid version object. 490 491=back 492 493All other keys are ignored. 494 495=head2 add_minimum 496 497 $req->add_minimum( $module => $version ); 498 499This adds a new minimum version requirement. If the new requirement is 500redundant to the existing specification, this has no effect. 501 502Minimum requirements are inclusive. C<$version> is required, along with any 503greater version number. 504 505This method returns the requirements object. 506 507=head2 add_maximum 508 509 $req->add_maximum( $module => $version ); 510 511This adds a new maximum version requirement. If the new requirement is 512redundant to the existing specification, this has no effect. 513 514Maximum requirements are inclusive. No version strictly greater than the given 515version is allowed. 516 517This method returns the requirements object. 518 519=head2 add_exclusion 520 521 $req->add_exclusion( $module => $version ); 522 523This adds a new excluded version. For example, you might use these three 524method calls: 525 526 $req->add_minimum( $module => '1.00' ); 527 $req->add_maximum( $module => '1.82' ); 528 529 $req->add_exclusion( $module => '1.75' ); 530 531Any version between 1.00 and 1.82 inclusive would be acceptable, except for 5321.75. 533 534This method returns the requirements object. 535 536=head2 exact_version 537 538 $req->exact_version( $module => $version ); 539 540This sets the version required for the given module to I<exactly> the given 541version. No other version would be considered acceptable. 542 543This method returns the requirements object. 544 545=head2 add_requirements 546 547 $req->add_requirements( $another_req_object ); 548 549This method adds all the requirements in the given CPAN::Meta::Requirements object 550to the requirements object on which it was called. If there are any conflicts, 551an exception is thrown. 552 553This method returns the requirements object. 554 555=head2 accepts_module 556 557 my $bool = $req->accepts_modules($module => $version); 558 559Given an module and version, this method returns true if the version 560specification for the module accepts the provided version. In other words, 561given: 562 563 Module => '>= 1.00, < 2.00' 564 565We will accept 1.00 and 1.75 but not 0.50 or 2.00. 566 567For modules that do not appear in the requirements, this method will return 568true. 569 570=head2 clear_requirement 571 572 $req->clear_requirement( $module ); 573 574This removes the requirement for a given module from the object. 575 576This method returns the requirements object. 577 578=head2 requirements_for_module 579 580 $req->requirements_for_module( $module ); 581 582This returns a string containing the version requirements for a given module in 583the format described in L<CPAN::Meta::Spec> or undef if the given module has no 584requirements. This should only be used for informational purposes such as error 585messages and should not be interpreted or used for comparison (see 586L</accepts_module> instead.) 587 588=head2 required_modules 589 590This method returns a list of all the modules for which requirements have been 591specified. 592 593=head2 clone 594 595 $req->clone; 596 597This method returns a clone of the invocant. The clone and the original object 598can then be changed independent of one another. 599 600=head2 is_simple 601 602This method returns true if and only if all requirements are inclusive minimums 603-- that is, if their string expression is just the version number. 604 605=head2 is_finalized 606 607This method returns true if the requirements have been finalized by having the 608C<finalize> method called on them. 609 610=head2 finalize 611 612This method marks the requirements finalized. Subsequent attempts to change 613the requirements will be fatal, I<if> they would result in a change. If they 614would not alter the requirements, they have no effect. 615 616If a finalized set of requirements is cloned, the cloned requirements are not 617also finalized. 618 619=head2 as_string_hash 620 621This returns a reference to a hash describing the requirements using the 622strings in the F<META.yml> specification. 623 624For example after the following program: 625 626 my $req = CPAN::Meta::Requirements->new; 627 628 $req->add_minimum('CPAN::Meta::Requirements' => 0.102); 629 630 $req->add_minimum('Library::Foo' => 1.208); 631 632 $req->add_maximum('Library::Foo' => 2.602); 633 634 $req->add_minimum('Module::Bar' => 'v1.2.3'); 635 636 $req->add_exclusion('Module::Bar' => 'v1.2.8'); 637 638 $req->exact_version('Xyzzy' => '6.01'); 639 640 my $hashref = $req->as_string_hash; 641 642C<$hashref> would contain: 643 644 { 645 'CPAN::Meta::Requirements' => '0.102', 646 'Library::Foo' => '>= 1.208, <= 2.206', 647 'Module::Bar' => '>= v1.2.3, != v1.2.8', 648 'Xyzzy' => '== 6.01', 649 } 650 651=head2 add_string_requirement 652 653 $req->add_string_requirement('Library::Foo' => '>= 1.208, <= 2.206'); 654 655This method parses the passed in string and adds the appropriate requirement 656for the given module. It understands version ranges as described in the 657L<CPAN::Meta::Spec/Version Ranges>. For example: 658 659=over 4 660 661=item 1.3 662 663=item >= 1.3 664 665=item <= 1.3 666 667=item == 1.3 668 669=item != 1.3 670 671=item > 1.3 672 673=item < 1.3 674 675=item >= 1.3, != 1.5, <= 2.0 676 677A version number without an operator is equivalent to specifying a minimum 678(C<E<gt>=>). Extra whitespace is allowed. 679 680=back 681 682=head2 from_string_hash 683 684 my $req = CPAN::Meta::Requirements->from_string_hash( \%hash ); 685 686This is an alternate constructor for a CPAN::Meta::Requirements object. It takes 687a hash of module names and version requirement strings and returns a new 688CPAN::Meta::Requirements object. 689 690=for :stopwords cpan testmatrix url annocpan anno bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan 691 692=head1 SUPPORT 693 694=head2 Bugs / Feature Requests 695 696Please report any bugs or feature requests through the issue tracker 697at L<https://github.com/dagolden/CPAN-Meta-Requirements/issues>. 698You will be notified automatically of any progress on your issue. 699 700=head2 Source Code 701 702This is open source software. The code repository is available for 703public review and contribution under the terms of the license. 704 705L<https://github.com/dagolden/CPAN-Meta-Requirements> 706 707 git clone https://github.com/dagolden/CPAN-Meta-Requirements.git 708 709=head1 AUTHORS 710 711=over 4 712 713=item * 714 715David Golden <dagolden@cpan.org> 716 717=item * 718 719Ricardo Signes <rjbs@cpan.org> 720 721=back 722 723=head1 COPYRIGHT AND LICENSE 724 725This software is copyright (c) 2010 by David Golden and Ricardo Signes. 726 727This is free software; you can redistribute it and/or modify it under 728the same terms as the Perl 5 programming language system itself. 729 730=cut 731