File Coverage

lib/SemVer/V2/Strict.pm
Criterion Covered Total %
statement 92 92 100.0
branch 30 30 100.0
condition 21 26 80.7
subroutine 23 23 100.0
pod 9 9 100.0
total 175 180 97.2


line stmt bran cond sub pod time code
1             package SemVer::V2::Strict;
2 8     8   809606 use strict;
  8         24  
  8         263  
3 8     8   50 use warnings;
  8         75  
  8         241  
4 8     8   47 use utf8;
  8         20  
  8         52  
5             our $VERSION = '0.16';
6              
7 8     8   751 use constant PRE_RELEASE_FORMAT => qr/(?:-(?<pre_release>[a-zA-Z0-9.\-]+))?/;
  8         34  
  8         903  
8 8     8   62 use constant BUILD_METADATA_FORMAT => qr/(?:\+(?<build_metadata>[a-zA-Z0-9.\-]+))?/;
  8         17  
  8         681  
9 8         15 use constant VERSION_FORMAT =>
10 8     8   52     qr/(?<major>[0-9]+)(?:\.(?<minor>[0-9]+))?(?:\.(?<patch>[0-9]+))?@{[PRE_RELEASE_FORMAT]}@{[BUILD_METADATA_FORMAT]}/;
  8         18  
  8         24  
  8         967  
11              
12 8     8   56 use List::Util qw/min max/;
  8         16  
  8         1081  
13 8     8   55 use Scalar::Util qw/looks_like_number/;
  8         19  
  8         742  
14              
15             use overload (
16 8         87     q{""} => \&as_string,
17                 q{<=>} => \&_cmp,
18                 fallback => 1,
19 8     8   90 );
  8         59  
20              
21 62     62 1 340 sub major { shift->{major} }
22 48     48 1 165 sub minor { shift->{minor} }
23 46     46 1 138 sub patch { shift->{patch} }
24 47     47 1 164 sub pre_release { shift->{pre_release} }
25 22     22 1 85 sub build_metadata { shift->{build_metadata} }
26              
27             sub new {
28 47     47 1 53166     my $class = shift;
29 47         101     my $self = bless {} => $class;
30              
31 47 100       142     $self->_init_by_version_numbers if @_ == 0;
32 47 100       1153     $self->_init_by_version_string(@_) if @_ == 1;
33 43 100       1671     $self->_init_by_version_numbers(@_) if @_ >= 2;
34              
35 43         10796     return $self;
36             }
37              
38             sub _init_by_version_string {
39 51     51   22616     my ($self, $version) = @_;
40              
41 51 100       81     die 'Invalid format' unless $version =~ qr/^@{[VERSION_FORMAT]}$/;
  51         1127  
42              
43 8     8   7881     $self->{major} = $+{major};
  8         3676  
  8         6943  
  45         643  
44 45   100     413     $self->{minor} = $+{minor} // 0;
45 45   100     271     $self->{patch} = $+{patch} // 0;
46 45         211     $self->{pre_release} = $+{pre_release};
47 45         216     $self->{build_metadata} = $+{build_metadata};
48             }
49              
50             sub _init_by_version_numbers {
51 1     1   1730     my ($self, $major, $minor, $patch, $pre_release, $build_metadata) = @_;
52              
53 1   50     36     $self->{major} = $major // 0;
54 1   50     5     $self->{minor} = $minor // 0;
55 1   50     4     $self->{patch} = $patch // 0;
56 1         3     $self->{pre_release} = $pre_release;
57 1         4     $self->{build_metadata} = $build_metadata;
58             }
59              
60             sub clean {
61 12     12 1 10095     my ($class, $version) = @_;
62              
63 12         102     $version =~ s/^\s*(.*?)\s*$/$1/; # trim
64 12         40     $version =~ s/^[=v]+//;
65              
66 12         21     return eval { $class->new($version)->as_string };
  12         46  
67             }
68              
69             sub sort {
70 1     1 1 2068     my ($class, @versions) = @_;
71 1         8     return sort { $class->new($a) <=> $class->new($b) } @versions;
  5         26  
72             }
73              
74             sub as_string {
75 14     14 1 417     my $self = shift;
76              
77 14         36     my $string = $self->major.'.'.$self->minor.'.'.$self->patch;
78 14 100       43     $string .= '-'.$self->pre_release if $self->pre_release;
79 14 100       35     $string .= '+'.$self->build_metadata if $self->build_metadata;
80              
81 14         118     return $string;
82             }
83              
84             sub _cmp {
85 17     17   5052     my ($self, $other) = @_;
86              
87 17 100       84     return $self->major <=> $other->major if $self->major != $other->major;
88 13 100       31     return $self->minor <=> $other->minor if $self->minor != $other->minor;
89 12 100       34     return $self->patch <=> $other->patch if $self->patch != $other->patch;
90 11         26     return _compare_pre_release($self->pre_release, $other->pre_release);
91             }
92              
93             sub _compare_pre_release {
94 11     11   23     my ($a, $b) = @_;
95              
96 11 100 100     48     return 1 if !defined $a && $b;
97 10 100 100     48     return -1 if $a && !defined $b;
98              
99 9 100 66     43     if ($a && $b) {
100 8         50         my @left = split /-|\./, $a;
101 8         28         my @right = split /-|\./, $b;
102 8         29         my $max = max(scalar @left, scalar @right);
103              
104 8         22         for (my $i = 0; $i < $max; ++$i) {
105 12   100     27             my $a = $left[$i] // 0;
106 12   50     19             my $b = $right[$i] // 0;
107              
108 12 100 100     49             if (looks_like_number($a) && looks_like_number($b)) {
109 4 100       51                 return $a <=> $b if $a != $b;
110                         }
111              
112 9         20             my $min = min(length $a, length $b);
113 9         20             for (my $n = 0; $n < $min; ++$n) {
114 25         38                 my $c = substr($a, $n, 1) cmp substr($b, $n, 1);
115 25 100       97                 return $c if $c != 0;
116                         }
117                     }
118                 }
119              
120 3         72     return 0;
121             }
122              
123             1;
124             __END__
125            
126             =encoding utf-8
127            
128             =head1 NAME
129            
130             SemVer::V2::Strict - Semantic version v2.0 object for Perl
131            
132             =head1 SYNOPSIS
133            
134             use SemVer::V2::Strict;
135            
136             my $v1 = SemVer::V2::Strict->new('1.0.2');
137             my $v2 = SemVer::V2::Strict->new('2.0.0-alpha.10');
138            
139             if ($v1 < $v2) {
140             print "$v1 < $v2\n"; # => '1.0.2 < 2.0.0-alpha.10'
141             }
142            
143             =head1 DESCRIPTION
144            
145             This module subclasses version to create semantic versions, as defined by the L<Semantic Versioning 2.0.0|http://semver.org/spec/v2.0.0.html> Specification.
146            
147             =head1 METHODS
148            
149             =head2 CLASS METHODS
150            
151             =head3 C<new()>
152            
153             Create new empty C<SemVer::V2::Strict> instance.
154             C<SemVer::V2::Strict-E<gt>new()> equals C<SemVer::V2::Strict-E<gt>new('0.0.0')>.
155            
156             =head3 C<new($version_string)>
157            
158             Create new C<SemVer::V2::Strict> instance from a version string.
159             C<SemVer::V2::Strict-E<gt>new('1.0.0')> equals C<SemVer::V2::Strict-E<gt>new(1, 0, 0)>.
160            
161             =head3 C<new($major, $minor = 0, $patch = 0, $pre_release = undef, $build_metadata = undef)>
162            
163             Create new C<SemVer::V2::Strict> instance from version numbers.
164             C<SemVer::V2::Strict-E<gt>new('1.0.0-alpha+100')> equals C<SemVer::V2::Strict-E<gt>new(1, 0, 0, 'alpha', '100')>.
165            
166             =head3 C<clean($version_string)>
167            
168             Clean version string. Trim spaces and C<'v'> prefix.
169            
170             =head3 C<sort(... $version_string)>
171            
172             Sort version strings.
173            
174             =head2 METHODS
175            
176             =head3 C<major>
177            
178             Get the major version number.
179            
180             =head3 C<minor>
181            
182             Return the minor version number.
183            
184             =head3 C<patch>
185            
186             Return the patch version number.
187            
188             =head3 C<pre_release>
189            
190             Return the pre_release string.
191            
192             =head3 C<build_metadata>
193            
194             Return the build_metadata string.
195            
196             =head3 C<E<lt>=E<gt>>
197            
198             Compare two C<SemVer::V2::Strict> instances.
199            
200             =head3 C<"">
201            
202             Convert a C<SemVer::V2::Strict> instance to string.
203            
204             =head3 C<as_string()>
205            
206             Convert a C<SemVer::V2::Strict> instance to string.
207            
208             =head1 SEE ALSO
209            
210             =over
211            
212             =item * L<SemVer>
213            
214             =item * L<version>
215            
216             =back
217            
218             =head1 LICENSE
219            
220             The MIT License (MIT)
221            
222             Copyright (c) 2015-2017 Pine Mizune
223            
224             Permission is hereby granted, free of charge, to any person obtaining a copy
225             of this software and associated documentation files (the "Software"), to deal
226             in the Software without restriction, including without limitation the rights
227             to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
228             copies of the Software, and to permit persons to whom the Software is
229             furnished to do so, subject to the following conditions:
230            
231             The above copyright notice and this permission notice shall be included in
232             all copies or substantial portions of the Software.
233            
234             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
235             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
236             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
237             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
238             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
239             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
240             THE SOFTWARE.
241            
242             =head1 ACKNOWLEDGEMENT
243            
244             C<SemVer::V2::Strict> is based from rosylilly's L<semver|https://github.com/rosylilly/semver>.
245             Thank you.
246            
247             =head1 AUTHOR
248            
249             Pine Mizune E<lt>pinemz@gmail.comE<gt>
250            
251             =cut
252            
253