File Coverage

blib/lib/Debian/Dpkg/Version.pm
Criterion Covered Total %
statement 126 128 98.4
branch 71 80 88.7
condition 47 59 79.6
subroutine 23 23 100.0
pod 13 15 86.6
total 280 305 91.8


line stmt bran cond sub pod time code
1             # Copyright © Colin Watson
2             # Copyright © Ian Jackson
3             # Copyright © 2007 Don Armstrong .
4             # Copyright © 2009 Raphaël Hertzog
5             #
6             # This program is free software; you can redistribute it and/or modify
7             # it under the terms of the GNU General Public License as published by
8             # the Free Software Foundation; either version 2 of the License, or
9             # (at your option) any later version.
10             #
11             # This program is distributed in the hope that it will be useful,
12             # but WITHOUT ANY WARRANTY; without even the implied warranty of
13             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14             # GNU General Public License for more details.
15             #
16             # You should have received a copy of the GNU General Public License
17             # along with this program. If not, see .
18              
19             package Debian::Dpkg::Version;
20              
21 1     1   1298 use strict;
  1         2  
  1         38  
22 1     1   5 use warnings;
  1         2  
  1         43  
23              
24             our $VERSION = '1.15.5.6.1';
25              
26 1     1   20 use Carp;
  1         2  
  1         77  
27              
28 1     1   4 use base qw(Exporter);
  1         2  
  1         134  
29             our @EXPORT = qw(version_compare version_compare_relation
30             version_normalize_relation version_compare_string
31             version_compare_part version_split_digits version_check
32             REL_LT REL_LE REL_EQ REL_GE REL_GT);
33              
34             use constant {
35 1         279 REL_LT => '<<',
36             REL_LE => '<=',
37             REL_EQ => '=',
38             REL_GE => '>=',
39             REL_GT => '>>',
40 1     1   5 };
  1         2  
41              
42             use overload
43             '<=>' => \&comparison,
44             'cmp' => \&comparison,
45             '""' => \&as_string,
46 2236     2236   5253 'bool' => sub { return $_[0]->is_valid(); },
47 1     1   7 'fallback' => 1;
  1         2  
  1         14  
48              
49             =head1 NAME
50              
51             Debian::Dpkg::Version - handling and comparing dpkg-style version numbers
52              
53             =head1 DESCRIPTION
54              
55             The Debian::Dpkg::Version module provides pure-Perl routines to compare
56             dpkg-style version numbers (as used in Debian packages) and also
57             an object oriented interface overriding perl operators
58             to do the right thing when you compare Debian::Dpkg::Version object between
59             them.
60              
61             =head1 OBJECT INTERFACE
62              
63             =over 4
64              
65             =item my $v = Debian::Dpkg::Version->new($version, %opts)
66              
67             Create a new Debian::Dpkg::Version object corresponding to the version indicated in
68             the string (scalar) $version. By default it will accepts any string
69             and consider it as a valid version. If you pass the option "check => 1",
70             it will return undef if the version is invalid (see version_check for
71             details).
72              
73             You can always call $v->is_valid() later on to verify that the version is
74             valid.
75              
76             =cut
77              
78             sub new {
79 1210     1210 1 30098 my ($this, $ver, %opts) = @_;
80 1210   33     4343 my $class = ref($this) || $this;
81 1210 50       2459 $ver = "$ver" if ref($ver); # Try to stringify objects
82              
83 1210 100       3963 if ($opts{'check'}) {
84 1204 50       2083 return undef unless version_check($ver);
85             }
86              
87 1210         2361 my $self = {};
88 1210 100       2994 if ($ver =~ /^(\d*):(.+)$/) {
89 126         425 $self->{'epoch'} = $1;
90 126         245 $ver = $2;
91             } else {
92 1084         2608 $self->{'epoch'} = 0;
93 1084         1930 $self->{'no_epoch'} = 1;
94             }
95 1210 100       3635 if ($ver =~ /(.+)-(.*)$/) {
96 520         1905 $self->{'version'} = $1;
97 520         1201 $self->{'revision'} = $2;
98             } else {
99 690         1231 $self->{'version'} = $ver;
100 690         1340 $self->{'revision'} = 0;
101 690         1241 $self->{'no_revision'} = 1;
102             }
103              
104 1210         6145 return bless $self, $class;
105             }
106              
107             =item $v->is_valid()
108              
109             Returns true if the version is valid, false otherwise.
110              
111             =cut
112              
113             sub is_valid {
114 2239     2239 1 3120 my ($self) = @_;
115 2239         4452 return scalar version_check($self);
116             }
117              
118             =item $v->epoch(), $v->version(), $v->revision()
119              
120             Returns the corresponding part of the full version string.
121              
122             =cut
123              
124             sub epoch {
125 2242     2242 1 2489 my $self = shift;
126 2242         16056 return $self->{'epoch'};
127             }
128              
129             sub version {
130 2086     2086 1 2130 my $self = shift;
131 2086         9224 return $self->{'version'};
132             }
133              
134             sub revision {
135 578     578 1 627 my $self = shift;
136 578         1458 return $self->{'revision'};
137             }
138              
139             =item $v1 <=> $v2, $v1 < $v2, $v1 <= $v2, $v1 > $v2, $v1 >= $v2
140              
141             Numerical comparison of various versions numbers. One of the two operands
142             needs to be a Debian::Dpkg::Version, the other one can be anything provided that
143             its string representation is a version number.
144              
145             =cut
146              
147             sub comparison {
148 1121     1121 0 2006 my ($a, $b, $inverted) = @_;
149 1121 100 66     5964 if (not ref($b) or not $b->isa("Debian::Dpkg::Version")) {
150 3         9 $b = Debian::Dpkg::Version->new($b);
151             }
152 1121 50       2185 ($a, $b) = ($b, $a) if $inverted;
153 1121         2073 my $r = $a->epoch() <=> $b->epoch();
154 1121 100       2756 return $r if $r;
155 1043         1881 $r = version_compare_part($a->version(), $b->version());
156 1043 100       6206 return $r if $r;
157 289         658 return version_compare_part($a->revision(), $b->revision());
158             }
159              
160             =item "$v"
161             =item $v->as_string()
162              
163             Returns the string representation of the version number.
164              
165             =cut
166             sub as_string {
167 2326     2326 1 3017 my ($self) = @_;
168 2326         3003 my $str = "";
169 2326 100       5893 $str .= $self->{epoch} . ":" unless $self->{no_epoch};
170 2326         3612 $str .= $self->{version};
171 2326 100       9012 $str .= "-" . $self->{revision} unless $self->{no_revision};
172 2326         6213 return $str;
173             }
174              
175             =back
176              
177             =head1 FUNCTIONS
178              
179             All the functions are exported by default.
180              
181             =over 4
182              
183             =item version_compare($a, $b)
184              
185             Returns -1 is $a is smaller than $b, 0 if they are equal and 1 if $a
186             is bigger than $b.
187              
188             If $a or $b are not valid version numbers, it dies with an error.
189              
190             =cut
191              
192             sub version_compare($$) {
193 559     559 1 882 my ($a, $b) = @_;
194 559   33     1850 my $va = Debian::Dpkg::Version->new($a, check => 1) || error(_g("%s is not a valid version"), "$a");
195 559   33     1473 my $vb = Debian::Dpkg::Version->new($b, check => 1) || error(_g("%s is not a valid version"), "$b");
196 559         1155 return $va <=> $vb;
197             }
198              
199             =item version_compare_relation($a, $rel, $b)
200              
201             Returns the result (0 or 1) of the given comparison operation. This
202             function is implemented on top of version_compare().
203              
204             Allowed values for $rel are the exported constants REL_GT, REL_GE,
205             REL_EQ, REL_LE, REL_LT. Use version_normalize_relation() if you
206             have an input string containing the operator.
207              
208             =cut
209              
210             sub version_compare_relation($$$) {
211 516     516 1 4909 my ($a, $op, $b) = @_;
212 516         933 my $res = version_compare($a, $b);
213              
214 516 100       2975 if ($op eq REL_GT) {
    100          
    100          
    100          
    50          
215 86         495 return $res > 0;
216             } elsif ($op eq REL_GE) {
217 129         819 return $res >= 0;
218             } elsif ($op eq REL_EQ) {
219 86         510 return $res == 0;
220             } elsif ($op eq REL_LE) {
221 129         744 return $res <= 0;
222             } elsif ($op eq REL_LT) {
223 86         545 return $res < 0;
224             } else {
225 0         0 internerr("unsupported relation for version_compare_relation(): '$op'");
226             }
227             }
228              
229             =item my $rel = version_normalize_relation($rel_string)
230              
231             Returns the normalized constant of the relation $rel (a value
232             among REL_GT, REL_GE, REL_EQ, REL_LE and REL_LT). Supported
233             relations names in input are: "gt", "ge", "eq", "le", "lt", ">>", ">=",
234             "=", "<=", "<<". ">" and "<" are also supported but should not be used as
235             they are obsolete aliases of ">=" and "<=".
236              
237             =cut
238              
239             sub version_normalize_relation($) {
240 516     516 1 217849 my $op = shift;
241              
242 516 100 100     3193 warning("relation %s is deprecated: use %s or %s",
243             $op, "$op$op", "$op=") if ($op eq '>' or $op eq '<');
244              
245 516 100 100     6362 if ($op eq '>>' or $op eq 'gt') {
    100 100        
    100 100        
    100 100        
    50 100        
      100        
      66        
246 86         255 return REL_GT;
247             } elsif ($op eq '>=' or $op eq 'ge' or $op eq '>') {
248 129         350 return REL_GE;
249             } elsif ($op eq '=' or $op eq 'eq') {
250 86         268 return REL_EQ;
251             } elsif ($op eq '<=' or $op eq 'le' or $op eq '<') {
252 129         448 return REL_LE;
253             } elsif ($op eq '<<' or $op eq 'lt') {
254 86         251 return REL_LT;
255             } else {
256 0         0 internerr("bad relation '$op'");
257             }
258             }
259              
260             =item version_compare_string($a, $b)
261              
262             String comparison function used for comparing non-numerical parts of version
263             numbers. Returns -1 is $a is smaller than $b, 0 if they are equal and 1 if $a
264             is bigger than $b.
265              
266             The "~" character always sort lower than anything else. Digits sort lower
267             than non-digits. Among remaining characters alphabetic characters (A-Za-z)
268             sort lower than the other ones. Within each range, the ASCII decimal value
269             of the character is used to sort between characters.
270              
271             =cut
272              
273             sub version_compare_string($$) {
274             sub order {
275 7810     7810 0 9867 my ($x) = @_;
276 7810 100       29788 if ($x eq '~') {
    100          
    100          
277 338         845 return -1;
278             } elsif ($x =~ /^\d$/) {
279 234         3770 return $x * 1 + 1;
280             } elsif ($x =~ /^[A-Za-z]$/) {
281 5280         13814 return ord($x);
282             } else {
283 1958         11183 return ord($x) + 256;
284             }
285             }
286 1486     1486 1 4705 my @a = map(order($_), split(//, shift));
287 1486         11667 my @b = map(order($_), split(//, shift));
288 1486         2326 while (1) {
289 4559         7023 my ($a, $b) = (shift @a, shift @b);
290 4559 100 66     11509 return 0 if not defined($a) and not defined($b);
291 3541   100     19342 $a ||= 0; # Default order for "no character"
292 3541   100     6031 $b ||= 0;
293 3541 100       6986 return 1 if $a > $b;
294 3281 100       7283 return -1 if $a < $b;
295             }
296             }
297              
298             =item version_compare_part($a, $b)
299              
300             Compare two corresponding sub-parts of a version number (either upstream
301             version or debian revision).
302              
303             Each parameter is split by version_split_digits() and resulting items
304             are compared together.in digits and non-digits items that are compared
305             together. As soon as a difference happens, it returns -1 if $a is smaller
306             than $b, 0 if they are equal and 1 if $a is bigger than $b.
307              
308             =cut
309              
310             sub version_compare_part($$) {
311 1332     1332 1 2456 my @a = version_split_digits(shift);
312 1332         2533 my @b = version_split_digits(shift);
313 1332         1677 while (1) {
314 3685         6245 my ($a, $b) = (shift @a, shift @b);
315 3685 100 66     9704 return 0 if not defined($a) and not defined($b);
316 3185   100     6583 $a ||= 0; # Default value for lack of version
317 3185   100     11701 $b ||= 0;
318 3185 100 100     15649 if ($a =~ /^\d+$/ and $b =~ /^\d+$/) {
319             # Numerical comparison
320 1699         2931 my $cmp = $a <=> $b;
321 1699 100       4436 return $cmp if $cmp;
322             } else {
323             # String comparison
324 1486         2630 my $cmp = version_compare_string($a, $b);
325 1486 100       3890 return $cmp if $cmp;
326             }
327             }
328             }
329              
330             =item my @items = version_split_digits($version)
331              
332             Splits a string in items that are each entirely composed either
333             of digits or of non-digits. For instance for "1.024~beta1+svn234" it would
334             return ("1", ".", "024", "~beta", "1", "+svn", "234").
335              
336             =cut
337              
338             sub version_split_digits($) {
339 2664     2664 1 21005 return split(/(?<=\d)(?=\D)|(?<=\D)(?=\d)/, $_[0]);
340             }
341              
342             =item my ($ok, $msg) = version_check($version)
343             =item my $ok = version_check($version)
344              
345             Checks the validity of $version as a version number. Returns 1 in $ok
346             if the version is valid, 0 otherwise. In the latter case, $msg
347             contains a description of the problem with the $version scalar.
348              
349             =cut
350              
351             sub version_check($) {
352 3443     3443 1 4253 my $version = shift;
353 3443 100       8814 $version = "$version" if ref($version);
354              
355 3443 100 33     20384 if (not defined($version) or not length($version)) {
356 1         4 my $msg = _g("version number cannot be empty");
357 1 50       13 return (0, $msg) if wantarray;
358 1         8 return 0;
359             }
360 3442 100       10276 if ($version =~ m/([^-+:.0-9a-zA-Z~])/o) {
361 1         3 my $msg = sprintf(_g("version number contains illegal character `%s'"), $1);
362 1 50       3 return (0, $msg) if wantarray;
363 1         5 return 0;
364             }
365 3441 100 100     10584 if ($version =~ /:/ and $version !~ /^\d*:/) {
366 1         4 $version =~ /^([^:]*):/;
367 1         3 my $msg = sprintf(_g("epoch part of the version number " .
368             "is not a number: '%s'"), $1);
369 1 50       6 return (0, $msg) if wantarray;
370 1         5 return 0;
371             }
372 3440 50       5872 return (1, "") if wantarray;
373 3440         9034 return 1;
374             }
375              
376             sub _g {
377 3     3   10 return @_;
378             }
379             sub warning {
380             carp(@_);
381             }
382              
383             =back
384              
385             =head1 AUTHOR
386              
387             Don Armstrong , Colin Watson
388             and Raphaël Hertzog , based on
389             the implementation in C by Ian Jackson and others.
390              
391             =cut
392              
393             1;