File Coverage

blib/lib/Version/Requirements.pm
Criterion Covered Total %
statement 176 177 99.4
branch 78 82 95.1
condition 33 34 97.0
subroutine 40 41 97.5
pod 11 11 100.0
total 338 345 97.9


line stmt bran cond sub pod time code
1 5     5   2186 use strict;
  5         6  
  5         135  
2 5     5   19 use warnings;
  5         6  
  5         254  
3             package Version::Requirements;
4             # ABSTRACT: a set of version requirements for a CPAN dist
5             $Version::Requirements::VERSION = '0.101023';
6             #pod =head1 SYNOPSIS
7             #pod
8             #pod use Version::Requirements;
9             #pod
10             #pod my $build_requires = Version::Requirements->new;
11             #pod
12             #pod $build_requires->add_minimum('Library::Foo' => 1.208);
13             #pod
14             #pod $build_requires->add_minimum('Library::Foo' => 2.602);
15             #pod
16             #pod $build_requires->add_minimum('Module::Bar' => 'v1.2.3');
17             #pod
18             #pod $METAyml->{build_requires} = $build_requires->as_string_hash;
19             #pod
20             #pod =head1 DESCRIPTION
21             #pod
22             #pod A Version::Requirements object models a set of version constraints like those
23             #pod specified in the F or F files in CPAN distributions. It
24             #pod can be built up by adding more and more constraints, and it will reduce them to
25             #pod the simplest representation.
26             #pod
27             #pod Logically impossible constraints will be identified immediately by thrown
28             #pod exceptions.
29             #pod
30             #pod =cut
31              
32 5     5   21 use Carp ();
  5         4  
  5         70  
33 5     5   17 use Scalar::Util ();
  5         6  
  5         88  
34 5     5   2128 use version 0.77 (); # the ->parse method
  5         7135  
  5         1074  
35              
36             # We silence this warning during core tests, because this is only in core
37             # because it has to be, and nobody wants to see this stupid warning.
38             # -- rjbs, 2012-01-20
39             Carp::cluck(
40             "Version::Requirements is deprecated; replace with CPAN::Meta::Requirements"
41             ) unless $ENV{PERL_CORE};
42              
43             #pod =method new
44             #pod
45             #pod my $req = Version::Requirements->new;
46             #pod
47             #pod This returns a new Version::Requirements object. It ignores any arguments
48             #pod given.
49             #pod
50             #pod =cut
51              
52             sub new {
53 31     31 1 9419 my ($class) = @_;
54 31         96 return bless {} => $class;
55             }
56              
57             sub _version_object {
58 107     107   92 my ($self, $version) = @_;
59              
60 107 100       651 $version = (! defined $version) ? version->parse(0)
    100          
61             : (! Scalar::Util::blessed($version)) ? version->parse($version)
62             : $version;
63              
64 107         133 return $version;
65             }
66              
67             #pod =method add_minimum
68             #pod
69             #pod $req->add_minimum( $module => $version );
70             #pod
71             #pod This adds a new minimum version requirement. If the new requirement is
72             #pod redundant to the existing specification, this has no effect.
73             #pod
74             #pod Minimum requirements are inclusive. C<$version> is required, along with any
75             #pod greater version number.
76             #pod
77             #pod This method returns the requirements object.
78             #pod
79             #pod =method add_maximum
80             #pod
81             #pod $req->add_maximum( $module => $version );
82             #pod
83             #pod This adds a new maximum version requirement. If the new requirement is
84             #pod redundant to the existing specification, this has no effect.
85             #pod
86             #pod Maximum requirements are inclusive. No version strictly greater than the given
87             #pod version is allowed.
88             #pod
89             #pod This method returns the requirements object.
90             #pod
91             #pod =method add_exclusion
92             #pod
93             #pod $req->add_exclusion( $module => $version );
94             #pod
95             #pod This adds a new excluded version. For example, you might use these three
96             #pod method calls:
97             #pod
98             #pod $req->add_minimum( $module => '1.00' );
99             #pod $req->add_maximum( $module => '1.82' );
100             #pod
101             #pod $req->add_exclusion( $module => '1.75' );
102             #pod
103             #pod Any version between 1.00 and 1.82 inclusive would be acceptable, except for
104             #pod 1.75.
105             #pod
106             #pod This method returns the requirements object.
107             #pod
108             #pod =method exact_version
109             #pod
110             #pod $req->exact_version( $module => $version );
111             #pod
112             #pod This sets the version required for the given module to I the given
113             #pod version. No other version would be considered acceptable.
114             #pod
115             #pod This method returns the requirements object.
116             #pod
117             #pod =cut
118              
119             BEGIN {
120 5     5   10 for my $type (qw(minimum maximum exclusion exact_version)) {
121 20         26 my $method = "with_$type";
122 20 100       34 my $to_add = $type eq 'exact_version' ? $type : "add_$type";
123              
124             my $code = sub {
125 101     101   322 my ($self, $name, $version) = @_;
126              
127 101         131 $version = $self->_version_object( $version );
128              
129 101         151 $self->__modify_entry_for($name, $method, $version);
130              
131 91         154 return $self;
132 20         45 };
133            
134 5     5   24 no strict 'refs';
  5         7  
  5         163  
135 20         8125 *$to_add = $code;
136             }
137             }
138              
139             #pod =method add_requirements
140             #pod
141             #pod $req->add_requirements( $another_req_object );
142             #pod
143             #pod This method adds all the requirements in the given Version::Requirements object
144             #pod to the requirements object on which it was called. If there are any conflicts,
145             #pod an exception is thrown.
146             #pod
147             #pod This method returns the requirements object.
148             #pod
149             #pod =cut
150              
151             sub add_requirements {
152 6     6 1 12 my ($self, $req) = @_;
153              
154 6         12 for my $module ($req->required_modules) {
155 14         23 my $modifiers = $req->__entry_for($module)->as_modifiers;
156 14         22 for my $modifier (@$modifiers) {
157 17         26 my ($method, @args) = @$modifier;
158 17         32 $self->$method($module => @args);
159             };
160             }
161              
162 6         15 return $self;
163             }
164              
165             #pod =method accepts_module
166             #pod
167             #pod my $bool = $req->accepts_modules($module => $version);
168             #pod
169             #pod Given an module and version, this method returns true if the version
170             #pod specification for the module accepts the provided version. In other words,
171             #pod given:
172             #pod
173             #pod Module => '>= 1.00, < 2.00'
174             #pod
175             #pod We will accept 1.00 and 1.75 but not 0.50 or 2.00.
176             #pod
177             #pod For modules that do not appear in the requirements, this method will return
178             #pod true.
179             #pod
180             #pod =cut
181              
182             sub accepts_module {
183 6     6 1 17 my ($self, $module, $version) = @_;
184              
185 6         30 $version = $self->_version_object( $version );
186              
187 6 50       7 return 1 unless my $range = $self->__entry_for($module);
188 6         10 return $range->_accepts($version);
189             }
190              
191             #pod =method clear_requirement
192             #pod
193             #pod $req->clear_requirement( $module );
194             #pod
195             #pod This removes the requirement for a given module from the object.
196             #pod
197             #pod This method returns the requirements object.
198             #pod
199             #pod =cut
200              
201             sub clear_requirement {
202 3     3 1 15 my ($self, $module) = @_;
203              
204 3 100       7 return $self unless $self->__entry_for($module);
205              
206 2 100       4 Carp::confess("can't clear requirements on finalized requirements")
207             if $self->is_finalized;
208              
209 1         5 delete $self->{requirements}{ $module };
210              
211 1         2 return $self;
212             }
213              
214             #pod =method required_modules
215             #pod
216             #pod This method returns a list of all the modules for which requirements have been
217             #pod specified.
218             #pod
219             #pod =cut
220              
221 31     31 1 27 sub required_modules { keys %{ $_[0]{requirements} } }
  31         99  
222              
223             #pod =method clone
224             #pod
225             #pod $req->clone;
226             #pod
227             #pod This method returns a clone of the invocant. The clone and the original object
228             #pod can then be changed independent of one another.
229             #pod
230             #pod =cut
231              
232             sub clone {
233 3     3 1 9 my ($self) = @_;
234 3         9 my $new = (ref $self)->new;
235              
236 3         9 return $new->add_requirements($self);
237             }
238              
239 128     128   213 sub __entry_for { $_[0]{requirements}{ $_[1] } }
240              
241             sub __modify_entry_for {
242 101     101   96 my ($self, $name, $method, $version) = @_;
243              
244 101         131 my $fin = $self->is_finalized;
245 101         124 my $old = $self->__entry_for($name);
246              
247 101 100 100     278 Carp::confess("can't add new requirements to finalized requirements")
248             if $fin and not $old;
249              
250 100   100     365 my $new = ($old || 'Version::Requirements::_Range::Range')
251             ->$method($version);
252              
253 92 100 100     160 Carp::confess("can't modify finalized requirements")
254             if $fin and $old->as_string ne $new->as_string;
255              
256 91         217 $self->{requirements}{ $name } = $new;
257             }
258              
259             #pod =method is_simple
260             #pod
261             #pod This method returns true if and only if all requirements are inclusive minimums
262             #pod -- that is, if their string expression is just the version number.
263             #pod
264             #pod =cut
265              
266             sub is_simple {
267 2     2 1 5 my ($self) = @_;
268 2         6 for my $module ($self->required_modules) {
269             # XXX: This is a complete hack, but also entirely correct.
270 4 100       8 return if $self->__entry_for($module)->as_string =~ /\s/;
271             }
272              
273 1         5 return 1;
274             }
275              
276             #pod =method is_finalized
277             #pod
278             #pod This method returns true if the requirements have been finalized by having the
279             #pod C method called on them.
280             #pod
281             #pod =cut
282              
283 103     103 1 236 sub is_finalized { $_[0]{finalized} }
284              
285             #pod =method finalize
286             #pod
287             #pod This method marks the requirements finalized. Subsequent attempts to change
288             #pod the requirements will be fatal, I they would result in a change. If they
289             #pod would not alter the requirements, they have no effect.
290             #pod
291             #pod If a finalized set of requirements is cloned, the cloned requirements are not
292             #pod also finalized.
293             #pod
294             #pod =cut
295              
296 1     1 1 3 sub finalize { $_[0]{finalized} = 1 }
297              
298             #pod =method as_string_hash
299             #pod
300             #pod This returns a reference to a hash describing the requirements using the
301             #pod strings in the F specification.
302             #pod
303             #pod For example after the following program:
304             #pod
305             #pod my $req = Version::Requirements->new;
306             #pod
307             #pod $req->add_minimum('Version::Requirements' => 0.102);
308             #pod
309             #pod $req->add_minimum('Library::Foo' => 1.208);
310             #pod
311             #pod $req->add_maximum('Library::Foo' => 2.602);
312             #pod
313             #pod $req->add_minimum('Module::Bar' => 'v1.2.3');
314             #pod
315             #pod $req->add_exclusion('Module::Bar' => 'v1.2.8');
316             #pod
317             #pod $req->exact_version('Xyzzy' => '6.01');
318             #pod
319             #pod my $hashref = $req->as_string_hash;
320             #pod
321             #pod C<$hashref> would contain:
322             #pod
323             #pod {
324             #pod 'Version::Requirements' => '0.102',
325             #pod 'Library::Foo' => '>= 1.208, <= 2.206',
326             #pod 'Module::Bar' => '>= v1.2.3, != v1.2.8',
327             #pod 'Xyzzy' => '== 6.01',
328             #pod }
329             #pod
330             #pod =cut
331              
332             sub as_string_hash {
333 23     23 1 63 my ($self) = @_;
334              
335 23         43 my %hash = map {; $_ => $self->{requirements}{$_}->as_string }
  47         95  
336             $self->required_modules;
337              
338 23         125 return \%hash;
339             }
340              
341             #pod =method from_string_hash
342             #pod
343             #pod my $req = Version::Requirements->from_string_hash( \%hash );
344             #pod
345             #pod This is an alternate constructor for a Version::Requirements object. It takes
346             #pod a hash of module names and version requirement strings and returns a new
347             #pod Version::Requirements object.
348             #pod
349             #pod =cut
350              
351             my %methods_for_op = (
352             '==' => [ qw(exact_version) ],
353             '!=' => [ qw(add_exclusion) ],
354             '>=' => [ qw(add_minimum) ],
355             '<=' => [ qw(add_maximum) ],
356             '>' => [ qw(add_minimum add_exclusion) ],
357             '<' => [ qw(add_maximum add_exclusion) ],
358             );
359              
360             sub from_string_hash {
361 2     2 1 898 my ($class, $hash) = @_;
362              
363 2         6 my $self = $class->new;
364              
365 2         8 for my $module (keys %$hash) {
366 6         38 my @parts = split qr{\s*,\s*}, $hash->{ $module };
367 6         9 for my $part (@parts) {
368 8         10 my ($op, $ver) = split /\s+/, $part, 2;
369              
370 8 100       15 if (! defined $ver) {
371 4         7 $self->add_minimum($module => $op);
372             } else {
373 4 100       173 Carp::confess("illegal requirement string: $hash->{ $module }")
374             unless my $methods = $methods_for_op{ $op };
375              
376 3         10 $self->$_($module => $ver) for @$methods;
377             }
378             }
379             }
380              
381 1         3 return $self;
382             }
383              
384             ##############################################################
385              
386             {
387             package
388             Version::Requirements::_Range::Exact;
389 16     16   40 sub _new { bless { version => $_[1] } => $_[0] }
390              
391 3     3   20 sub _accepts { return $_[0]{version} == $_[1] }
392              
393 6     6   29 sub as_string { return "== $_[0]{version}" }
394              
395 2     2   10 sub as_modifiers { return [ [ exact_version => $_[0]{version} ] ] }
396              
397             sub _clone {
398 8     8   35 (ref $_[0])->_new( version->new( $_[0]{version} ) )
399             }
400              
401             sub with_exact_version {
402 3     3   4 my ($self, $version) = @_;
403              
404 3 100       6 return $self->_clone if $self->_accepts($version);
405              
406 1         110 Carp::confess("illegal requirements: unequal exact version specified");
407             }
408              
409             sub with_minimum {
410 3     3   2 my ($self, $minimum) = @_;
411 3 100       15 return $self->_clone if $self->{version} >= $minimum;
412 1         76 Carp::confess("illegal requirements: minimum above exact specification");
413             }
414              
415             sub with_maximum {
416 3     3   3 my ($self, $maximum) = @_;
417 3 100       15 return $self->_clone if $self->{version} <= $maximum;
418 1         80 Carp::confess("illegal requirements: maximum below exact specification");
419             }
420              
421             sub with_exclusion {
422 3     3   6 my ($self, $exclusion) = @_;
423 3 100       11 return $self->_clone unless $exclusion == $self->{version};
424 1         77 Carp::confess("illegal requirements: excluded exact specification");
425             }
426             }
427              
428             ##############################################################
429              
430             {
431             package
432             Version::Requirements::_Range::Range;
433              
434 0 0   0   0 sub _self { ref($_[0]) ? $_[0] : (bless { } => $_[0]) }
435              
436             sub _clone {
437 88 100   88   226 return (bless { } => $_[0]) unless ref $_[0];
438              
439 36         33 my ($s) = @_;
440 9         52 my %guts = (
441             (exists $s->{minimum} ? (minimum => version->new($s->{minimum})) : ()),
442             (exists $s->{maximum} ? (maximum => version->new($s->{maximum})) : ()),
443              
444             (exists $s->{exclusions}
445 36 100       346 ? (exclusions => [ map { version->new($_) } @{ $s->{exclusions} } ])
  12 100       23  
    100          
446             : ()),
447             );
448              
449 36         125 bless \%guts => ref($s);
450             }
451              
452             sub as_modifiers {
453 12     12   10 my ($self) = @_;
454 12         10 my @mods;
455 12 100       39 push @mods, [ add_minimum => $self->{minimum} ] if exists $self->{minimum};
456 12 100       28 push @mods, [ add_maximum => $self->{maximum} ] if exists $self->{maximum};
457 12 100       9 push @mods, map {; [ add_exclusion => $_ ] } @{$self->{exclusions} || []};
  1         4  
  12         37  
458 12         20 return \@mods;
459             }
460              
461             sub as_string {
462 49     49   39 my ($self) = @_;
463              
464 49 50       126 return 0 if ! keys %$self;
465              
466 49 100 100     403 return "$self->{minimum}" if (keys %$self) == 1 and exists $self->{minimum};
467              
468 15 100       19 my @exclusions = @{ $self->{exclusions} || [] };
  15         49  
469              
470 15         19 my @parts;
471              
472 15         43 for my $pair (
473             [ qw( >= > minimum ) ],
474             [ qw( <= < maximum ) ],
475             ) {
476 30         38 my ($op, $e_op, $k) = @$pair;
477 30 100       61 if (exists $self->{$k}) {
478 24         27 my @new_exclusions = grep { $_ != $self->{ $k } } @exclusions;
  12         77  
479 24 100       38 if (@new_exclusions == @exclusions) {
480 23         99 push @parts, "$op $self->{ $k }";
481             } else {
482 1         4 push @parts, "$e_op $self->{ $k }";
483 1         3 @exclusions = @new_exclusions;
484             }
485             }
486             }
487              
488 15         28 push @parts, map {; "!= $_" } @exclusions;
  8         28  
489              
490 15         66 return join q{, }, @parts;
491             }
492              
493             sub with_exact_version {
494 8     8   9 my ($self, $version) = @_;
495 8         15 $self = $self->_clone;
496              
497 8 100       15 Carp::confess("illegal requirements: exact specification outside of range")
498             unless $self->_accepts($version);
499              
500 7         16 return Version::Requirements::_Range::Exact->_new($version);
501             }
502              
503             sub _simplify {
504 80     80   91 my ($self) = @_;
505              
506 80 100 100     268 if (defined $self->{minimum} and defined $self->{maximum}) {
507 19 100       74 if ($self->{minimum} == $self->{maximum}) {
508 1         243 Carp::confess("illegal requirements: excluded all values")
509 2 100       3 if grep { $_ == $self->{minimum} } @{ $self->{exclusions} || [] };
  2 100       9  
510              
511 1         5 return Version::Requirements::_Range::Exact->_new($self->{minimum})
512             }
513              
514 17 100       253 Carp::confess("illegal requirements: minimum exceeds maximum")
515             if $self->{minimum} > $self->{maximum};
516             }
517              
518             # eliminate irrelevant exclusions
519 76 100       122 if ($self->{exclusions}) {
520 21         20 my %seen;
521 21 100 100     47 @{ $self->{exclusions} } = grep {
  22   100     294  
      66        
522 21         39 (! defined $self->{minimum} or $_ >= $self->{minimum})
523             and
524             (! defined $self->{maximum} or $_ <= $self->{maximum})
525             and
526             ! $seen{$_}++
527 21         16 } @{ $self->{exclusions} };
528             }
529              
530 76         116 return $self;
531             }
532              
533             sub with_minimum {
534 49     49   81 my ($self, $minimum) = @_;
535 49         83 $self = $self->_clone;
536              
537 49 100       103 if (defined (my $old_min = $self->{minimum})) {
538 10         30 $self->{minimum} = (sort { $b cmp $a } ($minimum, $old_min))[0];
  10         52  
539             } else {
540 39         63 $self->{minimum} = $minimum;
541             }
542              
543 49         77 return $self->_simplify;
544             }
545              
546             sub with_maximum {
547 17     17   24 my ($self, $maximum) = @_;
548 17         30 $self = $self->_clone;
549              
550 17 100       41 if (defined (my $old_max = $self->{maximum})) {
551 1         5 $self->{maximum} = (sort { $a cmp $b } ($maximum, $old_max))[0];
  1         7  
552             } else {
553 16         26 $self->{maximum} = $maximum;
554             }
555              
556 17         27 return $self->_simplify;
557             }
558              
559             sub with_exclusion {
560 14     14   23 my ($self, $exclusion) = @_;
561 14         54 $self = $self->_clone;
562              
563 14   100     14 push @{ $self->{exclusions} ||= [] }, $exclusion;
  14         64  
564              
565 14         24 return $self->_simplify;
566             }
567              
568             sub _accepts {
569 14     14   12 my ($self, $version) = @_;
570              
571 14 100 100     158 return if defined $self->{minimum} and $version < $self->{minimum};
572 12 100 100     32 return if defined $self->{maximum} and $version > $self->{maximum};
573 2         12 return if defined $self->{exclusions}
574 11 100 100     24 and grep { $version == $_ } @{ $self->{exclusions} };
  2         3  
575              
576 10         26 return 1;
577             }
578             }
579              
580             1;
581              
582             __END__