File Coverage

blib/lib/Version/Dotted.pm
Criterion Covered Total %
statement 102 102 100.0
branch 22 24 91.6
condition 9 13 69.2
subroutine 24 24 100.0
pod 6 6 100.0
total 163 169 96.4


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