File Coverage

blib/lib/Python/Version.pm
Criterion Covered Total %
statement 112 113 99.1
branch 42 46 91.3
condition 16 23 69.5
subroutine 19 19 100.0
pod 9 10 90.0
total 198 211 93.8


line stmt bran cond sub pod time code
1             package Python::Version;
2              
3             #ABSTRACT: Python PEP440 compatible version string parser in Perl
4              
5 1     1   158074 use 5.010;
  1         10  
6 1     1   6 use strict;
  1         2  
  1         18  
7 1     1   4 use warnings;
  1         3  
  1         42  
8              
9             our $VERSION = '0.0000_01'; #TRIAL VERSION
10              
11 1     1   409 use Sort::Versions;
  1         499  
  1         117  
12              
13             use overload (
14 1         8 'cmp' => \&vcmp,
15             '<=>' => \&vcmp,
16             fallback => 1,
17 1     1   5 );
  1         2  
18              
19 1         99 use constant RE_python_version => qr/^
20             v?
21             (?:
22             (?:(?P[0-9]+)!)? # epoch
23             (?P[0-9]+(?:\.[0-9]+)*) # release segment
24             (?P
                                          # pre-release 
25             [-_\.]?
26             (?P(a|b|c|rc|alpha|beta|pre|preview))
27             [-_\.]?
28             (?P[0-9]+)?
29             )?
30             (?P # post release
31             (?:-(?P[0-9]+))
32             |
33             (?:
34             [-_\.]?
35             (?Ppost|rev|r)
36             [-_\.]?
37             (?P[0-9]+)?
38             )
39             )?
40             (?P # dev release
41             [-_\.]?
42             (?Pdev)
43             [-_\.]?
44             (?P[0-9]+)?
45             )?
46             )
47             (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
48 1     1   162 $/x;
  1         1  
49              
50              
51             sub parse {
52 49     49 1 7053 my ( $proto, $version_str ) = @_;
53 49   33     131 my $class = ref($proto) || $proto;
54              
55 49 50       314 if ( $version_str =~ RE_python_version ) {
56             my (
57             $epoch, $release, $pre, $post, $dev, $local,
58             $pre_l, $pre_n, $post_n1, $post_l, $post_n2, $dev_n
59             )
60 1     1   372 = map { $+{$_} }
  1         341  
  1         745  
  49         79  
  588         1462  
61             qw(
62             epoch release pre post dev local
63             pre_l pre_n post_n1 post_l post_n2 dev_n
64             );
65              
66 49         111 my $self = bless { _original => $version_str }, $class;
67             $self->{_base_version} =
68 49         94 [ map { int($_) } split( /\./, $release ) ];
  122         199  
69 49 100       105 if ( defined $epoch ) {
70 6         9 $self->{_epoch} = $epoch;
71             }
72 49 100       74 if ( defined $pre ) {
    100          
73 19   50     29 $self->{_prerelease} = [ $self->_normalize_prerelease_label($pre_l),
74             int( $pre_n // 0 ) ];
75             }
76             elsif ( defined $post ) {
77             $self->{_postrelease} =
78 13   100     43 [ 'post', int( $post_n1 // $post_n2 // 0 ) ];
      100        
79             }
80 49 100       82 if ( defined $dev ) {
81 13   50     27 $self->{_devrelease} = [ 'dev', int( $dev_n // 0 ) ];
82             }
83 49 100       61 if ( defined $local ) {
84             $self->{_local_version} =
85 11         35 [ split( /[-_\.]/, $local ) ];
86             }
87 49         100 return $self;
88             }
89             else {
90 0         0 die "Cannot parse Python version string '$version_str'";
91             }
92             }
93              
94             sub _normalize_prerelease_label {
95 19     19   28 my ( $self, $label ) = @_;
96 19 100       33 return 'a' if $label eq 'alpha';
97 16 50       24 return 'b' if $label eq 'beta';
98 16 100       17 return 'rc' if ( grep { $label eq $_ } qw(c pre preview) );
  48         80  
99 4         11 return $label;
100             }
101              
102              
103             sub base_version {
104 47     47 1 49 my $self = shift;
105 47         44 return join( '.', @{ $self->{_base_version} } );
  47         118  
106             }
107              
108              
109             sub is_prerelease {
110 40     40 1 53 my $self = shift;
111 40         77 return !!( $self->{_prerelease} );
112             }
113              
114             sub is_postrelease {
115 30     30 1 30 my $self = shift;
116 30         70 return !!( $self->{_postrelease} );
117             }
118              
119             sub is_devrelease {
120 41     41 1 43 my $self = shift;
121 41         63 return !!( $self->{_devrelease} );
122             }
123              
124              
125             sub local {
126 21     21 1 21 my $self = shift;
127 21 100       26 if ( defined $self->{_local_version} ) {
128 9         11 return join( '.', @{ $self->{_local_version} } );
  9         20  
129             }
130             else {
131 12         31 return '';
132             }
133             }
134              
135              
136             sub normal {
137 15     15 1 40 my $self = shift;
138              
139 15         24 my $s = $self->public;
140 15 100       23 if ( my $local = $self->local ) {
141 5         9 $s .= "+$local";
142             }
143 15         49 return $s;
144             }
145              
146              
147             sub original {
148 12     12 1 17 my ($self) = @_;
149 12         26 return $self->{_original};
150             }
151              
152              
153             sub public {
154 15     15 1 17 my $self = shift;
155              
156 15         16 my $s = '';
157 15 100       26 if ( $self->{_epoch} ) {
158 5         8 $s .= $self->{_epoch} . '!';
159             }
160 15         23 $s .= $self->base_version;
161 15 100       23 if ( $self->is_prerelease ) {
    50          
162 10         11 $s .= join( '', @{ $self->{_prerelease} } );
  10         17  
163             }
164             elsif ( $self->is_postrelease ) {
165 5         7 $s .= '.' . join( '', @{ $self->{_postrelease} } );
  5         9  
166             }
167 15 100       24 if ( $self->is_devrelease ) {
168 10         11 $s .= '.' . join( '', @{ $self->{_devrelease} } );
  10         14  
169             }
170 15         23 return $s;
171             }
172              
173             sub vcmp {
174 17     17 0 34 my ( $left, $right ) = @_;
175 17         20 my $class = ref($left);
176 17 50       66 unless ( UNIVERSAL::isa( $right, $class ) ) {
177 17         22 $right = $class->parse($right);
178             }
179              
180 17   100     23 my ( $l_epoch, $r_epoch ) = map { $_->{_epoch} // 0 } ( $left, $right );
  34         75  
181 17         36 my $rslt_epoch = versioncmp( $l_epoch, $r_epoch );
182 17 100       322 return $rslt_epoch if ( $rslt_epoch != 0 );
183              
184             my ( $l_base, $r_base ) =
185 16         19 map { $_->base_version } ( $left, $right );
  32         41  
186 16         29 my $rslt_base = versioncmp( $l_base, $r_base );
187 16 100       519 return $rslt_base if ( $rslt_base != 0 );
188              
189             my ( $l_converted, $r_converted ) =
190 12         15 map { $_->_convert_prepostdev; } ( $left, $right );
  24         33  
191 12         40 my $rslt_converted =
192             versioncmp( join( '.', @$l_converted ), join( '.', @$r_converted ) );
193 12 100       367 return $rslt_converted if ( $rslt_converted != 0 );
194              
195 3         6 return versioncmp( $left->local, $right->local );
196             }
197              
198             sub _convert_prepostdev {
199 24     24   24 my $self = shift;
200              
201             # dev < pre < nothing < post
202 24         32 my ( $dev, $pre, $final, $post ) = ( 0, 1, 2, 3 );
203              
204 24         31 my @segments;
205 24         39 my $is_prerelease = $self->is_prerelease;
206 24         30 my $is_postrelease = $self->is_postrelease;
207 24         29 my $is_devrelease = $self->is_devrelease;
208 24 100 100     70 if ( $is_prerelease or $is_postrelease ) {
    100          
209 17 100       21 if ($is_prerelease) {
210 9   50     19 push @segments, $pre, ( $self->{_prerelease}->[1] // 0 );
211             }
212             else {
213 8   50     15 push @segments, $post, ( $self->{_postrelease}->[1] // 0 );
214             }
215 17 100       21 if ($is_devrelease) {
216 2   50     4 push @segments, $dev, ( $self->{_devrelease}->[1] // 0 );
217             }
218             else {
219 15         15 push @segments, $final;
220             }
221             }
222             elsif ($is_devrelease) {
223 1         2 push @segments, $dev;
224             }
225             else {
226 6         7 push @segments, $final;
227             }
228              
229 24         42 return \@segments;
230             }
231              
232             1;
233              
234             __END__