File Coverage

lib/Changes/Version.pm
Criterion Covered Total %
statement 383 545 70.2
branch 153 330 46.3
condition 124 291 42.6
subroutine 60 94 63.8
pod 38 38 100.0
total 758 1298 58.4


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## Changes file management - ~/lib/Changes/Version.pm
3             ## Version v0.2.3
4             ## Copyright(c) 2023 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <jack@deguest.jp>
6             ## Created 2022/12/01
7             ## Modified 2025/07/28
8             ## All rights reserved
9             ##
10             ##
11             ## This program is free software; you can redistribute it and/or modify it
12             ## under the same terms as Perl itself.
13             ##----------------------------------------------------------------------------
14             package Changes::Version;
15             BEGIN
16             {
17 20     20   92113 use strict;
  20         38  
  20         744  
18 20     20   84 use warnings;
  20         37  
  20         968  
19 20     20   101 use warnings::register;
  20         29  
  20         1102  
20 20     20   479 use parent qw( Module::Generic );
  20         299  
  20         126  
21 20     20   185374 use vars qw( $VERSION $VERSION_LAX_REGEX $DEFAULT_TYPE );
  20         57  
  20         1296  
22 20     20   115 use version ();
  20         34  
  20         13479  
23             # From version::regex
24             # Comments in the regular expression below are taken from version::regex
25 20     20   5965 our $VERSION_LAX_REGEX = qr/
26             (?<ver_str>
27             # Lax dotted-decimal version number. Distinguished by having either leading "v"
28             # or at least three non-alpha parts. Alpha part is only permitted if there are
29             # at least two non-alpha parts. Strangely enough, without the leading "v", Perl
30             # takes .1.2 to mean v0.1.2, so when there is no "v", the leading part is optional
31             (?<dotted>
32             (?<has_v>v)
33             (?<ver>
34             (?<major>[0-9]+)
35             (?:
36             (?<minor_patch>(?:\.[0-9]+)+)
37             (?:_(?<alpha>[0-9]+))?
38             )?
39             )
40             |
41             (?<ver>
42             (?<major>[0-9]+)?
43             (?<minor_patch>(?:\.[0-9]+){2,})
44             (?:_(?<alpha>[0-9]+))?
45             )
46             )
47             |
48             (?<dotted>
49             (?<dotted_numified>
50             (?<dotted_numified_under>
51             (?<ver>
52             (?<release>
53             (?<major>[0-9]+)
54             (?<minor_patch>
55             \.
56             (?<minor>[0-9]{3})
57             (?:_(?<patch>[0-9]{3}))
58             )
59             )
60             )
61             )
62             |
63             (?<ver>
64             (?<release>
65             (?<major>[0-9]+)
66             (?<minor_patch>
67             \.
68             (?<minor>0[0-9]{2})
69             (?<patch>0[0-9]{2})
70             )
71             )
72             (?:_(?<alpha>[0-9]+))?
73             )
74             )
75             )
76             |
77             # Lax decimal version number. Just like the strict one except for allowing an
78             # alpha suffix or allowing a leading or trailing decimal-point
79             (?<decimal>
80             (?<ver>(?<release>(?<major>[0-9]+) (?: (?:\.(?<minor>[0-9]+)) | \. )?) (?:_(?<alpha>[0-9]+))?)
81             |
82             (?<ver>(?:\.(?<release>(?<major>[0-9]+))) (?:_(?<alpha>[0-9]+))?)
83             )
84             )/x;
85 20         85 our $DEFAULT_TYPE = 'dotted';
86             use overload (
87             '""' => \&as_string,
88             # '=' => \&clone,
89 0     0   0 '0+' => sub{ $_[0]->numify->as_string },
90             '<=>' => \&_compare,
91             'cmp' => \&_compare,
92             'bool' => \&_bool,
93 12     12   29167 '+' => sub { return( shift->_compute( @_, { op => '+' }) ); },
94 12     12   28476 '-' => sub { return( shift->_compute( @_, { op => '-' }) ); },
95 12     12   34754 '*' => sub { return( shift->_compute( @_, { op => '*' }) ); },
96 12     12   30979 '/' => sub { return( shift->_compute( @_, { op => '/' }) ); },
97 8     8   21713 '+=' => sub { return( shift->_compute( @_, { op => '+=' }) ); },
98 8     8   15946 '-=' => sub { return( shift->_compute( @_, { op => '-=' }) ); },
99 8     8   15445 '*=' => sub { return( shift->_compute( @_, { op => '*=' }) ); },
100 8     8   16613 '/=' => sub { return( shift->_compute( @_, { op => '/=' }) ); },
101 8     8   78 '++' => sub { return( shift->_compute( @_, { op => '++' }) ); },
102 8     8   78 '--' => sub { return( shift->_compute( @_, { op => '--' }) ); },
103             # We put it here so perl won't trigger the noop overload method
104 16     16   36070 '=' => sub { $_[0] },
105 20         794 'abs' => \&_noop,
106             'nomethod' => \&_noop,
107 20     20   155 );
  20         32  
108 20         573 our $VERSION = 'v0.2.3';
109             };
110              
111 20     20   134 use strict;
  20         49  
  20         516  
112 20     20   172 use warnings;
  20         124  
  20         21162  
113              
114             sub init
115             {
116 139     139 1 174434 my $self = shift( @_ );
117 139         2094 $self->{alpha} = undef;
118             # Used for other version types
119 139         373 $self->{beta} = undef;
120 139         333 $self->{compat} = 0;
121             # What version fragment to increase/decrease by default, such as when we do operations like $v++ or $v--
122 139         358 $self->{default_frag} = 'minor';
123 139         374 $self->{extra} = [];
124 139         384 $self->{major} = undef;
125 139         416 $self->{minor} = undef;
126 139         337 $self->{original} = undef;
127 139         344 $self->{padded} = 1;
128 139         304 $self->{patch} = undef;
129 139         366 $self->{pattern} = undef;
130 139         388 $self->{pretty} = 0;
131 139         533 $self->{qv} = 0;
132             # Release candidate used by non-perl open source softwares
133 139         501 $self->{rc} = undef;
134 139         380 $self->{target} = 'perl';
135 139         370 $self->{type} = undef;
136 139         795 my $keys = [qw( alpha beta compat default_frag extra major minor original patch qv rc target type _version )];
137 139         283 my $vstr;
138             # Changes::Version->new( 'v0.1.2_3' ); or
139             # Changes::Version->new( 'v0.1.2_3', alpha => 4 ); or
140             # Changes::Version->new( 'v0.1.2_3', { alpha => 4 } ); or
141             # Changes::Version->new( major => 0, minor => 1, patch => 2, alpha => 3, qv => 1 ); or
142             # Changes::Version->new({ major => 0, minor => 1, patch => 2, alpha => 3, qv => 1 }); or
143 139 100 33     2288 if( ( @_ == 1 && ref( $_[0] ) ne 'HASH' ) ||
      33        
      66        
      33        
      33        
144             ( @_ > 1 && ref( $_[0] ) ne 'HASH' && ( ( @_ % 2 ) || ref( $_[1] ) eq 'HASH' ) ) )
145             {
146 42         106 $vstr = shift( @_ );
147 42 50 33     288 return( $self->error( "version string provided is empty." ) ) if( !defined( $vstr ) || !length( "$vstr" ) );
148             # So we can get options like debug for parser
149 42         300 my $opts = $self->_get_args_as_hash( @_ );
150 42 100 66     47710 $self->debug( $opts->{debug} ) if( exists( $opts->{debug} ) && defined( $opts->{debug} ) && length( "$opts->{debug}" ) );
      66        
151             # A version string was provided, so we parse it
152 42         334 my $v = $self->parse( $vstr );
153 42 50       195 return( $self->pass_error ) if( !defined( $v ) );
154             # And we copy the collected value as default values for our new object, which can then be overriden by additional option passed here.
155 42         777 @$self{ @$keys } = @$v{ @$keys };
156             }
157 139         420 $self->{_init_strict_use_sub} = 1;
158 139         1026 my $rv = $self->SUPER::init( @_ );
159 139 50       1404151 return( $self->pass_error ) if( !defined( $rv ) );
160 139         918 return( $self );
161             }
162              
163 418     418 1 45975 sub alpha { return( shift->reset(@_)->_set_get_number( { field => 'alpha', undef_ok => 1 }, @_ ) ); }
164              
165             sub as_string
166             {
167 309     309 1 204481 my $self = shift( @_ );
168 309 50 66     2641 if( !exists( $self->{_reset} ) ||
      33        
169             !defined( $self->{_reset} ) ||
170             !CORE::length( $self->{_reset} ) )
171             {
172 192 100 66     2788 if( exists( $self->{_cache_value} ) &&
    50 66        
      33        
173             defined( $self->{_cache_value} ) &&
174             length( $self->{_cache_value} ) )
175             {
176 68         937 return( $self->{_cache_value} );
177             }
178             elsif( defined( $self->{original} ) && length( "$self->{original}" ) )
179             {
180 124         1626 return( $self->{original}->scalar );
181             }
182             }
183 117         538 my $type = $self->type;
184 117         135734 my $str;
185 117 100 100     1335 if( ( defined( $type ) && $type eq 'dotted' ) ||
      66        
      100        
186             ( !defined( $type ) && $DEFAULT_TYPE eq 'dotted' ) )
187             {
188 59         458 $str = $self->normal( raw => 1 );
189             }
190             else
191             {
192 58         1237 my $minor = $self->minor;
193 58         100270 my $patch = $self->patch;
194 58         95212 my $fmt = $self->pattern;
195 58 100 66     50100 if( defined( $fmt ) && length( $fmt ) )
196             {
197 1         5 $str = $self->format( $fmt );
198             }
199             else
200             {
201 57 50 33     521 if( defined( $minor ) &&
      33        
202             (
203             index( $minor, '_' ) != -1 ||
204             ( length( $minor ) == 3 && substr( $minor, 0, 1 ) eq '0' ) ||
205             length( $patch // '' ||
206             $self->padded )
207             ) )
208             {
209 57         24978 $str = $self->numify( raw => 1 );
210 57 100 66     277 if( !$self->padded && index( $str, '_' ) == -1 )
211             {
212 2         2149 return( $str * 1 );
213             }
214            
215 55 100 100     51727 if( $self->pretty && index( $str, '_' ) == -1 && !( length( [split( /\./, $str )]->[1] ) % 3 ) )
      66        
216             {
217             # $str = join( '_', grep{ $_ ne ''} split( /(...)/, $str ) );
218             # Credit: <https://stackoverflow.com/questions/33442240/perl-printf-to-use-commas-as-thousands-separator>
219 3         2993 while( $str =~ s/(\d+)(\d{3})/$1\_$2/ ){};
220             }
221             }
222             else
223             {
224 0         0 my $alpha = $self->alpha;
225 0 0       0 $str = $self->major . ( defined( $minor ) ? ".${minor}" : '' ) . ( defined( $alpha ) ? "_${alpha}" : '' );
    0          
226             }
227             }
228             }
229 115         44408 $self->{_cache_value} = $str;
230 115         462 CORE::delete( $self->{_reset} );
231 115         1263 return( $str );
232             }
233              
234             {
235 20     20   157 no warnings 'once';
  20         45  
  20         146351  
236             *stringify = \&as_string;
237             }
238              
239 0     0 1 0 sub beta { return( shift->reset(@_)->_set_get_number( { field => 'beta', undef_ok => 1 }, @_ ) ); }
240              
241             # NOTE: clone() is inherited
242              
243 0     0 1 0 sub compat { return( shift->_set_get_boolean( 'compat', @_ ) ); }
244              
245 0     0 1 0 sub dec { return( shift->_inc_dec( 'dec', @_ ) ); }
246              
247 0     0 1 0 sub dec_alpha { return( shift->_inc_dec( 'dec' => 'alpha', @_ ) ); }
248              
249             # For non-perl open source softwares
250 0     0 1 0 sub dec_beta { return( shift->_inc_dec( 'dec' => 'beta', @_ ) ); }
251              
252 0     0 1 0 sub dec_major { return( shift->_inc_dec( 'dec' => 'major', @_ ) ); }
253              
254 0     0 1 0 sub dec_minor { return( shift->_inc_dec( 'dec' => 'minor', @_ ) ); }
255              
256 0     0 1 0 sub dec_patch { return( shift->_inc_dec( 'dec' => 'patch', @_ ) ); }
257              
258 192     192 1 55720 sub default_frag { return( shift->_set_get_scalar_as_object( 'default_frag', @_ ) ); }
259              
260 386     386 1 400610 sub extra { return( shift->_set_get_array_as_object( 'extra', @_ ) ); }
261              
262             sub format
263             {
264 1     1 1 2 my $self = shift( @_ );
265 1   50     5 my $fmt = shift( @_ ) ||
266             return( $self->error( "No pattern was provided to format this version." ) );
267             # NOTE: numify()
268             my $numify = sub
269             {
270 1   50 1   8 my $sep = shift( @_ ) || '';
271 1         4 my $minor = $self->minor;
272 1         1078 my $patch = $self->patch;
273 1 50 33     1060 if( defined( $minor ) && length( $minor ) )
    0 0        
274             {
275 1 50 33     9 if( defined( $patch ) && length( $patch ) )
276             {
277 1         6 return( sprintf( "%03d${sep}%03d", ( $minor + 0 ), ( $patch + 0 ) ) );
278             }
279             else
280             {
281 0         0 return( sprintf( "%03d${sep}%03d", ( $minor + 0 ), 0 ) );
282             }
283             }
284             elsif( defined( $patch ) && length( $patch ) )
285             {
286 0         0 return( sprintf( "%03d${sep}%03d", 0, ( $patch + 0 ) ) );
287             }
288 0         0 return( '' );
289 1         3 };
290              
291             # NOTE: dotted()
292             my $dotted = sub
293             {
294 0     0   0 my $comp = $self->new_array;
295 0 0       0 if( !$self->extra->is_empty )
296             {
297 0         0 $comp->push( $self->extra->list );
298             }
299 0         0 for( qw( patch minor ) )
300             {
301 0   0     0 $comp->unshift( $self->$_ // 0 );
302             }
303 0 0       0 return( $comp->is_empty ? '' : $comp->map(sub{ 0 + $_ })->join( '.' )->scalar );
  0         0  
304 1         4 };
305              
306             # NOTE: map
307             my $map =
308             {
309             # NOTE: alpha
310 0   0 0   0 'A' => sub{ return( $self->alpha // '' ); },
311             # NOTE: alpha with leading underscore
312             'a' => sub
313             {
314 1   50 1   4 my $a = $self->alpha // '';
315 1 50       1089 return( length( $a ) ? "_${a}" : '' );
316             },
317             # NOTE: dotted versions like 1.2.3.4.5
318             'D' => sub
319             {
320 0     0   0 my $dots = $dotted->();
321 0 0       0 return( length( $dots ) ? $dots : '' );
322             },
323             # NOTE: dotted versions with leading dot like .1.2.3.4.5
324             'd' => sub
325             {
326 0     0   0 my $dots = $dotted->();
327 0 0       0 return( length( $dots ) ? ( '.' . $dots ) : '' );
328             },
329             # NOTE: minor
330 0   0 0   0 'M' => sub{ return( $self->minor // '' ); },
331             # NOTE: minor with leading dot
332             'm' => sub
333             {
334 0   0 0   0 my $minor = $self->minor // '';
335 0 0       0 return( length( $minor ) ? ( '.' . $minor ) : '' );
336             },
337             # NOTE: numified without underscore. e.g.: 5.006001 -> 006001
338 0     0   0 'N' => sub{ return( $numify->( '_' ) ); },
339             # NOTE: numified without underscore and with leading dot: 5.006001 -> .006001
340             'n' => sub
341             {
342 1     1   6 my $num = $numify->( '' );
343 1 50       4305 return( length( $num ) ? ( '.' . $num ) : '' );
344             },
345             # NOTE: patch
346 0   0 0   0 'P' => sub{ return( $self->patch // '' ); },
347             # NOTE: major; R for release
348 1   50 1   3 'R' => sub{ return( $self->major // '' ); },
349             # NOTE: numified with underscore. e.g.: 5.006_001 -> 006_001
350 0     0   0 'U' => sub{ return( $numify->( '_' ) ); },
351             # NOTE: numified with underscore. e.g.: 5.006_001 -> .006_001
352             'u' => sub
353             {
354 0     0   0 my $num = $numify->( '_' );
355 0 0       0 return( length( $num ) ? ( '.' . $num ) : '' );
356             },
357 1         33 };
358 1         2 my $str;
359 1 50 0     3 if( $self->_is_array( $fmt ) )
    50 33        
360             {
361 0         0 foreach my $this ( @$fmt )
362             {
363 0 0       0 $this = substr( $this, 1 ) if( substr( $this, 0, 1 ) eq '%' );
364 0 0       0 if( !exists( $map->{ $this } ) )
365             {
366 0 0       0 warn( "Unknown formatter '$this'" ) if( $self->_is_warnings_enabled );
367 0         0 next;
368             }
369 0         0 $str .= $map->{ $this }->();
370             }
371             }
372             elsif( !ref( $fmt ) || ( ref( $fmt ) && overload::Method( $fmt, '""' ) ) )
373             {
374 1         16 ( $str = "$fmt" ) =~ s
375             {
376             \%([a-zA-Z])
377             }
378             {
379 3         1091 my $this = $1;
380 3 50       8 if( exists( $map->{ $this } ) )
381             {
382 3         10 $map->{ $this }->();
383             }
384             else
385             {
386 0         0 "\%${this}";
387             }
388             }gexs;
389             }
390             else
391             {
392 0         0 return( $self->error( "Format must be a string or an array reference of pattern components." ) );
393             }
394 1         52 return( $str );
395             }
396              
397 4     4 1 23 sub inc { return( shift->_inc_dec( 'inc', @_ ) ); }
398              
399 0     0 1 0 sub inc_alpha { return( shift->_inc_dec( 'inc' => 'alpha', @_ ) ); }
400              
401 0     0 1 0 sub inc_beta { return( shift->_inc_dec( 'inc' => 'beta', @_ ) ); }
402              
403 0     0 1 0 sub inc_major { return( shift->_inc_dec( 'inc' => 'major', @_ ) ); }
404              
405 0     0 1 0 sub inc_minor { return( shift->_inc_dec( 'inc' => 'minor', @_ ) ); }
406              
407 0     0 1 0 sub inc_patch { return( shift->_inc_dec( 'inc' => 'patch', @_ ) ); }
408              
409 0 0   0 1 0 sub is_alpha { return( shift->alpha->length > 0 ? 1 : 0 ); }
410              
411 0 0   0 1 0 sub is_qv { return( shift->qv ? 1 : 0 ); }
412              
413 333     333 1 723010 sub major { return( shift->reset(@_)->_set_get_number( { field => 'major', undef_ok => 1 }, @_ ) ); }
414              
415 408     408 1 878183 sub minor { return( shift->reset(@_)->_set_get_number( { field => 'minor', undef_ok => 1 }, @_ ) ); }
416              
417             sub normal
418             {
419 60     60 1 137 my $self = shift( @_ );
420 60         367 my $opts = $self->_get_args_as_hash( @_ );
421 60   100     92090 $opts->{raw} //= 0;
422 60         120 my $v;
423             # try-catch
424 60         109 local $@;
425             my $rv = eval
426 60         219 {
427 60         2486 my $clone = $self->clone;
428 60 100       33547 if( !$self->qv )
429             {
430 1         576 $clone->qv(1);
431             }
432 60 100       60674 if( $opts->{raw} )
433             {
434 59         399 $v = $clone->_stringify;
435             # We already did it with stringify, so we return what we got
436 59         14427 return( $v );
437             }
438             else
439             {
440 1         4 $clone->type( 'dotted' );
441 1         155 return( $clone );
442             }
443             };
444 60 50       250 if( $@ )
445             {
446 0         0 return( $self->error( "Error normalising version $v: $@" ) );
447             }
448 60         444 return( $rv );
449             }
450              
451             sub numify
452             {
453 58     58 1 162 my $self = shift( @_ );
454 58         297 my $opts = $self->_get_args_as_hash( @_ );
455 58   100     66729 $opts->{raw} //= 0;
456 58         120 my $v;
457             # try-catch
458 58         133 local $@;
459             my $rv = eval
460 58         221 {
461 58 100       231 if( $opts->{raw} )
462             {
463             # If alpha is set, such as when we convert a dotted decimal into a decimal, we need to remove it and add it back later, because version mess it up
464             # For example: version->parse( '1.0_3' )->normal yields v1.30.0 instead of v1.0.0_3 whereas version->parse( '1.0' )->normal yields correctly v1.0.0
465 57         2260 my $clone = $self->clone;
466 57         26896 my $alpha = $clone->alpha;
467 57         93796 $clone->alpha( undef );
468 57         139366 $v = $clone->_stringify;
469 57         13431 my $str = version->parse( $v )->numify;
470 57 100 66     476 $str .= "_${alpha}" if( defined( $alpha ) && length( "$alpha" ) );
471 57         530 return( $str );
472             }
473             else
474             {
475 1         24 my $new = $self->clone;
476             # This will also remove qv boolean
477 1         338 $new->type( 'decimal' );
478 1         170 my $alpha = $self->alpha;
479 1 50 50     1101 if( length( $alpha // '' ) )
480             {
481 1         8 $new->pattern( '%R%n%a' );
482             }
483             else
484             {
485 0         0 $new->pattern( '%R%n' );
486             }
487 1         720 $new->reset(1);
488 1         3 return( $new );
489             }
490             };
491 58 50       251 if( $@ )
492             {
493 0         0 return( $self->error( "Error numifying version $v: $@" ) );
494             }
495 58         332 return( $rv );
496             }
497              
498 96     96 1 1232485 sub original { return( shift->_set_get_scalar_as_object( 'original', @_ ) ); }
499              
500 85     85 1 1165 sub padded { return( shift->reset(@_)->_set_get_boolean( 'padded', @_ ) ); }
501              
502             sub parse
503             {
504 96     96 1 83886 my $self = shift( @_ );
505 96         263 my $str = shift( @_ );
506 96 50 33     961 return( $self->error( "No version string was provided." ) ) if( !defined( $str ) || !length( "$str" ) );
507 96 50 33     862 if( $] >= 5.008_001 && ref( \$str ) eq 'VSTRING' )
508             {
509 0         0 my $def = { original => $str };
510 0         0 $def->{type} = 'dotted';
511 0         0 $def->{qv} = 1;
512 0         0 my @frags = map{ ord( $_ ) } split( //, $str );
  0         0  
513 0         0 @$def{qw( major minor patch )} = splice( @frags, 0, 3 );
514 0         0 $def->{extra} = \@frags;
515 0         0 $def->{pattern} = '%R%d';
516 0         0 my $new = $self->new( %$def );
517 0         0 $new->{_version} = version->parse( $str );
518 0         0 return( $new );
519             }
520              
521 96 50       6307 if( $str =~ /^$VERSION_LAX_REGEX$/ )
522             {
523 96         3735 my $re = { %+ };
524 96         685 my $def = { original => $str };
525 96         236 my $fmt = [];
526 96 100 66     1041 if( defined( $re->{dotted} ) && length( $re->{dotted} ) )
    50 33        
527             {
528 13         49 $def->{type} = 'dotted';
529             }
530             elsif( defined( $re->{decimal} ) && length( $re->{decimal} ) )
531             {
532 83         292 $def->{type} = 'decimal';
533             }
534             else
535             {
536 0         0 return( $self->error( "No version types found. This should not happen." ) );
537             }
538 96         175 my $v;
539 96 100 66     422 $def->{qv} = 1 if( defined( $re->{has_v} ) && length( $re->{has_v} ) );
540 96         302 $def->{major} = $re->{major};
541 96 100 66     660 $def->{minor} = $re->{minor} if( defined( $re->{minor} ) && length( $re->{minor} ) );
542 96 100 66     376 $def->{alpha} = $re->{alpha} if( defined( $re->{alpha} ) && length( $re->{alpha} ) );
543 96 100       516 if( $def->{type} eq 'dotted' )
    50          
544             {
545 13         53 push( @$fmt, '%R' );
546 13 100       74 if( defined( $re->{dotted_numified} ) )
547             {
548 1         2 $def->{type} = 'decimal';
549 1         12 $v = version->parse( $re->{release} );
550             # e.g.: 5.006_001
551 1 50       5 if( defined( $re->{dotted_numified_under} ) )
552             {
553 0         0 push( @$fmt, '%u' );
554             }
555             else
556             {
557 1         3 push( @$fmt, '%n' );
558 1 50       4 push( @$fmt, '%a' ) if( defined( $re->{alpha} ) );
559             }
560 1         8 my $vstr = $v->normal;
561 1 50       208 if( $vstr =~ /^$VERSION_LAX_REGEX$/ )
562             {
563 1         29 my $re2 = { %+ };
564 1 50 33     11 if( defined( $re2->{dotted} ) && length( $re2->{dotted} ) )
565             {
566 1 50       64 if( defined( $re2->{minor_patch} ) )
567             {
568             # delete( $def->{alpha} );
569 1         5 $def->{major} = $re2->{major};
570 1         6 my @frags = split( /\./, $re2->{minor_patch} );
571 1         2 shift( @frags );
572 1         3 $def->{minor} = shift( @frags );
573 1         5 $def->{patch} = shift( @frags );
574 1         6 $def->{extra} = \@frags;
575             }
576             }
577             }
578             }
579             else
580             {
581 12         164 $v = version->parse( $re->{dotted} );
582             # Same as %M%P%E -> 5.3.4.5.6.7.8
583 12         40 push( @$fmt, '%d' );
584 12 100 66     79 push( @$fmt, '%a' ) if( defined( $def->{alpha} ) && length( $def->{alpha} ) );
585 12 50       55 if( defined( $re->{minor_patch} ) )
586             {
587 12         67 my @frags = split( /\./, $re->{minor_patch} );
588             # throw away the empty data because of the leading dot
589 12         32 shift( @frags );
590 12         37 $def->{minor} = shift( @frags );
591 12         33 $def->{patch} = shift( @frags );
592 12         51 $def->{extra} = \@frags;
593             }
594             }
595 13         70 $def->{pattern} = join( '', @$fmt );
596             }
597             elsif( $def->{type} eq 'decimal' )
598             {
599             # $def->{minor} = $re->{minor} if( defined( $re->{minor} ) );
600             # $re->{release} is the decimal version without the alpha information if it is smaller than 3
601             # This issue stems from decimal number having an underscore can either mean they have a version like
602             # 5.006_002 which would be equivalent v5.6.2 and in this case, "_002" is not an alpha information; and
603             # 1.002_03 where 03 is the alpha version and should be converted to 1.2_03, but instead becomes v1.2.30
604             # If compatibility with 'compat' is enabled, then we use the classic albeit erroneous way of converting the decimal version
605 83         271 push( @$fmt, '%R' );
606 83 100 66     578 push( @$fmt, '%m' ) if( defined( $def->{minor} ) && length( $def->{minor} ) );
607 83         364 $def->{pattern} = join( '', @$fmt );
608 83 50 33     428 if( defined( $def->{alpha} ) &&
      33        
609             length( $def->{alpha} ) < 3 &&
610             !$self->compat )
611             {
612 0         0 $v = version->parse( "$re->{release}" );
613             }
614             else
615             {
616 83         981 $v = version->parse( "$str" );
617             }
618              
619             # if( (
620             # defined( $def->{alpha} ) &&
621             # ( $self->compat || length( $def->{alpha} ) == 3 )
622             # )
623             # ||
624             # ( defined( $def->{minor} ) &&
625             # length( $def->{minor} ) >= 3 &&
626             # substr( $def->{minor}, 0, 1 ) eq '0'
627             # ) )
628             # {
629             # my $vstr = $v->normal;
630             # if( $vstr =~ /^$VERSION_LAX_REGEX$/ )
631             # {
632             # my $re2 = { %+ };
633             # if( defined( $re2->{dotted} ) && length( $re2->{dotted} ) )
634             # {
635             # if( defined( $re2->{minor_patch} ) )
636             # {
637             # # delete( $def->{alpha} );
638             # $def->{major} = $re2->{major};
639             # my @frags = split( /\./, $re2->{minor_patch} );
640             # shift( @frags );
641             # $def->{minor} = shift( @frags );
642             # $def->{patch} = shift( @frags );
643             # $def->{extra} = \@frags;
644             # }
645             # }
646             # }
647             # }
648             }
649 96         894 my $new = $self->new( %$def );
650 96 50       1229 $new->{_version} = $v if( defined( $v ) );
651 96 50       332 return( $self->pass_error ) if( !defined( $new ) );
652 96         299 CORE::delete( $new->{_reset} );
653 96         1368 return( $new );
654             }
655             else
656             {
657 0         0 return( $self->error( "Invalid version '$str'" ) );
658             }
659             }
660              
661 352     352 1 296588 sub patch { return( shift->reset(@_)->_set_get_number( { field => 'patch', undef_ok => 1 }, @_ ) ); }
662              
663 155     155 1 1693849 sub pattern { return( shift->_set_get_scalar( 'pattern', @_ ) ); }
664              
665 56     56 1 288 sub pretty { return( shift->reset(@_)->_set_get_boolean( 'pretty', @_ ) ); }
666              
667 446     446 1 232238 sub qv { return( shift->reset(@_)->_set_get_boolean( 'qv', @_ ) ); }
668              
669 0     0 1 0 sub rc { return( shift->_set_get_scalar_as_object( 'rc', @_ ) ); }
670              
671             sub reset
672             {
673 2674     2674 1 5791 my $self = shift( @_ );
674 2674 100 33     26703 if( (
      100        
675             !exists( $self->{_reset} ) ||
676             !defined( $self->{_reset} ) ||
677             !CORE::length( $self->{_reset} )
678             ) && scalar( @_ ) )
679             {
680 170         523 $self->{_reset} = scalar( @_ );
681 170 100       613 if( defined( $self->{major} ) )
682             {
683 73         387 my $str = $self->_stringify;
684             # try-catch
685 73         15773 local $@;
686             eval
687 73         185 {
688 73         1103 my $v = version->parse( "$str" );
689 73         424 $self->{_version} = $v;
690             };
691 73 50       341 if( $@ )
692             {
693 0 0       0 warn( "Warning only: error trying to get a version object from version string '$str': $@\n" ) if( $self->_warnings_is_enabled );
694             }
695             }
696             }
697 2674         30245 return( $self );
698             }
699              
700             # Credit: Data::VString
701             sub satisfy
702             {
703 7     7 1 5331 my $this = shift( @_ );
704 7 50 33     39 my $self = ( __PACKAGE__->_is_object( $this ) && $this->isa( 'Changes::Version' ) ) ? $this : $this->parse( shift( @_ ) );
705 7         37 my $predicate = shift( @_ );
706             # spaces are irrelevant
707 7         48 $predicate =~ s/[[:blank:]\h\v]+//g;
708 7         41 my $vers = $self->_version;
709 7         34 my @p = split( ',', $predicate );
710             my $cmp =
711             {
712 0     0   0 '==' => sub{ $_[0] == $_[1] },
713 1     1   9 '!=' => sub{ $_[0] != $_[1] },
714 0     0   0 '<=' => sub{ $_[0] <= $_[1] },
715 0     0   0 '>=' => sub{ $_[0] >= $_[1] },
716 1     1   7 '<' => sub{ $_[0] < $_[1] },
717 2     2   25 '>' => sub{ $_[0] > $_[1] },
718 7         133 };
719 7         31 for( @p )
720             {
721 9 100       71 if( /^(\d+([._]\d+)*)$/ )
722             {
723 3 100       65 next if( $vers == version->parse( $1 ) );
724 2         31 return(0);
725             }
726 6 100       35 if( /^([=!<>]=|[<>])(\d+([._]\d+)*)$/ )
727             {
728 4 50       52 next if( $cmp->{ $1 }->( $vers, version->parse( $2 ) ) );
729 0         0 return(0);
730             }
731 2 50       21 if( /^(\d+([._]\d+)*)\.\.(\d+([._]\d+)*)$/ )
732             {
733 2 50 33     114 if( ( version->parse( $1 ) <= $vers ) &&
734             ( $vers <= version->parse( $3 ) ) )
735             {
736 2         13 next;
737             }
738 0         0 return(0);
739             }
740 0         0 return( $self->error( "Bad predicate '$_'" ) );
741             }
742 5         77 return(1);
743             }
744              
745 0     0 1 0 sub target { return( shift->_set_get_scalar_as_object( 'target', @_ ) ); }
746              
747             sub type { return( shift->reset(@_)->_set_get_scalar_as_object({
748             field => 'type',
749             callbacks =>
750             {
751             add => sub
752             {
753 101     101   177293 my $self = shift( @_ );
754 101 100       744 if( $self->{type} eq 'decimal' )
    50          
755             {
756 88         1291 $self->{qv} = 0;
757             }
758             elsif( $self->{type} eq 'dotted' )
759             {
760             # By default
761 13         383 $self->{qv} = 1;
762             }
763 101         389 return( $self->{type} );
764             }
765             }
766 491     491 1 1789664 }, @_ ) ); }
767              
768             sub _bool
769             {
770 0     0   0 my $self = shift( @_ );
771             # return( $self->_compare( $self->_version, version->new("0"), 1 ) );
772 0         0 return( $self->_compare( $self, "0", 1 ) );
773             }
774              
775             sub _bubble
776             {
777 0     0   0 my $self = shift( @_ );
778 0         0 my $frag = shift( @_ );
779 0         0 my $val = shift( @_ );
780             # We die, because this is an internal method and those cases should not happen unless this were a design bug
781 0 0 0     0 if( !defined( $frag ) || !length( $frag ) )
    0          
    0          
782             {
783 0         0 die( "No fragment was provided to cascade" );
784             }
785             elsif( $frag !~ /^(major|minor|patch|alpha|\d+)$/ )
786             {
787 0         0 die( "Unsupported version fragment '$frag'. Only use 'major', 'minor', 'patch' or 'alpha' or a number starting from 1 (1 = major, 2 = minor, etc)." );
788             }
789             # Not for us. We bubble only when a value is negative resulting from a cascading decrease
790             # e.g. 3.12.-1 -> 3.11.0, or 3.0.-1 -> 2.9.0, or 2.-1 -> 1.0
791             elsif( $val >= 0 )
792             {
793 0         0 return;
794             }
795 0         0 my $type = $self->type;
796 0         0 my $extra = $self->extra;
797 0 0       0 my $frag_is_int = ( $frag =~ /^\d+$/ ? 1 : 0 );
798 0         0 my $frag2num =
799             {
800             major => 1,
801             minor => 2,
802             patch => 3,
803             };
804 0         0 my $num2frag =
805             {
806             1 => 'major',
807             2 => 'minor',
808             3 => 'patch',
809             };
810              
811 0 0       0 if( $frag eq 'alpha' )
812             {
813 0         0 $self->alpha( undef );
814 0         0 return;
815             }
816 0 0 0     0 die( "Fragment provided '$frag' cannot be 0." ) if( $frag_is_int && $frag == 0 );
817 0 0       0 my $level = $frag_is_int ? $frag : $frag2num->{ $frag };
818              
819             # Should not be happening
820 0 0 0     0 if( $type eq 'decimal' && $level > 2 )
821             {
822 0         0 $self->patch( undef );
823 0         0 $self->alpha( undef );
824 0         0 @$extra = ();
825 0         0 return;
826             }
827              
828 0         0 for( my $i = $level; $level >= 1; $i-- )
829             {
830 0 0       0 if( $val < 0 )
831             {
832 0         0 my $new_val = 0;
833 0 0       0 unless( $i == 1 )
834             {
835 0         0 my $up_val;
836 0         0 my $j = $i - 1;
837 0 0       0 if( exists( $num2frag->{ $j } ) )
838             {
839             my $coderef = $self->can( $num2frag->{ $j } ) ||
840 0   0     0 die( "Cannot find reference for method ", $num2frag->{ $j } );
841 0         0 $up_val = $coderef->( $self );
842             }
843             else
844             {
845 0         0 $up_val = $extra->[ $j - 4 ];
846             }
847             # Set value for next iteration
848 0   0     0 $val = ( $up_val // 0 ) - 1;
849 0 0       0 $new_val = ( $up_val > 0 ) ? 9 : 0;
850             }
851              
852 0 0       0 if( exists( $num2frag->{ $i } ) )
853             {
854             # my $coderef = $self->can( $num2frag->{ $i } ) ||
855             # die( "Cannot find reference for method ", $num2frag->{ $i } );
856             # $coderef->( $self, 0 );
857 0         0 $self->{ $num2frag->{ $i } } = $new_val;
858             }
859             else
860             {
861 0         0 $extra->[ $i - 4 ] = $new_val;
862             }
863             }
864             else
865             {
866 0 0       0 if( exists( $num2frag->{ $i } ) )
867             {
868             # my $coderef = $self->can( $num2frag->{ $i } ) ||
869             # die( "Cannot find reference for method ", $num2frag->{ $i } );
870             # $coderef->( $self, 0 );
871 0         0 $self->{ $num2frag->{ $i } } = $val;
872             }
873             else
874             {
875 0         0 $extra->[ $i - 4 ] = $val;
876             }
877 0         0 last;
878             }
879             }
880 0         0 $self->_cascade( $level );
881             }
882              
883             sub _cascade
884             {
885 84     84   356 my $self = shift( @_ );
886 84         291 my $frag = shift( @_ );
887             # We die, because this is an internal method and those cases should not happen unless this were a design bug
888 84 50 33     840 if( !defined( $frag ) || !length( $frag ) )
    50          
889             {
890 0         0 die( "No fragment was provided to cascade" );
891             }
892             elsif( $frag !~ /^(major|minor|patch|alpha|\d+)$/ )
893             {
894 0         0 die( "Unsupported version fragment '$frag'. Only use 'major', 'minor', 'patch' or 'alpha' or a number starting from 1 (1 = major, 2 = minor, etc)." );
895             }
896 84         2019 my $type = $self->type;
897 84         104266 my $extra = $self->extra;
898 84 50       84948 my $frag_is_int = ( $frag =~ /^\d+$/ ? 1 : 0 );
899 84 100 33     1139 if( $frag eq 'major' || ( $frag_is_int && $frag == 1 ) )
    100 66        
    100 33        
    50 66        
    0 33        
      66        
      0        
900             {
901 21         444 $self->alpha( undef );
902 21         98238 $self->patch(0);
903             # $self->patch( $type eq 'decimal' ? undef : 0 );
904 21         868942 $self->minor(0);
905             }
906             elsif( $frag eq 'minor' || ( $frag_is_int && $frag == 2 ) )
907             {
908 21         764 $self->alpha( undef );
909 21         102780 $self->patch(0);
910             # $self->patch( $type eq 'decimal' ? undef : 0 );
911             }
912             elsif( $frag eq 'patch' || ( $frag_is_int && $frag == 3 ) )
913             {
914 21         992 $self->alpha( undef );
915             }
916             elsif( $frag eq 'alpha' )
917             {
918             # Nothing to do
919             }
920             elsif( $type eq 'dotted' && $frag_is_int )
921             {
922 0         0 my $offset = ( $frag - 4 );
923 0         0 my $len = $extra->length;
924             # Before the fragment offset, we set the value to 0 if it is undefined or empty, and
925             # after the fragment offset everything else is reset to 0
926 0 0       0 for( my $i = 0; $i < ( $offset < $len ? $len : $offset ); $i++ )
927             {
928 0 0 0     0 if( (
      0        
      0        
929             $i < $offset &&
930             ( !defined( $extra->[$i] ) || !length( $extra->[$i] ) )
931             ) || $i > $offset )
932             {
933 0         0 $extra->[$i] = 0;
934             }
935             }
936 0         0 $self->alpha( undef );
937             }
938             }
939              
940             sub _compare
941             {
942 40     40   27269 my( $left, $right, $swap ) = @_;
943 40         116 my $class = ref( $left );
944 40 50       218 return(0) if( ${^GLOBAL_PHASE} eq 'DESTRUCT' );
945 40 50       236 unless( $left->_is_a( $right => $class ) )
946             {
947 40         1090 $right = $class->new( $right, debug => $left->debug );
948             }
949              
950 40 50       413 if( $swap )
951             {
952 0         0 ( $left, $right ) = ( $right, $left );
953             }
954            
955 40 50       198 unless( _verify( $left ) )
956             {
957 0 0       0 warn( "Invalid version for left argument: ", ( $swap ? 'format' : 'object ' . overload::StrVal( $left ) ), "." );
958 0         0 return(0);
959             }
960 40 50       138 unless( _verify( $right ) )
961             {
962 0 0       0 warn( "Invalid version for right argument: ", ( $swap ? 'format' : 'object' . overload::StrVal( $right ) ), "." );
963 0         0 return(0);
964             }
965 40         212 my $lv = $left->_version;
966 40         134 my $rv = $right->_version;
967             # TODO: better compare version. perl's version fails at comparing version that have alpha.
968             # For example, the documentation states:
969             # Note that "alpha" version objects (where the version string contains a trailing underscore segment) compare as less than the equivalent version without an underscore:
970             # $bool = version->parse("1.23_45") < version->parse("1.2345"); # TRUE
971             # However, this is not true. The above doc example will yield FALSE, not TRUE, and even the following too:
972             # perl -Mversion -lE 'my $v = version->parse("v1.2.3"); my $v2 = version->parse("v1.2.3_4"); say $v > $v2'
973             # See RT#145290: <https://rt.cpan.org/Ticket/Display.html?id=145290>
974             # return( $left->{_version} == $right->{_version} );
975             # return( $lv == $rv );
976 40         390 return( $lv <=> $rv );
977             }
978              
979             sub _compute
980             {
981 96     96   308 my $self = shift( @_ );
982 96         233 my $opts = pop( @_ );
983 96         349 my( $other, $swap, $nomethod, $bitwise ) = @_;
984 96   50     323 my $frag = $self->default_frag // 'minor';
985 96 50       97588 $frag = 'minor' if( $frag !~ /^(major|minor|patch|alpha|\d+)$/ );
986 96 50 33     3128 if( !defined( $opts ) ||
      33        
      33        
      33        
987             ref( $opts ) ne 'HASH' ||
988             !exists( $opts->{op} ) ||
989             !defined( $opts->{op} ) ||
990             !length( $opts->{op} ) )
991             {
992 0         0 die( "No argument 'op' provided" );
993             }
994 96         403 my $op = $opts->{op};
995 96         4089 my $clone = $self->clone;
996 96         50106 my $extra = $self->extra;
997 96 50       159408 my $frag_is_int = ( $frag =~ /^\d+$/ ? 1 : 0 );
998 96         1673 my $map =
999             {
1000             1 => 'major',
1001             2 => 'minor',
1002             3 => 'patch',
1003             };
1004 96         265 my $coderef;
1005 96 50 33     929 if( ( $frag_is_int && exists( $map->{ $frag } ) ) || !$frag_is_int )
      33        
1006             {
1007             $coderef = $self->can( $map->{ $frag } // $frag ) ||
1008 96   50     329 die( "Cannot find code reference for method ", ( $frag_is_int ? $map->{ $frag } : $frag ) );
1009             }
1010 96 50       2213 my $val = defined( $coderef ) ? $coderef->( $self ) : $extra->[ $frag - 4 ];
1011 96         174536 my $err;
1012 96 50       763 if( !defined( $val ) )
    50          
1013             {
1014 0         0 $val = $self->new_number(0);
1015             }
1016             elsif( !$self->_is_a( $val => 'Module::Generic::Number' ) )
1017             {
1018 0         0 $val = $self->new_number( "$val" );
1019 0 0       0 if( !defined( $val ) )
1020             {
1021 0         0 $err = $self->error->message;
1022             }
1023             }
1024 96         5035 my $n = $val->scalar;
1025 96         1259 my $eval;
1026 96 100 100     712 if( $op eq '++' || $op eq '--' )
1027             {
1028 16         46 $eval = "\$n${op}";
1029             }
1030             else
1031             {
1032 80 50       586 $eval = $swap ? ( defined( $other ) ? $other : 'undef' ) . "${op} \$n" : "\$n ${op} " . ( defined( $other ) ? $other : 'undef' );
    50          
    100          
1033             }
1034 96         8676 my $rv = eval( $eval );
1035 96 50       723 $err = $@ if( $@ );
1036 96 50       389 if( defined( $err ) )
1037             {
1038 0 0       0 warn( $err, "\n" ) if( $self->_warnings_is_enabled );
1039             # Return unchanged
1040             # return( $swap ? $other : $self );
1041 0         0 return;
1042             }
1043            
1044 96 100       294 if( $swap )
1045             {
1046 16 50       167 return( ref( $rv ) ? $rv->scalar : $rv );
1047             }
1048             else
1049             {
1050 80         186 my $new = $clone;
1051 80         217 my $new_val;
1052 80 100 100     498 if( $op eq '++' || $op eq '--' )
1053             {
1054 16         40 $new = $self;
1055 16         41 $new_val = $n;
1056             }
1057             else
1058             {
1059 64         190 $new_val = int( $rv );
1060             }
1061            
1062 80 50       243 if( $new_val < 0 )
1063             {
1064 0         0 $new->_bubble( $frag, $new_val );
1065             }
1066             else
1067             {
1068 80 50       231 if( defined( $coderef ) )
1069             {
1070 80         285 $coderef->( $new, $new_val );
1071             }
1072             else
1073             {
1074 0         0 $extra->[( $frag - 4 )] = $new_val;
1075             }
1076 80         3389928 $new->_cascade( $frag );
1077             }
1078 80         1834346 $new->reset(1);
1079 80         1470 return( $new );
1080             }
1081             }
1082              
1083             sub _inc_dec
1084             {
1085 4     4   9 my $self = shift( @_ );
1086 4   50     20 my $op = shift( @_ ) || return( $self->error( "No op was provided." ) );
1087 4 50       28 return( $self->error( "Op can only be 'inc' or 'dec'" ) ) if( $op !~ /^(inc|dec)$/ );
1088 4         9 my $frag = shift( @_ );
1089 4         8 my $unit = shift( @_ );
1090 4 50 33     51 if( !defined( $frag ) || !length( "$frag" ) )
    50          
1091             {
1092 0 0       0 return( $self->error( "No version fragment was specified to ", ( $op eq 'inc' ? 'increase' : 'decrease' ), " the version number." ) );
1093             }
1094             elsif( $frag !~ /^(major|minor|patch|alpha|\d+)$/ )
1095             {
1096 0 0       0 return( $self->error( "Unsupported version fragment '$frag' to ", ( $op eq 'inc' ? 'increase' : 'decrease' ), ". Only use 'major', 'minor', 'patch' or 'alpha' or a number starting from 1 (1 = major, 2 = minor, etc)." ) );
1097             }
1098 4 50 33     16 if( defined( $unit ) && $unit !~ /^\d+$/ )
1099             {
1100 0 0       0 return( $self->error( "Unit to ", ( $op eq 'inc' ? 'increase' : 'decrease' ), " fragment $frag value must be an integer." ) );
1101             }
1102 4         15 my $extra = $self->extra;
1103 4 50       4460 my $frag_is_int = ( $frag =~ /^\d+$/ ? 1 : 0 );
1104 4         54 my $map =
1105             {
1106             1 => 'major',
1107             2 => 'minor',
1108             3 => 'patch',
1109             };
1110 4         7 my $coderef;
1111 4 50 33     33 if( ( $frag_is_int && exists( $map->{ $frag } ) ) || !$frag_is_int )
      33        
1112             {
1113             $coderef = $self->can( $map->{ $frag } // $frag ) ||
1114 4   50     42 die( "Cannot find code reference for method ", ( $frag_is_int ? $map->{ $frag } : $frag ) );
1115             }
1116 4 50       17 my $n = defined( $coderef ) ? $coderef->( $self ) : $extra->[ $frag - 4 ];
1117             # The offset specified is out of bound
1118 4 50 33     6596 if( $frag_is_int && ( $frag - 4 ) > $extra->size )
    50 33        
1119             {
1120 0 0       0 $n = (
    0          
1121             $op eq 'inc'
1122             ? ( defined( $unit ) ? $unit : 1 )
1123             : 0
1124             );
1125             }
1126             elsif( defined( $unit ) && $unit == 1 )
1127             {
1128 0 0       0 $op eq 'inc' ? ( $n += $unit ) : ( $n -= $unit );
1129             }
1130             else
1131             {
1132 4 50       25 $op eq 'inc' ? $n++ : $n--;
1133             }
1134            
1135 4 50       39 if( defined( $coderef ) )
1136             {
1137 4         15 $coderef->( $self, $n );
1138             }
1139             else
1140             {
1141 0         0 $extra->[( $frag - 4 )] = $n;
1142             }
1143 4         142487 $self->_cascade( $frag );
1144 4         93303 $self->reset(1);
1145 4         24 return( $self );
1146             }
1147              
1148             sub _noop
1149             {
1150 0     0   0 my( $self, $other, $swap, $nomethod, $bitwise ) = @_;
1151 0 0       0 warn( "This operation $nomethod is not supported by Changes::Version\n" ) if( $self->_warnings_is_enabled );
1152             }
1153              
1154             sub _stringify
1155             {
1156 189     189   459 my $self = shift( @_ );
1157 189         1002 my $comp = $self->new_array;
1158 189         141941 my $def = {};
1159 189         694 for( qw( major minor patch alpha ) )
1160             {
1161 756         948764 $def->{ $_ } = $self->$_;
1162             }
1163 189         314693 my $type = $self->type;
1164 189 50 33     185538 $def->{major} = 0 if( !defined( $def->{major} ) || !length( $def->{major} ) );
1165 189 100 50     2497 if( $self->qv || ( ( $type // '' ) eq 'dotted' ) )
    50 66        
      50        
1166             {
1167 94 50 33     88998 $def->{minor} = 0 if( !defined( $def->{minor} ) || !length( "$def->{minor}" ) );
1168 94 50 33     1448 $def->{patch} = 0 if( !defined( $def->{patch} ) || !length( "$def->{patch}" ) );
1169             }
1170             elsif( ( $type // '' ) eq 'decimal' )
1171             {
1172             # We need to avoid the scenario where we would have a major and alpha, but not minor.
1173             # For example: 3_6 would trigger version error "Invalid version format (alpha without decimal)"
1174 95 0 33     87119 $def->{minor} = 0 if( ( !defined( $def->{minor} ) || !length( "$def->{minor}" ) ) && defined( $def->{alpha} ) && length( "$def->{alpha}" ) );
      33        
      33        
1175             }
1176 189         1909 my $ok = 0;
1177 189 50       937 if( !$self->extra->is_empty )
1178             {
1179 0         0 $ok++;
1180 0         0 $comp->push( $self->extra->list );
1181             }
1182 189         212015 for( qw( patch minor major ) )
1183             {
1184 567 50 66     4217 next if( !length( $def->{ $_ } ) && !$ok );
1185             # We stop skipping version fragments as soon as one is defined
1186 564         3229 $ok++;
1187 564         2015 $comp->unshift( $def->{ $_ } );
1188             }
1189 189 100   564   1633 my $v = ( $self->qv ? 'v' : '' ) . $comp->map(sub{ 0 + $_ })->join( '.' )->scalar;
  564         606460  
1190 189 100 66     249390 $v .= '_' . $def->{alpha} if( defined( $def->{alpha} ) && length( $def->{alpha} ) );
1191 189         1398690 return( $v );
1192             }
1193              
1194             sub _verify
1195             {
1196 80     80   210 my $self = shift( @_ );
1197 80 50       304 return(0) if( ${^GLOBAL_PHASE} eq 'DESTRUCT' );
1198 80 50 33     514 unless( defined( $self ) && ref( $self ) eq 'Changes::Version' )
1199             {
1200 0         0 return(0);
1201             }
1202 80 50 33     151 if( eval{ exists( $self->{_version} ) } &&
  80         562  
1203             Module::Generic->_is_a( $self->{_version} => 'version' ) )
1204             {
1205 80         2848 return(1);
1206             }
1207             else
1208             {
1209 0         0 return(0);
1210             }
1211             }
1212              
1213             sub _version
1214             {
1215 87     87   171 my $self = shift( @_ );
1216 87 50 33     607 if( @_ )
    50          
1217             {
1218 0         0 my $v = shift( @_ );
1219 0 0       0 return( $self->error( "Value provided is not a version object." ) ) if( !$self->_is_a( $v => 'version' ) );
1220             }
1221             elsif( !exists( $self->{_version} ) || !defined( $self->{_version} ) )
1222             {
1223 0         0 my $str = $self->_stringify;
1224             # try-catch
1225 0         0 local $@;
1226             eval
1227 0         0 {
1228 0         0 $self->{_version} = version->parse( "$str" );
1229             };
1230 0 0       0 if( $@ )
1231             {
1232 0 0       0 warn( "Warning only: error trying to get a version object from version string '$str': $@\n" ) if( $self->_warnings_is_enabled );
1233             }
1234             }
1235 87         198 return( $self->{_version} );
1236             }
1237              
1238             sub DESTROY
1239             {
1240             # <https://perldoc.perl.org/perlobj#Destructors>
1241 456     456   294378 CORE::local( $., $@, $!, $^E, $? );
1242 456         1181 my $self = CORE::shift( @_ );
1243 456 50       1551 CORE::return if( !CORE::defined( $self ) );
1244 456         4521 %$self = ();
1245 456 50       3496011 CORE::return if( ${^GLOBAL_PHASE} eq 'DESTRUCT' );
1246             };
1247              
1248             1;
1249             # NOTE: POD
1250             __END__
1251              
1252             =encoding utf-8
1253              
1254             =head1 NAME
1255              
1256             Changes::Version - Version string object class
1257              
1258             =head1 SYNOPSIS
1259              
1260             use Changes::Version;
1261             my $v = Changes::Version->new(
1262             major => 1,
1263             minor => 2,
1264             patch => 3,
1265             alpha => 4,
1266             qv => 1,
1267             debug => 2,
1268             );
1269             # or
1270             my $v = Changes::Version->new( 'v0.1.2_3' );
1271             # or
1272             my $v = Changes::Version->new( 'v0.1.2_3', alpha => 4 );
1273             # or
1274             my $v = Changes::Version->new( 'v0.1.2_3', { alpha => 4 } );
1275             # or
1276             my $v = Changes::Version->new( major => 0, minor => 1, patch => 2, alpha => 3, qv => 1 );
1277             # or
1278             my $v = Changes::Version->new({ major => 0, minor => 1, patch => 2, alpha => 3, qv => 1 });
1279             die( Changes::Version->error ) if( !defined( $v ) );
1280             my $v = Changes::Version->parse( 'v1.2.3_4' );
1281             die( Changes::Version->error ) if( !defined( $v ) );
1282             my $type = $v->type;
1283             $v->type( 'decimal' );
1284             $v->padded(0);
1285             $v->pretty(1);
1286             $v->type( 'dotted' );
1287             $v++;
1288             # Updating 'minor'
1289             say "$v"; # v1.3.0
1290             $v += 2;
1291             $v->default_frag( 'major' );
1292             $v++;
1293             say "$v"; # v2.0.0
1294             $v->inc_patch;
1295             say $v->is_alpha; # false
1296             say $v->numify; # returns new Changes::Version object
1297             say $v->normal; # returns new Changes::Version object
1298             say $v->as_string; # same as say "$v";
1299             # 5.0.6_2
1300             say $v->format( "%R%d%A" );
1301              
1302             =head1 VERSION
1303              
1304             v0.2.3
1305              
1306             =head1 DESCRIPTION
1307              
1308             This class represents a software version based on perl's definition and providing for perl recommended C<dotted decimal> and also C<decimal> types. In the future, this will be expanded to other non-perl version formats.
1309              
1310             It allows for parsing and manipulation of version objects.
1311              
1312             =head1 CONSTRUCTOR
1313              
1314             =head2 new
1315              
1316             Provided with an optional version string and an optional hash or hash reference of options and this will instantiate a new L<Changes::Version> object.
1317              
1318             If an error occurs, it will return an L<error|Module::Generic/error>, so alway check for the definedness of the returned value.
1319              
1320             my $v = Changes::Version->new(
1321             major => 1,
1322             minor => 2,
1323             patch => 3,
1324             alpha => 4,
1325             );
1326             die( Changes::Version->error ) if( !defined( $v ) );
1327              
1328             Note that if you do:
1329              
1330             my $v = Changes::Version->new( ... ) || die( Changes::Version->error );
1331              
1332             would be dangerous, because you would be assessing the return version object in a boolean context that could return false if the version was C<0>.
1333              
1334             It supports the following options that can also be accessed or changed with their corresponding method.
1335              
1336             =over 4
1337              
1338             =item * C<alpha>
1339              
1340             Specifies the alpha fragment integer of the version. See L</alpha> for more information.
1341              
1342             my $v = Changes::Version->new(
1343             major => 1,
1344             minor => 2,
1345             patch => 3,
1346             alpha => 4,
1347             );
1348             my $alpha = $v->alpha; # 4
1349             $v->alpha(7);
1350             say "$v"; # v1.2.3_7
1351              
1352             =item * C<beta>
1353              
1354             Specifies the beta fragment integer of the version. See L</beta> for more information.
1355              
1356             Currently unused and reserved for future release.
1357              
1358             =item * C<compat>
1359              
1360             Boolean. When enabled, this will ensure the version formatting is strictly compliant with the L<version> module. Default to false.
1361              
1362             =item * C<default_frag>
1363              
1364             Specifies the fragment name or integer value used by overloaded operations.
1365              
1366             my $v = Changes::Version->new( 'v1.2.3_4' );
1367             my $default = $v->default_frag; # By default 'minor'
1368             $v->default_frag( 'major' );
1369             $v++; # Version is now v2.2.3_4
1370              
1371             =item * C<extra>
1372              
1373             Specifies the array reference of version fragments beyond C<patch>
1374              
1375             my $v = Changes::Version->new(
1376             major => 1,
1377             minor => 2,
1378             patch => 3,
1379             alpha => 12,
1380             extra => [qw( 4 5 6 7 )],
1381             );
1382             say "$v"; # v1.2.3.4.5.6.7_12
1383             my $a = $v->extra; # contains 4, 5, 6, 7
1384              
1385             =item * C<major>
1386              
1387             Specifies the C<major> fragment of the version string.
1388              
1389             my $v = Changes::Version->new(
1390             major => 1,
1391             minor => 2,
1392             patch => 3,
1393             alpha => 4,
1394             );
1395             my $major = $v->major; # 1
1396             say "$v"; # v1.2.3_4
1397             $v->major(3);
1398             say "$v"; # v3.0.0
1399              
1400             =item * C<minor>
1401              
1402             Specifies the C<minor> fragment of the version string.
1403              
1404             my $v = Changes::Version->new(
1405             major => 1,
1406             minor => 2,
1407             patch => 3,
1408             alpha => 4,
1409             );
1410             my $minor = $v->minor; # 2
1411             say "$v"; # v1.2.3_4
1412             $v->minor(3);
1413             say "$v"; # v1.3.0
1414              
1415             =item * C<original>
1416              
1417             Specifies an original version string. This is normally set by L</parse> and used by L</as_string> to bypass any formatting when nothing has been changed.
1418              
1419             =item * C<padded>
1420              
1421             Specifies whether version string of type decimal should be zero padded or not. Default to true.
1422              
1423             my $v = Change::Version->new(
1424             major => 1,
1425             minor => 20,
1426             patch => 300,
1427             type => 'decimal',
1428             );
1429             say "$v"; # 1.020300
1430             $v->padded(0);
1431             say "$v"; # 1.0203
1432              
1433             =item * C<patch>
1434              
1435             Specifies the C<patch> fragment of the version string.
1436              
1437             my $v = Changes::Version->new(
1438             major => 1,
1439             minor => 2,
1440             patch => 3,
1441             alpha => 4,
1442             );
1443             my $patch = $v->patch; # 3
1444             say "$v"; # v1.2.3_4
1445             $v->patch(7);
1446             say "$v"; # v1.3.7
1447              
1448             =item * C<pretty>
1449              
1450             Specifies whether version string of type C<decimal> should be formatted with an underscore (C<_>) separating thousands in the fraction part.
1451              
1452             my $v = Change::Version->new(
1453             major => 1,
1454             minor => 20,
1455             patch => 300,
1456             type => 'decimal',
1457             pretty => 1,
1458             );
1459             say "$v"; # 1.020_300
1460             $v->pretty(0);
1461             say "$v"; # 1.020300
1462              
1463             =item * C<qv>
1464              
1465             Specifies whether version string of type C<dotted> should be formatted with the prefix C<v>. Defaults to true.
1466              
1467             my $v = Changes::Version->new(
1468             major => 1,
1469             minor => 2,
1470             patch => 3,
1471             alpha => 4,
1472             );
1473             say "$v"; # v1.2.3_4
1474             $v->qv(0);
1475             say "$v"; # 1.2.3_4
1476              
1477             =item * C<rc>
1478              
1479             Specifies the release candidate value. This is currently unused and reserved for future release.
1480              
1481             =item * C<target>
1482              
1483             Specifies the target formatting for the version string. By default this is C<perl> and is the only supported value for now. In future release, other format types will be supported, such as C<opensource>.
1484              
1485             =item * C<type>
1486              
1487             Specifies the version type. Possible values are C<dotted> for dotted decimal versions such as C<v1.2.3> or C<decimal> for decimal versions such as C<1.002003>
1488              
1489             =back
1490              
1491             =head2 parse
1492              
1493             Provided with a version string, and this will parse it and return a new L<Changes::Version> object.
1494              
1495             Currently, only 2 version types are supported: C<dotted decimal> and C<decimal>
1496              
1497             v1.2
1498             1.2345.6
1499             v1.23_4
1500             1.2345
1501             1.2345_01
1502              
1503             are all legitimate version strings.
1504              
1505             If an error occurred, this will return an L<error|Module::Generic/error>.
1506              
1507             =head1 METHODS
1508              
1509             =head2 alpha
1510              
1511             Sets or gets the C<alpha> fragment integer of the version.
1512              
1513             Setting this to C<undef> effectively removes it.
1514              
1515             Returns a L<number object|Module::Generic::Number>
1516              
1517             =head2 as_string
1518              
1519             Returns a version string properly formatted according to the C<type> set with L</type> and other parameters sets such as L</qv>, L</padded> and L</pretty>
1520              
1521             Resulting value is cached, which means the second time this is called, the cached value will be returned for speed.
1522              
1523             Any change to the version object parameters, and this will force the re-formatting of the version string.
1524              
1525             For example:
1526              
1527             my $v = Changes::Version->new( 'v1.2.3_4' );
1528             # This is a version of type 'dotted' for dotted decimal
1529             say "$v"; # v1.2.3_4
1530             # Changing the patch level
1531             $v->inc( 'patch' );
1532             # Now forced to re-format
1533             say "$v"; # v1.2.4
1534             # No change, using the cache
1535             say "$v"; # v1.2.4
1536              
1537             =head2 beta
1538              
1539             The beta fragment integer of the version. This is currently unused and reserved for future release of this class.
1540              
1541             =head2 compat
1542              
1543             Boolean. When enabled, this will ensure the version formatting is strictly compliant with the L<version> module. Default to false.
1544              
1545             =head2 dec
1546              
1547             Provided with a version fragment, and an optiona integer, and this will decrease the version fragment value by as much. If no integer is provided, the default decrement is 1.
1548              
1549             my $v = Changes::Version->new(
1550             major => 1,
1551             minor => 2,
1552             patch => 3,
1553             alpha => 4,
1554             );
1555             say "$v"; # v1.2.3_4;
1556             $v->dec( 'alpha' );
1557             say "$v"; # v1.2.3_3;
1558             $v->dec( 'patch', 2 );
1559             say "$v"; # v1.2.1
1560              
1561             my $v = Changes::Version->new( 'v1.2.3.4.5.6.7_8' );
1562             # Decrease the 5th fragment
1563             $v->dec(5);
1564             say "$v"; # v1.2.3.4.4.0.0
1565              
1566             Any change to a fragment value will reset the lower fragment values to zero. Thus:
1567              
1568             =over 4
1569              
1570             =item * changing the C<major> value will reset C<minor> and C<patch> to 0 and C<alpha> to C<undef>
1571              
1572             =item * changing the C<minor> value will reset C<patch> to 0 and C<alpha> to C<undef>
1573              
1574             =item * changing the C<patch> value will reset C<alpha> to C<undef>
1575              
1576             =item * changing the nth fragment value will reset all fragment value after that to 0
1577              
1578             =back
1579              
1580             If you pass a fragment that is an integer and it is outside the maximum number of fragments, it will automatically expand the number of version fragments and initialise the intermediary fragments to 0. A fragment as an integer starts at 1.
1581              
1582             Using the example above:
1583              
1584             $v->dec(10);
1585             say "$v"; # v1.2.3.4.5.6.7.0.0.0
1586              
1587             The 10th element is set to 0 because it does not exist, so it cannot be decreased.
1588              
1589             =head2 dec_alpha
1590              
1591             This is a shortcut for calling L</dec> on fragment C<alpha>
1592              
1593             =head2 dec_beta
1594              
1595             This is a shortcut for calling L</dec> on fragment C<beta>
1596              
1597             =head2 dec_major
1598              
1599             This is a shortcut for calling L</dec> on fragment C<major>
1600              
1601             =head2 dec_minor
1602              
1603             This is a shortcut for calling L</dec> on fragment C<minor>
1604              
1605             =head2 dec_patch
1606              
1607             This is a shortcut for calling L</dec> on fragment C<patch>
1608              
1609             =head2 default_frag
1610              
1611             my $v = Changes::Version->new( 'v1.2.3_4' );
1612             my $default = $v->default_frag; # By default 'minor'
1613             $v->default_frag( 'major' );
1614             $v++; # Version is now v2.2.3_4
1615              
1616             String. Sets or gets the name or the integer value for the version fragment. Supported value can be C<major>, C<minor>. C<patch>, C<alpha>, or an integer.
1617              
1618             Returns a L<scalar object|Module::Generic::Scalar>
1619              
1620             =head2 extra
1621              
1622             Sets or gets an array reference of version fragments starting from C<1> for C<major>, C<2> for C<minor>, C<3> for C<patch>, etc. For example:
1623              
1624             my $v = Changes::Version->new( 'v1.2.3.4.5.6.7_8' );
1625             my $a = $v->extra; # contains 4, 5, 6, 7
1626              
1627             Note that C<alpha> is not accessible via digits, but only using L</alpha>
1628              
1629             You should not be accessing this directly.
1630              
1631             Returns an L<array object|Module::Generic::Array>
1632              
1633             =head2 format
1634              
1635             my $v = Changes::Version->parse( "5.0.6_2" );
1636             say $v->format( "%R%d" ); # 5.0.6
1637              
1638             This formats the version string. It takes a string representing a pattern, or an array reference of pattern elements and returns a regular string.
1639              
1640             If an error occurred, it sets an L<error object|Module::Generic::Exception> and returns C<undef> in scalar context, or an empty list in list context.
1641              
1642             See also L</pattern> to get or set a pattern used by L</as_string>
1643              
1644             See also below the L<possible patterns|/"PATTERNS">
1645              
1646             =head2 inc
1647              
1648             Same as L</dec>, but increasing instead of decreasing.
1649              
1650             =head2 inc_alpha
1651              
1652             This is a shortcut for calling L</inc> on fragment C<alpha>
1653              
1654             =head2 inc_beta
1655              
1656             This is a shortcut for calling L</inc> on fragment C<beta>
1657              
1658             =head2 inc_major
1659              
1660             This is a shortcut for calling L</inc> on fragment C<major>
1661              
1662             =head2 inc_minor
1663              
1664             This is a shortcut for calling L</inc> on fragment C<minor>
1665              
1666             =head2 inc_patch
1667              
1668             This is a shortcut for calling L</inc> on fragment C<patch>
1669              
1670             =head2 is_alpha
1671              
1672             Returns true if L</alpha> has a value set.
1673              
1674             =head2 is_qv
1675              
1676             Returns true if L</qv> is set to true, false otherwise.
1677              
1678             =head2 major
1679              
1680             Sets or gets the C<major> fragment of the version string.
1681              
1682             my $v = Changes::Version->new( 'v1.2.3_4' );
1683             my $major = $v->major; # 1
1684             $v->major(3);
1685             say "$v"; # v3.2.3_4
1686              
1687             Setting this to C<undef> effectively removes it.
1688              
1689             Returns a L<number object|Module::Generic::Number>
1690              
1691             =head2 minor
1692              
1693             Sets or gets the C<minor> fragment of the version string.
1694              
1695             my $v = Changes::Version->new( 'v1.2.3_4' );
1696             my $minor = $v->minor; # 2
1697             $v->minor(3);
1698             say "$v"; # v1.3.3_4
1699              
1700             Setting this to C<undef> effectively removes it.
1701              
1702             Returns a L<number object|Module::Generic::Number>
1703              
1704             =head2 normal
1705              
1706             Returns a new L<Changes::Version> object as a normalised version, which is a dotted decimal format with the C<v> prefix.
1707              
1708             If an error occurred, an L<error|Module::Generic/error> is returned.
1709              
1710             =head2 numify
1711              
1712             Returns a new L<Changes::Version> object as a number, which represent a decimal-type version
1713              
1714             Contrary to L<version> if there is an C<alpha> value set, it will add it to the numified version.
1715              
1716             my $v = Changes::Version->new(
1717             major => 1,
1718             minor => 2,
1719             patch => 3,
1720             alpha => 4,
1721             );
1722             say $v->numify; # 1.002003_4
1723              
1724             L<version> would yields a different, albeit wrong result:
1725              
1726             perl -Mversion -lE 'say version->parse("v1.2.3_4")->numify'
1727              
1728             would wrongly return C<1.002034> and not C<1.002003_4>
1729              
1730             perl -Mversion -lE 'say version->parse("1.002034")->normal'
1731              
1732             then yields C<v1.2.34>
1733              
1734             If an error occurred, an L<error|Module::Generic/error> is returned.
1735              
1736             =head2 original
1737              
1738             Sets or gets the original string. This is set by L</parse>
1739              
1740             Returns a L<scalar object|Module::Generic::Scalar>
1741              
1742             =head2 padded
1743              
1744             Boolean. Sets or ges whether the resulting version string of type C<decimal> should be '0' padded or not. Default to pad with zeroes decimal numbers.
1745              
1746             For example:
1747              
1748             my $v = Changes::Version->new(
1749             major => 1,
1750             minor => 2,
1751             patch => 30,
1752             type => 'decimal',
1753             padded => 1,
1754             );
1755             say "$v"; # 1.002030
1756             $v->padded(0);
1757             say "$v"; # 1.00203
1758              
1759             Returns a L<boolean object|Module::Generic::Boolean>
1760              
1761             =head2 patch
1762              
1763             Sets or gets the C<patch> fragment of the version string.
1764              
1765             my $v = Changes::Version->new( 'v1.2.3_4' );
1766             my $patch = $v->patch; # 3
1767             $v->patch(5);
1768             say "$v"; # v1.3.5_4
1769              
1770             Returns a L<number object|Module::Generic::Number>
1771              
1772             =head2 pattern
1773              
1774             Sets or gets a format pattern. This returns a regular string, or C<undef> if no pattern has been set.
1775              
1776             See also the L<list of patterns|/"PATTERNS">
1777              
1778             =head2 pretty
1779              
1780             Boolean. When enabled, this will render version number for decimal type a bit cleaner by separating blocks of 3 digits by an underscore (C<_>). This does not work on dotted decimal version numbers such as C<v1.2.3> or on version that have an C<alpha> set up.
1781              
1782             my $v = Changes::Version->new(
1783             major => 1,
1784             minor => 2,
1785             patch => 30,
1786             type => 'decimal',
1787             );
1788              
1789             Returns a L<boolean object|Module::Generic::Boolean>
1790              
1791             =head2 qv
1792              
1793             Boolean. When enabled, this will prepend the dotted decimal version strings with C<v>. This is true by default.
1794              
1795             my $v = Changes::Version->new(
1796             major => 1,
1797             minor => 2,
1798             patch => 3,
1799             alpha => 4,
1800             );
1801             say "$v"; # v1.2.3_4
1802             $v->qv(0);
1803             say "$v"; # 1.2.3_4
1804              
1805             Returns a L<boolean object|Module::Generic::Boolean>
1806              
1807             =head2 rc
1808              
1809             Sets or gets the release candidate value. This is currently unused and reserved for future releases.
1810              
1811             Returns a L<scalar object|Module::Generic::Scalar>
1812              
1813             =for Pod::Coverage reset
1814              
1815             =head2 satisfy
1816              
1817             $v->satisfy( $predicate );
1818              
1819             $v = Changes::Version->parse( '0.1.1' );
1820             $v->satisfy( '0.1.1' ); # true
1821             $v->satisfy( '0.1.1', '> 0, < 0.2, != 0.1.0' ); # true
1822             $v = Changes::Version->parse( '0.2.4' );
1823             $v->satisfy( '0.2.5..0.3.4' ); # false
1824             # or, using it as a class function:
1825             Changes::Version->satisfy( '0.1.1', '0.1.1' ); # true
1826             Changes::Version->satisfy( '0.1.1', '> 0, < 0.2, != 0.1.0' ); # true
1827             Changes::Version->satisfy( '0.2.4', '0.2.5..0.3.4' ); # false
1828              
1829             Determines if a v-string satisfy a predicate. The predicate is a list of simple predicates, each one must be satisfied (that is, an I<and>). Simple predicates takes one of three forms:
1830              
1831             '0.1.2' - exact match
1832             '>= 3.14.15' - (relational operator) (v-string)
1833             '5.6 .. 10.8' - meaning '>= 5.6, <= 10.8'
1834              
1835             A grammar for predicates in L<Parse::RecDescent>-like syntax is:
1836              
1837             <p> : <p0> (',' <p>)*
1838              
1839             <p0>: <v-string> # the same as '==' <v-string>
1840             | <op> <v-string>
1841             | <v-string> '..' <v-string> # the same as ">= <v-string1>, <= <v-string2>"
1842              
1843             <op>: '==' | '!=' | '<=' | '>=' | '<' | '>'
1844              
1845             Spaces are irrelevant in predicates.
1846              
1847             =head2 stringify
1848              
1849             This is an alias for L</as_string>
1850              
1851             =head2 target
1852              
1853             Sets or gets the target format. By default this is C<perl>. This means that L</as_string> will format the version string for C<perl>. In future release of this class, other format wil be supported, such as C<opensource>
1854              
1855             Returns a L<scalar object|Module::Generic::Scalar>
1856              
1857             =head2 type
1858              
1859             Sets or gets the version type. Currently, supported values are C<dotted> for dotted decimal versions such as C<v1.2.3>, and C<decimal> for decimal versions such as C<1.002003>.
1860              
1861             Returns a L<scalar object|Module::Generic::Scalar>
1862              
1863             =head1 OVERLOADED OPERATIONS
1864              
1865             The following operations are overloaded, and internally relies on L<version> to return the value. See also L<overload> for more information.
1866              
1867             Note that calling the version object with any operations other than those listed below will trigger a warning, if warnings are enabled with L<warnings> and C<undef> is return in scalar context or an empty list in list context.
1868              
1869             =over 4
1870              
1871             =item * C<stringification>
1872              
1873             Returns value from L</as_string>
1874              
1875             =item * C<0+>
1876              
1877             Returns value from L</numify>
1878              
1879             =item * C<< <=> >>
1880              
1881             Compares two versions. If the other version being compared is not a L<Changes::Version>, it is made one before comparison actually occurs.
1882              
1883             Note that, C<version> core module L<states in its documentation|version/"How to compare version objects"> that: "alpha" version objects (where the version string contains a trailing underscore segment) compare as less than the equivalent version without an underscore."
1884              
1885             $bool = version->parse("1.23_45") < version->parse("1.2345"); # TRUE
1886              
1887             However, as of perl v5.10, this is not true. The above will actually return false, not true. And so will the following:
1888              
1889             perl -Mversion -lE 'say version->parse("v1.002003") > version->parse("v1.002003_4");'
1890              
1891             This is on my bucket list of things to improve.
1892              
1893             =item * C<cmp>
1894              
1895             Same as above.
1896              
1897             =item * C<bool>
1898              
1899             =item * C<+>, C<->, C<*>, C</>
1900              
1901             When performing those operations, it will use the value of the fragment of the version set with L</default_frag>, which, by default, is C<minor>.
1902              
1903             It returns a new L<Changes::Version> object reflecting the new version value. However, if the operation is swapped, with the version object on the right-hand side instead of the left-hand side, this will return a regular number.
1904              
1905             my $vers = Changes::Version->new( 'v1.2.3_4' );
1906             my $new_version_object = $vers + 2; # Now v1.4.3_4 (minor has been bumped up by 2)
1907             $vers->default_frag( 'major' );
1908             my $new_version_object = $vers + 2; # Now v3.2.3_4 (this time, 'major' was increased)
1909              
1910             But, when swapped:
1911              
1912             my $vers = Changes::Version->new( 'v1.2.3_4' );
1913             my $n = 3 + $vers; # yields 5 (using the 'minor' fragment by default)
1914             $vers->default_frag( 'major' );
1915             my $n = 3 + $vers; # yields 4 (this time, using the 'major' fragment)
1916              
1917             =item * C<+=>, C<-=>, C<*=>, C</=>
1918              
1919             In this operations, it modifies the current object with the operand provided and returns the current object, instead of creating a new one.
1920              
1921             my $vers = Changes::Version->new( 'v1.2.3_4' );
1922             # By default, using the 'minor' fragment
1923             $vers += 1; # version is now v2.2.3_4
1924             $vers->default_frag( 'alpha' );
1925             $vers /= 2; # version is now v1.2.3_2
1926              
1927             =item * C<++>, C<-->
1928              
1929             When using those operations, it updates the current object directly and returns it. For example:
1930              
1931             my $vers = Changes::Version->new( 'v1.2.3_4' );
1932             # By default, using the 'minor' fragment
1933             $vers++; # version is now v1.3.3_4
1934              
1935             =back
1936              
1937             =head1 PATTERNS
1938              
1939             The following patterns can be used to format the version string.
1940              
1941             =over 4
1942              
1943             =item * C<%A>
1944              
1945             my $v = Changes::Version->parse( "5.0.6_2" );
1946             say $v->format( '%A' ); # _2
1947              
1948             This will return the alpha version, if any, prepended with an underscore.
1949              
1950             If there is no alpha version, it returns an empty string.
1951              
1952             =item * C<%a>
1953              
1954             my $v = Changes::Version->parse( "5.0.6_2" );
1955             say $v->format( '%a' ); # 2
1956              
1957             This will return the C<alpha> fragment value, if any.
1958              
1959             If there is no C<alpha> fragment value, it returns an empty string.
1960              
1961             =item * C<%D>
1962              
1963             my $v = Changes::Version->parse( "5.0.6.1.2.3.4_2" );
1964             say $v->format( '%D' ); # 0.6.1.2.3.4
1965             my $v = Changes::Version->parse( "5.0.6" );
1966             say $v->format( '%D' ); # 0.6
1967              
1968             This will return the C<minor>, C<patch>, and any extra fragments.
1969              
1970             This is designed for dotted-decimal types, and C<minor>, and C<patch> will always return a number, possibly C<0>
1971              
1972             =item * C<%d>
1973              
1974             my $v = Changes::Version->parse( "5.0.6.1.2.3.4_2" );
1975             say $v->format( '%D' ); # .0.6.1.2.3.4
1976             my $v = Changes::Version->parse( "5.0.6" );
1977             say $v->format( '%D' ); # .0.6
1978              
1979             This is similar to C<%D>, but will prepend a dot if the value is not null.
1980              
1981             This is designed so you can write:
1982              
1983             my $v = Changes::Version->parse( "5.0.6.1.2.3.4_2" );
1984             say $v->format( '%R%d%A' ); # 5.0.6.1.2.3.4_2
1985             my $v = Changes::Version->parse( "5" );
1986             say $v->format( '%R%d%A' ); # 5.0.0
1987              
1988             =item * C<%M>
1989              
1990             my $v = Changes::Version->parse( "5.2.6_3" );
1991             say $v->format( '%M' ); # 2
1992              
1993             This returns the C<minor> part of the version.
1994              
1995             If there is no C<minor> fragment value, it returns an empty string.
1996              
1997             =item * C<%m>
1998              
1999             my $v = Changes::Version->parse( "5.2.6_3" );
2000             say $v->format( '%m' ); # .2
2001              
2002             This is similar to C<%M>, but will prepend a dot if the value is not null.
2003              
2004             =item * C<%N>
2005              
2006             my $v = Changes::Version->parse( "5.2.6" ):
2007             say $v->format( '%R.%N' ); # 5.002006
2008              
2009             This returns the C<minor> and C<patch> value of a dotted-decimal version as numified version.
2010              
2011             =item * C<%n>
2012              
2013             my $v = Changes::Version->parse( "5.2.6" ):
2014             say $v->format( '%R%n' ); # 5.002006
2015             say $v->format( '%n' ); # .002006
2016              
2017             This is similar to C<%N>, but will prepend a dot if the value is not null.
2018              
2019             =item * C<%P>
2020              
2021             This returns the C<patch> fragment value of the version, if any.
2022              
2023             If there is no C<patch> fragment value, it returns an empty string.
2024              
2025             =item * C<%R>
2026              
2027             This returns the C<major> fragment value of the version, if any.
2028              
2029             If there is no C<major> fragment value, it returns an empty string.
2030              
2031             =item * C<%U>
2032              
2033             my $v = Changes::Version->parse( "5.2.6" ):
2034             say $v->format( '%R.%U' ); # 5.002_006
2035             say $v->format( '%U' ); # 002_006
2036              
2037             This returns the C<minor> and C<patch> value of a dotted-decimal version as numified version using the underscore to separate the C<minor> and the C<patch> fragments.
2038              
2039             =item * C<%u>
2040              
2041             my $v = Changes::Version->parse( "5.2.6" ):
2042             say $v->format( '%R%u' ); # 5.002_006
2043             say $v->format( '%u' ); # .002_006
2044              
2045             This is similar to C<%U>, but will prepend a dot if the value is not null.
2046              
2047             =back
2048              
2049             =head1 AUTHOR
2050              
2051             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
2052              
2053             =head1 SEE ALSO
2054              
2055             L<Changes>, L<Changes::Release>, L<Changes::Group>, L<Changes::Change> and L<Changes::NewLine>
2056              
2057             L<version>, L<Perl::Version>, L<version::Internals>, L<Data::VString>, L<perldata/"Version Strings">
2058              
2059             L<CPAN::Meta::Spec/"Version Formats">
2060              
2061             L<http://www.modernperlbooks.com/mt/2009/07/version-confusion.html>
2062              
2063             L<https://xdg.me/version-numbers-should-be-boring/>
2064              
2065             L<https://en.wikipedia.org/wiki/Software_versioning>
2066              
2067             =head1 COPYRIGHT & LICENSE
2068              
2069             Copyright(c) 2022 DEGUEST Pte. Ltd.
2070              
2071             All rights reserved
2072              
2073             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
2074              
2075             =cut