File Coverage

blib/lib/CPAN/Meta/Requirements/Range.pm
Criterion Covered Total %
statement 206 214 96.2
branch 88 104 84.6
condition 51 62 82.2
subroutine 37 39 94.8
pod 8 8 100.0
total 390 427 91.3


line stmt bran cond sub pod time code
1 11     11   163 use v5.10;
  11         46  
2 11     11   59 use strict;
  11         22  
  11         293  
3 11     11   62 use warnings;
  11         20  
  11         874  
4             package CPAN::Meta::Requirements::Range;
5             # ABSTRACT: a set of version requirements for a CPAN dist
6              
7             our $VERSION = '2.145';
8              
9 11     11   79 use Carp ();
  11         24  
  11         347  
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 11     11   72 use Carp ();
  11         28  
  11         971  
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 11     11   975 eval "use version ()"; ## no critic
  11     11   4721  
  11         21558  
  11         179  
47 11 50       34884 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 1367     1367   2677 my $value = shift;
55 1367         2419 my $tvalue = '';
56 1367         7514 require B;
57 1367         5614 my $sv = B::svref_2object(\$value);
58 1367 100       3938 my $magic = ref($sv) eq 'B::PVMG' ? $sv->MAGIC : undef;
59 1367         3445 while ( $magic ) {
60 1 50       6 if ( $magic->TYPE eq 'V' ) {
61 1         4 $tvalue = $magic->PTR;
62 1         16 $tvalue =~ s/^v?(.+)$/v$1/;
63 1         3 last;
64             }
65             else {
66 0         0 $magic = $magic->MOREMAGIC;
67             }
68             }
69 1367         3913 return $tvalue;
70             }
71              
72             # Perl 5.10.0 didn't have "is_qv" in version.pm
73 605     605   2492 *_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 1739     1739   3961 my ($self, $version, $module, $bad_version_hook) = @_;
85              
86 1739         3184 my ($vobj, $err);
87              
88 1739 100 100     10381 if (not defined $version or (!ref($version) && $version eq '0')) {
    100 66        
      33        
      66        
89 1131         3585 return $V0;
90             }
91             elsif ( ref($version) eq 'version' || ( ref($version) && _isa_version($version) ) ) {
92 271         515 $vobj = $version;
93             }
94             else {
95             # hack around version::vpp not handling <3 character vstring literals
96 337 50 33     1684 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 337         699 eval {
101 337     0   3454 local $SIG{__WARN__} = sub { die "Invalid version: $_[0]" };
  0         0  
102             # avoid specific segfault on some older version.pm versions
103 337 100       1076 die "Invalid version: $version" if $version eq 'version';
104 336         4148 $vobj = version->new($version);
105             };
106 337 100       1135 if ( my $err = $@ ) {
107 6 100       27 $vobj = eval { $bad_version_hook->($version, $module) }
  4         17  
108             if ref $bad_version_hook eq 'CODE';
109 6 100       231 unless (eval { $vobj->isa("version") }) {
  6         54  
110 3         26 $err =~ s{ at .* line \d+.*$}{};
111 3         34 die "Can't convert '$version': $err";
112             }
113             }
114             }
115              
116             # ensure no leading '.'
117 605 50       3765 if ( $vobj =~ m{\A\.} ) {
118 0         0 $vobj = version->new("0$vobj");
119             }
120              
121             # ensure normal v-string form
122 605 100       1688 if ( _is_qv($vobj) ) {
123 22         292 $vobj = version->new($vobj->normal);
124             }
125              
126 605         1675 return $vobj;
127             }
128              
129             #pod =method with_string_requirement
130             #pod
131             #pod $req->with_string_requirement('>= 1.208, <= 2.206');
132             #pod $req->with_string_requirement(v1.208);
133             #pod
134             #pod This method parses the passed in string and adds the appropriate requirement.
135             #pod A version can be a Perl "v-string". It understands version ranges as described
136             #pod in the L. For example:
137             #pod
138             #pod =over 4
139             #pod
140             #pod =item 1.3
141             #pod
142             #pod =item >= 1.3
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, != 1.5, <= 2.0
155             #pod
156             #pod A version number without an operator is equivalent to specifying a minimum
157             #pod (C=>). Extra whitespace is allowed.
158             #pod
159             #pod =back
160             #pod
161             #pod =cut
162              
163             my %methods_for_op = (
164             '==' => [ qw(with_exact_version) ],
165             '!=' => [ qw(with_exclusion) ],
166             '>=' => [ qw(with_minimum) ],
167             '<=' => [ qw(with_maximum) ],
168             '>' => [ qw(with_minimum with_exclusion) ],
169             '<' => [ qw(with_maximum with_exclusion) ],
170             );
171              
172             sub with_string_requirement {
173 1367     1367   25240 my ($self, $req, $module, $bad_version_hook) = @_;
174 1367   100     3226 $module //= 'module';
175              
176 1367 100 100     5141 unless ( defined $req && length $req ) {
177 4         8 $req = 0;
178 4         686 Carp::carp("Undefined requirement for $module treated as '0'");
179             }
180              
181 1367         2944 my $magic = _find_magic_vstring( $req );
182 1367 100       3447 if (length $magic) {
183 1         6 return $self->with_minimum($magic, $module, $bad_version_hook);
184             }
185              
186 1366         10410 my @parts = split qr{\s*,\s*}, $req;
187              
188 1366         4013 for my $part (@parts) {
189 1378         5609 my ($op, $ver) = $part =~ m{\A\s*(==|>=|>|<=|<|!=)\s*(.*)\z};
190              
191 1378 100       3148 if (! defined $op) {
192 1351         3810 $self = $self->with_minimum($part, $module, $bad_version_hook);
193             } else {
194             Carp::croak("illegal requirement string: $req")
195 27 50       90 unless my $methods = $methods_for_op{ $op };
196              
197 27         121 $self = $self->$_($ver, $module, $bad_version_hook) for @$methods;
198             }
199             }
200              
201 1364         4387 return $self;
202             }
203              
204             #pod =method with_range
205             #pod
206             #pod $range->with_range($other_range)
207             #pod
208             #pod This creates a new range object that is a merge two others.
209             #pod
210             #pod =cut
211              
212             sub with_range {
213 266     266   605 my ($self, $other, $module, $bad_version_hook) = @_;
214 266         646 for my $modifier($other->_as_modifiers) {
215 269         603 my ($method, $arg) = @$modifier;
216 269         761 $self = $self->$method($arg, $module, $bad_version_hook);
217             }
218 266         934 return $self;
219             }
220              
221             package CPAN::Meta::Requirements::Range;
222              
223             our @ISA = 'CPAN::Meta::Requirements::Range::_Base';
224              
225             sub _clone {
226 1728 100   1728   6038 return (bless { } => $_[0]) unless ref $_[0];
227              
228 131         396 my ($s) = @_;
229             my %guts = (
230             (exists $s->{minimum} ? (minimum => version->new($s->{minimum})) : ()),
231             (exists $s->{maximum} ? (maximum => version->new($s->{maximum})) : ()),
232              
233             (exists $s->{exclusions}
234 131 100       1333 ? (exclusions => [ map { version->new($_) } @{ $s->{exclusions} } ])
  9 100       87  
  12 100       59  
235             : ()),
236             );
237              
238 131         526 bless \%guts => ref($s);
239             }
240              
241             #pod =method with_exact_version
242             #pod
243             #pod $range->with_exact_version( $version );
244             #pod
245             #pod This sets the version required to I the given
246             #pod version. No other version would be considered acceptable.
247             #pod
248             #pod This method returns the version range object.
249             #pod
250             #pod =cut
251              
252             sub with_exact_version {
253 11     11 1 38 my ($self, $version, $module, $bad_version_hook) = @_;
254 11   50     62 $module //= 'module';
255 11         38 $self = $self->_clone;
256 11         110 $version = $self->_version_object($version, $module, $bad_version_hook);
257              
258 11 100       75 unless ($self->accepts($version)) {
259 1         10 $self->_reject_requirements(
260             $module,
261             "exact specification $version outside of range " . $self->as_string
262             );
263             }
264              
265 10         126 return CPAN::Meta::Requirements::Range::_Exact->_new($version);
266             }
267              
268             sub _simplify {
269 1715     1715   3820 my ($self, $module) = @_;
270              
271 1715 100 100     7118 if (defined $self->{minimum} and defined $self->{maximum}) {
272 31 100       164 if ($self->{minimum} == $self->{maximum}) {
273 2 100       5 if (grep { $_ == $self->{minimum} } @{ $self->{exclusions} || [] }) {
  1 100       8  
  2         14  
274 1         12 $self->_reject_requirements(
275             $module,
276             "minimum and maximum are both $self->{minimum}, which is excluded",
277             );
278             }
279              
280 1         15 return CPAN::Meta::Requirements::Range::_Exact->_new($self->{minimum});
281             }
282              
283 29 100       125 if ($self->{minimum} > $self->{maximum}) {
284 2         22 $self->_reject_requirements(
285             $module,
286             "minimum $self->{minimum} exceeds maximum $self->{maximum}",
287             );
288             }
289             }
290              
291             # eliminate irrelevant exclusions
292 1711 100       4191 if ($self->{exclusions}) {
293 32         60 my %seen;
294 32         129 @{ $self->{exclusions} } = grep {
295             (! defined $self->{minimum} or $_ >= $self->{minimum})
296             and
297             (! defined $self->{maximum} or $_ <= $self->{maximum})
298             and
299 33 100 100     574 ! $seen{$_}++
      100        
      100        
300 32         55 } @{ $self->{exclusions} };
  32         115  
301             }
302              
303 1711         5743 return $self;
304             }
305              
306             #pod =method with_minimum
307             #pod
308             #pod $range->with_minimum( $version );
309             #pod
310             #pod This adds a new minimum version requirement. If the new requirement is
311             #pod redundant to the existing specification, this has no effect.
312             #pod
313             #pod Minimum requirements are inclusive. C<$version> is required, along with any
314             #pod greater version number.
315             #pod
316             #pod This method returns the version range object.
317             #pod
318             #pod =cut
319              
320             sub with_minimum {
321 1666     1666 1 12351 my ($self, $minimum, $module, $bad_version_hook) = @_;
322 1666   100     3822 $module //= 'module';
323 1666         4226 $self = $self->_clone;
324 1666         4522 $minimum = $self->_version_object( $minimum, $module, $bad_version_hook );
325              
326 1664 100       4632 if (defined (my $old_min = $self->{minimum})) {
327 91         369 $self->{minimum} = (sort { $b cmp $a } ($minimum, $old_min))[0];
  91         487  
328             } else {
329 1573         4366 $self->{minimum} = $minimum;
330             }
331              
332 1664         4053 return $self->_simplify($module);
333             }
334              
335             #pod =method with_maximum
336             #pod
337             #pod $range->with_maximum( $version );
338             #pod
339             #pod This adds a new maximum version requirement. If the new requirement is
340             #pod redundant to the existing specification, this has no effect.
341             #pod
342             #pod Maximum requirements are inclusive. No version strictly greater than the given
343             #pod version is allowed.
344             #pod
345             #pod This method returns the version range object.
346             #pod
347             #pod =cut
348              
349             sub with_maximum {
350 26     26 1 507 my ($self, $maximum, $module, $bad_version_hook) = @_;
351 26   100     91 $module //= 'module';
352 26         140 $self = $self->_clone;
353 26         91 $maximum = $self->_version_object( $maximum, $module, $bad_version_hook );
354              
355 26 100       87 if (defined (my $old_max = $self->{maximum})) {
356 1         8 $self->{maximum} = (sort { $a cmp $b } ($maximum, $old_max))[0];
  1         11  
357             } else {
358 25         102 $self->{maximum} = $maximum;
359             }
360              
361 26         107 return $self->_simplify($module);
362             }
363              
364             #pod =method with_exclusion
365             #pod
366             #pod $range->with_exclusion( $version );
367             #pod
368             #pod This adds a new excluded version. For example, you might use these three
369             #pod method calls:
370             #pod
371             #pod $range->with_minimum( '1.00' );
372             #pod $range->with_maximum( '1.82' );
373             #pod
374             #pod $range->with_exclusion( '1.75' );
375             #pod
376             #pod Any version between 1.00 and 1.82 inclusive would be acceptable, except for
377             #pod 1.75.
378             #pod
379             #pod This method returns the requirements object.
380             #pod
381             #pod =cut
382              
383             sub with_exclusion {
384 25     25 1 526 my ($self, $exclusion, $module, $bad_version_hook) = @_;
385 25   100     104 $module //= 'module';
386 25         59 $self = $self->_clone;
387 25         73 $exclusion = $self->_version_object( $exclusion, $module, $bad_version_hook );
388              
389 25   100     50 push @{ $self->{exclusions} ||= [] }, $exclusion;
  25         153  
390              
391 25         70 return $self->_simplify($module);
392             }
393              
394             sub _as_modifiers {
395 264     264   532 my ($self) = @_;
396 264         444 my @mods;
397 264 100       1053 push @mods, [ with_minimum => $self->{minimum} ] if exists $self->{minimum};
398 264 100       677 push @mods, [ with_maximum => $self->{maximum} ] if exists $self->{maximum};
399 264 100       441 push @mods, map {; [ with_exclusion => $_ ] } @{$self->{exclusions} || []};
  1         5  
  264         1131  
400 264         702 return @mods;
401             }
402              
403             #pod =method as_struct
404             #pod
405             #pod $range->as_struct( $module );
406             #pod
407             #pod This returns a data structure containing the version requirements. This should
408             #pod not be used for version checks (see L instead).
409             #pod
410             #pod =cut
411              
412             sub as_struct {
413 1318     1318 1 2368 my ($self) = @_;
414              
415 1318 50       3872 return 0 if ! keys %$self;
416              
417 1318 100       2118 my @exclusions = @{ $self->{exclusions} || [] };
  1318         4650  
418              
419 1318         2269 my @parts;
420              
421 1318         4100 for my $tuple (
422             [ qw( >= > minimum ) ],
423             [ qw( <= < maximum ) ],
424             ) {
425 2636         5623 my ($op, $e_op, $k) = @$tuple;
426 2636 100       6979 if (exists $self->{$k}) {
427 1332         2321 my @new_exclusions = grep { $_ != $self->{ $k } } @exclusions;
  23         182  
428 1332 100       2693 if (@new_exclusions == @exclusions) {
429 1329         7779 push @parts, [ $op, "$self->{ $k }" ];
430             } else {
431 3         17 push @parts, [ $e_op, "$self->{ $k }" ];
432 3         11 @exclusions = @new_exclusions;
433             }
434             }
435             }
436              
437 1318         3102 push @parts, map {; [ "!=", "$_" ] } @exclusions;
  12         88  
438              
439 1318         3263 return \@parts;
440             }
441              
442             #pod =method as_string
443             #pod
444             #pod $range->as_string;
445             #pod
446             #pod This returns a string containing the version requirements in the format
447             #pod described in L. This should only be used for informational
448             #pod purposes such as error messages and should not be interpreted or used for
449             #pod comparison (see L instead).
450             #pod
451             #pod =cut
452              
453             sub as_string {
454 1317     1317 1 2589 my ($self) = @_;
455              
456 1317         2028 my @parts = @{ $self->as_struct };
  1317         2728  
457              
458 1317 100 100     8816 return $parts[0][1] if @parts == 1 and $parts[0][0] eq '>=';
459 20 100       75 @parts = grep { $_->[0] ne '>=' || $_->[1] ne '0' } @parts;
  44         305  
460              
461 20         75 return join q{, }, map {; join q{ }, @$_ } @parts;
  43         261  
462             }
463              
464             sub _reject_requirements {
465 4     4   11 my ($self, $module, $error) = @_;
466 4         982 Carp::croak("illegal requirements for $module: $error")
467             }
468              
469             #pod =method accepts
470             #pod
471             #pod my $bool = $range->accepts($version);
472             #pod
473             #pod Given a version, this method returns true if the version specification
474             #pod accepts the provided version. In other words, given:
475             #pod
476             #pod '>= 1.00, < 2.00'
477             #pod
478             #pod We will accept 1.00 and 1.75 but not 0.50 or 2.00.
479             #pod
480             #pod =cut
481              
482             sub accepts {
483 83     83 1 259 my ($self, $version) = @_;
484              
485 83 100 100     673 return if defined $self->{minimum} and $version < $self->{minimum};
486 73 100 100     388 return if defined $self->{maximum} and $version > $self->{maximum};
487             return if defined $self->{exclusions}
488 67 100 100     244 and grep { $version == $_ } @{ $self->{exclusions} };
  18         164  
  18         35  
489              
490 60         253 return 1;
491             }
492              
493             #pod =method is_simple
494             #pod
495             #pod This method returns true if and only if the range is an inclusive minimum
496             #pod -- that is, if their string expression is just the version number.
497             #pod
498             #pod =cut
499              
500             sub is_simple {
501 4     4 1 10 my ($self) = @_;
502             # XXX: This is a complete hack, but also entirely correct.
503 4 100       12 return if $self->as_string =~ /\s/;
504              
505 3         14 return 1;
506             }
507              
508             package
509             CPAN::Meta::Requirements::Range::_Exact;
510              
511             our @ISA = 'CPAN::Meta::Requirements::Range::_Base';
512              
513             our $VERSION = '2.141';
514              
515             BEGIN {
516 11     11   1183 eval "use version ()"; ## no critic
  11     11   91  
  11         26  
  11         184  
517 11 50       7623 if ( my $err = $@ ) {
518 0 0       0 eval "use ExtUtils::MakeMaker::version" or die $err; ## no critic
519             }
520             }
521              
522 17     17   128 sub _new { bless { version => $_[1] } => $_[0] }
523              
524 9     9   143 sub accepts { return $_[0]{version} == $_[1] }
525              
526             sub _reject_requirements {
527 4     4   13 my ($self, $module, $error) = @_;
528 4         1208 Carp::croak("illegal requirements for $module: $error")
529             }
530              
531             sub _clone {
532 6     6   50 (ref $_[0])->_new( version->new( $_[0]{version} ) )
533             }
534              
535             sub with_exact_version {
536 3     3   11 my ($self, $version, $module, $bad_version_hook) = @_;
537 3   50     13 $module //= 'module';
538 3         15 $version = $self->_version_object($version, $module, $bad_version_hook);
539              
540 3 100       16 return $self->_clone if $self->accepts($version);
541              
542 1         14 $self->_reject_requirements(
543             $module,
544             "can't be exactly $version when exact requirement is already $self->{version}",
545             );
546             }
547              
548             sub with_minimum {
549 2     2   9 my ($self, $minimum, $module, $bad_version_hook) = @_;
550 2   50     9 $module //= 'module';
551 2         12 $minimum = $self->_version_object( $minimum, $module, $bad_version_hook );
552              
553 1 50       8 return $self->_clone if $self->{version} >= $minimum;
554 1         9 $self->_reject_requirements(
555             $module,
556             "minimum $minimum exceeds exact specification $self->{version}",
557             );
558             }
559              
560             sub with_maximum {
561 3     3   11 my ($self, $maximum, $module, $bad_version_hook) = @_;
562 3   50     11 $module //= 'module';
563 3         14 $maximum = $self->_version_object( $maximum, $module, $bad_version_hook );
564              
565 3 100       28 return $self->_clone if $self->{version} <= $maximum;
566 1         13 $self->_reject_requirements(
567             $module,
568             "maximum $maximum below exact specification $self->{version}",
569             );
570             }
571              
572             sub with_exclusion {
573 3     3   12 my ($self, $exclusion, $module, $bad_version_hook) = @_;
574 3   50     12 $module //= 'module';
575 3         12 $exclusion = $self->_version_object( $exclusion, $module, $bad_version_hook );
576              
577 3 100       24 return $self->_clone unless $exclusion == $self->{version};
578 1         9 $self->_reject_requirements(
579             $module,
580             "tried to exclude $exclusion, which is already exactly specified",
581             );
582             }
583              
584 7     7   71 sub as_string { return "== $_[0]{version}" }
585              
586 1     1   20 sub as_struct { return [ [ '==', "$_[0]{version}" ] ] }
587              
588 2     2   11 sub _as_modifiers { return [ with_exact_version => $_[0]{version} ] }
589              
590              
591             1;
592              
593             # vim: ts=2 sts=2 sw=2 et:
594              
595             __END__