File Coverage

lib/Changes/Release.pm
Criterion Covered Total %
statement 212 274 77.3
branch 53 126 42.0
condition 44 96 45.8
subroutine 43 52 82.6
pod 28 30 93.3
total 380 578 65.7


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## Changes file management - ~/lib/Changes/Release.pm
3             ## Version v0.2.3
4             ## Copyright(c) 2023 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <jack@deguest.jp>
6             ## Created 2022/11/23
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::Release;
15             BEGIN
16             {
17 19     19   599426 use strict;
  19         54  
  19         732  
18 19     19   89 use warnings;
  19         33  
  19         872  
19 19     19   102 use warnings::register;
  19         33  
  19         878  
20 19     19   87 use parent qw( Module::Generic );
  19         30  
  19         105  
21 19     19   240165 use vars qw( $VERSION $VERSION_CLASS $DEFAULT_DATETIME_FORMAT );
  19         38  
  19         1380  
22 19     19   8422 use Changes::Group;
  19         57  
  19         250  
23 19     19   18400 use Changes::Version;
  19         65  
  19         213  
24 19     19   7102 use DateTime;
  19         528544  
  19         564  
25 19     19   120 use Wanted;
  19         30  
  19         1756  
26 19     19   58 our $VERSION_CLASS = 'Changes::Version';
27 19         34 our $DEFAULT_DATETIME_FORMAT = '%FT%T%z';
28 19         438 our $VERSION = 'v0.2.3';
29             };
30              
31 19     19   113 use strict;
  19         33  
  19         387  
32 19     19   78 use warnings;
  19         31  
  19         70304  
33              
34             sub init
35             {
36 47     47 1 240821 my $self = shift( @_ );
37 47         1366 $self->{changes} = [];
38 47         185 $self->{container} = undef;
39 47         157 $self->{datetime} = undef;
40 47         138 $self->{datetime_formatter} = undef;
41 47         151 $self->{defaults} = undef;
42 47         209 $self->{elements} = [];
43             # DateTime format
44 47         211 $self->{format} = undef;
45 47         153 $self->{line} = undef;
46 47         160 $self->{nl} = "\n";
47 47         180 $self->{note} = undef;
48 47         153 $self->{raw} = undef;
49 47         150 $self->{spacer} = undef;
50 47         130 $self->{time_zone} = undef;
51 47         186 $self->{version} = '';
52 47         139 $self->{_init_strict_use_sub} = 1;
53 47 50       379 $self->SUPER::init( @_ ) || return( $self->pass_error );
54 47         472393 $self->{_reset} = 1;
55 47         229 return( $self );
56             }
57              
58             sub add_change
59             {
60 1     1 1 498 my $self = shift( @_ );
61 1         2 my( $change, $opts );
62 1         3 my $elements = $self->elements;
63 1 50 33     478 if( scalar( @_ ) == 1 && $self->_is_a( $_[0] => 'Changes::Change' ) )
64             {
65 0         0 $change = shift( @_ );
66 0 0       0 if( $elements->exists( $change ) )
67             {
68 0         0 $self->_load_class( 'overload' );
69 0         0 return( $self->error( "A very same change object (", overload::StrVal( $change ), ") is already registered." ) );
70             }
71             }
72             else
73             {
74 1         6 $opts = $self->_get_args_as_hash( @_ );
75 1   50     790 $change = $self->new_change( %$opts ) || return( $self->pass_error );
76             }
77 1         6 $elements->push( $change );
78 1         7 return( $change );
79             }
80              
81             sub add_group
82             {
83 4     4 1 1854 my $self = shift( @_ );
84 4         43 my( $group, $opts );
85 4         16 my $elements = $self->elements;
86 4 100 66     3493 if( scalar( @_ ) == 1 && $self->_is_a( $_[0] => 'Changes::Group' ) )
87             {
88 2         97 $group = shift( @_ );
89 2 50       13 if( $elements->exists( $group ) )
90             {
91 0         0 $self->_load_class( 'overload' );
92 0         0 return( $self->error( "A very same group object (", overload::StrVal( $group ), ") is already registered." ) );
93             }
94 2         79 my $name = $group->name;
95 2 50 33     1875 if( !defined( $name ) || !length( "$name" ) )
96             {
97 0         0 return( $self->error( "Group object provided has empty name." ) );
98             }
99 2 50 50 1   40 my $same = $elements->grep(sub{ $self->_is_a( $_ => 'Changes::Group' ) && ( ( $_->name // '' ) eq "$name" ) });
  1         22  
100 2 50       1164 return( $self->error( "A similar group with name '$name' is already registered." ) ) if( !$same->is_empty );
101             }
102             else
103             {
104 2         12 $opts = $self->_get_args_as_hash( @_ );
105 2   50     2350 $group = $self->new_group( %$opts ) || return( $self->pass_error );
106 2         26 return( $self->add_group( $group ) );
107             }
108 2         539 my $last = $elements->last;
109             # If we are not the first element of this release, and the last element is not a blank new line, we add one to separate this new group from the preceding rest
110 2 100 66     217 if( $elements->length && !$self->_is_a( $last => 'Changes::NewLine' ) )
111             {
112 1   50     37368 $elements->push( $self->new_line( nl => ( $self->nl // "\n" ) ) );
113             }
114 2         37686 $elements->push( $group );
115 2         4629 return( $group );
116             }
117              
118             sub as_string
119             {
120 47     47 1 8878 my $self = shift( @_ );
121 47 50 66     321 if( !exists( $self->{_reset} ) ||
      33        
122             !defined( $self->{_reset} ) ||
123             !CORE::length( $self->{_reset} ) )
124             {
125 44         87 my $cache;
126 44 100 66     603 if( exists( $self->{_cache_value} ) &&
    50 66        
      33        
127             defined( $self->{_cache_value} ) &&
128             length( $self->{_cache_value} ) )
129             {
130 2         14 $cache = $self->{_cache_value};
131             }
132             elsif( defined( $self->{raw} ) && length( "$self->{raw}" ) )
133             {
134 42         465 $cache = $self->{raw};
135             }
136            
137 44         216 my $lines = $self->new_array( $cache->scalar );
138             $self->elements->foreach(sub
139             {
140 42     42   34417 my $this = $_->as_string;
141 42 50       2312 if( defined( $this ) )
142             {
143 42         225 $lines->push( $this->scalar );
144             }
145 44         33405 });
146             # my $str = $lines->join( "\n" );
147 44         50200 my $str = $lines->join( '' );
148 44         3347 return( $str );
149             }
150 3         16 my $v = $self->version;
151 3 50 33     712 return( $self->error( "No version set yet. Set a version before calling as_string()" ) ) if( !defined( $v ) || !length( "$v" ) );
152 3         46 my $dt = $self->datetime;
153 3         3204 my $code = $self->datetime_formatter;
154 3 100 66     2517 if( defined( $code ) && ref( $code ) eq 'CODE' )
155             {
156             # try-catch
157 1         27 local $@;
158             $dt = eval
159 1         3 {
160 1 50       9 $code->( defined( $dt ) ? $dt : () );
161             };
162 1 50       2835 if( $@ )
163             {
164 0 0       0 warn( "Warning only: error with datetime formatter calback: $@\n" ) if( $self->_warnings_is_enabled( 'Changes' ) );
165             }
166             }
167 3 50 33     51 if( !defined( $dt ) || !length( "$dt" ) )
168             {
169 0         0 $dt = DateTime->now;
170             }
171            
172 3         1394 my $fmt_pattern = $self->format;
173 3 50 33     2888 $fmt_pattern = $DEFAULT_DATETIME_FORMAT if( defined( $fmt_pattern ) && $fmt_pattern eq 'default' );
174 3         55 my $tz = $self->time_zone;
175 3 0 33     19 if( ( !defined( $fmt_pattern ) || !length( "$fmt_pattern" ) ) &&
      33        
      33        
176             !$dt->formatter &&
177             defined( $DEFAULT_DATETIME_FORMAT ) &&
178             length( "$DEFAULT_DATETIME_FORMAT" ) )
179             {
180 0         0 $fmt_pattern = $DEFAULT_DATETIME_FORMAT;
181             }
182 3 50       9158 if( defined( $tz ) )
183             {
184             # try-catch
185 3         8 local $@;
186             eval
187 3         7 {
188 3         20 $dt->set_time_zone( $tz );
189             };
190 3 50       1053 if( $@ )
191             {
192 0 0       0 warn( "Warning only: error trying to set the time zone '", $tz->name, "' (", overload::StrVal( $tz ), ") to DateTime object: $@\n" ) if( $self->_warnings_is_enabled( 'Changes' ) );
193             }
194             }
195 3 50 33     29 if( defined( $fmt_pattern ) &&
196             length( "$fmt_pattern" ) )
197             {
198             # try-catch
199 3         26 local $@;
200             eval
201 3         9 {
202 3         30 require DateTime::Format::Strptime;
203 3         32 my $dt_fmt = DateTime::Format::Strptime->new(
204             pattern => $fmt_pattern,
205             locale => 'en_GB',
206             );
207 3         6686 $dt->set_formatter( $dt_fmt );
208             };
209 3 50       500 if( $@ )
210             {
211 0         0 return( $self->error( "Error trying to set formatter for format '${fmt_pattern}': $@" ) );
212             }
213             }
214 3         19 my $nl = $self->nl;
215 3         4693 my $lines = $self->new_array;
216 3 100 100     2296 my $rel_str = $self->new_scalar( $v . ( $self->spacer // ' ' ) . "$dt" . ( $self->note->length ? ( ' ' . $self->note->scalar ) : '' ) . ( $nl // '' ) );
      50        
217 3         2245 $lines->push( $rel_str->scalar );
218             $self->elements->foreach(sub
219             {
220 4     4   3509 my $this = $_->as_string;
221 4 50       1557 if( defined( $this ) )
222             {
223 4         18 $lines->push( $this->scalar );
224             }
225 3         2789 });
226             # my $str = $lines->join( "$nl" );
227 3         3209 my $str = $lines->join( '' );
228 3         212 $self->{_cache_value} = $str;
229 3         8 CORE::delete( $self->{_reset} );
230 3         15 return( $str );
231             }
232              
233             sub changes
234             {
235 156     156 1 1318890 my $self = shift( @_ );
236             # my $a = $self->elements->grep(sub{ $self->_is_a( $_ => 'Changes::Change' ) });
237             # We account for both Changes::Change objects registered directly under this release object, and
238             # and Changes::Change objects registered under any Changes::Group objects
239 156         936 my $a = $self->new_array;
240             $self->elements->foreach(sub
241             {
242 110 100   110   82970 if( $self->_is_a( $_ => 'Changes::Change' ) )
    50          
243             {
244 102         3923 $a->push( $_ );
245             }
246             elsif( $self->_is_a( $_ => 'Changes::Group' ) )
247             {
248 8         428 my $changes = $_->elements->grep(sub{ $self->_is_a( $_ => 'Changes::Change' ) });
  9         5049  
249 8 50       917 $a->push( $changes->list ) if( defined( $changes ) );
250             }
251 156         127675 });
252 156         197422 return( $a );
253             }
254              
255 44     44 1 477192 sub container { return( shift->_set_get_object_without_init( 'container', 'Changes', @_ ) ); }
256              
257 86     86 1 194273 sub datetime { return( shift->reset(@_)->_set_get_datetime( 'datetime', @_ ) ); }
258              
259 5     5 1 523061 sub datetime_formatter { return( shift->reset(@_)->_set_get_code( { field => 'datetime_formatter', undef_ok => 1 }, @_ ) ); }
260              
261 5     5 1 3552 sub defaults { return( shift->_set_get_hash_as_mix_object( { field => 'defaults', undef_ok => 1 }, @_ ) ); }
262              
263             sub delete_change
264             {
265 0     0 1 0 my $self = shift( @_ );
266 0         0 my $elements = $self->elements;
267 0         0 my $removed = $self->new_array;
268 0         0 $self->_load_class( 'overload' );
269 0         0 foreach my $change ( @_ )
270             {
271 0 0       0 if( $self->_is_a( $change => 'Changes::Change' ) )
272             {
273 0         0 my $pos = $elements->pos( $change );
274 0 0       0 if( !defined( $pos ) )
275             {
276 0         0 next;
277             }
278 0         0 my $deleted = $elements->delete( $pos, 1 );
279 0 0       0 $removed->push( $deleted->list ) if( !$deleted->is_empty );
280             }
281             else
282             {
283 0 0 0     0 warn( "I was expecting a Changes::Change object, but instead got '", ( $_[0] // '' ), "' (", ( defined( $_[0] ) ? overload::StrVal( $_[0] ) : 'undef' ), ").\n" ) if( $self->_warnings_is_enabled );
    0          
284             }
285             }
286 0         0 return( $removed );
287             }
288              
289             sub delete_group
290             {
291 0     0 1 0 my $self = shift( @_ );
292 0         0 my $elements = $self->elements;
293 0         0 my $removed = $self->new_array;
294 0         0 $self->_load_class( 'overload' );
295 0         0 foreach my $group ( @_ )
296             {
297 0 0       0 if( $self->_is_a( $group => 'Changes::Group' ) )
298             {
299 0         0 my $pos = $elements->pos( $group );
300 0 0       0 if( !defined( $pos ) )
301             {
302 0         0 next;
303             }
304 0         0 my $deleted = $elements->delete( $pos, 1 );
305 0 0       0 $removed->push( $deleted->list ) if( !$deleted->is_empty );
306             }
307             else
308             {
309 0         0 my $name = $group;
310 0 0 0     0 if( !defined( $name ) || !length( "$name" ) )
311             {
312 0 0       0 warn( "No group name provided to remove its corresponding group object.\n" ) if( $self->_warnings_is_enabled );
313 0         0 next;
314             }
315 0 0   0   0 my $found = $elements->grep(sub{ $self->_is_a( $_ => 'Changes::Group' ) && $_->name eq "$name" });
  0         0  
316 0 0       0 if( $found->is_empty )
317             {
318 0         0 next;
319             }
320             $found->foreach(sub
321             {
322 0     0   0 my $deleted = $self->delete_group( $_ );
323 0 0       0 $removed->push( $deleted->list ) if( !$deleted->is_empty );
324 0         0 });
325             }
326             }
327 0         0 return( $removed );
328             }
329              
330 298     298 1 1650 sub elements { return( shift->_set_get_array_as_object( 'elements', @_ ) ); }
331              
332 7     7 1 3657 sub format { return( shift->reset(@_)->_set_get_scalar_as_object( 'format', @_ ) ); }
333              
334             sub freeze
335             {
336 44     44 0 128 my $self = shift( @_ );
337 44         176 CORE::delete( @$self{qw( _reset )} );
338             $self->elements->foreach(sub
339             {
340 40 50   40   30827 if( $self->_can( $_ => 'freeze' ) )
341             {
342 40         1124 $_->freeze;
343             }
344 44         137 });
345 44         23209 return( $self );
346             }
347              
348             sub groups
349             {
350 6     6 1 77860 my $self = shift( @_ );
351 6     8   29 my $a = $self->elements->grep(sub{ $self->_is_a( $_ => 'Changes::Group' ) });
  8         4590  
352 6         740 return( $a );
353             }
354              
355 44     44 1 48837 sub line { return( shift->reset(@_)->_set_get_number( 'line', @_ ) ); }
356              
357             sub new_change
358             {
359 1     1 1 2 my $self = shift( @_ );
360 1         3 my $opts = $self->_get_args_as_hash( @_ );
361 1 50       687 $self->_load_class( 'Changes::Change' ) || return( $self->pass_error );
362 1         652 my $defaults = $self->defaults;
363 1 50       558 if( defined( $defaults ) )
364             {
365 1         2 foreach my $opt ( qw( spacer1 marker spacer2 max_width wrapper ) )
366             {
367 5 100 33     105 $opts->{ $opt } //= $defaults->{ $opt } if( defined( $defaults->{ $opt } ) );
368             }
369             }
370 1   50     30 my $c = Changes::Change->new( $opts ) ||
371             return( $self->pass_error( Changes::Change->error ) );
372 1         9 return( $c );
373             }
374              
375             sub new_group
376             {
377 2     2 1 7 my $self = shift( @_ );
378 2         12 my $opts = $self->_get_args_as_hash( @_ );
379 2 50       2250 $self->_load_class( 'Changes::Group' ) || return( $self->pass_error );
380 2         1951 my $defaults = $self->defaults;
381 2 50       1755 if( defined( $defaults ) )
382             {
383 2         20 my $def = { %$defaults };
384 2         331 foreach my $opt ( qw( spacer type ) )
385             {
386 4 50 66     50 if( !defined( $opts->{ "group_${opt}" } ) &&
      66        
      66        
387             exists( $def->{ "group_${opt}" } ) &&
388             defined( $def->{ "group_${opt}" } ) &&
389             length( $def->{ "group_${opt}" } ) )
390             {
391 2         12 $opts->{ $opt } = CORE::delete( $def->{ "group_${opt}" } );
392             }
393             }
394 2   33     16 $opts->{defaults} //= $def;
395             }
396 2   50     29 my $g = Changes::Group->new( $opts ) ||
397             return( $self->pass_error( Changes::Group->error ) );
398 2         27 return( $g );
399             }
400              
401             sub new_line
402             {
403 1     1 1 1660 my $self = shift( @_ );
404 1 50       6 $self->_load_class( 'Changes::NewLine' ) || return( $self->pass_error );
405 1   50     673 my $nl = Changes::NewLine->new( @_ ) ||
406             return( $self->pass_error( Changes::NewLine->error ) );
407 1         16 return( $nl );
408             }
409              
410             sub new_version
411             {
412 0     0 1 0 my $self = shift( @_ );
413 0 0       0 $self->_load_class( 'Changes::Version' ) || return( $self->pass_error );
414 0   0     0 my $v = Changes::Version->new( @_ ) ||
415             return( $self->pass_error( Changes::Version->error ) );
416 0         0 return( $v );
417             }
418              
419 48     48 1 258280 sub nl { return( shift->reset(@_)->_set_get_scalar_as_object( 'nl', @_ ) ); }
420              
421 29     29 1 286521 sub note { return( shift->reset(@_)->_set_get_scalar_as_object( 'note', @_ ) ); }
422              
423 45     45 1 465319 sub raw { return( shift->_set_get_scalar_as_object( 'raw', @_ ) ); }
424              
425 0     0 1 0 sub remove_change { return( shift->delete_change( @_ ) ); }
426              
427 0     0 1 0 sub remove_group { return( shift->delete_group( @_ ) ); }
428              
429             sub reset
430             {
431 378     378 0 911 my $self = shift( @_ );
432 378 100 33     3729 if( (
      100        
433             !exists( $self->{_reset} ) ||
434             !defined( $self->{_reset} ) ||
435             !CORE::length( $self->{_reset} )
436             ) && scalar( @_ ) )
437             {
438 47         143 $self->{_reset} = scalar( @_ );
439             # Cascade down the need for reset
440             $self->changes->foreach(sub
441             {
442 0 0   0   0 if( $self->_can( $_ => 'reset' ) )
443             {
444 0         0 $_->reset(1);
445             }
446 47         273 });
447             }
448 378         14730 return( $self );
449             }
450              
451 0     0 1 0 sub set_default_format { return( shift->format( $DEFAULT_DATETIME_FORMAT ) ); }
452              
453 52     52 1 144819 sub spacer { return( shift->reset(@_)->_set_get_scalar_as_object( 'spacer', @_ ) ); }
454              
455             sub time_zone
456             {
457 10     10 1 11489 my $self = shift( @_ );
458 10 100       44 if( @_ )
459             {
460 5         15 my $v = shift( @_ );
461 5 100       31 if( $self->_is_a( $v => 'DateTime::TimeZone' ) )
462             {
463 2         113 $self->{time_zone} = $v;
464             }
465             else
466             {
467 3 50       54 $self->_load_class( 'DateTime::TimeZone' ) || return( $self->pass_error );
468             # try-catch
469 3         2126 local $@;
470             eval
471 3         9 {
472 3         44 my $tz = DateTime::TimeZone->new( name => "$v" );
473 3         28791 $self->{time_zone} = $tz;
474             };
475 3 50       15 if( $@ )
476             {
477 0         0 return( $self->error( "Error setting time zone for '$v': $@" ) );
478             }
479             }
480 5         57 $self->reset(1);
481             }
482 10 50       40 if( !defined( $self->{time_zone} ) )
483             {
484 0 0       0 if( Wanted::want( 'OBJECT' ) )
485             {
486 0         0 require Module::Generic::Null;
487 0         0 rreturn( Module::Generic::Null->new( wants => 'OBJECT' ) );
488             }
489             else
490             {
491 0         0 return;
492             }
493             }
494             else
495             {
496 10         48 return( $self->{time_zone} );
497             }
498             }
499              
500 102     102 1 609251 sub version { return( shift->reset(@_)->_set_get_version( { field => 'version', class => $VERSION_CLASS }, @_ ) ); }
501              
502             sub DESTROY
503             {
504             # <https://perldoc.perl.org/perlobj#Destructors>
505 3     3   33804 CORE::local( $., $@, $!, $^E, $? );
506 3         10 my $self = CORE::shift( @_ );
507 3 50       15 CORE::return if( !CORE::defined( $self ) );
508 3 50       173 CORE::return if( ${^GLOBAL_PHASE} eq 'DESTRUCT' );
509             };
510              
511             1;
512             # NOTE: POD
513             __END__
514              
515             =encoding utf-8
516              
517             =head1 NAME
518              
519             Changes::Release - Release object class
520              
521             =head1 SYNOPSIS
522              
523             use Changes::Release;
524             my $rel = Changes::Release->new(
525             # A Changes object
526             container => $changes_object,
527             datetime => '2022-11-17T08:12:42+0900',
528             datetime_formatter => sub
529             {
530             my $dt = shift( @_ ) || DateTime->now;
531             require DateTime::Format::Strptime;
532             my $fmt = DateTime::Format::Strptime->new(
533             pattern => '%FT%T%z',
534             locale => 'en_GB',
535             );
536             $dt->set_formatter( $fmt );
537             $dt->set_time_zone( 'Asia/Tokyo' );
538             return( $dt );
539             },
540             format => '%FT%T%z',
541             line => 12,
542             note => 'Initial release',
543             spacer => "\t",
544             time_zone => 'Asia/Tokyo',
545             version => 'v0.1.0',
546             ) || die( Changes::Release->error, "\n" );
547             my $change = $rel->add_change( $change_object );
548             # or
549             my $change = $rel->add_change( text => 'Some comments' );
550             my $group = $rel->add_group( $group_object );
551             # or
552             my $group = $rel->add_group( name => 'Some group' );
553             my $change = $rel->delete_change( $change_object );
554             my $group = $rel->delete_group( $group_object );
555             say $rel->as_string;
556              
557             =head1 VERSION
558              
559             v0.2.3
560              
561             =head1 DESCRIPTION
562              
563             This class implements a C<Changes> file release line. Such information usually comprise of a C<version> number, a C<release datetime> and an optional note
564              
565             Each release section can contain L<group|Changes::Group> and L<changes|Changes::Change> that are all stored and accessible in L</changes>
566              
567             If an error occurred, it returns an L<error|Module::Generic/error>
568              
569             The result of this method is cached so that the second time it is called, the cache is used unless there has been any change.
570              
571             =head1 METHODS
572              
573             =head2 add_change
574              
575             Provided with a L<Changes::Change> object, or an hash or hash reference of options passed to the constructor of L<Changes::Change>, and this will add the change object to the list of elements for this release object.
576              
577             It returns the L<Changes::Change> object, or an L<error|Module::Generic/error> if an error occurred.
578              
579             =head2 add_group
580              
581             Provided with a L<Changes::Group> object, or an hash or hash reference of options passed to the constructor of L<Changes::Group>, and this will add the change object to the list of elements.
582              
583             It returns the L<Changes::Group> object, or an L<error|Module::Generic/error> if an error occurred.
584              
585             =head2 as_string
586              
587             Returns a L<string object|Module::Generic::Scalar> representing the release. It does so by calling C<as_string> on each element stored in L</elements>. Those elements can be L<Changes::Group> and L<Changes::Change> objects.
588              
589             If an error occurred, it returns an L<error|Module::Generic/error>
590              
591             The result of this method is cached so that the second time it is called, the cache is used unless there has been any change.
592              
593             =head2 changes
594              
595             Read only. This returns an L<array object|Module::Generic::Array> containing all the L<change objects|Changes::Change> within this release object.
596              
597             =head2 container
598              
599             Sets or gets the L<container object|Changes> for this release object. A container is the object representing the C<Changes> file: a L<Changes> object.
600              
601             Note that if you instantiate a release object directly, this value will obviously be C<undef>. This value is set by L<Changes> upon parsing the C<Changes> file.
602              
603             =head2 datetime
604              
605             Sets or gets the release datetime information. This uses L<Module::Generic/_parse_datetime> to parse the string, so please check that documentation for supported formats.
606              
607             However, most format are supported including ISO8601 format and L<W3CDTF format|http://www.w3.org/TR/NOTE-datetime> (e.g. C<2022-07-17T12:10:03+09:00>)
608              
609             Note that if you use a relative datetime format such as C<-2D> for 2 days ago, the datetime format will be set to a unix timestamp, and in that case you need to also specify the C<format> option with the desired datetime format.
610              
611             You can alternatively directly set a L<DateTime> object.
612              
613             It returns a L<DateTime> object whose L<date formatter|DateTime::Format::Strptime> object is set to the same format as provided. This ensures that any stringification of the L<DateTime> object reverts back to the string as found in the C<Changes> file or as provided by the user.
614              
615             =head2 datetime_formatter
616              
617             Sets or gets a code reference callback to be used when formatting the release datetime. This allows you to use alternative formatter and greater control over the formatting of the release datetime.
618              
619             This code is called with a L<DateTime> object, and it must return a L<DateTime> object. Any other value will be discarded and it will fallback on setting up a L<DateTime> with current date and time using UTC as time zone and C<$DEFAULT_DATETIME_FORMAT> as default datetime format.
620              
621             The code executed may die if needed and any exception will be caught and a warning will be issued if L<warnings> are enabled for L<Changes>.
622              
623             =head2 defaults
624              
625             Sets or gets an hash of default values for the L<Changes::Change> object when it is instantiated by the C<new_change> method.
626              
627             Default is C<undef>, which means no default value is set.
628              
629             my $ch = Changes->new(
630             file => '/some/where/Changes',
631             defaults => {
632             # For Changes::Change
633             spacer1 => "\t",
634             spacer2 => ' ',
635             marker => '-',
636             max_width => 72,
637             wrapper => $code_reference,
638             # For Changes::Group
639             group_spacer => "\t",
640             group_type => 'bracket', # [Some group]
641             }
642             );
643              
644             =head2 delete_change
645              
646             This takes a list of change to remove and returns an L<array object|Module::Generic::Array> of those changes thus removed.
647              
648             A change provided can only be a L<Changes::Change> object.
649              
650             If an error occurred, this will return an L<error|Module::Generic/error>
651              
652             =head2 delete_group
653              
654             This takes a list of group to remove and returns an L<array object|Module::Generic::Array> of those groups thus removed.
655              
656             A group provided can either be a L<Changes::Group> object, or a group name as a string.
657              
658             If an error occurred, this will return an L<error|Module::Generic/error>
659              
660             =head2 elements
661              
662             Sets or gets an L<array object|Module::Generic::Array> of all the elements within this release object. Those elements can be L<Changes::Group>, L<Changes::Change> and C<Changes::NewLine> objects.
663              
664             =head2 format
665              
666             Sets or gets a L<DateTime> format to be used with L<DateTime::Format::Strptime>. See L<DateTime::Format::Strptime/"STRPTIME PATTERN TOKENS"> for details on possible patterns.
667              
668             You can also specify an alternative formatter with L</datetime_formatter>
669              
670             If you specify the special value C<default>, it will use default value set in the global variable C<$DEFAULT_DATETIME_FORMAT>, which is C<%FT%T%z> (for example: C<2022-12-08T20:13:09+0900>)
671              
672             It returns a L<scalar object|Module::Generic::Scalar>
673              
674             =for Pod::Coverage freeze
675              
676             =head2 groups
677              
678             Read only. This returns an L<array object|Module::Generic::Array> containing all the L<group objects|Changes::Group> within this release object.
679              
680             =head2 line
681              
682             Sets or gets an integer representing the line number where this release line was found in the original C<Changes> file. If this object was instantiated separately, then obviously this value will be C<undef>
683              
684             =head2 new_change
685              
686             Instantiates and returns a new L<Changes::Change>, passing its constructor any argument provided.
687              
688             my $change = $rel->new_change( text => 'Some change' ) ||
689             die( $rel->error );
690              
691             =head2 new_group
692              
693             Instantiates and returns a new L<Changes::Group>, passing its constructor any argument provided.
694              
695             my $change = $rel->new_group( name => 'Some group' ) ||
696             die( $rel->error );
697              
698             =head2 new_line
699              
700             Returns a new C<Changes::NewLine> object, passing it any parameters provided.
701              
702             If an error occurred, it returns an L<error object|Module::Generic/error>
703              
704             =head2 new_version
705              
706             Returns a new C<Changes::Version> object, passing it any parameters provided.
707              
708             If an error occurred, it returns an L<error object|Module::Generic/error>
709              
710             =head2 nl
711              
712             Sets or gets the new line character, which defaults to C<\n>
713              
714             It returns a L<number object|Module::Generic::Number>
715              
716             =head2 note
717              
718             Sets or gets an optional note that is set after the release datetime.
719              
720             It returns a L<scalar object|Module::Generic::Scalar>
721              
722             =head2 raw
723              
724             Sets or gets the raw line as found in the C<Changes> file for this release. If nothing is change, and a raw version exists, then it is returned instead of computing the formatting of the line.
725              
726             It returns a L<scalar object|Module::Generic::Scalar>
727              
728             =head2 remove_change
729              
730             This is an alias for L</delete_change>
731              
732             =head2 remove_group
733              
734             This is an alias for L</delete_group>
735              
736             =for Pod::Coverage reset
737              
738             =head2 set_default_format
739              
740             Sets the default L<DateTime> format pattern used by L<DateTime::Format::Strptime>. This default value used is C<$DEFAULT_DATETIME_FORMAT>, which, by default is: C<%FT%T%z>, i.e. something that would look like C<2022-12-06T20:13:09+0900>
741              
742             =head2 spacer
743              
744             Sets or gets the space that can be found between the version information and the datetime. Normally this would be just one space, but since it can be other space, this is used to capture it and ensure the result is identical to what was parsed.
745              
746             This defaults to a single space if it is not set.
747              
748             It returns a L<scalar object|Module::Generic::Scalar>
749              
750             =head2 time_zone
751              
752             Sets or gets a time zone to use for the release date. A valid time zone can either be an olson time zone string such as C<Asia/Tokyo>, or an L<DateTime::TimeZone> object.
753              
754             It returns a L<DateTime::TimeZone> object upon success, or an L<error|Module::Generic/error> if an error occurred.
755              
756             =head2 version
757              
758             Sets or gets the version information for this release. This returns a L<version> object. If you prefer to use a different class, such as L<Perl::Version>, then you can set the global variable C<$VERSION_CLASS> accordingly.
759              
760             It returns a L<version object|version>, or an object of whatever class you have set with C<$VERSION_CLASS>
761              
762             =head2 changes
763              
764             Sets or gets the L<array object|Module::Generic::Array> containing all the object representing the changes for that release. Those changes can be L<Changes::Group>, L<Changes::Change> or C<Changes::Line>
765              
766             =head1 AUTHOR
767              
768             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
769              
770             =head1 SEE ALSO
771              
772             L<Changes>, L<Changes::Group>, L<Changes::Change>, L<Changes::Version>, L<Changes::NewLine>
773              
774             =head1 COPYRIGHT & LICENSE
775              
776             Copyright(c) 2022 DEGUEST Pte. Ltd.
777              
778             All rights reserved
779              
780             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
781              
782             =cut