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