File Coverage

blib/lib/Version/libversion/PP.pm
Criterion Covered Total %
statement 184 196 93.8
branch 76 84 90.4
condition 31 41 75.6
subroutine 35 39 89.7
pod 5 7 71.4
total 331 367 90.1


line stmt bran cond sub pod time code
1             package Version::libversion::PP;
2              
3 4     4   620284 use feature ':5.10';
  4         8  
  4         701  
4 4     4   29 use strict;
  4         26  
  4         114  
5 4     4   2224 use utf8;
  4         1291  
  4         40  
6 4     4   123 use warnings;
  4         10  
  4         675  
7              
8             require Exporter;
9              
10             our @ISA = qw(Exporter);
11              
12             our @EXPORT = qw(
13             version_compare
14             version_compare2
15             version_compare4
16             );
17              
18             our @EXPORT_OK = qw(
19             VERSIONFLAG_P_IS_PATCH
20             VERSIONFLAG_ANY_IS_PATCH
21             VERSIONFLAG_LOWER_BOUND
22             VERSIONFLAG_UPPER_BOUND
23              
24             P_IS_PATCH
25             ANY_IS_PATCH
26             LOWER_BOUND
27             UPPER_BOUND
28             );
29              
30             our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
31              
32             our $VERSION = '1.00';
33              
34 4     4   1869 use overload ('""' => \&value, cmp => \&cmp, '<=>' => \&cmp, fallback => 1);
  4         6271  
  4         39  
35              
36 4     4   434 use constant VERSIONFLAG_P_IS_PATCH => 0x1;
  4         8  
  4         334  
37 4     4   20 use constant VERSIONFLAG_ANY_IS_PATCH => 0x2;
  4         7  
  4         177  
38 4     4   25 use constant VERSIONFLAG_LOWER_BOUND => 0x4;
  4         21  
  4         183  
39 4     4   18 use constant VERSIONFLAG_UPPER_BOUND => 0x8;
  4         10  
  4         155  
40              
41 4     4   21 use constant P_IS_PATCH => 0x1;
  4         5  
  4         134  
42 4     4   19 use constant ANY_IS_PATCH => 0x2;
  4         5  
  4         139  
43 4     4   32 use constant LOWER_BOUND => 0x4;
  4         22  
  4         212  
44 4     4   19 use constant UPPER_BOUND => 0x8;
  4         8  
  4         165  
45              
46 4     4   36 use constant METAORDER_LOWER_BOUND => 0;
  4         7  
  4         141  
47 4     4   26 use constant METAORDER_PRE_RELEASE => 1;
  4         5  
  4         186  
48 4     4   30 use constant METAORDER_ZERO => 2;
  4         6  
  4         278  
49 4     4   24 use constant METAORDER_POST_RELEASE => 3;
  4         13  
  4         189  
50 4     4   17 use constant METAORDER_NONZERO => 4;
  4         7  
  4         177  
51 4     4   18 use constant METAORDER_LETTER_SUFFIX => 5;
  4         6  
  4         157  
52 4     4   18 use constant METAORDER_UPPER_BOUND => 6;
  4         5  
  4         162  
53              
54 4     4   39 use constant KEYWORD_UNKNOWN => 0;
  4         5  
  4         183  
55 4     4   21 use constant KEYWORD_PRE_RELEASE => 1;
  4         7  
  4         197  
56 4     4   21 use constant KEYWORD_POST_RELEASE => 2;
  4         32  
  4         10876  
57              
58             sub new {
59              
60 0     0 1 0 my ($class, $value, $flags) = @_;
61 0         0 my $self = {value => $value, flags => $flags};
62              
63 0         0 return bless $self, $class;
64              
65             }
66              
67             *parse = \&new;
68              
69 0     0 0 0 sub value { shift->{value} }
70 0     0 1 0 sub flags { shift->{flags} }
71              
72             sub cmp {
73              
74 0     0 0 0 my ($left, $right) = @_;
75              
76 0 0       0 unless (ref($right)) {
77 0         0 $right = __PACKAGE__->new($right);
78             }
79              
80 0   0     0 return version_compare($left->value, $right->value, ($left->flags || 0), ($right->flags || 0));
      0        
81              
82             }
83              
84             sub _skip_separator {
85              
86 252528     252528   497276 my $string = shift;
87              
88 252528         949512 $string =~ s/^[^[:alnum:]]+//;
89 252528         626244 return $string;
90              
91             }
92              
93             sub _split_alpha {
94              
95 192342     192342   357586 my $string = shift;
96              
97 192342 50       699353 if ($string =~ /^([[:alpha:]]+)/) {
98 192342         985615 return ($1, substr($string, length($1)));
99             }
100              
101 0         0 return ('', $string);
102              
103             }
104              
105             sub _split_number {
106              
107 118832     118832   239145 my $string = shift;
108              
109 118832 50       464106 if ($string =~ /^([[:digit:]]+)/) {
110 118832         311121 my $num = $1;
111 118832         290313 my $rest = substr($string, length($1));
112              
113 118832         412463 $num =~ s/^0+//; # skip_zeroes
114 118832         494992 return ($num, $rest);
115             }
116              
117 0         0 return ('', $string);
118              
119             }
120              
121             sub _classify_keyword {
122              
123 150270     150270   342811 my ($string, $flags) = @_;
124              
125 150270         310263 $string = lc($string);
126              
127 150270 100       428059 return KEYWORD_PRE_RELEASE if ($string =~ /^(alpha|beta|rc)$/);
128 147310 100       350840 return KEYWORD_PRE_RELEASE if ($string =~ /^pre/);
129 146729 100       326621 return KEYWORD_POST_RELEASE if ($string =~ /^(post|patch)/);
130 143867 100       349890 return KEYWORD_POST_RELEASE if ($string =~ /^(pl|errata)$/);
131 143859 100 100     432495 return KEYWORD_POST_RELEASE if ($string eq 'p' && ($flags & VERSIONFLAG_P_IS_PATCH));
132              
133 143837         350698 return KEYWORD_UNKNOWN;
134              
135             }
136              
137             sub _parse_token_to_component {
138              
139 250128     250128   562587 my ($string, $flags) = @_;
140              
141 250128         462136 my $component = {};
142              
143 250128 100       813914 if ($string =~ /^[[:alpha:]]/) {
144              
145 131296         283167 my ($alpha, $rest) = _split_alpha($string);
146              
147 131296         314998 my $keyword_class = _classify_keyword($alpha, $flags);
148              
149 131296 100       340179 if ($keyword_class == KEYWORD_UNKNOWN) {
150 125055 100       464830 $component->{order} = ($flags & VERSIONFLAG_ANY_IS_PATCH) ? METAORDER_POST_RELEASE : METAORDER_PRE_RELEASE;
151             }
152              
153 131296 100       290509 $component->{order} = METAORDER_PRE_RELEASE if $keyword_class == KEYWORD_PRE_RELEASE;
154 131296 100       301787 $component->{order} = METAORDER_POST_RELEASE if $keyword_class == KEYWORD_POST_RELEASE;
155              
156 131296         361060 $component->{value} = lc(substr($alpha, 0, 1));
157              
158 131296         457122 return ($component, $rest);
159             }
160             else {
161              
162 118832         301960 my ($number, $rest) = _split_number($string);
163              
164 118832         445011 $component->{value} = $number;
165 118832 100       356241 $component->{order} = (length($number) == 0) ? METAORDER_ZERO : METAORDER_NONZERO;
166              
167 118832         366303 return ($component, $rest);
168              
169             }
170              
171             }
172              
173             sub _make_default_component {
174              
175 11781   100 11781   45411 my $flags = shift || 0x0;
176              
177 11781 100       46125 my $order
    100          
178             = ($flags & VERSIONFLAG_LOWER_BOUND) ? METAORDER_LOWER_BOUND
179             : ($flags & VERSIONFLAG_UPPER_BOUND) ? METAORDER_UPPER_BOUND
180             : METAORDER_ZERO;
181              
182 11781         52083 return {order => $order, value => ''};
183             }
184              
185             sub _get_next_version_component {
186              
187 252528     252528   523788 my ($string, $flags) = @_;
188              
189 252528         604158 $string = _skip_separator($string);
190              
191 252528 100       654087 return ([_make_default_component($flags)], $string) if (length($string) == 0);
192              
193 250128         593390 my ($component, $rest) = _parse_token_to_component($string, $flags);
194              
195 250128         632197 my @components = ($component);
196              
197             # Special case for letter suffix:
198             # - We taste whether the next component is alpha not followed by a number,
199             # e.g 1a, 1a.1, but not 1a1
200             # - We check whether it's known keyword (in which case it's treated normally)
201             # - Otherwise, it's treated as letter suffix
202              
203 250128 100       705247 if ($rest =~ /^[[:alpha:]]/) {
204              
205 61046         142638 my ($alpha, $alpha_rest) = _split_alpha($rest);
206              
207 61046 100       248654 if ($alpha_rest !~ /^[[:digit:]]/) {
208              
209 18974         41964 my $keyword_class = _classify_keyword($alpha, $flags);
210              
211 18974 100       50608 my $order
    100          
212             = ($keyword_class == KEYWORD_UNKNOWN) ? METAORDER_LETTER_SUFFIX
213             : ($keyword_class == KEYWORD_PRE_RELEASE) ? METAORDER_PRE_RELEASE
214             : METAORDER_POST_RELEASE;
215              
216 18974         87052 push @components, {value => lc(substr($alpha, 0, 1)), order => $order};
217              
218 18974         59907 $rest = $alpha_rest;
219              
220             }
221             }
222              
223 250128         778325 return (\@components, $rest);
224              
225             }
226              
227             sub _compare_components {
228              
229 133308     133308   258864 my ($u1, $u2) = @_;
230              
231 133308   50     363720 my $o1 = $u1->{order} // 0;
232 133308   50     322390 my $o2 = $u2->{order} // 0;
233              
234 133308 100       348799 return -1 if $o1 < $o2;
235 79980 100       211245 return 1 if $o1 > $o2;
236              
237 48754   50     126332 my $v1 = $u1->{value} // '';
238 48754   50     123870 my $v2 = $u2->{value} // '';
239              
240 48754         103227 my $u1_is_empty = length($v1) == 0;
241 48754         78985 my $u2_is_empty = length($v2) == 0;
242              
243 48754 100 66     147221 return 0 if $u1_is_empty && $u2_is_empty;
244 43238 50       101566 return -1 if $u1_is_empty;
245 43238 50       92903 return 1 if $u2_is_empty;
246              
247 43238         134471 my $u1_is_alpha = ($v1 =~ /^[[:alpha:]]/);
248 43238         104224 my $u2_is_alpha = ($v2 =~ /^[[:alpha:]]/);
249              
250 43238 100 66     178442 if ($u1_is_alpha && $u2_is_alpha) {
251              
252 31198 100       126226 return -1 if $v1 lt $v2;
253 14473 100       71865 return 1 if $v1 gt $v2;
254 3399         15971 return 0;
255              
256             }
257              
258 12040 50       25732 return -1 if $u1_is_alpha;
259 12040 50       32313 return 1 if $u2_is_alpha;
260              
261 12040         19927 my $len1 = length($v1);
262 12040         19202 my $len2 = length($v2);
263              
264 12040 100       26484 return -1 if $len1 < $len2;
265 12032 100       30696 return 1 if $len1 > $len2;
266              
267 8798         35156 return ($v1 cmp $v2);
268              
269             }
270              
271             sub version_compare2 {
272 117959     117959 1 52673815 my ($v1, $v2) = @_;
273 117959         383143 return version_compare4($v1, $v2, 0, 0);
274             }
275              
276             sub version_compare4 {
277              
278 118103     118103 1 410830 my ($v1, $v2, $v1_flags, $v2_flags) = @_;
279              
280 118103 100 100     430912 return 0 if $v1 eq $v2 && $v1_flags == $v2_flags;
281              
282 118063         200455 my @v1_components = ();
283 118063         193751 my @v2_components = ();
284              
285 118063 100       350813 my $v1_extra_components = ($v1_flags & (VERSIONFLAG_LOWER_BOUND | VERSIONFLAG_UPPER_BOUND)) ? 1 : 0;
286 118063 100       260565 my $v2_extra_components = ($v2_flags & (VERSIONFLAG_LOWER_BOUND | VERSIONFLAG_UPPER_BOUND)) ? 1 : 0;
287              
288 118063         187932 while (1) {
289              
290 133308         446149 _components(\@v1_components, \$v1, $v1_flags, \$v1_extra_components);
291 133308         404630 _components(\@v2_components, \$v2, $v2_flags, \$v2_extra_components);
292              
293 133308         300268 my $c1 = shift @v1_components;
294 133308         249614 my $c2 = shift @v2_components;
295              
296 133308         327366 my $res = _compare_components($c1, $c2);
297 133308 100       882281 return $res if $res != 0;
298              
299 17689 100 100     149568 return 0
      100        
      100        
      100        
      100        
300             if (!length($v1)
301             && !length($v2)
302             && $v1_extra_components == 0
303             && $v2_extra_components == 0
304             && !@v1_components
305             && !@v2_components);
306              
307             }
308              
309 0         0 return 0;
310              
311             }
312              
313             sub _components {
314              
315 266616     266616   600962 my ($v_components, $v_string, $v_flags, $v_extra) = @_;
316              
317 266616 100       406433 return if @{$v_components};
  266616         693235  
318              
319 261873 100       455570 if (length(${$v_string})) {
  261873         667181  
320              
321 252528         412353 my ($components, $rest) = _get_next_version_component(${$v_string}, $v_flags);
  252528         561570  
322              
323 252528         503970 push @{$v_components}, @{$components};
  252528         465902  
  252528         491758  
324 252528         436112 ${$v_string} = $rest;
  252528         469968  
325              
326 252528         677365 return;
327              
328             }
329              
330             # Fill extra components
331 9345         23839 my $component = _make_default_component();
332              
333 9345 100       16335 if (${$v_extra} > 0) {
  9345         26110  
334 36         64 ${$v_extra}--;
  36         78  
335 36         93 $component = _make_default_component($v_flags);
336             }
337              
338 9345         14407 push @{$v_components}, $component;
  9345         22981  
339              
340 9345         21039 return;
341              
342             }
343              
344             sub version_compare {
345 227 100   227 1 358744 (@_ == 2) ? version_compare2(@_) : version_compare4(@_);
346             }
347              
348             1;
349             __END__