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   326648 use strict;
  8         14  
  8         218  
3 8     8   32 use warnings;
  8         8  
  8         183  
4 8     8   41 use utf8;
  8         9  
  8         43  
5             our $VERSION = '0.15';
6              
7 8     8   697 use constant PRE_RELEASE_FORMAT => qr/(?:-(?<pre_release>[a-zA-Z0-9.\-]+))?/;
  8         12  
  8         809  
8 8     8   37 use constant BUILD_METADATA_FORMAT => qr/(?:\+(?<build_metadata>[a-zA-Z0-9.\-]+))?/;
  8         16  
  8         639  
9 8         14 use constant VERSION_FORMAT =>
10 8     8   35     qr/(?<major>[0-9]+)(?:\.(?<minor>[0-9]+))?(?:\.(?<patch>[0-9]+))?@{[PRE_RELEASE_FORMAT]}@{[BUILD_METADATA_FORMAT]}/;
  8         15  
  8         21  
  8         883  
11              
12 8     8   39 use List::Util qw/min max/;
  8         11  
  8         679  
13 8     8   35 use Scalar::Util qw/looks_like_number/;
  8         11  
  8         605  
14              
15             use overload (
16 8         71     q{""} => \&as_string,
17                 q{<=>} => \&_cmp,
18                 fallback => 1,
19 8     8   35 );
  8         60  
20              
21 62     62 1 184 sub major { shift->{major} }
22 48     48 1 154 sub minor { shift->{minor} }
23 46     46 1 88 sub patch { shift->{patch} }
24 47     47 1 102 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 14348     my $class = shift;
29 47         71     my $self = bless {} => $class;
30              
31 47 100       98     $self->_init_by_version_numbers if @_ == 0;
32 47 100       500     $self->_init_by_version_string(@_) if @_ == 1;
33 43 100       564     $self->_init_by_version_numbers(@_) if @_ >= 2;
34              
35 43         6331     return $self;
36             }
37              
38             sub _init_by_version_string {
39 51     51   6821     my ($self, $version) = @_;
40              
41 51 100       51     die 'Invalid format' unless $version =~ qr/^@{[VERSION_FORMAT]}$/;
  51         1009  
42              
43 8     8   6008     $self->{major} = $+{major};
  8         2986  
  8         5714  
  45         458  
44 45   100     199     $self->{minor} = $+{minor} // 0;
45 45   100     167     $self->{patch} = $+{patch} // 0;
46 45         125     $self->{pre_release} = $+{pre_release};
47 45         138     $self->{build_metadata} = $+{build_metadata};
48             }
49              
50             sub _init_by_version_numbers {
51 1     1   823     my ($self, $major, $minor, $patch, $pre_release, $build_metadata) = @_;
52              
53 1   50     29     $self->{major} = $major // 0;
54 1   50     5     $self->{minor} = $minor // 0;
55 1   50     5     $self->{patch} = $patch // 0;
56 1         2     $self->{pre_release} = $pre_release;
57 1         4     $self->{build_metadata} = $build_metadata;
58             }
59              
60             sub clean {
61 12     12 1 5470     my ($class, $version) = @_;
62              
63 12         94     $version =~ s/^\s*(.*?)\s*$/$1/; # trim
64 12         24     $version =~ s/^[=v]+//;
65              
66 12         13     return eval { $class->new($version)->as_string };
  12         25  
67             }
68              
69             sub sort {
70 1     1 1 798     my ($class, @versions) = @_;
71 1         11     return sort { $class->new($a) <=> $class->new($b) } @versions;
  5         26  
72             }
73              
74             sub as_string {
75 14     14 1 102     my $self = shift;
76              
77 14         36     my $string = $self->major.'.'.$self->minor.'.'.$self->patch;
78 14 100       44     $string .= '-'.$self->pre_release if $self->pre_release;
79 14 100       19     $string .= '+'.$self->build_metadata if $self->build_metadata;
80              
81 14         65     return $string;
82             }
83              
84             sub _cmp {
85 17     17   2062     my ($self, $other) = @_;
86              
87 17 100       24     return $self->major <=> $other->major if $self->major != $other->major;
88 13 100       20     return $self->minor <=> $other->minor if $self->minor != $other->minor;
89 12 100       24     return $self->patch <=> $other->patch if $self->patch != $other->patch;
90 11         45     return _compare_pre_release($self->pre_release, $other->pre_release);
91             }
92              
93             sub _compare_pre_release {
94 11     11   15     my ($a, $b) = @_;
95              
96 11 100 100     63     return 1 if !defined $a && $b;
97 10 100 100     45     return -1 if $a && !defined $b;
98              
99 9 100 66     24     if ($a && $b) {
100 8         30         my @left = split /-|\./, $a;
101 8         18         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     23             my $a = $left[$i] // 0;
106 12   50     14             my $b = $right[$i] // 0;
107              
108 12 100 100     42             if (looks_like_number($a) && looks_like_number($b)) {
109 4 100       45                 return $a <=> $b if $a != $b;
110                         }
111              
112 9         15             my $min = min(length $a, length $b);
113 9         14             for (my $n = 0; $n < $min; ++$n) {
114 25         22                 my $c = substr($a, $n, 1) cmp substr($b, $n, 1);
115 25 100       82                 return $c if $c != 0;
116                         }
117                     }
118                 }
119              
120 3         39     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-2016 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