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   567398 use strict;
  8         51  
  8         184  
3 8     8   32 use warnings;
  8         10  
  8         144  
4 8     8   30 use utf8;
  8         10  
  8         39  
5             our $VERSION = '0.17';
6              
7 8     8   570 use constant PRE_RELEASE_FORMAT => qr/(?:-(?<pre_release>[a-zA-Z0-9.\-]+))?/;
  8         17  
  8         648  
8 8     8   43 use constant BUILD_METADATA_FORMAT => qr/(?:\+(?<build_metadata>[a-zA-Z0-9.\-]+))?/;
  8         25  
  8         628  
9 8         11 use constant VERSION_FORMAT =>
10 8     8   44     qr/(?<major>[0-9]+)(?:\.(?<minor>[0-9]+))?(?:\.(?<patch>[0-9]+))?@{[PRE_RELEASE_FORMAT]}@{[BUILD_METADATA_FORMAT]}/;
  8         12  
  8         21  
  8         730  
11              
12 8     8   47 use List::Util qw/min max/;
  8         18  
  8         733  
13 8     8   50 use Scalar::Util qw/looks_like_number/;
  8         10  
  8         496  
14              
15             use overload (
16 8         55     q{""} => \&as_string,
17                 q{<=>} => \&_cmp,
18                 fallback => 1,
19 8     8   47 );
  8         20  
20              
21 62     62 1 192 sub major { shift->{major} }
22 48     48 1 118 sub minor { shift->{minor} }
23 46     46 1 92 sub patch { shift->{patch} }
24 47     47 1 98 sub pre_release { shift->{pre_release} }
25 22     22 1 46 sub build_metadata { shift->{build_metadata} }
26              
27             sub new {
28 47     47 1 31101     my $class = shift;
29 47         76     my $self = bless {} => $class;
30              
31 47 100       121     $self->_init_by_version_numbers if @_ == 0;
32 47 100       746     $self->_init_by_version_string(@_) if @_ == 1;
33 43 100       1003     $self->_init_by_version_numbers(@_) if @_ >= 2;
34              
35 43         7535     return $self;
36             }
37              
38             sub _init_by_version_string {
39 51     51   12952     my ($self, $version) = @_;
40              
41 51 100       48     die 'Invalid format' unless $version =~ qr/^@{[VERSION_FORMAT]}$/;
  51         766  
42              
43 8     8   5134     $self->{major} = $+{major};
  8         2296  
  8         4998  
  45         438  
44 45   100     184     $self->{minor} = $+{minor} // 0;
45 45   100     149     $self->{patch} = $+{patch} // 0;
46 45         137     $self->{pre_release} = $+{pre_release};
47 45         132     $self->{build_metadata} = $+{build_metadata};
48             }
49              
50             sub _init_by_version_numbers {
51 1     1   1081     my ($self, $major, $minor, $patch, $pre_release, $build_metadata) = @_;
52              
53 1   50     24     $self->{major} = $major // 0;
54 1   50     4     $self->{minor} = $minor // 0;
55 1   50     23     $self->{patch} = $patch // 0;
56 1         4     $self->{pre_release} = $pre_release;
57 1         3     $self->{build_metadata} = $build_metadata;
58             }
59              
60             sub clean {
61 12     12 1 5958     my ($class, $version) = @_;
62              
63 12         73     $version =~ s/^\s*(.*?)\s*$/$1/; # trim
64 12         23     $version =~ s/^[=v]+//;
65              
66 12         34     return eval { $class->new($version)->as_string };
  12         23  
67             }
68              
69             sub sort {
70 1     1 1 982     my ($class, @versions) = @_;
71 1         4     return sort { $class->new($a) <=> $class->new($b) } @versions;
  5         43  
72             }
73              
74             sub as_string {
75 14     14 1 243     my $self = shift;
76              
77 14         22     my $string = $self->major.'.'.$self->minor.'.'.$self->patch;
78 14 100       26     $string .= '-'.$self->pre_release if $self->pre_release;
79 14 100       18     $string .= '+'.$self->build_metadata if $self->build_metadata;
80              
81 14         60     return $string;
82             }
83              
84             sub _cmp {
85 17     17   4231     my ($self, $other) = @_;
86              
87 17 100       31     return $self->major <=> $other->major if $self->major != $other->major;
88 13 100       27     return $self->minor <=> $other->minor if $self->minor != $other->minor;
89 12 100       20     return $self->patch <=> $other->patch if $self->patch != $other->patch;
90 11         19     return _compare_pre_release($self->pre_release, $other->pre_release);
91             }
92              
93             sub _compare_pre_release {
94 11     11   17     my ($a, $b) = @_;
95              
96 11 100 100     33     return 1 if !defined $a && $b;
97 10 100 100     48     return -1 if $a && !defined $b;
98              
99 9 100 66     22     if ($a && $b) {
100 8         48         my @left = split /-|\./, $a;
101 8         24         my @right = split /-|\./, $b;
102 8         20         my $max = max(scalar @left, scalar @right);
103              
104 8         15         for (my $i = 0; $i < $max; ++$i) {
105 12   100     22             my $a = $left[$i] // 0;
106 12   50     16             my $b = $right[$i] // 0;
107              
108 12 100 100     38             if (looks_like_number($a) && looks_like_number($b)) {
109 4 100       51                 return $a <=> $b if $a != $b;
110                         }
111              
112 9         19             my $min = min(length $a, length $b);
113 9         13             for (my $n = 0; $n < $min; ++$n) {
114 25         33                 my $c = substr($a, $n, 1) cmp substr($b, $n, 1);
115 25 100       80                 return $c if $c != 0;
116                         }
117                     }
118                 }
119              
120 3         46     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-2019 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