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   105 use v5.10;
  9         35  
2 9     9   48 use strict;
  9         34  
  9         219  
3 9     9   44 use warnings;
  9         18  
  9         486  
4             package CPAN::Meta::Requirements::Range;
5             # ABSTRACT: a set of version requirements for a CPAN dist
6              
7             our $VERSION = '2.143';
8              
9 9     9   92 use Carp ();
  9         30  
  9         228  
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   56 use Carp ();
  9         23  
  9         629  
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   692 eval "use version ()"; ## no critic
  9     9   3847  
  9         17148  
  9         108  
47 9 50       24939 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 450     450   684 my $value = shift;
55 450         648 my $tvalue = '';
56 450         1771 require B;
57 450         1418 my $sv = B::svref_2object(\$value);
58 450 100       1061 my $magic = ref($sv) eq 'B::PVMG' ? $sv->MAGIC : undef;
59 450         929 while ( $magic ) {
60 1 50       12 if ( $magic->TYPE eq 'V' ) {
61 1         7 $tvalue = $magic->PTR;
62 1         13 $tvalue =~ s/^v?(.+)$/v$1/;
63 1         3 last;
64             }
65             else {
66 0         0 $magic = $magic->MOREMAGIC;
67             }
68             }
69 450         909 return $tvalue;
70             }
71              
72             # Perl 5.10.0 didn't have "is_qv" in version.pm
73 456     456   1488 *_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 818     818   1662 my ($self, $version, $module, $bad_version_hook) = @_;
85              
86 818         1206 my ($vobj, $err);
87              
88 818 100 100     3713 if (not defined $version or (!ref($version) && $version eq '0')) {
    100 66        
      33        
      66        
89 359         826 return $V0;
90             }
91             elsif ( ref($version) eq 'version' || ( ref($version) && _isa_version($version) ) ) {
92 271         420 $vobj = $version;
93             }
94             else {
95             # hack around version::vpp not handling <3 character vstring literals
96 188 50 33     708 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 188 0 33     460 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 188         302 eval {
105 188     0   1154 local $SIG{__WARN__} = sub { die "Invalid version: $_[0]" };
  0         0  
106             # avoid specific segfault on some older version.pm versions
107 188 100       509 die "Invalid version: $version" if $version eq 'version';
108 187         1671 $vobj = version->new($version);
109             };
110 188 100       576 if ( my $err = $@ ) {
111 6 100       22 $vobj = eval { $bad_version_hook->($version, $module) }
  4         13  
112             if ref $bad_version_hook eq 'CODE';
113 6 100       50 unless (eval { $vobj->isa("version") }) {
  6         47  
114 3         34 $err =~ s{ at .* line \d+.*$}{};
115 3         35 die "Can't convert '$version': $err";
116             }
117             }
118             }
119              
120             # ensure no leading '.'
121 456 50       1851 if ( $vobj =~ m{\A\.} ) {
122 0         0 $vobj = version->new("0$vobj");
123             }
124              
125             # ensure normal v-string form
126 456 100       1033 if ( _is_qv($vobj) ) {
127 21         193 $vobj = version->new($vobj->normal);
128             }
129              
130 456         1074 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 450     450   3338 my ($self, $req, $module, $bad_version_hook) = @_;
178 450   100     886 $module //= 'module';
179              
180 450 100 100     1377 unless ( defined $req && length $req ) {
181 4         8 $req = 0;
182 4         492 Carp::carp("Undefined requirement for $module treated as '0'");
183             }
184              
185 450         797 my $magic = _find_magic_vstring( $req );
186 450 100       893 if (length $magic) {
187 1         7 return $self->with_minimum($magic, $module, $bad_version_hook);
188             }
189              
190 449         2298 my @parts = split qr{\s*,\s*}, $req;
191              
192 449         1103 for my $part (@parts) {
193 459         1499 my ($op, $ver) = $part =~ m{\A\s*(==|>=|>|<=|<|!=)\s*(.*)\z};
194              
195 459 100       917 if (! defined $op) {
196 437         895 $self = $self->with_minimum($part, $module, $bad_version_hook);
197             } else {
198             Carp::croak("illegal requirement string: $req")
199 22 50       59 unless my $methods = $methods_for_op{ $op };
200              
201 22         90 $self = $self->$_($ver, $module, $bad_version_hook) for @$methods;
202             }
203             }
204              
205 447         1111 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   530 my ($self, $other, $module, $bad_version_hook) = @_;
218 266         488 for my $modifier($other->_as_modifiers) {
219 269         517 my ($method, $arg) = @$modifier;
220 269         552 $self = $self->$method($arg, $module, $bad_version_hook);
221             }
222 266         653 return $self;
223             }
224              
225             package CPAN::Meta::Requirements::Range;
226              
227             our @ISA = 'CPAN::Meta::Requirements::Range::_Base';
228              
229             sub _clone {
230 807 100   807   2065 return (bless { } => $_[0]) unless ref $_[0];
231              
232 127         218 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 127 100       949 ? (exclusions => [ map { version->new($_) } @{ $s->{exclusions} } ])
  9 100       61  
  12 100       39  
239             : ()),
240             );
241              
242 127         356 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 30 my ($self, $version, $module, $bad_version_hook) = @_;
258 11   50     28 $module //= 'module';
259 11         29 $self = $self->_clone;
260 11         34 $version = $self->_version_object($version, $module, $bad_version_hook);
261              
262 11 100       34 unless ($self->accepts($version)) {
263 1         49 $self->_reject_requirements(
264             $module,
265             "exact specification $version outside of range " . $self->as_string
266             );
267             }
268              
269 10         35 return CPAN::Meta::Requirements::Range::_Exact->_new($version);
270             }
271              
272             sub _simplify {
273 794     794   1399 my ($self, $module) = @_;
274              
275 794 100 100     2655 if (defined $self->{minimum} and defined $self->{maximum}) {
276 27 100       121 if ($self->{minimum} == $self->{maximum}) {
277 2 100       5 if (grep { $_ == $self->{minimum} } @{ $self->{exclusions} || [] }) {
  1 100       10  
  2         14  
278 1         10 $self->_reject_requirements(
279             $module,
280             "minimum and maximum are both $self->{minimum}, which is excluded",
281             );
282             }
283              
284 1         10 return CPAN::Meta::Requirements::Range::_Exact->_new($self->{minimum});
285             }
286              
287 25 100       104 if ($self->{minimum} > $self->{maximum}) {
288 2         16 $self->_reject_requirements(
289             $module,
290             "minimum $self->{minimum} exceeds maximum $self->{maximum}",
291             );
292             }
293             }
294              
295             # eliminate irrelevant exclusions
296 790 100       1441 if ($self->{exclusions}) {
297 30         48 my %seen;
298 30         102 @{ $self->{exclusions} } = grep {
299             (! defined $self->{minimum} or $_ >= $self->{minimum})
300             and
301             (! defined $self->{maximum} or $_ <= $self->{maximum})
302             and
303 31 100 100     382 ! $seen{$_}++
      100        
      100        
304 30         59 } @{ $self->{exclusions} };
  30         81  
305             }
306              
307 790         2172 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 749     749 1 2226 my ($self, $minimum, $module, $bad_version_hook) = @_;
326 749   100     1414 $module //= 'module';
327 749         1398 $self = $self->_clone;
328 749         1555 $minimum = $self->_version_object( $minimum, $module, $bad_version_hook );
329              
330 747 100       1597 if (defined (my $old_min = $self->{minimum})) {
331 91         270 $self->{minimum} = (sort { $b cmp $a } ($minimum, $old_min))[0];
  91         376  
332             } else {
333 656         1462 $self->{minimum} = $minimum;
334             }
335              
336 747         1421 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 357 my ($self, $maximum, $module, $bad_version_hook) = @_;
355 24   100     66 $module //= 'module';
356 24         67 $self = $self->_clone;
357 24         70 $maximum = $self->_version_object( $maximum, $module, $bad_version_hook );
358              
359 24 100       71 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         56 $self->{maximum} = $maximum;
363             }
364              
365 24         55 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 23     23 1 343 my ($self, $exclusion, $module, $bad_version_hook) = @_;
389 23   100     64 $module //= 'module';
390 23         59 $self = $self->_clone;
391 23         67 $exclusion = $self->_version_object( $exclusion, $module, $bad_version_hook );
392              
393 23   100     37 push @{ $self->{exclusions} ||= [] }, $exclusion;
  23         127  
394              
395 23         104 return $self->_simplify($module);
396             }
397              
398             sub _as_modifiers {
399 264     264   408 my ($self) = @_;
400 264         360 my @mods;
401 264 100       801 push @mods, [ with_minimum => $self->{minimum} ] if exists $self->{minimum};
402 264 100       529 push @mods, [ with_maximum => $self->{maximum} ] if exists $self->{maximum};
403 264 100       364 push @mods, map {; [ with_exclusion => $_ ] } @{$self->{exclusions} || []};
  1         3  
  264         791  
404 264         590 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 402     402 1 611 my ($self) = @_;
418              
419 402 50       1004 return 0 if ! keys %$self;
420              
421 402 100       574 my @exclusions = @{ $self->{exclusions} || [] };
  402         1209  
422              
423 402         623 my @parts;
424              
425 402         1004 for my $tuple (
426             [ qw( >= > minimum ) ],
427             [ qw( <= < maximum ) ],
428             ) {
429 804         1513 my ($op, $e_op, $k) = @$tuple;
430 804 100       1691 if (exists $self->{$k}) {
431 414         622 my @new_exclusions = grep { $_ != $self->{ $k } } @exclusions;
  19         93  
432 414 100       755 if (@new_exclusions == @exclusions) {
433 413         2030 push @parts, [ $op, "$self->{ $k }" ];
434             } else {
435 1         7 push @parts, [ $e_op, "$self->{ $k }" ];
436 1         5 @exclusions = @new_exclusions;
437             }
438             }
439             }
440              
441 402         769 push @parts, map {; [ "!=", "$_" ] } @exclusions;
  12         54  
442              
443 402         862 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 401     401 1 707 my ($self) = @_;
459              
460 401         577 my @parts = @{ $self->as_struct };
  401         641  
461              
462 401 100 100     2706 return $parts[0][1] if @parts == 1 and $parts[0][0] eq '>=';
463              
464 18         55 return join q{, }, map {; join q{ }, @$_ } @parts;
  40         193  
465             }
466              
467             sub _reject_requirements {
468 4     4   11 my ($self, $module, $error) = @_;
469 4         544 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 83     83 1 232 my ($self, $version) = @_;
487              
488 83 100 100     680 return if defined $self->{minimum} and $version < $self->{minimum};
489 73 100 100     358 return if defined $self->{maximum} and $version > $self->{maximum};
490             return if defined $self->{exclusions}
491 67 100 100     190 and grep { $version == $_ } @{ $self->{exclusions} };
  18         182  
  18         42  
492              
493 60         244 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 9 my ($self) = @_;
505             # XXX: This is a complete hack, but also entirely correct.
506 4 100       7 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   764 eval "use version ()"; ## no critic
  9     9   69  
  9         19  
  9         122  
520 9 50       5609 if ( my $err = $@ ) {
521 0 0       0 eval "use ExtUtils::MakeMaker::version" or die $err; ## no critic
522             }
523             }
524              
525 17     17   88 sub _new { bless { version => $_[1] } => $_[0] }
526              
527 9     9   114 sub accepts { return $_[0]{version} == $_[1] }
528              
529             sub _reject_requirements {
530 4     4   12 my ($self, $module, $error) = @_;
531 4         402 Carp::croak("illegal requirements for $module: $error")
532             }
533              
534             sub _clone {
535 6     6   54 (ref $_[0])->_new( version->new( $_[0]{version} ) )
536             }
537              
538             sub with_exact_version {
539 3     3   13 my ($self, $version, $module, $bad_version_hook) = @_;
540 3   50     27 $module //= 'module';
541 3         27 $version = $self->_version_object($version, $module, $bad_version_hook);
542              
543 3 100       72 return $self->_clone if $self->accepts($version);
544              
545 1         11 $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   16 my ($self, $minimum, $module, $bad_version_hook) = @_;
553 2   50     9 $module //= 'module';
554 2         9 $minimum = $self->_version_object( $minimum, $module, $bad_version_hook );
555              
556 1 50       7 return $self->_clone if $self->{version} >= $minimum;
557 1         7 $self->_reject_requirements(
558             $module,
559             "minimum $minimum exceeds exact specification $self->{version}",
560             );
561             }
562              
563             sub with_maximum {
564 3     3   15 my ($self, $maximum, $module, $bad_version_hook) = @_;
565 3   50     8 $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         11 $self->_reject_requirements(
570             $module,
571             "maximum $maximum below exact specification $self->{version}",
572             );
573             }
574              
575             sub with_exclusion {
576 3     3   10 my ($self, $exclusion, $module, $bad_version_hook) = @_;
577 3   50     8 $module //= 'module';
578 3         7 $exclusion = $self->_version_object( $exclusion, $module, $bad_version_hook );
579              
580 3 100       26 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   63 sub as_string { return "== $_[0]{version}" }
588              
589 1     1   12 sub as_struct { return [ [ '==', "$_[0]{version}" ] ] }
590              
591 2     2   9 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__