File Coverage

blib/lib/Version/Dotted.pm
Criterion Covered Total %
statement 102 102 100.0
branch 23 24 95.8
condition 5 5 100.0
subroutine 23 23 100.0
pod 6 6 100.0
total 159 160 99.3


line stmt bran cond sub pod time code
1             # ---------------------------------------------------------------------- copyright and license ---
2             #
3             # file: lib/Version/Dotted.pm
4             #
5             # Copyright © 2016 Van de Bugger.
6             #
7             # This file is part of perl-Version-Dotted.
8             #
9             # perl-Version-Dotted is free software: you can redistribute it and/or modify it under the terms
10             # of the GNU General Public License as published by the Free Software Foundation, either version
11             # 3 of the License, or (at your option) any later version.
12             #
13             # perl-Version-Dotted is distributed in the hope that it will be useful, but WITHOUT ANY
14             # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15             # PURPOSE. See the GNU General Public License for more details.
16             #
17             # You should have received a copy of the GNU General Public License along with
18             # perl-Version-Dotted. If not, see .
19             #
20             # ---------------------------------------------------------------------- copyright and license ---
21              
22             #pod =for :this This is C module/class documentation. Read it first, but use one of its
23             #pod subclasses.
24             #pod
25             #pod =for :those General topics like getting source, building, installing, bug reporting and some others
26             #pod are covered in the F.
27             #pod
28             #pod =for test_synopsis my ( $v, $i, $p );
29             #pod
30             #pod =head1 SYNOPSIS
31             #pod
32             #pod use Version::Dotted; # import nothing
33             #pod use Version::Dotted 'qv'; # import qv
34             #pod
35             #pod # Construct:
36             #pod $v = Version::Dotted->new( v1.2.3 ); # same as qv( v1.2.3 )
37             #pod $v = qv( v1.2.3 ); # v1.2.3
38             #pod $v = qv( '1.2.0' ); # v1.2 (trailing zero parts ignored)
39             #pod $v = qv( 'v1' ); # v1
40             #pod
41             #pod # Access individual parts:
42             #pod $p = $v->part( $i ); # Get i-th part.
43             #pod
44             #pod # Stringify:
45             #pod $v->stringify; # "v1.2.3" (always with 'v' prefix)
46             #pod "$v"; # ditto
47             #pod
48             #pod # Bump the version:
49             #pod $v->bump( $i ); # Bump i-th part
50             #pod # and drop all parts behind i-th.
51             #pod
52             #pod # Compare:
53             #pod $v > v1.2.3;
54             #pod $v == '1.2.3';
55             #pod
56             #pod =head1 DESCRIPTION
57             #pod
58             #pod =head2 Purpose
59             #pod
60             #pod C is an official Perl module for comparing versions. For example, (starting from Perl
61             #pod 5.12.0) C with a specified version:
62             #pod
63             #pod package Assa v1.2.3;
64             #pod
65             #pod automatically assigns C<$VERSION> variable a version object (an object of C class).
66             #pod
67             #pod C is I a replacement for C. C should be used to declare
68             #pod package's version implicitly (as shown in the example above) or explicitly:
69             #pod
70             #pod package Assa;
71             #pod use version 0.77; our $VERSION = version->declare( 'v1.2.3' );
72             #pod
73             #pod C and its specialized descendants C and
74             #pod C are intended for cases where you need to I version object, for
75             #pod example, in C C plugin:
76             #pod
77             #pod sub after_release {
78             #pod my ( $self ) = @_;
79             #pod my $v = qv( $self->zilla->version ); # Version of just-release distro.
80             #pod $v->bump( 'trial' ); # Version of the next release.
81             #pod ...
82             #pod };
83             #pod
84             #pod or detect version release status, for example, in C plugin:
85             #pod
86             #pod sub provide_release_status {
87             #pod my ( $self ) = @_;
88             #pod my $v = qv( $self->zilla->version );
89             #pod return $v->is_trial ? "testing" : "stable";
90             #pod };
91             #pod
92             #pod =head2 Dotted Version
93             #pod
94             #pod Dotted (aka dotted-decimal) version is a series of parts joined with dots, each part is a cardinal
95             #pod (non-negative) integer. Every part (except the first) should be in range [0..999], the first part
96             #pod can be bigger than 999.
97             #pod
98             #pod C creates only dotted version objects.
99             #pod
100             #pod See also L.
101             #pod
102             #pod =head2 Release Status
103             #pod
104             #pod Unfortunately, Perl terminology in this area in not well-defined and not consistently used:
105             #pod
106             #pod =over
107             #pod
108             #pod =item *
109             #pod
110             #pod The C module names a version containing underscore "alpha version" and refers to CPAN.
111             #pod
112             #pod =item *
113             #pod
114             #pod C defines status as one of: C, C, and C. Word "alpha"
115             #pod is used in the description of C release, while C release is described as "beta".
116             #pod There is also requirement that C release version should not contain underscore.
117             #pod
118             #pod =item *
119             #pod
120             #pod pause.perl.org site has section named "Developer Releases" which is about releasing "code for
121             #pod testing". Such releases should either have underscore in version or "-TRIAL" suffix.
122             #pod
123             #pod =item *
124             #pod
125             #pod meta::cpan site in the list of module releases shows "DEV" after versions containing underscore.
126             #pod
127             #pod =item *
128             #pod
129             #pod C tool has C<--trial> command line option to build a "release that PAUSE will not index".
130             #pod
131             #pod =back
132             #pod
133             #pod I think using word "alpha" by C module is a confusing, because C does not provide
134             #pod any support for "beta" and "release candidate". Thus, "alpha" term is dropped in favor of more
135             #pod generic term "trial": trial could be any of "alpha", "beta", "release candidate", "unstable", or
136             #pod "testing".
137             #pod
138             #pod C does not define C method but its descendants do.
139             #pod
140             #pod =head2 Parent(s)
141             #pod
142             #pod C is heavily influenced by C, but C is I a
143             #pod subclass of C, C I a subclass of C.
144             #pod
145             #pod The class narrows C — C creates only I (aka dotted-decimal)
146             #pod version objects, support for creating decimal versions is not provided. Support for "alpha"
147             #pod versions is dropped too (subclasses provide support for "trial" versions instead).
148             #pod
149             #pod The class extends C — C objects are I.
150             #pod
151             #pod =head2 Error Reporting
152             #pod
153             #pod The class reports error by C. It gives the caller flexibility: warning may be
154             #pod either suppressed
155             #pod
156             #pod no warnings 'Version::Dotted';
157             #pod
158             #pod or made fatal:
159             #pod
160             #pod use warnings FATAL => 'Version::Dotted';
161             #pod
162             #pod =cut
163              
164             package Version::Dotted;
165              
166 3     3   34232 use strict;
  3         3  
  3         70  
167 3     3   10 use warnings;
  3         3  
  3         57  
168 3     3   11 use warnings::register;
  3         2  
  3         276  
169 3     3   11 use version 0.77 qw{};
  3         43  
  3         146  
170              
171             # ABSTRACT: TODO
172             our $VERSION = 'v0.0.0_06'; # TRIAL VERSION
173              
174 3     3   385 use parent 'version';
  3         258  
  3         18  
175             use overload (
176 3         18 'cmp' => \&_cmp,
177             '<=>' => \&_cmp,
178 3     3   2955 );
  3         2318  
179              
180 121     121   116 sub _min_len { 1 }; ## no critic ( RequireFinalReturn )
181 141     141   218 sub _max_len { 1000 }; ## no critic ( RequireFinalReturn )
182             # TODO: INTMAX?
183              
184             sub _warn {
185 28     28   30 my ( $self, $message ) = @_;
186 28         381 warnings::warnif( 'Version::Dotted', $message );
187 28         15022 return;
188             };
189              
190             # --------------------------------------------------------------------------------------------------
191              
192             #pod =Method C
193             #pod
194             #pod Constructs a new version object.
195             #pod
196             #pod $version = Version::Dotted->new( $arg );
197             #pod
198             #pod The constructor accepts one argument and creates dotted version object. An argument can be either
199             #pod integer number (C<1>), floating point number (C<1.2>), v-string (C), or string (with or
200             #pod without leading v: C<'1.2'>, C<'v1.2'>), or version object. Trailing zero parts are stripped,
201             #pod leading zeros in parts are insignificant:
202             #pod
203             #pod Version::Dotted->new( 1.2.0 ) == Version::Dotted->new( v1.2 )
204             #pod Version::Dotted->new( 1.002 ) == Version::Dotted->new( v1.2 )
205             #pod
206             #pod TODO: Issue a warning if argument is not v-string or string?
207             #pod
208             #pod Actually, C has a notion of "minimum number of parts": version object is
209             #pod maintained to have at least minimum number of parts. In C minimum number of parts
210             #pod is C<1>, but subclasses may raise the bar.
211             #pod
212             #pod =caveat Leading Zeros
213             #pod
214             #pod Leading zeros in parts are insignificant:
215             #pod
216             #pod qv( v01.02.03 ) == v1.2.3;
217             #pod qv( 1.002 ) == v1.2;
218             #pod
219             #pod However, Perl interprets numbers with leading zero as octal, so aware of:
220             #pod
221             #pod qv( 010 ) == v8;
222             #pod qv( 010.011 ) == v89; # concatenation of two numbers 010 and 011
223             #pod
224             #pod To avoid surprises stick to using v-strings or strings:
225             #pod
226             #pod qv( v010 ) == v10;
227             #pod qv( v010.011 ) == v10.10;
228             #pod qv( '010.011' ) == v10.10;
229             #pod
230             #pod =caveat Floating Point Numbers with Trailing Zeroes
231             #pod
232             #pod Perl ignores trailing zeros in floating point numbers:
233             #pod
234             #pod 1.200 == 1.2;
235             #pod
236             #pod so
237             #pod
238             #pod qv( 1.200 ) == v1.2; # not v1.200
239             #pod
240             #pod To avoid such surprises stick to using v-strings or strings:
241             #pod
242             #pod qv( v1.200 ) == v1.200;
243             #pod qv( '1.200' ) == v1.200;
244             #pod
245             #pod =cut
246              
247             sub new {
248 126     126 1 879 my ( $class, $arg ) = @_;
249 126         79 my $v;
250 126 100       116 if ( eval { $arg->isa( 'version' ) } ) {
  126         562  
251 4         15 $v = $class->declare( 0 ); # Create a new version object.
252 4         3 $v->{ version } = [ @{ $arg->{ version } } ]; # Copy version parts.
  4         12  
253             } else {
254 122 100       217 if ( not defined $arg ) {
255 1         3 $class->_warn( "Use of undefined value to construct version" );
256 1         1 $arg = 'v0';
257             };
258 122         606 $v = $class->declare( $arg );
259             };
260 126         225 return $v->_norm();
261             };
262              
263             # --------------------------------------------------------------------------------------------------
264              
265             # If $arg is a version object, return it as-is. Otherwise create a version object and return it.
266             sub _obj {
267 70     70   57 my ( $self, $arg ) = @_;
268 70 100       82 if ( not eval { $arg->isa( 'version' ) } ) {
  70         377  
269 61         84 $arg = $self->new( $arg );
270             };
271 70         77 return $arg;
272             };
273              
274             # --------------------------------------------------------------------------------------------------
275              
276             #pod =method C
277             #pod
278             #pod Returns i-th part of the version.
279             #pod
280             #pod $int = $v->part( $i ); # Get i-th part.
281             #pod
282             #pod If index is larger than actual number of version parts minus one, C is returned.
283             #pod
284             #pod Negative part index causes warning but works like index to regular Perl array: C<-1> is index
285             #pod of the last version part, C<-2> — second last, etc.
286             #pod
287             #pod =cut
288              
289             sub part {
290 25     25 1 56 my ( $self, $idx ) = @_;
291 25 100       54 $idx >= 0 or $self->_warn( "Negative version part index '$idx'" );
292 25         92 return $self->{ version }->[ $idx ];
293             };
294              
295             # --------------------------------------------------------------------------------------------------
296              
297             #pod =method C
298             #pod
299             #pod Bumps i-th version part and drops all the parts behind i-th.
300             #pod
301             #pod $v->bump( $i );
302             #pod
303             #pod If index is larger than actual number of version parts (minus one), missed parts are autovivified
304             #pod with zero values. If result of bumping is bigger than allowed upper boundary for the part (C<999>
305             #pod for all the parts except the first), warning is printed.
306             #pod
307             #pod Negative part index causes warning but works.
308             #pod
309             #pod The method returns reference to version object.
310             #pod
311             #pod $v = qv( v1.2.3 ); # v1.2.3
312             #pod $v->bump( 3 ); # v1.2.3.1
313             #pod $v->bump( 2 ); # v1.2.4
314             #pod $v->bump( 1 ); # v1.3
315             #pod $v->bump( 0 ); # v2
316             #pod
317             #pod =cut
318              
319             sub bump {
320 16     16 1 161 my ( $self, $idx ) = @_;
321 16         20 my $v = $self->{ version };
322 16 100       38 if ( $idx < - abs( @$v ) ) {
323 1         4 $self->_warn( "Invalid version part index '$idx'" );
324 1         5 return;
325             };
326 15 100       27 $idx >= 0 or $self->_warn( "Negative version part index '$idx'" );
327 15         18 ++ $v->[ $idx ];
328 15 100       24 if ( $idx == -1 ) {
329             # -1 denotes the last part, nothing to delete behind it.
330             } else {
331             # Ok, it is not the last part, let us delete everything behind it:
332 14         21 splice( @$v, $idx + 1 );
333             };
334 15         24 return $self->_norm();
335             };
336              
337             # --------------------------------------------------------------------------------------------------
338              
339             #pod =operator C=E>
340             #pod
341             #pod Compares two versions.
342             #pod
343             #pod $v <=> $other;
344             #pod
345             #pod The operator is inherited from parent's class (see L).
346             #pod However, there is a difference: if C<$other> is not a version object, it converted to a version
347             #pod object using C (I C).
348             #pod
349             #pod Other comparison operators (e. g. C>, C>, C=>, etc) are created by Perl.
350             #pod
351             #pod =operator C
352             #pod
353             #pod The same as C=E>.
354             #pod
355             #pod =cut
356              
357             sub _cmp {
358 70     70   18765 my ( $self, $other, $swap ) = @_;
359 70         119 $other = $self->_obj( $other );
360 3     3   1264 no strict 'refs'; ## no critic ( ProhibitNoStrict )
  3         4  
  3         951  
361 70         56 return &{ 'version::(cmp' }( $self, $other, $swap );
  70         425  
362             };
363              
364             # --------------------------------------------------------------------------------------------------
365              
366             # Normalize version representation.
367             sub _norm {
368 141     141   130 my ( $self ) = @_;
369 141         310 my $v = $self->{ version };
370 141         198 my $m = $self->_min_len;
371             # Make sure there are no undefined elements in the array (which can appear after `bump`):
372 141   100     579 $_ // ( $_ = 0 ) for @$v;
373             # Make sure we have at least $m parts:
374 141         226 while ( @$v < $m ) {
375 3         6 push( @$v, 0 );
376             };
377             # Drop zero parts from the end (but keep at lest $m parts):
378 141   100     469 while ( @$v > $m and $v->[ -1 ] == 0 ) {
379 108         366 -- $#$v;
380             };
381             # Update version string representation:
382 141         322 my $s = 'v' . join( '.', @$v );
383 141         146 $self->{ original } = $s;
384             # Check number of parts:
385 141 50       186 @$v <= $self->_max_len or $self->_warn( "Bad version '$s': too many parts" );
386             # Verify all the parts after the first are in range [0..999]:
387 141         279 for my $i ( 1 .. $#$v ) {
388 240 100       352 $v->[ $i ] <= 999
389             or $self->_warn( "Bad version '$s': too large part #$i '$v->[ $i ]'" );
390             };
391 141         311 return $self;
392             };
393              
394             # --------------------------------------------------------------------------------------------------
395              
396             #pod =Method C
397             #pod
398             #pod This method issues warning "Operation 'parse' is not supported" and always returns C.
399             #pod
400             #pod (The parent's method creates decimal version object. However, this class is intended to create only
401             #pod dotted-decimal version objects.)
402             #pod
403             #pod =cut
404              
405             sub parse {
406 2     2 1 404 my ( $class ) = @_;
407 2         4 $class->_warn( "Operation 'parse' is not supported" );
408 2         6 return;
409             };
410              
411             # --------------------------------------------------------------------------------------------------
412              
413             #pod =method C
414             #pod
415             #pod The method prints a warning and always returns C.
416             #pod
417             #pod =cut
418              
419             sub is_alpha {
420 2     2 1 392 my ( $self ) = @_;
421 2         4 $self->_warn( "Operation 'is_alpha' is not supported" );
422 2         6 return;
423             };
424              
425             # --------------------------------------------------------------------------------------------------
426              
427             #pod =method C
428             #pod
429             #pod The method prints a warning and always returns C.
430             #pod
431             #pod =cut
432              
433             sub numify {
434 2     2 1 377 my ( $self ) = @_;
435 2         5 $self->_warn( "operation 'numify' is not supported" );
436 2         7 return;
437             };
438              
439             # --------------------------------------------------------------------------------------------------
440              
441             #pod =method C
442             #pod
443             #pod $str = $v->stringify;
444             #pod
445             #pod The method is inherited from the parent class.
446             #pod
447             #pod Since the C class constructs only dotted version objects, result of
448             #pod stringification is always a dotted version string with leading C<'v'>, e. g.:
449             #pod
450             #pod Version::Dotted->new( 1.2 )->stringify eq 'v1.2';
451             #pod
452             #pod =operator ""
453             #pod
454             #pod The same as C.
455             #pod
456             #pod $v->stringify eq "$v";
457             #pod
458             #pod =cut
459              
460             # --------------------------------------------------------------------------------------------------
461              
462             #pod =head1 EXPORT
463             #pod
464             #pod The module exports nothing by default. The module installs C function (I a method) into
465             #pod caller namespace by explicit request:
466             #pod
467             #pod use Version::Dotted 'qv';
468             #pod
469             #pod If caller module already has C function, warning is issued and function is redefined.
470             #pod
471             #pod Note: C exports C by default, if caller package does not have C function yet.
472             #pod
473             #pod The module (unlike to C) does not play any tricks with importer's C and/or
474             #pod C.
475             #pod
476             #pod =func qv
477             #pod
478             #pod Shortcut for Cnew>.
479             #pod
480             #pod $v = Version::Dotted->new( $arg );
481             #pod $v = qv( $arv ); # ditto
482             #pod
483             #pod Note: There is I function C, C function is installed into importer
484             #pod package by explicit request, see L.
485             #pod
486             #pod =cut
487              
488             # We have to redefine parents' import. Otherwise we will export `qv` into importer namespace by
489             # default. Explicit import of `qv` is a good idea, though.
490              
491             sub import { ## no critic ( RequireArgUnpacking )
492 10     10   3669 my ( $class, @list ) = @_;
493 10         23 my $pkg = caller();
494 10         176 my %args = map( { $_ => 1 } @list );
  6         20  
495 10 100       29 if ( delete( $args{ qv } ) ) {
496 3         6 my $qv = $pkg . '::qv';
497 3     3   12 no strict 'refs'; ## no critic ( ProhibitNoStrict )
  3         4  
  3         70  
498 3     3   9 no warnings qw{ redefine prototype }; ## no critic ( ProhibitNoWarnings )
  3         3  
  3         448  
499 3 100       19 $class->_warn( "Subroutine '$qv' redefined" ) if defined &$qv;
500             *$qv = sub ($) {
501 55     55   3981 return $class->new( @_ );
502 3         21 };
503             };
504 10 100       24 if ( %args ) {
505 3         22 $class->_warn( "Bad $class import: " . join( ', ', map( { "'$_'" } keys( %args ) ) ) );
  3         34  
506             };
507 10         1448 return;
508             };
509              
510             1;
511              
512             # --------------------------------------------------------------------------------------------------
513              
514             #pod =head1 SEE ALSO
515             #pod
516             #pod =begin :list
517             #pod
518             #pod = L
519             #pod
520             #pod Parent class. It provides most of functionality, can work with decimal versions, but does not
521             #pod provide any modifiers. Release status depends on presence of underscore character in version.
522             #pod
523             #pod = L
524             #pod
525             #pod An alternative to C. It works with both decimal and dotted versions, provides modification
526             #pod operations. Release status depends on presence of underscore character in version.
527             #pod
528             #pod = L
529             #pod
530             #pod TODO
531             #pod
532             #pod = L
533             #pod
534             #pod Subclass implementing Perlish approach to Semantic Versioning.
535             #pod
536             #pod = L
537             #pod
538             #pod Subclass implementing odd/even versioning scheme.
539             #pod
540             #pod =end :list
541             #pod
542             #pod =head1 COPYRIGHT AND LICENSE
543             #pod
544             #pod Copyright (C) 2016 Van de Bugger
545             #pod
546             #pod License GPLv3+: The GNU General Public License version 3 or later
547             #pod .
548             #pod
549             #pod This is free software: you are free to change and redistribute it. There is
550             #pod NO WARRANTY, to the extent permitted by law.
551             #pod
552             #pod
553             #pod =cut
554              
555             # ------------------------------------------------------------------------------------------------
556             #
557             # file: doc/what.pod
558             #
559             # This file is part of perl-Version-Dotted.
560             #
561             # ------------------------------------------------------------------------------------------------
562              
563             #pod =encoding UTF-8
564             #pod
565             #pod =head1 WHAT?
566             #pod
567             #pod C and its subclasses complement standard C class with version
568             #pod modification operations, which can be useful in distribution release tools.
569             #pod
570             #pod =cut
571              
572             # end of file #
573             # ------------------------------------------------------------------------------------------------
574             #
575             # file: doc/why.pod
576             #
577             # This file is part of perl-Version-Dotted.
578             #
579             # ------------------------------------------------------------------------------------------------
580              
581             #pod =encoding UTF-8
582             #pod
583             #pod =head1 WHY?
584             #pod
585             #pod In my C C plugin I need to bump current distribution version.
586             #pod
587             #pod I C is an official Perl module (and also a module recommended by C) to
588             #pod compare versions:
589             #pod
590             #pod if ( version->parse( $Module::VERSION ) < '0.10.1' ) {
591             #pod plan skip_all => "Module $Module::VERSION too old";
592             #pod };
593             #pod
594             #pod
595             #pod
596             #pod When I had a need to manipulate versions, I started to use C (another module
597             #pod recommended by C) because C does not provide any method to modify version
598             #pod object. I wanted to bump version of a distribution automatically after release, and
599             #pod C did the job for me:
600             #pod
601             #pod my $v = Perl::Version->new( $self->zilla->version );
602             #pod $v->inc_alpha();
603             #pod
604             #pod (The idea is: If I just released v0.10.1, the version of the next release would be automatically
605             #pod set to v0.10.1_01. If I released v0.10.1_01, the next version would be v0.10.1_02, and so on. If I
606             #pod decided it is time to release non-trial version, I would manually set version to v0.10.2 or
607             #pod whatever else, e. g. v0.11.0 or v1.0.0.)
608             #pod
609             #pod Everything was ok. However, I accidentally found that
610             #pod
611             #pod version->parse( 'v0.10.1_01' ) > 'v0.10.2' # is true
612             #pod
613             #pod Oops. That's was quite surprising, because some time ago this expression had opposite result:
614             #pod
615             #pod version->parse( 'v0.10.1_01' ) < 'v0.10.2' # was true
616             #pod
617             #pod Little investigation shown the breaking change is in C 0.9913: earlier versions interpret
618             #pod underscore as version part delimiter ('v0.10.1_01' is the same as 'v0.10.1.1'+trial), but 0.9913
619             #pod and later versions do not ('v0.10.1_01' is the same as 'v.10.101'+trial).
620             #pod
621             #pod Ignoring underscore is probably a right thing to do, because it is the way how Perl itself
622             #pod interprets v-strings:
623             #pod
624             #pod v0.10.1_01 eq v0.10.101 # is true
625             #pod
626             #pod but it is definitely a change which makes C useless (to me).
627             #pod
628             #pod =cut
629              
630             # end of file #
631              
632              
633             # end of file #
634              
635             __END__