File Coverage

lib/SemVer.pm
Criterion Covered Total %
statement 112 118 94.9
branch 45 50 90.0
condition 17 21 80.9
subroutine 17 17 100.0
pod 8 8 100.0
total 199 214 92.9


line stmt bran cond sub pod time code
1             package SemVer;
2            
3 1     1   53064 use 5.008001;
  1         3  
4 1     1   4 use strict;
  1         2  
  1         17  
5 1     1   352 use version 0.82;
  1         1461  
  1         5  
6 1     1   55 use Scalar::Util ();
  1         1  
  1         18  
7            
8             use overload (
9 1         12 '""' => 'stringify',
10             '<=>' => 'vcmp',
11             'cmp' => 'vcmp',
12 1     1   869 );
  1         700  
13            
14             our @ISA = qw(version);
15             our $VERSION = '0.7.0'; # For Module::Build
16            
17 9     9   55 sub _die { require Carp; Carp::croak(@_) }
  9         953  
18            
19             # Prevent version.pm from mucking with our internals.
20       1     sub import {}
21            
22             # Adapted from version.pm.
23             my $STRICT_INTEGER_PART = qr/0|[1-9][0-9]*/;
24             my $DOT_SEPARATOR = qr/\./;
25             my $PLUS_SEPARATOR = qr/\+/;
26             my $DASH_SEPARATOR = qr/-/;
27             my $STRICT_DOTTED_INTEGER_PART = qr/$DOT_SEPARATOR$STRICT_INTEGER_PART/;
28             my $STRICT_DOTTED_INTEGER_VERSION = qr/ $STRICT_INTEGER_PART $STRICT_DOTTED_INTEGER_PART{2,} /x;
29             my $IDENTIFIER = qr/[-0-9A-Za-z]+/;
30             my $DOTTED_IDENTIFIER = qr/(?:$DOT_SEPARATOR$IDENTIFIER)*/;
31             my $PRERELEASE = qr/$IDENTIFIER$DOTTED_IDENTIFIER/;
32             my $METADATA = qr/$IDENTIFIER$DOTTED_IDENTIFIER/;
33            
34             my $OPTIONAL_EXTRA_PART = qr/$PRERELEASE($PLUS_SEPARATOR$METADATA)?/;
35            
36             sub new {
37 596     596 1 57157 my ($class, $ival) = @_;
38            
39             # Handle vstring.
40 596 100       1509 return $class->SUPER::new($ival) if Scalar::Util::isvstring($ival);
41            
42             # Let version handle cloning.
43 591 100       759 if (eval { $ival->isa('version') }) {
  591         2340  
44 315         1432 my $self = $class->SUPER::new($ival);
45 315         628 $self->{extra} = $ival->{extra};
46 315         431 $self->{dash} = $ival->{dash};
47 315         633 $self->_evalPreRelease($self->{extra});
48 315         705 return $self;
49             }
50            
51 276         2839 my ($val, $dash, $extra) = (
52             $ival =~ /^v?($STRICT_DOTTED_INTEGER_VERSION)(?:($DASH_SEPARATOR)($OPTIONAL_EXTRA_PART))?$/
53             );
54 276 100       726 _die qq{Invalid semantic version string format: "$ival"}
55             unless defined $val;
56            
57 268         1582 my $self = $class->SUPER::new($val);
58 268         677 $self->{dash} = $dash;
59 268         416 $self->{extra} = $extra;
60 268         692 $self->_evalPreRelease($self->{extra});
61            
62 268         606 return $self;
63             }
64            
65             # Internal function to split up given string into prerelease- and patch-components
66             sub _evalPreRelease {
67 1     1   387 no warnings 'uninitialized';
  1         2  
  1         761  
68 643     643   730 my $self = shift;
69 643         729 my $v = shift;
70 643         3366 my ($preRelease, $plus, $patch) = (
71             $v =~ /^($PRERELEASE)(?:($PLUS_SEPARATOR)($METADATA))?$/
72             );
73 643         1609 @{$self->{prerelease}} = split $DOT_SEPARATOR,$preRelease;
  643         1939  
74 643         1041 $self->{plus} = $plus;
75 643   50     1926 @{$self->{patch}} = (split $DOT_SEPARATOR, $patch || undef);
  643         1617  
76 643         1025 return;
77             }
78            
79             $VERSION = __PACKAGE__->new($VERSION); # For ourselves.
80            
81             sub declare {
82 347     347 1 3553 my ($class, $ival) = @_;
83             return $class->new($ival) if Scalar::Util::isvstring($ival)
84 347 100 100     908 or eval { $ival->isa('version') };
  345         1621  
85            
86 51         336 (my $v = $ival) =~ s/^v?$STRICT_DOTTED_INTEGER_VERSION(?:($DASH_SEPARATOR)($OPTIONAL_EXTRA_PART))[[:space:]]*$//;
87 51         92 my $dash = $1;
88 51         62 my $extra = $2;
89 51 100       102 $v += 0 if $v =~ s/_//g; # ignore underscores.
90 51         337 my $self = $class->SUPER::declare($v);
91 44         108 $self->{dash} = $dash;
92 44         50 $self->{extra} = $extra;
93 44         83 $self->_evalPreRelease($self->{extra});
94 44         125 return $self;
95             }
96            
97             sub parse {
98 17     17 1 52 my ($class, $ival) = @_;
99             return $class->new($ival) if Scalar::Util::isvstring($ival)
100 17 100 66     76 or eval { $ival->isa('version') };
  16         135  
101            
102 16         194 (my $v = $ival) =~ s/^v?$STRICT_DOTTED_INTEGER_VERSION(?:($DASH_SEPARATOR)($OPTIONAL_EXTRA_PART))[[:space:]]*$//;
103 16         47 my $dash = $1;
104 16         34 my $extra = $2;
105 16 100       69 $v += 0 if $v =~ s/_//g; # ignore underscores.
106 16         139 my $self = $class->SUPER::parse($v);
107 16         47 $self->{dash} = $dash;
108 16         32 $self->{extra} = $extra;
109 16         46 $self->_evalPreRelease($self->{extra});
110 16         83 return $self;
111             }
112            
113             sub stringify {
114 665     665 1 1333 my $self = shift;
115 665         1940 my $str = $self->SUPER::stringify;
116             # This is purely for SemVers constructed from version objects.
117 665 100       1717 $str += 0 if $str =~ s/_//g; # ignore underscores.
118 665   100     4243 return $str . ($self->{dash} || '') . ($self->{extra} || '');
      100        
119             }
120            
121             sub normal {
122 111     111 1 6893 my $self = shift;
123 111         874 (my $norm = $self->SUPER::normal) =~ s/^v//;
124 111         282 $norm =~ s/_/./g;
125 111 100       683 return $norm . ($self->{extra} ? "-$self->{extra}" : '');
126             }
127            
128 1     1 1 4 sub numify { _die 'Semantic versions cannot be numified'; }
129 12     12 1 7441 sub is_alpha { !!shift->{extra} }
130            
131            
132             # Sort Ordering:
133             # Precedence refers to how versions are compared to each other when ordered. Precedence MUST be calculated by
134             # separating the version into major, minor, patch and pre-release identifiers in that order (Build metadata does not figure into precedence).
135             # Precedence is determined by the first difference when comparing each of these identifiers from left to right as follows:
136             # 1. Major, minor, and patch versions are always compared numerically. Example: 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1.
137             # 2. When major, minor, and patch are equal, a pre-release version has lower precedence than a normal version.
138             # Example: 1.0.0-alpha < 1.0.0.
139             # 3. Precedence for two pre-release versions with the same major, minor, and patch version MUST be determined by
140             # comparing each dot separated identifier from left to right until a difference is found as follows:
141             # 3.a. identifiers consisting of only digits are compared numerically and identifiers with letters or hyphens are
142             # compared lexically in ASCII sort order.
143             # 3.b. Numeric identifiers always have lower precedence than non-numeric identifiers.
144             # 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.
145             # 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.
146             sub vcmp {
147 321     321 1 34574 my $left = shift;
148 321         776 my $right = ref($left)->declare(shift);
149            
150             # Reverse?
151 321 100       674 ($left, $right) = shift() ? ($right, $left): ($left, $right);
152            
153             # Major and minor win. - case 1.
154 321 100       947 if (my $ret = $left->SUPER::vcmp($right, 0)) {
155 108         982 return $ret;
156             } else { #cases 2, 3
157 213         226 my $lenLeft = 0;
158 213         191 my $lenRight = 0;
159 213 100       370 if (defined $left->{prerelease}) {
160 211         192 $lenLeft = scalar(@{$left->{prerelease}});
  211         253  
161             }
162 213 50       297 if (defined $right->{prerelease}) {
163 213         194 $lenRight = scalar(@{$right->{prerelease}});
  213         218  
164             }
165 213         267 my $lenMin = ($lenLeft, $lenRight)[$lenLeft > $lenRight];
166 213 100       273 if ( $lenLeft == 0) {
167 108 100       118 if ($lenRight == 0) {
168 100         929 return 0; # Neither LEFT nor RIGHT have prerelease identifiers - versions are equal
169             } else {
170             # Case 2: When major, minor, and patch are equal, a pre-release version has lower precedence than a normal version.
171 8         117 return 1; # Only RIGHT has prelease - not LEFT -> LEFT wins
172             }
173             } else {
174 105 100       149 if ($lenRight == 0) {
175             # Case 2: When major, minor, and patch are equal, a pre-release version has lower precedence than a normal version.
176 17         176 return -1; # Only LEFT has prelease identifiers - not RIGHT -> RIGHT wins
177             } else {
178             # LEFT and RIGHT have prelease identifiers - compare each part separately
179 88         141 for (my $i = 0; $i < $lenMin; $i++) {
180 95         208 my $isNumLeft = Scalar::Util::looks_like_number($left->{prerelease}->[$i]);
181 95         131 my $isNumRight = Scalar::Util::looks_like_number($right->{prerelease}->[$i]);
182             # Case 3.b: Numeric identifiers always have lower precedence than non-numeric identifiers
183 95 50 66     329 if (!$isNumLeft && $isNumRight) {
    100 100        
    100 66        
184 0         0 return 1; # LEFT identifier is Non-numeric - RIGHT identifier is numeric -> LEFT wins
185             } elsif ($isNumLeft && !$isNumRight) {
186 1         12 return -1; # LEFT identifier is numeric - RIGHT identifier is non-numeric -> RIGHT wins
187             } elsif ($isNumLeft && $isNumRight) {
188             # Case 3.a.1: identifiers consisting of only digits are compared numerically
189 6 50       22 if ($left->{prerelease}->[$i] == $right->{prerelease}->[$i] ) {
    50          
190 0         0 next; # LEFT identifier and RIGHT identifier are equal - step to next part
191             } elsif ($left->{prerelease}->[$i] > $right->{prerelease}->[$i] ) {
192 0         0 return 1; # LEFT identifier is bigger than RIGHT identifier -> LEFT wins
193             } else {
194 6         76 return -1; return 1; # LEFT identifier is smaller than RIGHT identifier -> RIGHT wins
  0         0  
195             }
196             } else {
197             # Case 3.a.2: identifiers with letters or hyphens are compared lexically in ASCII sort order.
198 88 100       215 if (lc $left->{prerelease}->[$i] eq lc $right->{prerelease}->[$i] ) {
    100          
199 39         65 next; # LEFT identifier and RIGHT identifier are equal - step to next part
200             } elsif (lc $left->{prerelease}->[$i] gt lc $right->{prerelease}->[$i] ) {
201 10         95 return 1; # LEFT identifier is bigger than RIGHT identifier -> LEFT wins
202             } else {
203 39         403 return -1; return 1; # LEFT identifier is smaller than RIGHT identifier -> RIGHT wins
  0         0  
204             }
205             }
206             }
207             # 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
208 32 50       54 if ($lenLeft > $lenRight) {
    100          
209 0         0 return 1; # All existing identifiers are equal, but LEFT has more identifiers -> LEFT wins
210             } elsif ($lenLeft < $lenRight) {
211 6         63 return -1; # All existing identifiers are equal, but RIGHT has more identifiers -> RIGHT wins
212             }
213             # All identifiers are equal
214 26         213 return 0;
215             }
216             }
217             }
218             }
219            
220             1;
221             __END__