File Coverage

blib/lib/App/newver/Version.pm
Criterion Covered Total %
statement 55 55 100.0
branch 26 26 100.0
condition 6 6 100.0
subroutine 8 8 100.0
pod 2 3 66.6
total 97 98 98.9


line stmt bran cond sub pod time code
1             package App::newver::Version;
2 2     2   144894 use 5.016;
  2         7  
3 2     2   10 use strict;
  2         13  
  2         53  
4 2     2   8 use warnings;
  2         5  
  2         159  
5             our $VERSION = '0.02';
6              
7 2     2   10 use Exporter qw(import);
  2         4  
  2         183  
8             our @EXPORT_OK = qw(version_components version_compare);
9              
10             # Algorithim adapted from libversion
11             # https://github.com/repology/libversion/blob/master/doc/ALGORITHM.md
12              
13             use constant {
14 2         1744 RANK_PRE_RELEASE => 0,
15             RANK_ZERO => 1,
16             RANK_POST_RELEASE => 2,
17             RANK_NONZERO => 3,
18 2     2   11 };
  2         4  
19              
20             my %NORMALIZE_PRE = (
21             a => 'alpha',
22             b => 'beta',
23             );
24              
25             # This set isn't actually used, any string that isn't a part of the
26             # %POST_RELEASE_COMPONENTS set is considered to be a pre-release component.
27             my %PRE_RELEASE_COMPONENTS = map { $_ => 1 } qw(
28             a alpha b beta pre rc
29             );
30              
31             # libversion allows for p to either mean patch or pre-release. We'll just
32             # consider p to mean patch.
33             my %POST_RELEASE_COMPONENTS = map { $_ => 1 } qw(
34             errata patch post pl p
35             );
36              
37             sub id_component {
38              
39 754     754 0 973 my ($comp) = @_;
40 754         927 $comp = lc $comp;
41              
42 754 100       1457 if ($comp =~ /^\d+$/) {
43 611 100       984 if ($comp == 0) {
44 295         408 return RANK_ZERO;
45             } else {
46 316         527 return RANK_NONZERO;
47             }
48             } else {
49 143 100 100     449 if ($POST_RELEASE_COMPONENTS{ $comp } or $comp =~ /^post/) {
50 31         47 return RANK_POST_RELEASE;
51             } else {
52 112         161 return RANK_PRE_RELEASE;
53             }
54             }
55              
56             }
57              
58             sub version_components {
59              
60 292     292 1 376 my ($version) = @_;
61              
62 292         1103 $version =~ s/^\s+|\s+$//g;
63 292         422 $version =~ s/^v//;
64              
65 292         1251 my @comps = $version =~ /(\d+|[a-zA-Z]+)/g;
66              
67 292         785 return @comps;
68              
69             }
70              
71             sub version_compare {
72              
73 146     146 1 314819 my ($v1, $v2) = @_;
74              
75 146         275 my @v1c = version_components($v1);
76 146         212 my @v2c = version_components($v2);
77              
78             # Pad components with zeros.
79 146 100       485 if (@v1c < @v2c) {
    100          
80 15         42 push @v1c, (0) x (@v2c - @v1c);
81             } elsif (@v2c < @v1c) {
82 68         157 push @v2c, (0) x (@v1c - @v2c);
83             }
84              
85 146         422 for my $i (0 .. $#v1c) {
86 377         568 my $v1r = id_component($v1c[$i]);
87 377         514 my $v2r = id_component($v2c[$i]);
88 377 100       644 if ($v1r != $v2r) {
89 86         434 return $v1r <=> $v2r;
90             }
91 291 100 100     782 if ($v1r == RANK_NONZERO or $v1r == RANK_ZERO) {
    100          
92             # Treat numerical versions as strings so that we can compare very
93             # long version integars that would cause overflow problems when
94             # using the '<=>' operator.
95 248         618 my $tr1 = $v1c[$i] =~ s/^0+//r;
96 248         648 my $tr2 = $v2c[$i] =~ s/^0+//r;
97 248 100       568 if ($tr1 ne $tr2) {
98 13 100       30 if (length $tr1 == length $tr2) {
99 10         61 return $tr1 cmp $tr2;
100             } else {
101 3         16 return length $tr1 <=> length $tr2;
102             }
103             }
104             } elsif ($v1r == RANK_PRE_RELEASE) {
105 39         54 my $p1 = lc $v1c[$i];
106 39         45 my $p2 = lc $v2c[$i];
107 39 100       83 if (exists $NORMALIZE_PRE{ $p1 }) {
108 14         25 $p1 = $NORMALIZE_PRE{ $p1 };
109             }
110 39 100       90 if (exists $NORMALIZE_PRE{ $p2 }) {
111 12         15 $p2 = $NORMALIZE_PRE{ $p2 };
112             }
113 39 100       108 if ($p1 ne $p2) {
114 12         75 return $p1 cmp $p2;
115             }
116             }
117             }
118              
119 35         165 return 0;
120              
121             }
122              
123             1;
124              
125             =head1 NAME
126              
127             App::newver::Version - Compare version number strings
128              
129             =head1 SYNOPSIS
130              
131             use App::newver::Versoin qw(version_compare);
132              
133             my @sorted = sort { version_compare($a, $b) } qw(
134             1.0
135             1.1
136             1.0.1
137             1.0alpha1
138             );
139              
140             =head1 DESCRIPTION
141              
142             B is a module for comparing version strings. This is a
143             private module, please consult the L manual for user documentation.
144              
145             B adapts most of its logic from
146             L. This module can handle
147             pre-release version components (alpha, beta, rc, etc.), post-release version
148             components (post, patch, errata, pl, etc.), and normal numerical versions.
149              
150             =head1 SUBROUTINES
151              
152             Subroutines are not exported by default.
153              
154             =head2 $r = version_compare($v1, $v2)
155              
156             Compares two version strings and returns C<1> if C<$v1 > $v2>, C<-1> if
157             C<$v1 < $v2>, and C<0> if C<$v1 == $v2>.
158              
159             =head2 @components = version_components($version)
160              
161             Returns the list of version components from C<$version>.
162              
163             =head1 AUTHOR
164              
165             Written by L
166              
167             This project's source can be found on its
168             L. Comments and pull
169             requests are welcome.
170              
171             =head1 COPYRIGHT
172              
173             Copyright (C) 2025 Samuel Young.
174              
175             This program is free software; you can redistribute it and/or modify it under
176             the terms of the Artistic License 2.0.
177              
178             =head1 SEE ALSO
179              
180             L
181              
182             =cut