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   3861 use strict;
  5         9  
  5         158  
2 5     5   26 use warnings;
  5         8  
  5         237  
3             package Version::Requirements;
4             {
5             $Version::Requirements::VERSION = '0.101022';
6             }
7             # ABSTRACT: a set of version requirements for a CPAN dist
8              
9              
10 5     5   23 use Carp ();
  5         26  
  5         100  
11 5     5   24 use Scalar::Util ();
  5         13  
  5         98  
12 5     5   4679 use version 0.77 (); # the ->parse method
  5         19457  
  5         1418  
13              
14             # We silence this warning during core tests, because this is only in core
15             # because it has to be, and nobody wants to see this stupid warning.
16             # -- rjbs, 2012-01-20
17             Carp::cluck(
18             "Version::Requirements is deprecated; replace with CPAN::Meta::Requirements"
19             ) unless $ENV{PERL_CORE};
20              
21              
22             sub new {
23 31     31 1 13867 my ($class) = @_;
24 31         137 return bless {} => $class;
25             }
26              
27             sub _version_object {
28 105     105   151 my ($self, $version) = @_;
29              
30 105 100       911 $version = (! defined $version) ? version->parse(0)
    100          
31             : (! Scalar::Util::blessed($version)) ? version->parse($version)
32             : $version;
33              
34 105         219 return $version;
35             }
36              
37              
38             BEGIN {
39 5     5   14 for my $type (qw(minimum maximum exclusion exact_version)) {
40 20         40 my $method = "with_$type";
41 20 100       54 my $to_add = $type eq 'exact_version' ? $type : "add_$type";
42              
43             my $code = sub {
44 99     99   439 my ($self, $name, $version) = @_;
45              
46 99         224 $version = $self->_version_object( $version );
47              
48 99         240 $self->__modify_entry_for($name, $method, $version);
49              
50 89         243 return $self;
51 20         75 };
52            
53 5     5   39 no strict 'refs';
  5         18  
  5         212  
54 20         12896 *$to_add = $code;
55             }
56             }
57              
58              
59             sub add_requirements {
60 6     6 1 15 my ($self, $req) = @_;
61              
62 6         15 for my $module ($req->required_modules) {
63 14         29 my $modifiers = $req->__entry_for($module)->as_modifiers;
64 14         26 for my $modifier (@$modifiers) {
65 17         35 my ($method, @args) = @$modifier;
66 17         42 $self->$method($module => @args);
67             };
68             }
69              
70 6         17 return $self;
71             }
72              
73              
74             sub accepts_module {
75 6     6 1 22 my ($self, $module, $version) = @_;
76              
77 6         11 $version = $self->_version_object( $version );
78              
79 6 50       14 return 1 unless my $range = $self->__entry_for($module);
80 6         16 return $range->_accepts($version);
81             }
82              
83              
84             sub clear_requirement {
85 3     3 1 8 my ($self, $module) = @_;
86              
87 3 100       10 return $self unless $self->__entry_for($module);
88              
89 2 100       13 Carp::confess("can't clear requirements on finalized requirements")
90             if $self->is_finalized;
91              
92 1         4 delete $self->{requirements}{ $module };
93              
94 1         2 return $self;
95             }
96              
97              
98 31     31 1 33 sub required_modules { keys %{ $_[0]{requirements} } }
  31         132  
99              
100              
101             sub clone {
102 3     3 1 12 my ($self) = @_;
103 3         12 my $new = (ref $self)->new;
104              
105 3         10 return $new->add_requirements($self);
106             }
107              
108 126     126   328 sub __entry_for { $_[0]{requirements}{ $_[1] } }
109              
110             sub __modify_entry_for {
111 99     99   158 my ($self, $name, $method, $version) = @_;
112              
113 99         318 my $fin = $self->is_finalized;
114 99         202 my $old = $self->__entry_for($name);
115              
116 99 100 100     426 Carp::confess("can't add new requirements to finalized requirements")
117             if $fin and not $old;
118              
119 98   100     508 my $new = ($old || 'Version::Requirements::_Range::Range')
120             ->$method($version);
121              
122 90 100 100     820 Carp::confess("can't modify finalized requirements")
123             if $fin and $old->as_string ne $new->as_string;
124              
125 89         336 $self->{requirements}{ $name } = $new;
126             }
127              
128              
129             sub is_simple {
130 2     2 1 5 my ($self) = @_;
131 2         5 for my $module ($self->required_modules) {
132             # XXX: This is a complete hack, but also entirely correct.
133 4 100       10 return if $self->__entry_for($module)->as_string =~ /\s/;
134             }
135              
136 1         7 return 1;
137             }
138              
139              
140 101     101 1 727 sub is_finalized { $_[0]{finalized} }
141              
142              
143 1     1 1 5 sub finalize { $_[0]{finalized} = 1 }
144              
145              
146             sub as_string_hash {
147 23     23 1 83 my ($self) = @_;
148              
149 23         55 my %hash = map {; $_ => $self->{requirements}{$_}->as_string }
  47         134  
150             $self->required_modules;
151              
152 23         158 return \%hash;
153             }
154              
155              
156             my %methods_for_op = (
157             '==' => [ qw(exact_version) ],
158             '!=' => [ qw(add_exclusion) ],
159             '>=' => [ qw(add_minimum) ],
160             '<=' => [ qw(add_maximum) ],
161             '>' => [ qw(add_minimum add_exclusion) ],
162             '<' => [ qw(add_maximum add_exclusion) ],
163             );
164              
165             sub from_string_hash {
166 2     2 1 1227 my ($class, $hash) = @_;
167              
168 2         9 my $self = $class->new;
169              
170 2         10 for my $module (keys %$hash) {
171 4         38 my @parts = split qr{\s*,\s*}, $hash->{ $module };
172 4         11 for my $part (@parts) {
173 6         15 my ($op, $ver) = split /\s+/, $part, 2;
174              
175 6 100       15 if (! defined $ver) {
176 2         4 $self->add_minimum($module => $op);
177             } else {
178 4 100       216 Carp::confess("illegal requirement string: $hash->{ $module }")
179             unless my $methods = $methods_for_op{ $op };
180              
181 3         12 $self->$_($module => $ver) for @$methods;
182             }
183             }
184             }
185              
186 1         4 return $self;
187             }
188              
189             ##############################################################
190              
191             {
192             package
193             Version::Requirements::_Range::Exact;
194 16     16   75 sub _new { bless { version => $_[1] } => $_[0] }
195              
196 3     3   30 sub _accepts { return $_[0]{version} == $_[1] }
197              
198 6     6   39 sub as_string { return "== $_[0]{version}" }
199              
200 2     2   12 sub as_modifiers { return [ [ exact_version => $_[0]{version} ] ] }
201              
202             sub _clone {
203 8     8   67 (ref $_[0])->_new( version->new( $_[0]{version} ) )
204             }
205              
206             sub with_exact_version {
207 3     3   7 my ($self, $version) = @_;
208              
209 3 100       7 return $self->_clone if $self->_accepts($version);
210              
211 1         197 Carp::confess("illegal requirements: unequal exact version specified");
212             }
213              
214             sub with_minimum {
215 3     3   5 my ($self, $minimum) = @_;
216 3 100       23 return $self->_clone if $self->{version} >= $minimum;
217 1         130 Carp::confess("illegal requirements: minimum above exact specification");
218             }
219              
220             sub with_maximum {
221 3     3   6 my ($self, $maximum) = @_;
222 3 100       26 return $self->_clone if $self->{version} <= $maximum;
223 1         124 Carp::confess("illegal requirements: maximum below exact specification");
224             }
225              
226             sub with_exclusion {
227 3     3   7 my ($self, $exclusion) = @_;
228 3 100       23 return $self->_clone unless $exclusion == $self->{version};
229 1         126 Carp::confess("illegal requirements: excluded exact specification");
230             }
231             }
232              
233             ##############################################################
234              
235             {
236             package
237             Version::Requirements::_Range::Range;
238              
239 0 0   0   0 sub _self { ref($_[0]) ? $_[0] : (bless { } => $_[0]) }
240              
241             sub _clone {
242 86 100   86   339 return (bless { } => $_[0]) unless ref $_[0];
243              
244 36         80 my ($s) = @_;
245 9         69 my %guts = (
246             (exists $s->{minimum} ? (minimum => version->new($s->{minimum})) : ()),
247             (exists $s->{maximum} ? (maximum => version->new($s->{maximum})) : ()),
248              
249             (exists $s->{exclusions}
250 36 100       446 ? (exclusions => [ map { version->new($_) } @{ $s->{exclusions} } ])
  12 100       28  
    100          
251             : ()),
252             );
253              
254 36         145 bless \%guts => ref($s);
255             }
256              
257             sub as_modifiers {
258 12     12   14 my ($self) = @_;
259 12         11 my @mods;
260 12 100       49 push @mods, [ add_minimum => $self->{minimum} ] if exists $self->{minimum};
261 12 100       41 push @mods, [ add_maximum => $self->{maximum} ] if exists $self->{maximum};
262 12 100       16 push @mods, map {; [ add_exclusion => $_ ] } @{$self->{exclusions} || []};
  1         3  
  12         49  
263 12         28 return \@mods;
264             }
265              
266             sub as_string {
267 49     49   59 my ($self) = @_;
268              
269 49 50       130 return 0 if ! keys %$self;
270              
271 49 100 100     601 return "$self->{minimum}" if (keys %$self) == 1 and exists $self->{minimum};
272              
273 15 100       18 my @exclusions = @{ $self->{exclusions} || [] };
  15         58  
274              
275 15         21 my @parts;
276              
277 15         55 for my $pair (
278             [ qw( >= > minimum ) ],
279             [ qw( <= < maximum ) ],
280             ) {
281 30         46 my ($op, $e_op, $k) = @$pair;
282 30 100       70 if (exists $self->{$k}) {
283 24         37 my @new_exclusions = grep { $_ != $self->{ $k } } @exclusions;
  12         66  
284 24 100       48 if (@new_exclusions == @exclusions) {
285 23         133 push @parts, "$op $self->{ $k }";
286             } else {
287 1         7 push @parts, "$e_op $self->{ $k }";
288 1         4 @exclusions = @new_exclusions;
289             }
290             }
291             }
292              
293 15         35 push @parts, map {; "!= $_" } @exclusions;
  8         36  
294              
295 15         87 return join q{, }, @parts;
296             }
297              
298             sub with_exact_version {
299 8     8   13 my ($self, $version) = @_;
300 8         19 $self = $self->_clone;
301              
302 8 100       23 Carp::confess("illegal requirements: exact specification outside of range")
303             unless $self->_accepts($version);
304              
305 7         23 return Version::Requirements::_Range::Exact->_new($version);
306             }
307              
308             sub _simplify {
309 78     78   101 my ($self) = @_;
310              
311 78 100 100     352 if (defined $self->{minimum} and defined $self->{maximum}) {
312 19 100       109 if ($self->{minimum} == $self->{maximum}) {
313 1         221 Carp::confess("illegal requirements: excluded all values")
314 2 100       4 if grep { $_ == $self->{minimum} } @{ $self->{exclusions} || [] };
  2 100       13  
315              
316 1         8 return Version::Requirements::_Range::Exact->_new($self->{minimum})
317             }
318              
319 17 100       336 Carp::confess("illegal requirements: minimum exceeds maximum")
320             if $self->{minimum} > $self->{maximum};
321             }
322              
323             # eliminate irrelevant exclusions
324 74 100       162 if ($self->{exclusions}) {
325 21         25 my %seen;
326 21 100 100     78 @{ $self->{exclusions} } = grep {
  22   100     359  
      66        
327 21         78 (! defined $self->{minimum} or $_ >= $self->{minimum})
328             and
329             (! defined $self->{maximum} or $_ <= $self->{maximum})
330             and
331             ! $seen{$_}++
332 21         24 } @{ $self->{exclusions} };
333             }
334              
335 74         165 return $self;
336             }
337              
338             sub with_minimum {
339 47     47   68 my ($self, $minimum) = @_;
340 47         105 $self = $self->_clone;
341              
342 47 100       139 if (defined (my $old_min = $self->{minimum})) {
343 10         39 $self->{minimum} = (sort { $b cmp $a } ($minimum, $old_min))[0];
  10         74  
344             } else {
345 37         100 $self->{minimum} = $minimum;
346             }
347              
348 47         98 return $self->_simplify;
349             }
350              
351             sub with_maximum {
352 17     17   24 my ($self, $maximum) = @_;
353 17         36 $self = $self->_clone;
354              
355 17 100       60 if (defined (my $old_max = $self->{maximum})) {
356 1         5 $self->{maximum} = (sort { $a cmp $b } ($maximum, $old_max))[0];
  1         8  
357             } else {
358 16         33 $self->{maximum} = $maximum;
359             }
360              
361 17         42 return $self->_simplify;
362             }
363              
364             sub with_exclusion {
365 14     14   71 my ($self, $exclusion) = @_;
366 14         30 $self = $self->_clone;
367              
368 14   100     19 push @{ $self->{exclusions} ||= [] }, $exclusion;
  14         75  
369              
370 14         33 return $self->_simplify;
371             }
372              
373             sub _accepts {
374 14     14   19 my ($self, $version) = @_;
375              
376 14 100 100     213 return if defined $self->{minimum} and $version < $self->{minimum};
377 12 100 100     54 return if defined $self->{maximum} and $version > $self->{maximum};
378 2         18 return if defined $self->{exclusions}
379 11 100 100     34 and grep { $version == $_ } @{ $self->{exclusions} };
  2         4  
380              
381 10         36 return 1;
382             }
383             }
384              
385             1;
386              
387             __END__