File Coverage

lib/Changes/Release.pm
Criterion Covered Total %
statement 315 681 46.2
branch 95 634 14.9
condition 76 280 27.1
subroutine 51 64 79.6
pod 28 30 93.3
total 565 1689 33.4


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## Changes file management - ~/lib/Changes/Release.pm
3             ## Version v0.2.1
4             ## Copyright(c) 2022 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <jack@deguest.jp>
6             ## Created 2022/11/23
7             ## Modified 2022/12/18
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   667899 use strict;
  19         46  
  19         651  
18 19     19   99 use warnings;
  19         40  
  19         547  
19 19     19   102 use warnings::register;
  19         48  
  19         2121  
20 19     19   132 use parent qw( Module::Generic );
  19         90  
  19         135  
21 19     19   12418125 use vars qw( $VERSION $VERSION_CLASS $DEFAULT_DATETIME_FORMAT );
  19         42  
  19         986  
22 19     19   7370 use Changes::Group;
  19         54  
  19         213  
23 19     19   16704 use Changes::Version;
  19         97  
  19         316  
24 19     19   9021 use DateTime;
  19         535629  
  19         570  
25 19     19   108 use Nice::Try;
  19         38  
  19         154  
26 19     19   18072373 use Want;
  19         58  
  19         2552  
27 19     19   73 our $VERSION_CLASS = 'Changes::Version';
28 19         44 our $DEFAULT_DATETIME_FORMAT = '%FT%T%z';
29 19         399 our $VERSION = 'v0.2.1';
30             };
31              
32 19     19   117 use strict;
  19         41  
  19         523  
33 19     19   104 use warnings;
  19         40  
  19         33681  
34              
35             sub init
36             {
37 47     47 1 4151 my $self = shift( @_ );
38 47         1406 $self->{changes} = [];
39 47         171 $self->{container} = undef;
40 47         161 $self->{datetime} = undef;
41 47         160 $self->{datetime_formatter} = undef;
42 47         152 $self->{defaults} = undef;
43 47         179 $self->{elements} = [];
44             # DateTime format
45 47         174 $self->{format} = undef;
46 47         181 $self->{line} = undef;
47 47         165 $self->{nl} = "\n";
48 47         158 $self->{note} = undef;
49 47         141 $self->{raw} = undef;
50 47         136 $self->{spacer} = undef;
51 47         145 $self->{time_zone} = undef;
52 47         149 $self->{version} = '';
53 47         144 $self->{_init_strict_use_sub} = 1;
54 47 50       321 $self->SUPER::init( @_ ) || return( $self->pass_error );
55 47         840271 $self->{_reset} = 1;
56 47         196 return( $self );
57             }
58              
59             sub add_change
60             {
61 1     1 1 676 my $self = shift( @_ );
62 1         2 my( $change, $opts );
63 1         5 my $elements = $self->elements;
64 1 50 33     895 if( scalar( @_ ) == 1 && $self->_is_a( $_[0] => 'Changes::Change' ) )
65             {
66 0         0 $change = shift( @_ );
67 0 0       0 if( $elements->exists( $change ) )
68             {
69 0         0 $self->_load_class( 'overload' );
70 0         0 return( $self->error( "A very same change object (", overload::StrVal( $change ), ") is already registered." ) );
71             }
72             }
73             else
74             {
75 1         8 $opts = $self->_get_args_as_hash( @_ );
76 1   50     166 $change = $self->new_change( %$opts ) || return( $self->pass_error );
77             }
78 1         6 $elements->push( $change );
79 1         9 return( $change );
80             }
81              
82             sub add_group
83             {
84 4     4 1 1163 my $self = shift( @_ );
85 4         16 my( $group, $opts );
86 4         16 my $elements = $self->elements;
87 4 100 66     3648 if( scalar( @_ ) == 1 && $self->_is_a( $_[0] => 'Changes::Group' ) )
88             {
89 2         92 $group = shift( @_ );
90 2 50       19 if( $elements->exists( $group ) )
91             {
92 0         0 $self->_load_class( 'overload' );
93 0         0 return( $self->error( "A very same group object (", overload::StrVal( $group ), ") is already registered." ) );
94             }
95 2         78 my $name = $group->name;
96 2 50 33     1874 if( !defined( $name ) || !length( "$name" ) )
97             {
98 0         0 return( $self->error( "Group object provided has empty name." ) );
99             }
100 2 50 50 1   42 my $same = $elements->grep(sub{ $self->_is_a( $_ => 'Changes::Group' ) && ( ( $_->name // '' ) eq "$name" ) });
  1         17  
101 2 50       1206 return( $self->error( "A similar group with name '$name' is already registered." ) ) if( !$same->is_empty );
102             }
103             else
104             {
105 2         15 $opts = $self->_get_args_as_hash( @_ );
106 2   50     278 $group = $self->new_group( %$opts ) || return( $self->pass_error );
107 2         15 return( $self->add_group( $group ) );
108             }
109 2         56 my $last = $elements->last;
110             # 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
111 2 100 66     139 if( $elements->length && !$self->_is_a( $last => 'Changes::NewLine' ) )
112             {
113 1   50     41626 $elements->push( $self->new_line( nl => ( $self->nl // "\n" ) ) );
114             }
115 2         40686 $elements->push( $group );
116 2         306 return( $group );
117             }
118              
119             sub as_string
120             {
121 47     47 1 7609 my $self = shift( @_ );
122 47 50 66     251 if( !exists( $self->{_reset} ) ||
      33        
123             !defined( $self->{_reset} ) ||
124             !CORE::length( $self->{_reset} ) )
125             {
126 44         86 my $cache;
127 44 100 66     525 if( exists( $self->{_cache_value} ) &&
    50 66        
      33        
128             defined( $self->{_cache_value} ) &&
129             length( $self->{_cache_value} ) )
130             {
131 2         20 $cache = $self->{_cache_value};
132             }
133             elsif( defined( $self->{raw} ) && length( "$self->{raw}" ) )
134             {
135 42         374 $cache = $self->{raw};
136             }
137            
138 44         179 my $lines = $self->new_array( $cache->scalar );
139             $self->elements->foreach(sub
140             {
141 42     42   23444 my $this = $_->as_string;
142 42 50       268 if( defined( $this ) )
143             {
144 42         140 $lines->push( $this->scalar );
145             }
146 44         1366 });
147             # my $str = $lines->join( "\n" );
148 44         10894 my $str = $lines->join( '' );
149 44         1639 return( $str );
150             }
151 3         16 my $v = $self->version;
152 3 50 33     880 return( $self->error( "No version set yet. Set a version before calling as_string()" ) ) if( !defined( $v ) || !length( "$v" ) );
153 3         48 my $dt = $self->datetime;
154 3         3210 my $code = $self->datetime_formatter;
155 3 100 66     2819 if( defined( $code ) && ref( $code ) eq 'CODE' )
156             {
157 1 50 33     5 try
  1         2  
  1         3  
  1         5  
  0         0  
  1         2  
  1         4  
  1         3  
158 1     1   1 {
159 1 50       7 $dt = $code->( defined( $dt ) ? $dt : () );
160             }
161 1 0 50     20 catch( $e )
  1 0 33     2750  
  1 0       6  
  1 0       2  
  1 0       3  
  1 0       2  
  1 0       2  
  1 0       6  
  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  
  1         7  
  0         0  
  1         4  
  0         0  
  0         0  
  1         5  
  1         6  
  1         3  
  1         4  
  0         0  
  0         0  
  0         0  
  0         0  
162 0     0   0 {
163 0 0       0 warn( "Warning only: error with datetime formatter calback: $e\n" ) if( $self->_warnings_is_enabled( 'Changes' ) );
164 19 0 0 19   164 }
  19 0 0     43  
  19 0 33     23955  
  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  
  1 0       3  
  0 0       0  
  1 0       39  
  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  
  1         4  
  0         0  
  0         0  
  0         0  
  0         0  
  1         6  
165             }
166 3 50 33     37 if( !defined( $dt ) || !length( "$dt" ) )
167             {
168 0         0 $dt = DateTime->now;
169             }
170            
171 3         1499 my $fmt_pattern = $self->format;
172 3 50 33     2881 $fmt_pattern = $DEFAULT_DATETIME_FORMAT if( defined( $fmt_pattern ) && $fmt_pattern eq 'default' );
173 3         72 my $tz = $self->time_zone;
174 3 0 33     26 if( ( !defined( $fmt_pattern ) || !length( "$fmt_pattern" ) ) &&
      33        
      33        
175             !$dt->formatter &&
176             defined( $DEFAULT_DATETIME_FORMAT ) &&
177             length( "$DEFAULT_DATETIME_FORMAT" ) )
178             {
179 0         0 $fmt_pattern = $DEFAULT_DATETIME_FORMAT;
180             }
181 3 50       46 if( defined( $tz ) )
182             {
183 3 50 33     12 try
  3         7  
  3         9  
  3         20  
  0         0  
  3         8  
  3         14  
  3         8  
184 3     3   6 {
185 3         18 $dt->set_time_zone( $tz );
186             }
187 3 0 50     29 catch( $e )
  3 0 33     959  
  3 0       15  
  3 0       10  
  3 0       5  
  3 0       7  
  3 0       8  
  3 0       26  
  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  
  3         16  
  0         0  
  3         8  
  0         0  
  0         0  
  3         19  
  3         16  
  3         8  
  3         12  
  0         0  
  0         0  
  0         0  
  0         0  
188 0     0   0 {
189 0 0       0 warn( "Warning only: error trying to set the time zone '", $tz->name, "' (", overload::StrVal( $tz ), ") to DateTime object: $e\n" ) if( $self->_warnings_is_enabled( 'Changes' ) );
190 19 0 0 19   164 }
  19 0 0     41  
  19 0 33     22951  
  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  
  3 0       23  
  0 0       0  
  3 0       177  
  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  
  3         15  
  0         0  
  0         0  
  0         0  
  0         0  
  3         13  
191             }
192 3 50 33     27 if( defined( $fmt_pattern ) &&
193             length( "$fmt_pattern" ) )
194             {
195 3 50 33     29 try
  3         6  
  3         6  
  3         15  
  0         0  
  3         6  
  3         9  
  3         6  
196 3     3   21 {
197 3         30 require DateTime::Format::Strptime;
198 3         19 my $dt_fmt = DateTime::Format::Strptime->new(
199             pattern => $fmt_pattern,
200             locale => 'en_GB',
201             );
202 3         5441 $dt->set_formatter( $dt_fmt );
203             }
204 3 0 50     28 catch( $e )
  3 0 33     432  
  3 0       20  
  3 0       10  
  3 0       6  
  3 0       5  
  3 0       8  
  3 0       18  
  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  
  3         15  
  0         0  
  3         9  
  0         0  
  0         0  
  3         14  
  3         17  
  3         11  
  3         11  
  0         0  
  0         0  
  0         0  
  0         0  
205 0     0   0 {
206 0         0 return( $self->error( "Error trying to set formatter for format '${fmt_pattern}': $e" ) );
207 19 0 0 19   160 }
  19 0 0     39  
  19 0 33     55170  
  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  
  3 0       13  
  0 0       0  
  3 0       147  
  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  
  3         18  
  0         0  
  0         0  
  0         0  
  0         0  
  3         20  
208             }
209 3         19 my $nl = $self->nl;
210 3         2906 my $lines = $self->new_array;
211 3 100 100     78 my $rel_str = $self->new_scalar( $v . ( $self->spacer // ' ' ) . "$dt" . ( $self->note->length ? ( ' ' . $self->note->scalar ) : '' ) . ( $nl // '' ) );
      50        
212 3         2294 $lines->push( $rel_str->scalar );
213             $self->elements->foreach(sub
214             {
215 4     4   1604 my $this = $_->as_string;
216 4 50       94 if( defined( $this ) )
217             {
218 4         16 $lines->push( $this->scalar );
219             }
220 3         231 });
221             # my $str = $lines->join( "$nl" );
222 3         1113 my $str = $lines->join( '' );
223 3         140 $self->{_cache_value} = $str;
224 3         11 CORE::delete( $self->{_reset} );
225 3         17 return( $str );
226             }
227              
228             sub changes
229             {
230 156     156 1 1447867 my $self = shift( @_ );
231             # my $a = $self->elements->grep(sub{ $self->_is_a( $_ => 'Changes::Change' ) });
232             # We account for both Changes::Change objects registered directly under this release object, and
233             # and Changes::Change objects registered under any Changes::Group objects
234 156         780 my $a = $self->new_array;
235             $self->elements->foreach(sub
236             {
237 110 100   110   65209 if( $self->_is_a( $_ => 'Changes::Change' ) )
    50          
238             {
239 102         4416 $a->push( $_ );
240             }
241             elsif( $self->_is_a( $_ => 'Changes::Group' ) )
242             {
243 8         578 my $changes = $_->elements->grep(sub{ $self->_is_a( $_ => 'Changes::Change' ) });
  9         5004  
244 8 50       902 $a->push( $changes->list ) if( defined( $changes ) );
245             }
246 156         13742 });
247 156         56444 return( $a );
248             }
249              
250 44     44 1 1462726 sub container { return( shift->_set_get_object_without_init( 'container', 'Changes', @_ ) ); }
251              
252 86     86 1 245324 sub datetime { return( shift->reset(@_)->_set_get_datetime( 'datetime', @_ ) ); }
253              
254 5     5 1 4631455 sub datetime_formatter { return( shift->reset(@_)->_set_get_code( { field => 'datetime_formatter', undef_ok => 1 }, @_ ) ); }
255              
256 5     5 1 4631744 sub defaults { return( shift->_set_get_hash_as_mix_object( { field => 'defaults', undef_ok => 1 }, @_ ) ); }
257              
258             sub delete_change
259             {
260 0     0 1 0 my $self = shift( @_ );
261 0         0 my $elements = $self->elements;
262 0         0 my $removed = $self->new_array;
263 0         0 $self->_load_class( 'overload' );
264 0         0 foreach my $change ( @_ )
265             {
266 0 0       0 if( $self->_is_a( $change => 'Changes::Change' ) )
267             {
268 0         0 my $pos = $elements->pos( $change );
269 0 0       0 if( !defined( $pos ) )
270             {
271 0         0 next;
272             }
273 0         0 my $deleted = $elements->delete( $pos, 1 );
274 0 0       0 $removed->push( $deleted->list ) if( !$deleted->is_empty );
275             }
276             else
277             {
278 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          
279             }
280             }
281 0         0 return( $removed );
282             }
283              
284             sub delete_group
285             {
286 0     0 1 0 my $self = shift( @_ );
287 0         0 my $elements = $self->elements;
288 0         0 my $removed = $self->new_array;
289 0         0 $self->_load_class( 'overload' );
290 0         0 foreach my $group ( @_ )
291             {
292 0 0       0 if( $self->_is_a( $group => 'Changes::Group' ) )
293             {
294 0         0 my $pos = $elements->pos( $group );
295 0 0       0 if( !defined( $pos ) )
296             {
297 0         0 next;
298             }
299 0         0 my $deleted = $elements->delete( $pos, 1 );
300 0 0       0 $removed->push( $deleted->list ) if( !$deleted->is_empty );
301             }
302             else
303             {
304 0         0 my $name = $group;
305 0 0 0     0 if( !defined( $name ) || !length( "$name" ) )
306             {
307 0 0       0 warn( "No group name provided to remove its corresponding group object.\n" ) if( $self->_warnings_is_enabled );
308 0         0 next;
309             }
310 0 0   0   0 my $found = $elements->grep(sub{ $self->_is_a( $_ => 'Changes::Group' ) && $_->name eq "$name" });
  0         0  
311 0 0       0 if( $found->is_empty )
312             {
313 0         0 next;
314             }
315             $found->foreach(sub
316             {
317 0     0   0 my $deleted = $self->delete_group( $_ );
318 0 0       0 $removed->push( $deleted->list ) if( !$deleted->is_empty );
319 0         0 });
320             }
321             }
322 0         0 return( $removed );
323             }
324              
325 298     298 1 1437 sub elements { return( shift->_set_get_array_as_object( 'elements', @_ ) ); }
326              
327 7     7 1 2946 sub format { return( shift->reset(@_)->_set_get_scalar_as_object( 'format', @_ ) ); }
328              
329             sub freeze
330             {
331 44     44 0 92 my $self = shift( @_ );
332 44         203 CORE::delete( @$self{qw( _reset )} );
333             $self->elements->foreach(sub
334             {
335 40 50   40   22059 if( $self->_can( $_ => 'freeze' ) )
336             {
337 40         1006 $_->freeze;
338             }
339 44         135 });
340 44         6021 return( $self );
341             }
342              
343             sub groups
344             {
345 6     6 1 91228 my $self = shift( @_ );
346 6     8   29 my $a = $self->elements->grep(sub{ $self->_is_a( $_ => 'Changes::Group' ) });
  8         3976  
347 6         688 return( $a );
348             }
349              
350 44     44 1 912226 sub line { return( shift->reset(@_)->_set_get_number( 'line', @_ ) ); }
351              
352             sub new_change
353             {
354 1     1 1 3 my $self = shift( @_ );
355 1         4 my $opts = $self->_get_args_as_hash( @_ );
356 1 50       114 $self->_load_class( 'Changes::Change' ) || return( $self->pass_error );
357 1         47 my $defaults = $self->defaults;
358 1 50       885 if( defined( $defaults ) )
359             {
360 1         11 foreach my $opt ( qw( spacer1 marker spacer2 max_width wrapper ) )
361             {
362 5 100 33     129 $opts->{ $opt } //= $defaults->{ $opt } if( defined( $defaults->{ $opt } ) );
363             }
364             }
365 1   50     33 my $c = Changes::Change->new( $opts ) ||
366             return( $self->pass_error( Changes::Change->error ) );
367 1         12 return( $c );
368             }
369              
370             sub new_group
371             {
372 2     2 1 6 my $self = shift( @_ );
373 2         8 my $opts = $self->_get_args_as_hash( @_ );
374 2 50       222 $self->_load_class( 'Changes::Group' ) || return( $self->pass_error );
375 2         125 my $defaults = $self->defaults;
376 2 50       1767 if( defined( $defaults ) )
377             {
378 2         14 my $def = { %$defaults };
379 2         222 foreach my $opt ( qw( spacer type ) )
380             {
381 4 50 66     99 if( !defined( $opts->{ "group_${opt}" } ) &&
      66        
      66        
382             exists( $def->{ "group_${opt}" } ) &&
383             defined( $def->{ "group_${opt}" } ) &&
384             length( $def->{ "group_${opt}" } ) )
385             {
386 2         13 $opts->{ $opt } = CORE::delete( $def->{ "group_${opt}" } );
387             }
388             }
389 2   33     18 $opts->{defaults} //= $def;
390             }
391 2   50     23 my $g = Changes::Group->new( $opts ) ||
392             return( $self->pass_error( Changes::Group->error ) );
393 2         25 return( $g );
394             }
395              
396             sub new_line
397             {
398 1     1 1 932 my $self = shift( @_ );
399 1 50       8 $self->_load_class( 'Changes::NewLine' ) || return( $self->pass_error );
400 1   50     49 my $nl = Changes::NewLine->new( @_ ) ||
401             return( $self->pass_error( Changes::NewLine->error ) );
402 1         14 return( $nl );
403             }
404              
405             sub new_version
406             {
407 0     0 1 0 my $self = shift( @_ );
408 0 0       0 $self->_load_class( 'Changes::Version' ) || return( $self->pass_error );
409 0   0     0 my $v = Changes::Version->new( @_ ) ||
410             return( $self->pass_error( Changes::Version->error ) );
411 0         0 return( $v );
412             }
413              
414 48     48 1 1068612 sub nl { return( shift->reset(@_)->_set_get_scalar_as_object( 'nl', @_ ) ); }
415              
416 29     29 1 4258862 sub note { return( shift->reset(@_)->_set_get_scalar_as_object( 'note', @_ ) ); }
417              
418 45     45 1 2063863 sub raw { return( shift->_set_get_scalar_as_object( 'raw', @_ ) ); }
419              
420 0     0 1 0 sub remove_change { return( shift->delete_change( @_ ) ); }
421              
422 0     0 1 0 sub remove_group { return( shift->delete_group( @_ ) ); }
423              
424             sub reset
425             {
426 378     378 0 907 my $self = shift( @_ );
427 378 100 33     3748 if( (
      100        
428             !exists( $self->{_reset} ) ||
429             !defined( $self->{_reset} ) ||
430             !CORE::length( $self->{_reset} )
431             ) && scalar( @_ ) )
432             {
433 47         165 $self->{_reset} = scalar( @_ );
434             # Cascade down the need for reset
435             $self->changes->foreach(sub
436             {
437 0 0   0   0 if( $self->_can( $_ => 'reset' ) )
438             {
439 0         0 $_->reset(1);
440             }
441 47         261 });
442             }
443 378         5831 return( $self );
444             }
445              
446 0     0 1 0 sub set_default_format { return( shift->format( $DEFAULT_DATETIME_FORMAT ) ); }
447              
448 52     52 1 634315 sub spacer { return( shift->reset(@_)->_set_get_scalar_as_object( 'spacer', @_ ) ); }
449              
450             sub time_zone
451             {
452 10     10 1 5846 my $self = shift( @_ );
453 10 100       44 if( @_ )
454             {
455 5         15 my $v = shift( @_ );
456 5 100       41 if( $self->_is_a( $v => 'DateTime::TimeZone' ) )
457             {
458 2         89 $self->{time_zone} = $v;
459             }
460             else
461             {
462 3 50 33     63 try
  3         6  
  3         7  
  3         38  
  0         0  
  3         13  
  3         28  
  3         10  
463 3     3   5 {
464 3 50       23 $self->_load_class( 'DateTime::TimeZone' ) || return( $self->pass_error );
465 3         187 my $tz = DateTime::TimeZone->new( name => "$v" );
466 3         26556 $self->{time_zone} = $tz;
467             }
468 3 0 50     31 catch( $e )
  3 0 33     14  
  3 0       12  
  3 0       10  
  3 0       5  
  3 0       10  
  3 0       8  
  3 0       15  
  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  
  3         12  
  0         0  
  3         8  
  0         0  
  0         0  
  3         14  
  3         15  
  3         8  
  3         8  
  0         0  
  0         0  
  0         0  
  0         0  
469 0     0   0 {
470 0         0 return( $self->error( "Error setting time zone for '$v': $e" ) );
471 19 0 0 19   173 }
  19 0 0     53  
  19 0 33     5770  
  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  
  3 0       10  
  0 0       0  
  3 0       136  
  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  
  3         13  
  0         0  
  0         0  
  0         0  
  0         0  
  3         13  
472             }
473 5         47 $self->reset(1);
474             }
475 10 50       47 if( !defined( $self->{time_zone} ) )
476             {
477 0 0       0 if( Want::want( 'OBJECT' ) )
478             {
479 0         0 require Module::Generic::Null;
480 0         0 rreturn( Module::Generic::Null->new( wants => 'OBJECT' ) );
481             }
482             else
483             {
484 0         0 return;
485             }
486             }
487             else
488             {
489 10         46 return( $self->{time_zone} );
490             }
491             }
492              
493 102     102 1 737011 sub version { return( shift->reset(@_)->_set_get_version( { field => 'version', class => $VERSION_CLASS }, @_ ) ); }
494              
495             1;
496             # NOTE: POD
497             __END__
498              
499             =encoding utf-8
500              
501             =head1 NAME
502              
503             Changes::Release - Release object class
504              
505             =head1 SYNOPSIS
506              
507             use Changes::Release;
508             my $rel = Changes::Release->new(
509             # A Changes object
510             container => $changes_object,
511             datetime => '2022-11-17T08:12:42+0900',
512             datetime_formatter => sub
513             {
514             my $dt = shift( @_ ) || DateTime->now;
515             require DateTime::Format::Strptime;
516             my $fmt = DateTime::Format::Strptime->new(
517             pattern => '%FT%T%z',
518             locale => 'en_GB',
519             );
520             $dt->set_formatter( $fmt );
521             $dt->set_time_zone( 'Asia/Tokyo' );
522             return( $dt );
523             },
524             format => '%FT%T%z',
525             line => 12,
526             note => 'Initial release',
527             spacer => "\t",
528             time_zone => 'Asia/Tokyo',
529             version => 'v0.1.0',
530             ) || die( Changes::Release->error, "\n" );
531             my $change = $rel->add_change( $change_object );
532             # or
533             my $change = $rel->add_change( text => 'Some comments' );
534             my $group = $rel->add_group( $group_object );
535             # or
536             my $group = $rel->add_group( name => 'Some group' );
537             my $change = $rel->delete_change( $change_object );
538             my $group = $rel->delete_group( $group_object );
539             say $rel->as_string;
540              
541             =head1 VERSION
542              
543             v0.2.1
544              
545             =head1 DESCRIPTION
546              
547             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
548              
549             Each release section can contain L<group|Changes::Group> and L<changes|Changes::Change> that are all stored and accessible in L</changes>
550              
551             If an error occurred, it returns an L<error|Module::Generic/error>
552              
553             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.
554              
555             =head1 METHODS
556              
557             =head2 add_change
558              
559             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.
560              
561             It returns the L<Changes::Change> object, or an L<error|Module::Generic/error> if an error occurred.
562              
563             =head2 add_group
564              
565             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.
566              
567             It returns the L<Changes::Group> object, or an L<error|Module::Generic/error> if an error occurred.
568              
569             =head2 as_string
570              
571             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.
572              
573             If an error occurred, it returns an L<error|Module::Generic/error>
574              
575             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.
576              
577             =head2 changes
578              
579             Read only. This returns an L<array object|Module::Generic::Array> containing all the L<change objects|Changes::Change> within this release object.
580              
581             =head2 container
582              
583             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.
584              
585             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.
586              
587             =head2 datetime
588              
589             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.
590              
591             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>)
592              
593             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.
594              
595             You can alternatively directly set a L<DateTime> object.
596              
597             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.
598              
599             =head2 datetime_formatter
600              
601             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.
602              
603             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.
604              
605             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>.
606              
607             =head2 defaults
608              
609             Sets or gets an hash of default values for the L<Changes::Change> object when it is instantiated by the C<new_change> method.
610              
611             Default is C<undef>, which means no default value is set.
612              
613             my $ch = Changes->new(
614             file => '/some/where/Changes',
615             defaults => {
616             # For Changes::Change
617             spacer1 => "\t",
618             spacer2 => ' ',
619             marker => '-',
620             max_width => 72,
621             wrapper => $code_reference,
622             # For Changes::Group
623             group_spacer => "\t",
624             group_type => 'bracket', # [Some group]
625             }
626             );
627              
628             =head2 delete_change
629              
630             This takes a list of change to remove and returns an L<array object|Module::Generic::Array> of those changes thus removed.
631              
632             A change provided can only be a L<Changes::Change> object.
633              
634             If an error occurred, this will return an L<error|Module::Generic/error>
635              
636             =head2 delete_group
637              
638             This takes a list of group to remove and returns an L<array object|Module::Generic::Array> of those groups thus removed.
639              
640             A group provided can either be a L<Changes::Group> object, or a group name as a string.
641              
642             If an error occurred, this will return an L<error|Module::Generic/error>
643              
644             =head2 elements
645              
646             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.
647              
648             =head2 format
649              
650             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.
651              
652             You can also specify an alternative formatter with L</datetime_formatter>
653              
654             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>)
655              
656             It returns a L<scalar object|Module::Generic::Scalar>
657              
658             =for Pod::Coverage freeze
659              
660             =head2 groups
661              
662             Read only. This returns an L<array object|Module::Generic::Array> containing all the L<group objects|Changes::Group> within this release object.
663              
664             =head2 line
665              
666             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>
667              
668             =head2 new_change
669              
670             Instantiates and returns a new L<Changes::Change>, passing its constructor any argument provided.
671              
672             my $change = $rel->new_change( text => 'Some change' ) ||
673             die( $rel->error );
674              
675             =head2 new_group
676              
677             Instantiates and returns a new L<Changes::Group>, passing its constructor any argument provided.
678              
679             my $change = $rel->new_group( name => 'Some group' ) ||
680             die( $rel->error );
681              
682             =head2 new_line
683              
684             Returns a new C<Changes::NewLine> object, passing it any parameters provided.
685              
686             If an error occurred, it returns an L<error object|Module::Generic/error>
687              
688             =head2 new_version
689              
690             Returns a new C<Changes::Version> object, passing it any parameters provided.
691              
692             If an error occurred, it returns an L<error object|Module::Generic/error>
693              
694             =head2 nl
695              
696             Sets or gets the new line character, which defaults to C<\n>
697              
698             It returns a L<number object|Module::Generic::Number>
699              
700             =head2 note
701              
702             Sets or gets an optional note that is set after the release datetime.
703              
704             It returns a L<scalar object|Module::Generic::Scalar>
705              
706             =head2 raw
707              
708             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.
709              
710             It returns a L<scalar object|Module::Generic::Scalar>
711              
712             =head2 remove_change
713              
714             This is an alias for L</delete_change>
715              
716             =head2 remove_group
717              
718             This is an alias for L</delete_group>
719              
720             =for Pod::Coverage reset
721              
722             =head2 set_default_format
723              
724             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>
725              
726             =head2 spacer
727              
728             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.
729              
730             This defaults to a single space if it is not set.
731              
732             It returns a L<scalar object|Module::Generic::Scalar>
733              
734             =head2 time_zone
735              
736             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.
737              
738             It returns a L<DateTime::TimeZone> object upon success, or an L<error|Module::Generic/error> if an error occurred.
739              
740             =head2 version
741              
742             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.
743              
744             It returns a L<version object|version>, or an object of whatever class you have set with C<$VERSION_CLASS>
745              
746             =head2 changes
747              
748             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>
749              
750             =head1 AUTHOR
751              
752             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
753              
754             =head1 SEE ALSO
755              
756             L<Changes>, L<Changes::Group>, L<Changes::Change>, L<Changes::Version>, L<Changes::NewLine>
757              
758             =head1 COPYRIGHT & LICENSE
759              
760             Copyright(c) 2022 DEGUEST Pte. Ltd.
761              
762             All rights reserved
763              
764             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
765              
766             =cut