File Coverage

lib/SemVer.pm
Criterion Covered Total %
statement 95 100 95.0
branch 47 50 94.0
condition 18 21 85.7
subroutine 18 18 100.0
pod 9 9 100.0
total 187 198 94.4


line stmt bran cond sub pod time code
1             package SemVer;
2              
3 2     2   141967 use 5.008001;
  2         16  
4 2     2   10 use strict;
  2         5  
  2         46  
5 2     2   865 use version 0.82;
  2         3887  
  2         15  
6 2     2   162 use Scalar::Util ();
  2         5  
  2         53  
7              
8             use overload (
9 2         22 '""' => 'stringify',
10             '<=>' => 'vcmp',
11             'cmp' => 'vcmp',
12             'bool' => 'vbool',
13 2     2   2358 );
  2         1838  
14              
15             our @ISA = qw(version);
16             our $VERSION = '0.10.0'; # For Module::Build
17              
18 56     56   277 sub _die { require Carp; Carp::croak(@_) }
  56         5747  
19              
20             # Prevent version.pm from mucking with our internals.
21       2     sub import {}
22              
23             sub new {
24 1061     1061 1 99085 my ($class, $ival) = @_;
25              
26             # Handle vstring.
27 1061 100       2913 return $class->SUPER::new($ival) if Scalar::Util::isvstring($ival);
28              
29             # Let version handle cloning.
30 1056 100       1451 if (eval { $ival->isa('version') }) {
  1056         4322  
31 597         3316 my $self = $class->SUPER::new($ival);
32 597         1414 $self->{extra} = $ival->{extra};
33 597         937 $self->{patch} = $ival->{patch};
34 597         836 $self->{prerelease} = $ival->{prerelease};
35 597         1724 return $self;
36             }
37              
38             # Regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string.
39 459         3598 my ($major, $minor, $patch, $prerelease, $meta) = (
40             $ival =~ /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
41             );
42 459 100       1188 _die qq{Invalid semantic version string format: "$ival"}
43             unless defined $major;
44              
45 411         2878 return _init($class->SUPER::new("$major.$minor.$patch"), $prerelease, $meta);
46             }
47              
48             sub _init {
49 623     623   1225 my ($self, $pre, $meta) = @_;
50 623 100       1132 if (defined $pre) {
51 250         698 $self->{extra} = "-$pre";
52 250         323 @{$self->{prerelease}} = split /[.]/, $pre;
  250         830  
53             }
54 623 100       1172 if (defined $meta) {
55 33         83 $self->{extra} .= "+$meta";
56 33         45 @{$self->{patch}} = split /[.]/, $meta;
  33         98  
57             }
58              
59 623         2179 return $self;
60             }
61              
62             $VERSION = __PACKAGE__->new($VERSION); # For ourselves.
63              
64             sub _lenient {
65 798     798   1494 my ($class, $ctor, $ival) = @_;
66             return $class->new($ival) if Scalar::Util::isvstring($ival)
67 798 100 100     2587 or eval { $ival->isa('version') };
  795         4748  
68              
69             # Use official regex for prerelease and meta, use more lenient version num matching and whitespace.
70 219         1628 my ($v, $prerelease, $meta) = (
71             $ival =~ /^[[:space:]]*
72             v?([\d_]+(?:\.[\d_]+(?:\.[\d_]+)?)?)
73             (?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?
74             [[:space:]]*$/x
75             );
76              
77 219 100       563 _die qq{Invalid semantic version string format: "$ival"}
78             unless defined $v;
79              
80 212 100 100     811 $v += 0 if $v && $v =~ s/_//g; # ignore underscores.
81 212         862 my $code = $class->can("SUPER::$ctor");
82 212         1376 return _init($code->($class, $v), $prerelease, $meta);
83             }
84              
85             sub declare {
86 710     710 1 10840 shift->_lenient('declare', @_);
87             }
88              
89             sub parse {
90 88     88 1 5313 shift->_lenient('parse', @_);
91             }
92              
93             sub stringify {
94 1361     1361 1 2577 my $self = shift;
95 1361         4240 my $str = $self->SUPER::stringify;
96             # This is purely for SemVers constructed from version objects.
97 1361 100       3645 $str += 0 if $str =~ s/_//g; # ignore underscores.
98 1361   50     8765 return $str . ($self->{dash} || '') . ($self->{extra} || '');
      100        
99             }
100              
101             sub normal {
102 125     125 1 210 my $self = shift;
103 125         912 (my $norm = $self->SUPER::normal) =~ s/^v//;
104 125         290 $norm =~ s/_/./g;
105 125   100     788 return $norm . ($self->{extra} || '');
106             }
107              
108 1     1 1 3 sub numify { _die 'Semantic versions cannot be numified'; }
109 12     12 1 6795 sub is_alpha { !!shift->{extra} }
110             sub vbool {
111 103     103 1 7976 my $self = shift;
112 103         222 return version::vcmp($self, $self->new("0.0.0"), 1);
113             }
114              
115             # Sort Ordering:
116             # Precedence refers to how versions are compared to each other when ordered. Precedence MUST be calculated by
117             # separating the version into major, minor, patch and pre-release identifiers in that order (Build metadata does not figure into precedence).
118             # Precedence is determined by the first difference when comparing each of these identifiers from left to right as follows:
119             # 1. Major, minor, and patch versions are always compared numerically. Example: 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1.
120             # 2. When major, minor, and patch are equal, a pre-release version has lower precedence than a normal version.
121             # Example: 1.0.0-alpha < 1.0.0.
122             # 3. Precedence for two pre-release versions with the same major, minor, and patch version MUST be determined by
123             # comparing each dot separated identifier from left to right until a difference is found as follows:
124             # 3.a. identifiers consisting of only digits are compared numerically and identifiers with letters or hyphens are
125             # compared lexically in ASCII sort order.
126             # 3.b. Numeric identifiers always have lower precedence than non-numeric identifiers.
127             # 3.c. A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal.
128             # Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.
129             sub vcmp {
130 610     610 1 68838 my $left = shift;
131 610         1830 my $right = ref($left)->declare(shift);
132              
133             # Reverse?
134 610 100       1700 ($left, $right) = shift() ? ($right, $left): ($left, $right);
135              
136             # Major and minor win. - case 1.
137 610 100       2226 if (my $ret = $left->SUPER::vcmp($right, 0)) {
138 138         1547 return $ret;
139             } else { #cases 2, 3
140 472         657 my $lenLeft = 0;
141 472         625 my $lenRight = 0;
142 472 100       1039 if (defined $left->{prerelease}) {
143 215         271 $lenLeft = scalar(@{$left->{prerelease}});
  215         378  
144             }
145 472 100       888 if (defined $right->{prerelease}) {
146 206         235 $lenRight = scalar(@{$right->{prerelease}});
  206         303  
147             }
148 472         818 my $lenMin = ($lenLeft, $lenRight)[$lenLeft > $lenRight];
149 472 100       851 if ( $lenLeft == 0) {
150 257 100       384 if ($lenRight == 0) {
151 243         2627 return 0; # Neither LEFT nor RIGHT have prerelease identifiers - versions are equal
152             } else {
153             # Case 2: When major, minor, and patch are equal, a pre-release version has lower precedence than a normal version.
154 14         149 return 1; # Only RIGHT has prelease - not LEFT -> LEFT wins
155             }
156             } else {
157 215 100       404 if ($lenRight == 0) {
158             # Case 2: When major, minor, and patch are equal, a pre-release version has lower precedence than a normal version.
159 23         272 return -1; # Only LEFT has prelease identifiers - not RIGHT -> RIGHT wins
160             } else {
161             # LEFT and RIGHT have prelease identifiers - compare each part separately
162 192         398 for (my $i = 0; $i < $lenMin; $i++) {
163 225         622 my $isNumLeft = Scalar::Util::looks_like_number($left->{prerelease}->[$i]);
164 225         446 my $isNumRight = Scalar::Util::looks_like_number($right->{prerelease}->[$i]);
165             # Case 3.b: Numeric identifiers always have lower precedence than non-numeric identifiers
166 225 50 66     1052 if (!$isNumLeft && $isNumRight) {
    100 100        
    100 66        
167 0         0 return 1; # LEFT identifier is Non-numeric - RIGHT identifier is numeric -> LEFT wins
168             } elsif ($isNumLeft && !$isNumRight) {
169 1         27 return -1; # LEFT identifier is numeric - RIGHT identifier is non-numeric -> RIGHT wins
170             } elsif ($isNumLeft && $isNumRight) {
171             # Case 3.a.1: identifiers consisting of only digits are compared numerically
172 32 100       76 if ($left->{prerelease}->[$i] == $right->{prerelease}->[$i] ) {
    50          
173 26         53 next; # LEFT identifier and RIGHT identifier are equal - step to next part
174             } elsif ($left->{prerelease}->[$i] > $right->{prerelease}->[$i] ) {
175 0         0 return 1; # LEFT identifier is bigger than RIGHT identifier -> LEFT wins
176             } else {
177 6         89 return -1; return 1; # LEFT identifier is smaller than RIGHT identifier -> RIGHT wins
  0         0  
178             }
179             } else {
180             # Case 3.a.2: identifiers with letters or hyphens are compared lexically in ASCII sort order.
181 192 100       537 if (lc $left->{prerelease}->[$i] eq lc $right->{prerelease}->[$i] ) {
    100          
182 131         294 next; # LEFT identifier and RIGHT identifier are equal - step to next part
183             } elsif (lc $left->{prerelease}->[$i] gt lc $right->{prerelease}->[$i] ) {
184 10         123 return 1; # LEFT identifier is bigger than RIGHT identifier -> LEFT wins
185             } else {
186 51         651 return -1; return 1; # LEFT identifier is smaller than RIGHT identifier -> RIGHT wins
  0         0  
187             }
188             }
189             }
190             # Case 3.c: A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal
191 124 50       282 if ($lenLeft > $lenRight) {
    100          
192 0         0 return 1; # All existing identifiers are equal, but LEFT has more identifiers -> LEFT wins
193             } elsif ($lenLeft < $lenRight) {
194 6         98 return -1; # All existing identifiers are equal, but RIGHT has more identifiers -> RIGHT wins
195             }
196             # All identifiers are equal
197 118         1306 return 0;
198             }
199             }
200             }
201             }
202              
203             1;
204             __END__