File Coverage

lib/Changes/Version.pm
Criterion Covered Total %
statement 381 769 49.5
branch 149 730 20.4
condition 130 400 32.5
subroutine 58 81 71.6
pod 35 35 100.0
total 753 2015 37.3


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## Changes file management - ~/lib/Changes/Version.pm
3             ## Version v0.1.0
4             ## Copyright(c) 2022 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <jack@deguest.jp>
6             ## Created 2022/12/01
7             ## Modified 2022/12/01
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   105105 use strict;
  20         49  
  20         725  
18 20     20   114 use warnings;
  20         51  
  20         562  
19 20     20   125 use warnings::register;
  20         199  
  20         2212  
20 20     20   556 use parent qw( Module::Generic );
  20         320  
  20         138  
21 20     20   12481005 use vars qw( $VERSION $VERSION_LAX_REGEX $DEFAULT_TYPE );
  20         54  
  20         1098  
22 20     20   134 use version ();
  20         45  
  20         353  
23 20     20   98 use Nice::Try;
  20         32  
  20         190  
24             # From version::regex
25             # Comments in the regular expression below are taken from version::regex
26 20     20   5328 our $VERSION_LAX_REGEX = qr/
27             (?<ver_str>
28             # Lax dotted-decimal version number. Distinguished by having either leading "v"
29             # or at least three non-alpha parts. Alpha part is only permitted if there are
30             # at least two non-alpha parts. Strangely enough, without the leading "v", Perl
31             # takes .1.2 to mean v0.1.2, so when there is no "v", the leading part is optional
32             (?<dotted>
33             (?<has_v>v)
34             (?<ver>
35             (?<major>[0-9]+)
36             (?:
37             (?<minor_patch>(?:\.[0-9]+)+)
38             (?:_(?<alpha>[0-9]+))?
39             )?
40             )
41             |
42             (?<ver>
43             (?<major>[0-9]+)?
44             (?<minor_patch>(?:\.[0-9]+){2,})
45             (?:_(?<alpha>[0-9]+))?
46             )
47             )
48             |
49             # Lax decimal version number. Just like the strict one except for allowing an
50             # alpha suffix or allowing a leading or trailing decimal-point
51             (?<decimal>
52             (?<ver>(?<release>(?<major>[0-9]+) (?: (?:\.(?<minor>[0-9]+)) | \. )?) (?:_(?<alpha>[0-9]+))?)
53             |
54             (?<ver>(?:\.(?<release>(?<major>[0-9]+))) (?:_(?<alpha>[0-9]+))?)
55             )
56             )/x;
57 20         48 our $DEFAULT_TYPE = 'dotted';
58             use overload (
59             '""' => \&as_string,
60             # '=' => \&clone,
61             '0+' => \&numify,
62             '<=>' => \&_compare,
63             'cmp' => \&_compare,
64             'bool' => \&_bool,
65 12     12   13803 '+' => sub { return( shift->_compute( @_, { op => '+' }) ); },
66 12     12   14140 '-' => sub { return( shift->_compute( @_, { op => '-' }) ); },
67 12     12   13485 '*' => sub { return( shift->_compute( @_, { op => '*' }) ); },
68 12     12   13653 '/' => sub { return( shift->_compute( @_, { op => '/' }) ); },
69 8     8   9078 '+=' => sub { return( shift->_compute( @_, { op => '+=' }) ); },
70 8     8   9111 '-=' => sub { return( shift->_compute( @_, { op => '-=' }) ); },
71 8     8   8944 '*=' => sub { return( shift->_compute( @_, { op => '*=' }) ); },
72 8     8   9042 '/=' => sub { return( shift->_compute( @_, { op => '/=' }) ); },
73 8     8   75 '++' => sub { return( shift->_compute( @_, { op => '++' }) ); },
74 8     8   121 '--' => sub { return( shift->_compute( @_, { op => '--' }) ); },
75             # We put it here so perl won't trigger the noop overload method
76 16     16   18417 '=' => sub { $_[0] },
77 20         628 'abs' => \&_noop,
78             'nomethod' => \&_noop,
79 20     20   30443293 );
  20         47  
80 20         433 our $VERSION = 'v0.1.0';
81             };
82              
83 20     20   161 use strict;
  20         46  
  20         569  
84 20     20   109 use warnings;
  20         44  
  20         12853  
85              
86             sub init
87             {
88 132     132 1 20767 my $self = shift( @_ );
89 132         1819 $self->{alpha} = undef;
90             # Used for other version types
91 132         362 $self->{beta} = undef;
92 132         358 $self->{compat} = 0;
93             # What version fragment to increase/decrease by default, such as when we do operations like $v++ or $v--
94 132         364 $self->{default_frag} = 'minor';
95 132         354 $self->{extra} = [];
96 132         328 $self->{major} = undef;
97 132         288 $self->{minor} = undef;
98 132         431 $self->{original} = undef;
99 132         295 $self->{padded} = 1;
100 132         277 $self->{patch} = undef;
101 132         298 $self->{pretty} = 0;
102 132         291 $self->{qv} = 0;
103             # Release candidate used by non-perl open source softwares
104 132         296 $self->{rc} = undef;
105 132         332 $self->{target} = 'perl';
106 132         338 $self->{type} = undef;
107 132         782 my $keys = [qw( alpha beta compat default_frag extra major minor original patch qv rc target type _version )];
108 132         262 my $vstr;
109             # Changes::Version->new( 'v0.1.2_3' ); or
110             # Changes::Version->new( 'v0.1.2_3', alpha => 4 ); or
111             # Changes::Version->new( 'v0.1.2_3', { alpha => 4 } ); or
112             # Changes::Version->new( major => 0, minor => 1, patch => 2, alpha => 3, qv => 1 ); or
113             # Changes::Version->new({ major => 0, minor => 1, patch => 2, alpha => 3, qv => 1 }); or
114 132 100 33     2161 if( ( @_ == 1 && ref( $_[0] ) ne 'HASH' ) ||
      33        
      66        
      33        
      33        
115             ( @_ > 1 && ref( $_[0] ) ne 'HASH' && ( ( @_ % 2 ) || ref( $_[1] ) eq 'HASH' ) ) )
116             {
117 42         121 $vstr = shift( @_ );
118 42 50 33     343 return( $self->error( "version string provided is empty." ) ) if( !defined( $vstr ) || !length( "$vstr" ) );
119             # So we can get options like debug for parser
120 42         292 my $opts = $self->_get_args_as_hash( @_ );
121 42 100 66     6665 $self->debug( $opts->{debug} ) if( exists( $opts->{debug} ) && defined( $opts->{debug} ) && length( "$opts->{debug}" ) );
      66        
122             # A version string was provided, so we parse it
123 42         295 my $v = $self->parse( $vstr );
124 42 50       234 return( $self->pass_error ) if( !defined( $v ) );
125             # And we copy the collected value as default values for our new object, which can then be overriden by additional option passed here.
126 42         1195 @$self{ @$keys } = @$v{ @$keys };
127             }
128 132         601 $self->{_init_strict_use_sub} = 1;
129 132         1198 my $rv = $self->SUPER::init( @_ );
130 132 50       2092460 return( $self->pass_error ) if( !defined( $rv ) );
131 132         1031 return( $self );
132             }
133              
134 419     419 1 43218 sub alpha { return( shift->reset(@_)->_set_get_number( { field => 'alpha', undef_ok => 1 }, @_ ) ); }
135              
136             sub as_string
137             {
138 309     309 1 244198 my $self = shift( @_ );
139 309 50 66     2529 if( !exists( $self->{_reset} ) ||
      33        
140             !defined( $self->{_reset} ) ||
141             !CORE::length( $self->{_reset} ) )
142             {
143 192 100 66     2526 if( exists( $self->{_cache_value} ) &&
    50 66        
      33        
144             defined( $self->{_cache_value} ) &&
145             length( $self->{_cache_value} ) )
146             {
147 68         877 return( $self->{_cache_value} );
148             }
149             elsif( defined( $self->{original} ) && length( "$self->{original}" ) )
150             {
151 124         1684 return( $self->{original}->scalar );
152             }
153             }
154 117         601 my $type = $self->type;
155 117         110038 my $str;
156 117 100 100     1267 if( ( defined( $type ) && $type eq 'dotted' ) ||
      66        
      100        
157             ( !defined( $type ) && $DEFAULT_TYPE eq 'dotted' ) )
158             {
159 59         357 $str = $self->normal( raw => 1 );
160             }
161             else
162             {
163 58         1280 $str = $self->numify( raw => 1 );
164 58 100 66     267 if( !$self->padded && index( $str, '_' ) == -1 )
165             {
166 2         1585 return( $str * 1 );
167             }
168            
169 56 100 100     45183 if( $self->pretty && index( $str, '_' ) == -1 && !( length( [split( /\./, $str )]->[1] ) % 3 ) )
      66        
170             {
171             # $str = join( '_', grep{ $_ ne ''} split( /(...)/, $str ) );
172             # Credit: <https://stackoverflow.com/questions/33442240/perl-printf-to-use-commas-as-thousands-separator>
173 3         2442 while( $str =~ s/(\d+)(\d{3})/$1\_$2/ ){};
174             }
175             }
176 115         41258 $self->{_cache_value} = $str;
177 115         313 CORE::delete( $self->{_reset} );
178 115         856 return( $str );
179             }
180              
181             {
182 20     20   166 no warnings 'once';
  20         40  
  20         29635  
183             *stringify = \&as_string;
184             }
185              
186 0     0 1 0 sub beta { return( shift->reset(@_)->_set_get_number( { field => 'beta', undef_ok => 1 }, @_ ) ); }
187              
188             # NOTE: clone() is inherited
189              
190 1     1 1 6 sub compat { return( shift->_set_get_boolean( 'compat', @_ ) ); }
191              
192 0     0 1 0 sub dec { return( shift->_inc_dec( 'dec', @_ ) ); }
193              
194 0     0 1 0 sub dec_alpha { return( shift->_inc_dec( 'dec' => 'alpha', @_ ) ); }
195              
196             # For non-perl open source softwares
197 0     0 1 0 sub dec_beta { return( shift->_inc_dec( 'dec' => 'beta', @_ ) ); }
198              
199 0     0 1 0 sub dec_major { return( shift->_inc_dec( 'dec' => 'major', @_ ) ); }
200              
201 0     0 1 0 sub dec_minor { return( shift->_inc_dec( 'dec' => 'minor', @_ ) ); }
202              
203 0     0 1 0 sub dec_patch { return( shift->_inc_dec( 'dec' => 'patch', @_ ) ); }
204              
205 192     192 1 392872 sub default_frag { return( shift->_set_get_scalar_as_object( 'default_frag', @_ ) ); }
206              
207 273     273 1 1918383 sub extra { return( shift->_set_get_array_as_object( 'extra', @_ ) ); }
208              
209 4     4 1 24 sub inc { return( shift->_inc_dec( 'inc', @_ ) ); }
210              
211 0     0 1 0 sub inc_alpha { return( shift->_inc_dec( 'inc' => 'alpha', @_ ) ); }
212              
213 0     0 1 0 sub inc_beta { return( shift->_inc_dec( 'inc' => 'beta', @_ ) ); }
214              
215 0     0 1 0 sub inc_major { return( shift->_inc_dec( 'inc' => 'major', @_ ) ); }
216              
217 0     0 1 0 sub inc_minor { return( shift->_inc_dec( 'inc' => 'minor', @_ ) ); }
218              
219 0     0 1 0 sub inc_patch { return( shift->_inc_dec( 'inc' => 'patch', @_ ) ); }
220              
221 0 0   0 1 0 sub is_alpha { return( shift->alpha->length > 0 ? 1 : 0 ); }
222              
223 0 0   0 1 0 sub is_qv { return( shift->qv ? 1 : 0 ); }
224              
225 326     326 1 974347 sub major { return( shift->reset(@_)->_set_get_number( { field => 'major', undef_ok => 1 }, @_ ) ); }
226              
227 347     347 1 4508298 sub minor { return( shift->reset(@_)->_set_get_number( { field => 'minor', undef_ok => 1 }, @_ ) ); }
228              
229             sub normal
230             {
231 60     60 1 140 my $self = shift( @_ );
232 60         312 my $opts = $self->_get_args_as_hash( @_ );
233 60   100     9146 $opts->{raw} //= 0;
234 60         162 my $v;
235 60 50 33     220 try
  60         113  
  60         130  
  60         327  
  0         0  
  60         119  
  60         253  
  60         156  
236 60     60   102 {
237 60         2206 my $clone = $self->clone;
238 60 100       83602 if( !$self->qv )
239             {
240 1         771 $clone->qv(1);
241             }
242 60 100       49903 if( $opts->{raw} )
243             {
244 59         284 $v = $clone->_stringify;
245             # We already did it with stringify, so we return what we got
246 59         761 return( $v );
247             }
248             else
249             {
250 1         5 $clone->type( 'dotted' );
251 1         985 return( $clone );
252             }
253             }
254 60 0 0     354 catch( $e )
  0 0 33     0  
  0 0       0  
  60 0       133  
  60 0       102  
  60 0       114  
  60 0       137  
  60 0       259  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  60         265  
  0         0  
  60         137  
  0         0  
  0         0  
  60         22574  
  60         358  
  60         186  
  60         218  
  0         0  
  0         0  
  0         0  
  0         0  
255 0     0   0 {
256 0         0 return( $self->error( "Error normalising version $v: $e" ) );
257 20 0 0 20   171 }
  20 0 0     43  
  20 0 33     23499  
  0 0 66     0  
  0 0 33     0  
  0 0 33     0  
  0 0 33     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  60 0       241  
  0 0       0  
  60 50       685  
  60 50       415  
  60 50       265  
  0 0       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  60         780  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
258             }
259              
260             sub numify
261             {
262 59     59 1 169 my $self = shift( @_ );
263 59         311 my $opts = $self->_get_args_as_hash( @_ );
264 59   100     9052 $opts->{raw} //= 0;
265 59         133 my $v;
266 59 50 33     191 try
  59         99  
  59         137  
  59         295  
  0         0  
  59         100  
  59         214  
  59         145  
267 59     59   111 {
268 59 100       219 if( $opts->{raw} )
269             {
270             # 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
271             # 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
272 58         2045 my $clone = $self->clone;
273 58         78057 my $alpha = $clone->alpha;
274 58         51719 $clone->alpha( undef );
275 58         59131 $v = $clone->_stringify;
276 58         1485 my $str = version->parse( $v )->numify;
277 58 100 66     447 $str .= "_${alpha}" if( defined( $alpha ) && length( "$alpha" ) );
278 58         616 return( $str );
279             }
280             else
281             {
282 1         25 my $new = $self->clone;
283             # This will also remove qv boolean
284 1         1164 $new->type( 'decimal' );
285 1         1338 return( $new );
286             }
287             }
288 59 0 0     363 catch( $e )
  0 0 33     0  
  0 0       0  
  59 0       131  
  59 0       96  
  59 0       100  
  59 0       151  
  59 0       246  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  59         214  
  0         0  
  59         151  
  0         0  
  0         0  
  59         22020  
  59         322  
  59         151  
  59         195  
  0         0  
  0         0  
  0         0  
  0         0  
289 0     0   0 {
290 0         0 return( $self->error( "Error numifying version $v: $e" ) );
291 20 0 0 20   165 }
  20 0 0     48  
  20 0 33     34553  
  0 0 66     0  
  0 0 33     0  
  0 0 33     0  
  0 0 33     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  59 0       253  
  0 0       0  
  59 50       660  
  59 50       402  
  59 50       272  
  0 0       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  59         859  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
292             }
293              
294 89     89 1 2068325 sub original { return( shift->_set_get_scalar_as_object( 'original', @_ ) ); }
295              
296 61     61 1 239 sub padded { return( shift->reset(@_)->_set_get_boolean( 'padded', @_ ) ); }
297              
298             sub parse
299             {
300 89     89 1 83598 my $self = shift( @_ );
301 89         264 my $str = shift( @_ );
302 89 50 33     783 return( $self->error( "No version string was provided." ) ) if( !defined( $str ) || !length( "$str" ) );
303 89 50       3467 if( $str =~ /^$VERSION_LAX_REGEX$/ )
304             {
305 89         2892 my $re = { %+ };
306 89         616 my $def = { original => $str };
307 89 100 66     1057 if( defined( $re->{dotted} ) && length( $re->{dotted} ) )
    50 33        
308             {
309 10         37 $def->{type} = 'dotted';
310             }
311             elsif( defined( $re->{decimal} ) && length( $re->{decimal} ) )
312             {
313 79         295 $def->{type} = 'decimal';
314             }
315             else
316             {
317 0         0 return( $self->error( "No version types found. This should not happen." ) );
318             }
319 89         231 my $v;
320 89 100 66     493 $def->{qv} = 1 if( defined( $re->{has_v} ) && length( $re->{has_v} ) );
321 89         286 $def->{major} = $re->{major};
322 89 100 66     473 $def->{alpha} = $re->{alpha} if( defined( $re->{alpha} ) && length( $re->{alpha} ) );
323 89 100       552 if( $def->{type} eq 'dotted' )
    50          
324             {
325 10 50       42 if( defined( $re->{minor_patch} ) )
326             {
327 10         50 my @frags = split( /\./, $re->{minor_patch} );
328 10         24 shift( @frags );
329 10         28 $def->{minor} = shift( @frags );
330 10         31 $def->{patch} = shift( @frags );
331 10         66 $def->{extra} = \@frags;
332             }
333 10         123 $v = version->parse( $re->{dotted} );
334             }
335             elsif( $def->{type} eq 'decimal' )
336             {
337             # $def->{minor} = $re->{minor} if( defined( $re->{minor} ) );
338             # $re->{release} is the decimal version without the alpha information if it is smaller than 3
339             # This issue stems from decimal number having an underscore can either mean they have a version like
340             # 5.006_002 which would be equivalent v5.6.2 and in this case, "_002" is not an alpha information; and
341             # 1.002_03 where 03 is the alpha version and should be converted to 1.2_03, but instead becomes v1.2.30
342             # If compatibility with 'compat' is enabled, then we use the classic albeit erroneous way of converting the decimal version
343 79 100 66     493 if( defined( $def->{alpha} ) &&
      66        
344             length( $def->{alpha} ) < 3 &&
345             !$self->compat )
346             {
347 1         858 $v = version->parse( "$re->{release}" );
348             }
349             else
350             {
351 78         970 $v = version->parse( "$str" );
352             }
353 79         1086 my $vstr = $v->normal;
354 79 50       2475 if( $vstr =~ /^$VERSION_LAX_REGEX$/ )
355             {
356 79         1871 my $re2 = { %+ };
357 79 50 33     949 if( defined( $re2->{dotted} ) && length( $re2->{dotted} ) )
358             {
359 79 50       318 if( defined( $re2->{minor_patch} ) )
360             {
361 79         206 $def->{major} = $re2->{major};
362 79         406 my @frags = split( /\./, $re2->{minor_patch} );
363 79         210 shift( @frags );
364 79         209 $def->{minor} = shift( @frags );
365 79         215 $def->{patch} = shift( @frags );
366 79         412 $def->{extra} = \@frags;
367             }
368             }
369             }
370             }
371 89         850 my $new = $self->new( %$def );
372 89 50       1196 $new->{_version} = $v if( defined( $v ) );
373 89 50       428 return( $self->pass_error ) if( !defined( $new ) );
374 89         286 CORE::delete( $new->{_reset} );
375 89         1232 return( $new );
376             }
377             else
378             {
379 0         0 return( $self->error( "Invalid version '$str'" ) );
380             }
381             }
382              
383 370     370 1 1184813 sub patch { return( shift->reset(@_)->_set_get_number( { field => 'patch', undef_ok => 1 }, @_ ) ); }
384              
385 57     57 1 189 sub pretty { return( shift->reset(@_)->_set_get_boolean( 'pretty', @_ ) ); }
386              
387 448     448 1 207723 sub qv { return( shift->reset(@_)->_set_get_boolean( 'qv', @_ ) ); }
388              
389 0     0 1 0 sub rc { return( shift->_set_get_scalar_as_object( 'rc', @_ ) ); }
390              
391             sub reset
392             {
393 2429     2429 1 4135 my $self = shift( @_ );
394 2429 100 33     17581 if( (
      100        
395             !exists( $self->{_reset} ) ||
396             !defined( $self->{_reset} ) ||
397             !CORE::length( $self->{_reset} )
398             ) && scalar( @_ ) )
399             {
400 163         625 $self->{_reset} = scalar( @_ );
401 163 100       695 if( defined( $self->{major} ) )
402             {
403 73         226 my $str = $self->_stringify;
404 73 50 33     839 try
  73         170  
  73         165  
  73         456  
  0         0  
  73         153  
  73         246  
  73         197  
405 73     73   196 {
406 73         789 my $v = version->parse( "$str" );
407 73         455 $self->{_version} = $v;
408             }
409 73 0 50     503 catch( $e )
  73 0 33     302  
  73 0       211  
  73 0       168  
  73 0       148  
  73 0       132  
  73 0       164  
  73 0       379  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  73         338  
  0         0  
  73         170  
  0         0  
  0         0  
  73         238  
  73         388  
  73         182  
  73         259  
  0         0  
  0         0  
  0         0  
  0         0  
410 0     0   0 {
411 0 0       0 warn( "Warning only: error trying to get a version object from version string '$str': $e\n" ) if( $self->_warnings_is_enabled );
412 20 0 0 20   181 }
  20 0 0     44  
  20 0 33     59156  
  0 0 33     0  
  0 0 33     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 33     0  
  0 0 33     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  73 0       291  
  0 0       0  
  73 0       2524  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  73         342  
  0         0  
  0         0  
  0         0  
  0         0  
  73         278  
413             }
414             }
415 2429         15418 return( $self );
416             }
417              
418 0     0 1 0 sub target { return( shift->_set_get_scalar_as_object( 'target', @_ ) ); }
419              
420             sub type { return( shift->reset(@_)->_set_get_scalar_as_object({
421             field => 'type',
422             callbacks =>
423             {
424             add => sub
425             {
426 92     92   76929 my $self = shift( @_ );
427 92 100       643 if( $self->{type} eq 'decimal' )
    50          
428             {
429 82         1316 $self->{qv} = 0;
430             }
431             elsif( $self->{type} eq 'dotted' )
432             {
433             # By default
434 10         251 $self->{qv} = 1;
435             }
436             }
437             }
438 401     401 1 2090155 }, @_ ) ); }
439              
440             sub _bool
441             {
442 0     0   0 my $self = shift( @_ );
443             # return( $self->_compare( $self->_version, version->new("0"), 1 ) );
444 0         0 return( $self->_compare( $self, "0", 1 ) );
445             }
446              
447             sub _cascade
448             {
449 84     84   271 my $self = shift( @_ );
450 84         246 my $frag = shift( @_ );
451             # We die, because this is an internal method and those cases should not happen unless this were a design bug
452 84 50 33     777 if( !defined( $frag ) || !length( $frag ) )
    50          
453             {
454 0         0 die( "No fragment was provided to cascade" );
455             }
456             elsif( $frag !~ /^(major|minor|patch|alpha|\d+)$/ )
457             {
458 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)." );
459             }
460 84         2127 my $extra = $self->extra;
461 84 50       75824 my $frag_is_int = ( $frag =~ /^\d+$/ ? 1 : 0 );
462 84 100 33     1105 if( $frag eq 'major' || ( $frag_is_int && $frag == 1 ) )
    100 66        
    100 33        
    50 66        
    0 33        
      66        
463             {
464 21         390 $self->alpha( undef );
465 21         23959 $self->patch(0);
466 21         865054 $self->minor(0);
467             }
468             elsif( $frag eq 'minor' || ( $frag_is_int && $frag == 2 ) )
469             {
470 21         679 $self->alpha( undef );
471 21         24415 $self->patch(0);
472             }
473             elsif( $frag eq 'patch' || ( $frag_is_int && $frag == 3 ) )
474             {
475 21         879 $self->alpha( undef );
476             }
477             elsif( $frag eq 'alpha' )
478             {
479             # Nothing to do
480             }
481             elsif( $frag_is_int )
482             {
483 0         0 my $offset = ( $frag - 4 );
484 0         0 my $len = $extra->length;
485             # Before the fragment offset, we set the value to 0 if it is undefined or empty, and
486             # after the fragment offset everything else is reset to 0
487 0 0       0 for( my $i = 0; $i < ( $offset < $len ? $len : $offset ); $i++ )
488             {
489 0 0 0     0 if( (
      0        
      0        
490             $i < $offset &&
491             ( !defined( $extra->[$i] ) || !length( $extra->[$i] ) )
492             ) || $i > $offset )
493             {
494 0         0 $extra->[$i] = 0;
495             }
496             }
497 0         0 $self->alpha( undef );
498             }
499             }
500              
501             sub _compare
502             {
503 40     40   25364 my( $left, $right, $swap ) = @_;
504 40         138 my $class = ref( $left );
505 40 50       288 unless( $left->_is_a( $right => $class ) )
506             {
507 40         1025 $right = $class->new( $right, debug => $left->debug );
508             }
509              
510 40 50       398 if( $swap )
511             {
512 0         0 ( $left, $right ) = ( $right, $left );
513             }
514            
515 40 50       209 unless( _verify( $left ) )
516             {
517 0 0       0 die( "Invalid version ", ( $swap ? 'format' : 'object ' . overload::StrVal( $left ) ), "." );
518             }
519 40 50       140 unless( _verify( $right ) )
520             {
521 0 0       0 die( "Invalid version ", ( $swap ? 'format' : 'object' . overload::StrVal( $right ) ), "." );
522             }
523 40         280 my $lv = $left->_version;
524 40         141 my $rv = $right->_version;
525             # TODO: better compare version. perl's version fails at comparing version that have alpha.
526             # For example, the documentation states:
527             # Note that "alpha" version objects (where the version string contains a trailing underscore segment) compare as less than the equivalent version without an underscore:
528             # $bool = version->parse("1.23_45") < version->parse("1.2345"); # TRUE
529             # However, this is not true. The above doc example will yield FALSE, not TRUE, and even the following too:
530             # perl -Mversion -lE 'my $v = version->parse("v1.2.3"); my $v2 = version->parse("v1.2.3_4"); say $v > $v2'
531             # See RT#145290: <https://rt.cpan.org/Ticket/Display.html?id=145290>
532             # return( $left->{_version} == $right->{_version} );
533             # return( $lv == $rv );
534 40         618 return( $lv <=> $rv );
535             }
536              
537             sub _compute
538             {
539 96     96   290 my $self = shift( @_ );
540 96         263 my $opts = pop( @_ );
541 96         364 my( $other, $swap, $nomethod, $bitwise ) = @_;
542 96   50     312 my $frag = $self->default_frag // 'minor';
543 96 50       89566 $frag = 'minor' if( $frag !~ /^(major|minor|patch|alpha|\d+)$/ );
544 96 50 33     2822 if( !defined( $opts ) ||
      33        
      33        
      33        
545             ref( $opts ) ne 'HASH' ||
546             !exists( $opts->{op} ) ||
547             !defined( $opts->{op} ) ||
548             !length( $opts->{op} ) )
549             {
550 0         0 die( "No argument 'op' provided" );
551             }
552 96         288 my $op = $opts->{op};
553 96         2509 my $clone = $self->clone;
554 96         121224 my $extra = $self->extra;
555 96 50       88074 my $frag_is_int = ( $frag =~ /^\d+$/ ? 1 : 0 );
556 96         1303 my $map =
557             {
558             1 => 'major',
559             2 => 'minor',
560             3 => 'patch',
561             };
562 96         228 my $coderef;
563 96 50 33     835 if( ( $frag_is_int && exists( $map->{ $frag } ) ) || !$frag_is_int )
      33        
564             {
565             $coderef = $self->can( $map->{ $frag } // $frag ) ||
566 96   50     297 die( "Cannot find code reference for method ", ( $frag_is_int ? $map->{ $frag } : $frag ) );
567             }
568 96 50       2267 my $val = defined( $coderef ) ? $coderef->( $self ) : $extra->[ $frag - 4 ];
569 96         84940 my $err;
570 96 50       719 if( !defined( $val ) )
    50          
571             {
572 0         0 $val = $self->new_number(0);
573             }
574             elsif( !$self->_is_a( $val => 'Module::Generic::Number' ) )
575             {
576 0         0 $val = $self->new_number( "$val" );
577 0 0       0 if( !defined( $val ) )
578             {
579 0         0 $err = $self->error->message;
580             }
581             }
582 96         4647 my $n = $val->scalar;
583 96         926 my $eval;
584 96 100 100     778 if( $opts->{op} eq '++' || $opts->{op} eq '--' )
585             {
586 16         52 $eval = "\$n${op}";
587             }
588             else
589             {
590 80 50       550 $eval = $swap ? ( defined( $other ) ? $other : 'undef' ) . "${op} \$n" : "\$n ${op} " . ( defined( $other ) ? $other : 'undef' );
    50          
    100          
591             }
592 96         6732 my $rv = eval( $eval );
593 96 50       594 $err = $@ if( $@ );
594 96 50       341 if( defined( $err ) )
595             {
596 0 0       0 warn( $err, "\n" ) if( $self->_warnings_is_enabled );
597             # Return unchanged
598             # return( $swap ? $other : $self );
599 0         0 return;
600             }
601            
602 96 100       287 if( $swap )
603             {
604 16 50       140 return( ref( $rv ) ? $rv->scalar : $rv );
605             }
606             else
607             {
608 80         161 my $new = $clone;
609 80         109 my $new_val;
610 80 100 100     503 if( $op eq '++' || $op eq '--' )
611             {
612 16         36 $new = $self;
613 16         31 $new_val = $n;
614             }
615             else
616             {
617 64         183 $new_val = int( $rv );
618             }
619            
620 80 50       300 if( defined( $coderef ) )
621             {
622 80         229 $coderef->( $new, $new_val );
623             }
624             else
625             {
626 0         0 $extra->[( $frag - 4 )] = $new_val;
627             }
628 80         3306814 $new->_cascade( $frag );
629 80         1673312 return( $new );
630             }
631             }
632              
633             sub _inc_dec
634             {
635 4     4   11 my $self = shift( @_ );
636 4   50     17 my $op = shift( @_ ) || return( $self->error( "No op was provided." ) );
637 4 50       30 return( $self->error( "Op can only be 'inc' or 'dec'" ) ) if( $op !~ /^(inc|dec)$/ );
638 4         10 my $frag = shift( @_ );
639 4         9 my $unit = shift( @_ );
640 4 50 33     58 if( !defined( $frag ) || !length( "$frag" ) )
    50          
641             {
642 0 0       0 return( $self->error( "No version fragment was specified to ", ( $op eq 'inc' ? 'increase' : 'decrease' ), " the version number." ) );
643             }
644             elsif( $frag !~ /^(major|minor|patch|alpha|\d+)$/ )
645             {
646 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)." ) );
647             }
648 4 50 33     18 if( defined( $unit ) && $unit !~ /^\d+$/ )
649             {
650 0 0       0 return( $self->error( "Unit to ", ( $op eq 'inc' ? 'increase' : 'decrease' ), " fragment $frag value must be an integer." ) );
651             }
652 4         13 my $extra = $self->extra;
653 4 50       3725 my $frag_is_int = ( $frag =~ /^\d+$/ ? 1 : 0 );
654 4         47 my $map =
655             {
656             1 => 'major',
657             2 => 'minor',
658             3 => 'patch',
659             };
660 4         9 my $coderef;
661 4 50 33     38 if( ( $frag_is_int && exists( $map->{ $frag } ) ) || !$frag_is_int )
      33        
662             {
663             $coderef = $self->can( $map->{ $frag } // $frag ) ||
664 4   50     42 die( "Cannot find code reference for method ", ( $frag_is_int ? $map->{ $frag } : $frag ) );
665             }
666 4 50       18 my $n = defined( $coderef ) ? $coderef->( $self ) : $extra->[ $frag - 4 ];
667             # The offset specified is out of bound
668 4 50 33     3463 if( $frag_is_int && ( $frag - 4 ) > $extra->size )
    50 33        
669             {
670 0 0       0 $n = (
    0          
671             $op eq 'inc'
672             ? ( defined( $unit ) ? $unit : 1 )
673             : 0
674             );
675             }
676             elsif( defined( $unit ) && $unit == 1 )
677             {
678 0 0       0 $op eq 'inc' ? ( $n += $unit ) : ( $n -= $unit );
679             }
680             else
681             {
682 4 50       40 $op eq 'inc' ? $n++ : $n--;
683             }
684            
685 4 50       47 if( defined( $coderef ) )
686             {
687 4         9 $coderef->( $self, $n );
688             }
689             else
690             {
691 0         0 $extra->[( $frag - 4 )] = $n;
692             }
693 4         164159 $self->_cascade( $frag );
694 4         83693 return( $self );
695             }
696              
697             sub _noop
698             {
699 0     0   0 my( $self, $other, $swap, $nomethod, $bitwise ) = @_;
700 0 0       0 warn( "This operation $nomethod is not supported by Changes::Version\n" ) if( $self->_warnings_is_enabled );
701             }
702              
703             sub _stringify
704             {
705 190     190   466 my $self = shift( @_ );
706 190         695 my $comp = $self->new_array;
707 190         3623 my $def = {};
708 190         599 for( qw( major minor patch alpha ) )
709             {
710 760         496124 $def->{ $_ } = $self->$_;
711             }
712 190         162587 my $type = $self->type;
713 190 50 33     175449 $def->{major} = 0 if( !defined( $def->{major} ) || !length( $def->{major} ) );
714 190 100 50     1826 if( $self->qv || ( ( $type // '' ) eq 'dotted' ) )
    50 66        
      50        
715             {
716 94 50 33     73910 $def->{minor} = 0 if( !defined( $def->{minor} ) || !length( "$def->{minor}" ) );
717 94 50 33     1092 $def->{patch} = 0 if( !defined( $def->{patch} ) || !length( "$def->{patch}" ) );
718             }
719             elsif( ( $type // '' ) eq 'decimal' )
720             {
721             # We need to avoid the scenario where we would have a major and alpha, but not minor.
722             # For example: 3_6 would trigger version error "Invalid version format (alpha without decimal)"
723 96 0 33     79347 $def->{minor} = 0 if( ( !defined( $def->{minor} ) || !length( "$def->{minor}" ) ) && defined( $def->{alpha} ) && length( "$def->{alpha}" ) );
      33        
      33        
724             }
725 190         1676 my $ok = 0;
726 190         482 for( qw( patch minor major ) )
727             {
728 570 50 66     3249 next if( !length( $def->{ $_ } ) && !$ok );
729             # We stop skipping version fragments as soon as one is defined
730 567         2437 $ok++;
731 567         1569 $comp->unshift( $def->{ $_ } );
732             }
733 190 100   567   1382 my $v = ( $self->qv ? 'v' : '' ) . $comp->map(sub{ 0 + $_ })->join( '.' )->scalar;
  567         566189  
734 190 100 66     229659 $v .= '_' . $def->{alpha} if( defined( $def->{alpha} ) && length( $def->{alpha} ) );
735 190         66734 return( $v );
736             }
737              
738             sub _verify
739             {
740 80     80   186 my $self = shift( @_ );
741 80 50       244 if( defined( $self ) )
742             {
743             }
744 80 50 33     500 if( defined( $self ) &&
      33        
      33        
745             Module::Generic->_is_a( $self => 'Changes::Version' ) &&
746 80         3575 eval{ exists( $self->{_version} ) } &&
747             Module::Generic->_is_a( $self->{_version} => 'version' ) )
748             {
749 80         2617 return(1);
750             }
751             else
752             {
753 0         0 return(0);
754             }
755             }
756              
757             sub _version
758             {
759 80     80   167 my $self = shift( @_ );
760 80 50 33     611 if( @_ )
    50          
761             {
762 0         0 my $v = shift( @_ );
763 0 0       0 return( $self->error( "Value provided is not a version object." ) ) if( !$self->_is_a( $v => 'version' ) );
764             }
765             elsif( !exists( $self->{_version} ) || !defined( $self->{_version} ) )
766             {
767 0         0 my $str = $self->_stringify;
768 0 0 0     0 try
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
769 0     0   0 {
770 0         0 $self->{_version} = version->parse( "$str" );
771             }
772 0 0 0     0 catch( $e )
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
773 0     0   0 {
774 0 0       0 warn( "Warning only: error trying to get a version object from version string '$str': $e\n" ) if( $self->_warnings_is_enabled );
775 20 0 0 20   180 }
  20 0 0     47  
  20 0 0     3949  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
776             }
777 80         246 return( $self->{_version} );
778             }
779              
780             1;
781             # NOTE: POD
782             __END__
783              
784             =encoding utf-8
785              
786             =head1 NAME
787              
788             Changes::Version - Version string object class
789              
790             =head1 SYNOPSIS
791              
792             use Changes::Version;
793             my $v = Changes::Version->new(
794             major => 1,
795             minor => 2,
796             patch => 3,
797             alpha => 4,
798             qv => 1,
799             debug => 2,
800             );
801             # or
802             my $v = Changes::Version->new( 'v0.1.2_3' );
803             # or
804             my $v = Changes::Version->new( 'v0.1.2_3', alpha => 4 );
805             # or
806             my $v = Changes::Version->new( 'v0.1.2_3', { alpha => 4 } );
807             # or
808             my $v = Changes::Version->new( major => 0, minor => 1, patch => 2, alpha => 3, qv => 1 );
809             # or
810             my $v = Changes::Version->new({ major => 0, minor => 1, patch => 2, alpha => 3, qv => 1 });
811             die( Changes::Version->error ) if( !defined( $v ) );
812             my $v = Changes::Version->parse( 'v1.2.3_4' );
813             die( Changes::Version->error ) if( !defined( $v ) );
814             my $type = $v->type;
815             $v->type( 'decimal' );
816             $v->padded(0);
817             $v->pretty(1);
818             $v->type( 'dotted' );
819             $v++;
820             # Updating 'minor'
821             say "$v"; # v1.3.0
822             $v += 2;
823             $v->default_frag( 'major' );
824             $v++;
825             say "$v"; # v2.0.0
826             $v->inc_patch;
827             say $v->is_alpha; # false
828             say $v->numify; # returns new Changes::Version object
829             say $v->normal; # returns new Changes::Version object
830             say $v->as_string; # same as say "$v";
831              
832             =head1 VERSION
833              
834             v0.1.0
835              
836             =head1 DESCRIPTION
837              
838             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.
839              
840             It allows for parsing and manipulation of version objects.
841              
842             =head1 CONSTRUCTOR
843              
844             =head2 new
845              
846             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.
847              
848             If an error occurs, it will return an L<error|Module::Generic/error>, so alway check for the definedness of the returned value.
849              
850             my $v = Changes::Version->new(
851             major => 1,
852             minor => 2,
853             patch => 3,
854             alpha => 4,
855             );
856             die( Changes::Version->error ) if( !defined( $v ) );
857              
858             Note that if you do:
859              
860             my $v = Changes::Version->new( ... ) || die( Changes::Version->error );
861              
862             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>.
863              
864             It supports the following options that can also be accessed or changed with their corresponding method.
865              
866             =over 4
867              
868             =item * C<alpha>
869              
870             Specifies the alpha fragment integer of the version. See L</alpha> for more information.
871              
872             my $v = Changes::Version->new(
873             major => 1,
874             minor => 2,
875             patch => 3,
876             alpha => 4,
877             );
878             my $alpha = $v->alpha; # 4
879             $v->alpha(7);
880             say "$v"; # v1.2.3_7
881              
882             =item * C<beta>
883              
884             Specifies the beta fragment integer of the version. See L</beta> for more information.
885              
886             Currently unused and reserved for future release.
887              
888             =item * C<compat>
889              
890             Boolean. When enabled, this will ensure the version formatting is strictly compliant with the L<version> module. Default to false.
891              
892             =item * C<default_frag>
893              
894             Specifies the fragment name or integer value used by overloaded operations.
895              
896             my $v = Changes::Version->new( 'v1.2.3_4' );
897             my $default = $v->default_frag; # By default 'minor'
898             $v->default_frag( 'major' );
899             $v++; # Version is now v2.2.3_4
900              
901             =item * C<extra>
902              
903             Specifies the array reference of version fragments beyond C<patch>
904              
905             my $v = Changes::Version->new(
906             major => 1,
907             minor => 2,
908             patch => 3,
909             alpha => 12,
910             extra => [qw( 4 5 6 7 )],
911             );
912             say "$v"; # v1.2.3.4.5.6.7_12
913             my $a = $v->extra; # contains 4, 5, 6, 7
914              
915             =item * C<major>
916              
917             Specifies the C<major> fragment of the version string.
918              
919             my $v = Changes::Version->new(
920             major => 1,
921             minor => 2,
922             patch => 3,
923             alpha => 4,
924             );
925             my $major = $v->major; # 1
926             say "$v"; # v1.2.3_4
927             $v->major(3);
928             say "$v"; # v3.0.0
929              
930             =item * C<minor>
931              
932             Specifies the C<minor> fragment of the version string.
933              
934             my $v = Changes::Version->new(
935             major => 1,
936             minor => 2,
937             patch => 3,
938             alpha => 4,
939             );
940             my $minor = $v->minor; # 2
941             say "$v"; # v1.2.3_4
942             $v->minor(3);
943             say "$v"; # v1.3.0
944              
945             =item * C<original>
946              
947             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.
948              
949             =item * C<padded>
950              
951             Specifies whether version string of type decimal should be zero padded or not. Default to true.
952              
953             my $v = Change::Version->new(
954             major => 1,
955             minor => 20,
956             patch => 300,
957             type => 'decimal',
958             );
959             say "$v"; # 1.020300
960             $v->padded(0);
961             say "$v"; # 1.0203
962              
963             =item * C<patch>
964              
965             Specifies the C<patch> fragment of the version string.
966              
967             my $v = Changes::Version->new(
968             major => 1,
969             minor => 2,
970             patch => 3,
971             alpha => 4,
972             );
973             my $patch = $v->patch; # 3
974             say "$v"; # v1.2.3_4
975             $v->patch(7);
976             say "$v"; # v1.3.7
977              
978             =item * C<pretty>
979              
980             Specifies whether version string of type C<decimal> should be formatted with an underscore (C<_>) separating thousands in the fraction part.
981              
982             my $v = Change::Version->new(
983             major => 1,
984             minor => 20,
985             patch => 300,
986             type => 'decimal',
987             pretty => 1,
988             );
989             say "$v"; # 1.020_300
990             $v->pretty(0);
991             say "$v"; # 1.020300
992              
993             =item * C<qv>
994              
995             Specifies whether version string of type C<dotted> should be formatted with the prefix C<v>. Defaults to true.
996              
997             my $v = Changes::Version->new(
998             major => 1,
999             minor => 2,
1000             patch => 3,
1001             alpha => 4,
1002             );
1003             say "$v"; # v1.2.3_4
1004             $v->qv(0);
1005             say "$v"; # 1.2.3_4
1006              
1007             =item * C<rc>
1008              
1009             Specifies the release candidate value. This is currently unused and reserved for future release.
1010              
1011             =item * C<target>
1012              
1013             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>.
1014              
1015             =item * C<type>
1016              
1017             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>
1018              
1019             =back
1020              
1021             =head2 parse
1022              
1023             Provided with a version string, and this will parse it and return a new L<Changes::Version> object.
1024              
1025             Currently, only 2 version types are supported: C<dotted decimal> and C<decimal>
1026              
1027             v1.2
1028             1.2345.6
1029             v1.23_4
1030             1.2345
1031             1.2345_01
1032              
1033             are all legitimate version strings.
1034              
1035             If an error occurred, this will return an L<error|Module::Generic/error>.
1036              
1037             =head1 METHODS
1038              
1039             =head2 alpha
1040              
1041             Sets or gets the C<alpha> fragment integer of the version.
1042              
1043             Setting this to C<undef> effectively removes it.
1044              
1045             Returns a L<number object|Module::Generic::Number>
1046              
1047             =head2 as_string
1048              
1049             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>
1050              
1051             Resulting value is cached, which means the second time this is called, the cached value will be returned for speed.
1052              
1053             Any change to the version object parameters, and this will force the re-formatting of the version string.
1054              
1055             For example:
1056              
1057             my $v = Changes::Version->new( 'v1.2.3_4' );
1058             # This is a version of type 'dotted' for dotted decimal
1059             say "$v"; # v1.2.3_4
1060             # Changing the patch level
1061             $v->inc( 'patch' );
1062             # Now forced to re-format
1063             say "$v"; # v1.2.4
1064             # No change, using the cache
1065             say "$v"; # v1.2.4
1066              
1067             =head2 beta
1068              
1069             The beta fragment integer of the version. This is currently unused and reserved for future release of this class.
1070              
1071             =head2 compat
1072              
1073             Boolean. When enabled, this will ensure the version formatting is strictly compliant with the L<version> module. Default to false.
1074              
1075             =head2 dec
1076              
1077             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.
1078              
1079             my $v = Changes::Version->new(
1080             major => 1,
1081             minor => 2,
1082             patch => 3,
1083             alpha => 4,
1084             );
1085             say "$v"; # v1.2.3_4;
1086             $v->dec( 'alpha' );
1087             say "$v"; # v1.2.3_3;
1088             $v->dec( 'patch', 2 );
1089             say "$v"; # v1.2.1
1090              
1091             my $v = Changes::Version->new( 'v1.2.3.4.5.6.7_8' );
1092             # Decrease the 5th fragment
1093             $v->dec(5);
1094             say "$v"; # v1.2.3.4.4.0.0
1095              
1096             Any change to a fragment value will reset the lower fragment values to zero. Thus:
1097              
1098             =over 4
1099              
1100             =item * changing the C<major> value will reset C<minor> and C<patch> to 0 and C<alpha> to C<undef>
1101              
1102             =item * changing the C<minor> value will reset C<patch> to 0 and C<alpha> to C<undef>
1103              
1104             =item * changing the C<patch> value will reset C<alpha> to C<undef>
1105              
1106             =item * changing the nth fragment value will reset all fragment value after that to 0
1107              
1108             =back
1109              
1110             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.
1111              
1112             Using the example above:
1113              
1114             $v->dec(10);
1115             say "$v"; # v1.2.3.4.5.6.7.0.0.0
1116              
1117             The 10th element is set to 0 because it does not exist, so it cannot be decreased.
1118              
1119             =head2 dec_alpha
1120              
1121             This is a shortcut for calling L</dec> on fragment C<alpha>
1122              
1123             =head2 dec_beta
1124              
1125             This is a shortcut for calling L</dec> on fragment C<beta>
1126              
1127             =head2 dec_major
1128              
1129             This is a shortcut for calling L</dec> on fragment C<major>
1130              
1131             =head2 dec_minor
1132              
1133             This is a shortcut for calling L</dec> on fragment C<minor>
1134              
1135             =head2 dec_patch
1136              
1137             This is a shortcut for calling L</dec> on fragment C<patch>
1138              
1139             =head2 default_frag
1140              
1141             my $v = Changes::Version->new( 'v1.2.3_4' );
1142             my $default = $v->default_frag; # By default 'minor'
1143             $v->default_frag( 'major' );
1144             $v++; # Version is now v2.2.3_4
1145              
1146             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.
1147              
1148             Returns a L<scalar object|Module::Generic::Scalar>
1149              
1150             =head2 extra
1151              
1152             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:
1153              
1154             my $v = Changes::Version->new( 'v1.2.3.4.5.6.7_8' );
1155             my $a = $v->extra; # contains 4, 5, 6, 7
1156              
1157             Note that C<alpha> is not accessible via digits, but only using L</alpha>
1158              
1159             You should not be accessing this directly.
1160              
1161             Returns an L<array object|Module::Generic::Array>
1162              
1163             =head2 inc
1164              
1165             Same as L</dec>, but increasing instead of decreasing.
1166              
1167             =head2 inc_alpha
1168              
1169             This is a shortcut for calling L</inc> on fragment C<alpha>
1170              
1171             =head2 inc_beta
1172              
1173             This is a shortcut for calling L</inc> on fragment C<beta>
1174              
1175             =head2 inc_major
1176              
1177             This is a shortcut for calling L</inc> on fragment C<major>
1178              
1179             =head2 inc_minor
1180              
1181             This is a shortcut for calling L</inc> on fragment C<minor>
1182              
1183             =head2 inc_patch
1184              
1185             This is a shortcut for calling L</inc> on fragment C<patch>
1186              
1187             =head2 is_alpha
1188              
1189             Returns true if L</alpha> has a value set.
1190              
1191             =head2 is_qv
1192              
1193             Returns true if L</qv> is set to true, false otherwise.
1194              
1195             =head2 major
1196              
1197             Sets or gets the C<major> fragment of the version string.
1198              
1199             my $v = Changes::Version->new( 'v1.2.3_4' );
1200             my $major = $v->major; # 1
1201             $v->major(3);
1202             say "$v"; # v3.2.3_4
1203              
1204             Setting this to C<undef> effectively removes it.
1205              
1206             Returns a L<number object|Module::Generic::Number>
1207              
1208             =head2 minor
1209              
1210             Sets or gets the C<minor> fragment of the version string.
1211              
1212             my $v = Changes::Version->new( 'v1.2.3_4' );
1213             my $minor = $v->minor; # 2
1214             $v->minor(3);
1215             say "$v"; # v1.3.3_4
1216              
1217             Setting this to C<undef> effectively removes it.
1218              
1219             Returns a L<number object|Module::Generic::Number>
1220              
1221             =head2 normal
1222              
1223             Returns a new L<Changes::Version> object as a normalised version, which is a dotted decimal format with the C<v> prefix.
1224              
1225             If an error occurred, an L<error|Module::Generic/error> is returned.
1226              
1227             =head2 numify
1228              
1229             Returns a new L<Changes::Version> object as a number, which represent a decimal-type version
1230              
1231             Contrary to L<version> if there is an C<alpha> value set, it will add it to the numified version.
1232              
1233             my $v = Changes::Version->new(
1234             major => 1,
1235             minor => 2,
1236             patch => 3,
1237             alpha => 4,
1238             );
1239             say $v->numify; # 1.002003_4
1240              
1241             L<version> would yields a different, albeit wrong result:
1242              
1243             perl -Mversion -lE 'say version->parse("v1.2.3_4")->numify'
1244              
1245             would wrongly return C<1.002034> and not C<1.002003_4>
1246              
1247             perl -Mversion -lE 'say version->parse("1.002034")->normal'
1248              
1249             then yields C<v1.2.34>
1250              
1251             If an error occurred, an L<error|Module::Generic/error> is returned.
1252              
1253             =head2 original
1254              
1255             Sets or gets the original string. This is set by L</parse>
1256              
1257             Returns a L<scalar object|Module::Generic::Scalar>
1258              
1259             =head2 padded
1260              
1261             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.
1262              
1263             For example:
1264              
1265             my $v = Changes::Version->new(
1266             major => 1,
1267             minor => 2,
1268             patch => 30,
1269             type => 'decimal',
1270             padded => 1,
1271             );
1272             say "$v"; # 1.002030
1273             $v->padded(0);
1274             say "$v"; # 1.00203
1275              
1276             Returns a L<boolean object|Module::Generic::Boolean>
1277              
1278             =head2 patch
1279              
1280             Sets or gets the C<patch> fragment of the version string.
1281              
1282             my $v = Changes::Version->new( 'v1.2.3_4' );
1283             my $patch = $v->patch; # 3
1284             $v->patch(5);
1285             say "$v"; # v1.3.5_4
1286              
1287             Returns a L<number object|Module::Generic::Number>
1288              
1289             =head2 pretty
1290              
1291             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.
1292              
1293             my $v = Changes::Version->new(
1294             major => 1,
1295             minor => 2,
1296             patch => 30,
1297             type => 'decimal',
1298             );
1299              
1300             Returns a L<boolean object|Module::Generic::Boolean>
1301              
1302             =head2 qv
1303              
1304             Boolean. When enabled, this will prepend the dotted decimal version strings with C<v>. This is true by default.
1305              
1306             my $v = Changes::Version->new(
1307             major => 1,
1308             minor => 2,
1309             patch => 3,
1310             alpha => 4,
1311             );
1312             say "$v"; # v1.2.3_4
1313             $v->qv(0);
1314             say "$v"; # 1.2.3_4
1315              
1316             Returns a L<boolean object|Module::Generic::Boolean>
1317              
1318             =head2 rc
1319              
1320             Sets or gets the release candidate value. This is currently unused and reserved for future releases.
1321              
1322             Returns a L<scalar object|Module::Generic::Scalar>
1323              
1324             =for Pod::Coverage reset
1325              
1326             =head2 stringify
1327              
1328             This is an alias for L</as_string>
1329              
1330             =head2 target
1331              
1332             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>
1333              
1334             Returns a L<scalar object|Module::Generic::Scalar>
1335              
1336             =head2 type
1337              
1338             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>.
1339              
1340             Returns a L<scalar object|Module::Generic::Scalar>
1341              
1342             =head1 OVERLOADED OPERATIONS
1343              
1344             The following operations are overloaded, and internally relies on L<version> to return the value. See also L<overload> for more information.
1345              
1346             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.
1347              
1348             =over 4
1349              
1350             =item * C<stringification>
1351              
1352             Returns value from L</as_string>
1353              
1354             =item * C<0+>
1355              
1356             Returns value from L</numify>
1357              
1358             =item * C<< <=> >>
1359              
1360             Compares two versions. If the other version being compared is not a L<Changes::Version>, it is made one before comparison actually occurs.
1361              
1362             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."
1363              
1364             $bool = version->parse("1.23_45") < version->parse("1.2345"); # TRUE
1365              
1366             However, as of perl v5.10, this is not true. The above will actually return false, not true. And so will the following:
1367              
1368             perl -Mversion -lE 'say version->parse("v1.002003") > version->parse("v1.002003_4");'
1369              
1370             This is on my bucket list of things to improve.
1371              
1372             =item * C<cmp>
1373              
1374             Same as above.
1375              
1376             =item * C<bool>
1377              
1378             =item * C<+>, C<->, C<*>, C</>
1379              
1380             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>.
1381              
1382             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.
1383              
1384             my $vers = Changes::Version->new( 'v1.2.3_4' );
1385             my $new_version_object = $vers + 2; # Now v1.4.3_4 (minor has been bumped up by 2)
1386             $vers->default_frag( 'major' );
1387             my $new_version_object = $vers + 2; # Now v3.2.3_4 (this time, 'major' was increased)
1388              
1389             But, when swapped:
1390              
1391             my $vers = Changes::Version->new( 'v1.2.3_4' );
1392             my $n = 3 + $vers; # yields 5 (using the 'minor' fragment by default)
1393             $vers->default_frag( 'major' );
1394             my $n = 3 + $vers; # yields 4 (this time, using the 'major' fragment)
1395              
1396             =item * C<+=>, C<-=>, C<*=>, C</=>
1397              
1398             In this operations, it modifies the current object with the operand provided and returns the current object, instead of creating a new one.
1399              
1400             my $vers = Changes::Version->new( 'v1.2.3_4' );
1401             # By default, using the 'minor' fragment
1402             $vers += 1; # version is now v2.2.3_4
1403             $vers->default_frag( 'alpha' );
1404             $vers /= 2; # version is now v1.2.3_2
1405              
1406             =item * C<++>, C<-->
1407              
1408             When using those operations, it updates the current object directly and returns it. For example:
1409              
1410             my $vers = Changes::Version->new( 'v1.2.3_4' );
1411             # By default, using the 'minor' fragment
1412             $vers++; # version is now v1.3.3_4
1413              
1414             =back
1415              
1416             =head1 AUTHOR
1417              
1418             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
1419              
1420             =head1 SEE ALSO
1421              
1422             L<Changes>, L<Changes::Release>, L<Changes::Group>, L<Changes::Change> and L<Changes::NewLine>
1423              
1424             L<version>, L<Perl::Version>
1425              
1426             L<CPAN::Meta::Spec/"Version Formats">
1427              
1428             L<http://www.modernperlbooks.com/mt/2009/07/version-confusion.html>
1429              
1430             L<https://xdg.me/version-numbers-should-be-boring/>
1431              
1432             L<https://en.wikipedia.org/wiki/Software_versioning>
1433              
1434             =head1 COPYRIGHT & LICENSE
1435              
1436             Copyright(c) 2022 DEGUEST Pte. Ltd.
1437              
1438             All rights reserved
1439              
1440             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
1441              
1442             =cut