File Coverage

blib/lib/CPAN/Meta/Requirements/Range.pm
Criterion Covered Total %
statement 205 214 95.7
branch 86 104 82.6
condition 53 71 74.6
subroutine 37 39 94.8
pod 8 8 100.0
total 389 436 89.2


line stmt bran cond sub pod time code
1 9     9   102 use v5.10;
  9         32  
2 9     9   44 use strict;
  9         26  
  9         186  
3 9     9   42 use warnings;
  9         15  
  9         464  
4             package CPAN::Meta::Requirements::Range;
5             # ABSTRACT: a set of version requirements for a CPAN dist
6              
7             our $VERSION = '2.141'; # TRIAL
8              
9 9     9   80 use Carp ();
  9         34  
  9         205  
10              
11             #pod =head1 SYNOPSIS
12             #pod
13             #pod use CPAN::Meta::Requirements::Range;
14             #pod
15             #pod my $range = CPAN::Meta::Requirements::Range->with_minimum(1);
16             #pod
17             #pod $range = $range->with_maximum('v2.2');
18             #pod
19             #pod my $stringified = $range->as_string;
20             #pod
21             #pod =head1 DESCRIPTION
22             #pod
23             #pod A CPAN::Meta::Requirements::Range object models a set of version constraints like
24             #pod those specified in the F or F files in CPAN distributions,
25             #pod and as defined by L;
26             #pod It can be built up by adding more and more constraints, and it will reduce them
27             #pod to the simplest representation.
28             #pod
29             #pod Logically impossible constraints will be identified immediately by thrown
30             #pod exceptions.
31             #pod
32             #pod =cut
33              
34 9     9   41 use Carp ();
  9         17  
  9         558  
35              
36             package
37             CPAN::Meta::Requirements::Range::_Base;
38              
39             # To help ExtUtils::MakeMaker bootstrap CPAN::Meta::Requirements on perls
40             # before 5.10, we fall back to the EUMM bundled compatibility version module if
41             # that's the only thing available. This shouldn't ever happen in a normal CPAN
42             # install of CPAN::Meta::Requirements, as version.pm will be picked up from
43             # prereqs and be available at runtime.
44              
45             BEGIN {
46 9     9   695 eval "use version ()"; ## no critic
  9     9   3191  
  9         15505  
  9         106  
47 9 50       23243 if ( my $err = $@ ) {
48 0 0       0 eval "use ExtUtils::MakeMaker::version" or die $err; ## no critic
49             }
50             }
51              
52             # from version::vpp
53             sub _find_magic_vstring {
54 447     447   723 my $value = shift;
55 447         665 my $tvalue = '';
56 447         1756 require B;
57 447         1251 my $sv = B::svref_2object(\$value);
58 447 100       992 my $magic = ref($sv) eq 'B::PVMG' ? $sv->MAGIC : undef;
59 447         909 while ( $magic ) {
60 1 50       10 if ( $magic->TYPE eq 'V' ) {
61 1         5 $tvalue = $magic->PTR;
62 1         10 $tvalue =~ s/^v?(.+)$/v$1/;
63 1         3 last;
64             }
65             else {
66 0         0 $magic = $magic->MOREMAGIC;
67             }
68             }
69 447         978 return $tvalue;
70             }
71              
72             # Perl 5.10.0 didn't have "is_qv" in version.pm
73 451     451   1433 *_is_qv = version->can('is_qv') ? sub { $_[0]->is_qv } : sub { exists $_[0]->{qv} };
74              
75             # construct once, reuse many times
76             my $V0 = version->new(0);
77              
78             # safe if given an unblessed reference
79             sub _isa_version {
80 0 0   0   0 UNIVERSAL::isa( $_[0], 'UNIVERSAL' ) && $_[0]->isa('version')
81             }
82              
83             sub _version_object {
84 813     813   1535 my ($self, $version, $module, $bad_version_hook) = @_;
85              
86 813         1200 my ($vobj, $err);
87              
88 813 100 100     3751 if (not defined $version or (!ref($version) && $version eq '0')) {
    100 66        
      33        
      66        
89 359         807 return $V0;
90             }
91             elsif ( ref($version) eq 'version' || ( ref($version) && _isa_version($version) ) ) {
92 271         426 $vobj = $version;
93             }
94             else {
95             # hack around version::vpp not handling <3 character vstring literals
96 183 50 33     1003 if ( $INC{'version/vpp.pm'} || $INC{'ExtUtils/MakeMaker/version/vpp.pm'} ) {
97 0         0 my $magic = _find_magic_vstring( $version );
98 0 0       0 $version = $magic if length $magic;
99             }
100             # pad to 3 characters if before 5.8.1 and appears to be a v-string
101 183 0 33     439 if ( $] < 5.008001 && $version !~ /\A[0-9]/ && substr($version,0,1) ne 'v' && length($version) < 3 ) {
      33        
      0        
102 0         0 $version .= "\0" x (3 - length($version));
103             }
104 183         296 eval {
105 183     0   1089 local $SIG{__WARN__} = sub { die "Invalid version: $_[0]" };
  0         0  
106             # avoid specific segfault on some older version.pm versions
107 183 100       490 die "Invalid version: $version" if $version eq 'version';
108 182         1525 $vobj = version->new($version);
109             };
110 183 100       551 if ( my $err = $@ ) {
111 6 100       36 $vobj = eval { $bad_version_hook->($version, $module) }
  4         13  
112             if ref $bad_version_hook eq 'CODE';
113 6 100       41 unless (eval { $vobj->isa("version") }) {
  6         64  
114 3         31 $err =~ s{ at .* line \d+.*$}{};
115 3         29 die "Can't convert '$version': $err";
116             }
117             }
118             }
119              
120             # ensure no leading '.'
121 451 50       1777 if ( $vobj =~ m{\A\.} ) {
122 0         0 $vobj = version->new("0$vobj");
123             }
124              
125             # ensure normal v-string form
126 451 100       954 if ( _is_qv($vobj) ) {
127 21         188 $vobj = version->new($vobj->normal);
128             }
129              
130 451         1031 return $vobj;
131             }
132              
133             #pod =method with_string_requirement
134             #pod
135             #pod $req->with_string_requirement('>= 1.208, <= 2.206');
136             #pod $req->with_string_requirement(v1.208);
137             #pod
138             #pod This method parses the passed in string and adds the appropriate requirement.
139             #pod A version can be a Perl "v-string". It understands version ranges as described
140             #pod in the L. For example:
141             #pod
142             #pod =over 4
143             #pod
144             #pod =item 1.3
145             #pod
146             #pod =item >= 1.3
147             #pod
148             #pod =item <= 1.3
149             #pod
150             #pod =item == 1.3
151             #pod
152             #pod =item != 1.3
153             #pod
154             #pod =item > 1.3
155             #pod
156             #pod =item < 1.3
157             #pod
158             #pod =item >= 1.3, != 1.5, <= 2.0
159             #pod
160             #pod A version number without an operator is equivalent to specifying a minimum
161             #pod (C=>). Extra whitespace is allowed.
162             #pod
163             #pod =back
164             #pod
165             #pod =cut
166              
167             my %methods_for_op = (
168             '==' => [ qw(with_exact_version) ],
169             '!=' => [ qw(with_exclusion) ],
170             '>=' => [ qw(with_minimum) ],
171             '<=' => [ qw(with_maximum) ],
172             '>' => [ qw(with_minimum with_exclusion) ],
173             '<' => [ qw(with_maximum with_exclusion) ],
174             );
175              
176             sub with_string_requirement {
177 447     447   3031 my ($self, $req, $module, $bad_version_hook) = @_;
178 447   100     900 $module //= 'module';
179              
180 447 100 100     1336 unless ( defined $req && length $req ) {
181 4         7 $req = 0;
182 4         626 Carp::carp("Undefined requirement for $module treated as '0'");
183             }
184              
185 447         1213 my $magic = _find_magic_vstring( $req );
186 447 100       917 if (length $magic) {
187 1         5 return $self->with_minimum($magic, $module, $bad_version_hook);
188             }
189              
190 446         2134 my @parts = split qr{\s*,\s*}, $req;
191              
192 446         1076 for my $part (@parts) {
193 454         1487 my ($op, $ver) = $part =~ m{\A\s*(==|>=|>|<=|<|!=)\s*(.*)\z};
194              
195 454 100       887 if (! defined $op) {
196 432         913 return $self->with_minimum($part, $module, $bad_version_hook);
197             } else {
198             Carp::croak("illegal requirement string: $req")
199 22 50       89 unless my $methods = $methods_for_op{ $op };
200              
201 22         91 $self = $self->$_($ver, $module, $bad_version_hook) for @$methods;
202             }
203             }
204              
205 14         46 return $self;
206             }
207              
208             #pod =method with_range
209             #pod
210             #pod $range->with_range($other_range)
211             #pod
212             #pod This creates a new range object that is a merge two others.
213             #pod
214             #pod =cut
215              
216             sub with_range {
217 266     266   509 my ($self, $other, $module, $bad_version_hook) = @_;
218 266         454 for my $modifier($other->_as_modifiers) {
219 269         1098 my ($method, $arg) = @$modifier;
220 269         568 $self = $self->$method($arg, $module, $bad_version_hook);
221             }
222 266         637 return $self;
223             }
224              
225             package CPAN::Meta::Requirements::Range;
226              
227             our @ISA = 'CPAN::Meta::Requirements::Range::_Base';
228              
229             sub _clone {
230 802 100   802   2038 return (bless { } => $_[0]) unless ref $_[0];
231              
232 125         212 my ($s) = @_;
233             my %guts = (
234             (exists $s->{minimum} ? (minimum => version->new($s->{minimum})) : ()),
235             (exists $s->{maximum} ? (maximum => version->new($s->{maximum})) : ()),
236              
237             (exists $s->{exclusions}
238 125 100       853 ? (exclusions => [ map { version->new($_) } @{ $s->{exclusions} } ])
  9 100       50  
  12 100       30  
239             : ()),
240             );
241              
242 125         325 bless \%guts => ref($s);
243             }
244              
245             #pod =method with_exact_version
246             #pod
247             #pod $range->with_exact_version( $version );
248             #pod
249             #pod This sets the version required to I the given
250             #pod version. No other version would be considered acceptable.
251             #pod
252             #pod This method returns the version range object.
253             #pod
254             #pod =cut
255              
256             sub with_exact_version {
257 11     11 1 31 my ($self, $version, $module, $bad_version_hook) = @_;
258 11   50     30 $module //= 'module';
259 11         29 $self = $self->_clone;
260 11         38 $version = $self->_version_object($version, $module, $bad_version_hook);
261              
262 11 100       36 unless ($self->accepts($version)) {
263 1         7 $self->_reject_requirements(
264             $module,
265             "exact specification $version outside of range " . $self->as_string
266             );
267             }
268              
269 10         48 return CPAN::Meta::Requirements::Range::_Exact->_new($version);
270             }
271              
272             sub _simplify {
273 789     789   1393 my ($self, $module) = @_;
274              
275 789 100 100     2595 if (defined $self->{minimum} and defined $self->{maximum}) {
276 27 100       113 if ($self->{minimum} == $self->{maximum}) {
277 2 100       4 if (grep { $_ == $self->{minimum} } @{ $self->{exclusions} || [] }) {
  1 100       7  
  2         12  
278 1         7 $self->_reject_requirements(
279             $module,
280             "minimum and maximum are both $self->{minimum}, which is excluded",
281             );
282             }
283              
284 1         8 return CPAN::Meta::Requirements::Range::_Exact->_new($self->{minimum});
285             }
286              
287 25 100       88 if ($self->{minimum} > $self->{maximum}) {
288 2         13 $self->_reject_requirements(
289             $module,
290             "minimum $self->{minimum} exceeds maximum $self->{maximum}",
291             );
292             }
293             }
294              
295             # eliminate irrelevant exclusions
296 785 100       1520 if ($self->{exclusions}) {
297 28         46 my %seen;
298 28         96 @{ $self->{exclusions} } = grep {
299             (! defined $self->{minimum} or $_ >= $self->{minimum})
300             and
301             (! defined $self->{maximum} or $_ <= $self->{maximum})
302             and
303 29 100 100     391 ! $seen{$_}++
      100        
      100        
304 28         54 } @{ $self->{exclusions} };
  28         64  
305             }
306              
307 785         2351 return $self;
308             }
309              
310             #pod =method with_minimum
311             #pod
312             #pod $range->with_minimum( $version );
313             #pod
314             #pod This adds a new minimum version requirement. If the new requirement is
315             #pod redundant to the existing specification, this has no effect.
316             #pod
317             #pod Minimum requirements are inclusive. C<$version> is required, along with any
318             #pod greater version number.
319             #pod
320             #pod This method returns the version range object.
321             #pod
322             #pod =cut
323              
324             sub with_minimum {
325 746     746 1 2357 my ($self, $minimum, $module, $bad_version_hook) = @_;
326 746   100     1516 $module //= 'module';
327 746         1406 $self = $self->_clone;
328 746         1519 $minimum = $self->_version_object( $minimum, $module, $bad_version_hook );
329              
330 744 100       1664 if (defined (my $old_min = $self->{minimum})) {
331 91         233 $self->{minimum} = (sort { $b cmp $a } ($minimum, $old_min))[0];
  91         384  
332             } else {
333 653         1357 $self->{minimum} = $minimum;
334             }
335              
336 744         1368 return $self->_simplify($module);
337             }
338              
339             #pod =method with_maximum
340             #pod
341             #pod $range->with_maximum( $version );
342             #pod
343             #pod This adds a new maximum version requirement. If the new requirement is
344             #pod redundant to the existing specification, this has no effect.
345             #pod
346             #pod Maximum requirements are inclusive. No version strictly greater than the given
347             #pod version is allowed.
348             #pod
349             #pod This method returns the version range object.
350             #pod
351             #pod =cut
352              
353             sub with_maximum {
354 24     24 1 434 my ($self, $maximum, $module, $bad_version_hook) = @_;
355 24   100     67 $module //= 'module';
356 24         69 $self = $self->_clone;
357 24         61 $maximum = $self->_version_object( $maximum, $module, $bad_version_hook );
358              
359 24 100       64 if (defined (my $old_max = $self->{maximum})) {
360 1         4 $self->{maximum} = (sort { $a cmp $b } ($maximum, $old_max))[0];
  1         6  
361             } else {
362 23         54 $self->{maximum} = $maximum;
363             }
364              
365 24         54 return $self->_simplify($module);
366             }
367              
368             #pod =method with_exclusion
369             #pod
370             #pod $range->with_exclusion( $version );
371             #pod
372             #pod This adds a new excluded version. For example, you might use these three
373             #pod method calls:
374             #pod
375             #pod $range->with_minimum( '1.00' );
376             #pod $range->with_maximum( '1.82' );
377             #pod
378             #pod $range->with_exclusion( '1.75' );
379             #pod
380             #pod Any version between 1.00 and 1.82 inclusive would be acceptable, except for
381             #pod 1.75.
382             #pod
383             #pod This method returns the requirements object.
384             #pod
385             #pod =cut
386              
387             sub with_exclusion {
388 21     21 1 462 my ($self, $exclusion, $module, $bad_version_hook) = @_;
389 21   100     67 $module //= 'module';
390 21         57 $self = $self->_clone;
391 21         56 $exclusion = $self->_version_object( $exclusion, $module, $bad_version_hook );
392              
393 21   100     41 push @{ $self->{exclusions} ||= [] }, $exclusion;
  21         101  
394              
395 21         90 return $self->_simplify($module);
396             }
397              
398             sub _as_modifiers {
399 264     264   406 my ($self) = @_;
400 264         354 my @mods;
401 264 100       794 push @mods, [ with_minimum => $self->{minimum} ] if exists $self->{minimum};
402 264 100       541 push @mods, [ with_maximum => $self->{maximum} ] if exists $self->{maximum};
403 264 100       383 push @mods, map {; [ with_exclusion => $_ ] } @{$self->{exclusions} || []};
  1         4  
  264         794  
404 264         589 return @mods;
405             }
406              
407             #pod =method as_struct
408             #pod
409             #pod $range->as_struct( $module );
410             #pod
411             #pod This returns a data structure containing the version requirements. This should
412             #pod not be used for version checks (see L instead).
413             #pod
414             #pod =cut
415              
416             sub as_struct {
417 401     401 1 731 my ($self) = @_;
418              
419 401 50       974 return 0 if ! keys %$self;
420              
421 401 100       575 my @exclusions = @{ $self->{exclusions} || [] };
  401         1213  
422              
423 401         646 my @parts;
424              
425 401         1048 for my $tuple (
426             [ qw( >= > minimum ) ],
427             [ qw( <= < maximum ) ],
428             ) {
429 802         1530 my ($op, $e_op, $k) = @$tuple;
430 802 100       1660 if (exists $self->{$k}) {
431 413         643 my @new_exclusions = grep { $_ != $self->{ $k } } @exclusions;
  18         80  
432 413 100       749 if (@new_exclusions == @exclusions) {
433 412         1848 push @parts, [ $op, "$self->{ $k }" ];
434             } else {
435 1         4 push @parts, [ $e_op, "$self->{ $k }" ];
436 1         5 @exclusions = @new_exclusions;
437             }
438             }
439             }
440              
441 401         809 push @parts, map {; [ "!=", "$_" ] } @exclusions;
  11         45  
442              
443 401         852 return \@parts;
444             }
445              
446             #pod =method as_string
447             #pod
448             #pod $range->as_string;
449             #pod
450             #pod This returns a string containing the version requirements in the format
451             #pod described in L. This should only be used for informational
452             #pod purposes such as error messages and should not be interpreted or used for
453             #pod comparison (see L instead).
454             #pod
455             #pod =cut
456              
457             sub as_string {
458 400     400 1 662 my ($self) = @_;
459              
460 400         549 my @parts = @{ $self->as_struct };
  400         638  
461              
462 400 100 100     2305 return $parts[0][1] if @parts == 1 and $parts[0][0] eq '>=';
463              
464 17         46 return join q{, }, map {; join q{ }, @$_ } @parts;
  38         165  
465             }
466              
467             sub _reject_requirements {
468 4     4   9 my ($self, $module, $error) = @_;
469 4         526 Carp::croak("illegal requirements for $module: $error")
470             }
471              
472             #pod =method accepts
473             #pod
474             #pod my $bool = $range->accepts($version);
475             #pod
476             #pod Given a version, this method returns true if the version specification
477             #pod accepts the provided version. In other words, given:
478             #pod
479             #pod '>= 1.00, < 2.00'
480             #pod
481             #pod We will accept 1.00 and 1.75 but not 0.50 or 2.00.
482             #pod
483             #pod =cut
484              
485             sub accepts {
486 78     78 1 238 my ($self, $version) = @_;
487              
488 78 100 100     628 return if defined $self->{minimum} and $version < $self->{minimum};
489 69 100 100     348 return if defined $self->{maximum} and $version > $self->{maximum};
490             return if defined $self->{exclusions}
491 63 100 100     164 and grep { $version == $_ } @{ $self->{exclusions} };
  14         147  
  14         29  
492              
493 57         205 return 1;
494             }
495              
496             #pod =method is_simple
497             #pod
498             #pod This method returns true if and only if the range is an inclusive minimum
499             #pod -- that is, if their string expression is just the version number.
500             #pod
501             #pod =cut
502              
503             sub is_simple {
504 4     4 1 7 my ($self) = @_;
505             # XXX: This is a complete hack, but also entirely correct.
506 4 100       10 return if $self->as_string =~ /\s/;
507              
508 3         10 return 1;
509             }
510              
511             package
512             CPAN::Meta::Requirements::Range::_Exact;
513              
514             our @ISA = 'CPAN::Meta::Requirements::Range::_Base';
515              
516             our $VERSION = '2.141';
517              
518             BEGIN {
519 9     9   758 eval "use version ()"; ## no critic
  9     9   69  
  9         23  
  9         117  
520 9 50       5165 if ( my $err = $@ ) {
521 0 0       0 eval "use ExtUtils::MakeMaker::version" or die $err; ## no critic
522             }
523             }
524              
525 17     17   96 sub _new { bless { version => $_[1] } => $_[0] }
526              
527 9     9   102 sub accepts { return $_[0]{version} == $_[1] }
528              
529             sub _reject_requirements {
530 4     4   10 my ($self, $module, $error) = @_;
531 4         430 Carp::croak("illegal requirements for $module: $error")
532             }
533              
534             sub _clone {
535 6     6   41 (ref $_[0])->_new( version->new( $_[0]{version} ) )
536             }
537              
538             sub with_exact_version {
539 3     3   12 my ($self, $version, $module, $bad_version_hook) = @_;
540 3   50     33 $module //= 'module';
541 3         20 $version = $self->_version_object($version, $module, $bad_version_hook);
542              
543 3 100       12 return $self->_clone if $self->accepts($version);
544              
545 1         8 $self->_reject_requirements(
546             $module,
547             "can't be exactly $version when exact requirement is already $self->{version}",
548             );
549             }
550              
551             sub with_minimum {
552 2     2   9 my ($self, $minimum, $module, $bad_version_hook) = @_;
553 2   50     8 $module //= 'module';
554 2         10 $minimum = $self->_version_object( $minimum, $module, $bad_version_hook );
555              
556 1 50       8 return $self->_clone if $self->{version} >= $minimum;
557 1         8 $self->_reject_requirements(
558             $module,
559             "minimum $minimum exceeds exact specification $self->{version}",
560             );
561             }
562              
563             sub with_maximum {
564 3     3   9 my ($self, $maximum, $module, $bad_version_hook) = @_;
565 3   50     10 $module //= 'module';
566 3         10 $maximum = $self->_version_object( $maximum, $module, $bad_version_hook );
567              
568 3 100       18 return $self->_clone if $self->{version} <= $maximum;
569 1         9 $self->_reject_requirements(
570             $module,
571             "maximum $maximum below exact specification $self->{version}",
572             );
573             }
574              
575             sub with_exclusion {
576 3     3   9 my ($self, $exclusion, $module, $bad_version_hook) = @_;
577 3   50     9 $module //= 'module';
578 3         9 $exclusion = $self->_version_object( $exclusion, $module, $bad_version_hook );
579              
580 3 100       22 return $self->_clone unless $exclusion == $self->{version};
581 1         7 $self->_reject_requirements(
582             $module,
583             "tried to exclude $exclusion, which is already exactly specified",
584             );
585             }
586              
587 7     7   46 sub as_string { return "== $_[0]{version}" }
588              
589 1     1   12 sub as_struct { return [ [ '==', "$_[0]{version}" ] ] }
590              
591 2     2   10 sub _as_modifiers { return [ with_exact_version => $_[0]{version} ] }
592              
593              
594             1;
595              
596             # vim: ts=2 sts=2 sw=2 et:
597              
598             __END__