File Coverage

lib/Changes/Group.pm
Criterion Covered Total %
statement 104 126 82.5
branch 19 44 43.1
condition 19 42 45.2
subroutine 28 31 90.3
pod 16 18 88.8
total 186 261 71.2


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## Changes file management - ~/lib/Changes/Group.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 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::Group;
15             BEGIN
16             {
17 20     20   123820 use strict;
  20         32  
  20         749  
18 20     20   85 use warnings;
  20         71  
  20         952  
19 20     20   89 use warnings::register;
  20         30  
  20         829  
20 20     20   703 use parent qw( Module::Generic );
  20         390  
  20         157  
21 20     20   265865 use vars qw( $VERSION );
  20         84  
  20         1013  
22 20     20   500 our $VERSION = 'v0.2.1';
23             };
24              
25 20     20   114 use strict;
  20         35  
  20         534  
26 20     20   88 use warnings;
  20         46  
  20         34876  
27              
28             sub init
29             {
30 9     9 1 255625 my $self = shift( @_ );
31 9         313 $self->{defaults} = undef;
32 9         28 $self->{elements} = [];
33 9         27 $self->{line} = undef;
34 9         25 $self->{name} = undef;
35 9         30 $self->{nl} = "\n";
36 9         27 $self->{raw} = undef;
37 9         26 $self->{spacer} = undef;
38 9         28 $self->{type} = 'bracket';
39 9         26 $self->{_init_strict_use_sub} = 1;
40 9 50       74 $self->SUPER::init( @_ ) || return( $self->pass_error );
41 9         17504 $self->{_reset} = 1;
42 9         32 return( $self );
43             }
44              
45             sub add_change
46             {
47 3     3 1 2490 my $self = shift( @_ );
48 3         6 my( $change, $opts );
49 3         39 my $elements = $self->elements;
50 3 50 33     4125 if( scalar( @_ ) == 1 && $self->_is_a( $_[0] => 'Changes::Change' ) )
51             {
52 0         0 $change = shift( @_ );
53 0 0       0 if( $elements->exists( $change ) )
54             {
55 0         0 $self->_load_class( 'overload' );
56 0         0 return( $self->error( "A very same change object (", $self->_str_val( $change ), ") is already registered." ) );
57             }
58             }
59             else
60             {
61 3         20 $opts = $self->_get_args_as_hash( @_ );
62 3   50     3397 $change = $self->new_change( %$opts ) || return( $self->pass_error );
63             }
64 3         20 $elements->push( $change );
65 3         35 return( $change );
66             }
67              
68             sub as_string
69             {
70 11     11 1 5403 my $self = shift( @_ );
71 11 50 66     93 if( !exists( $self->{_reset} ) ||
      33        
72             !defined( $self->{_reset} ) ||
73             !CORE::length( $self->{_reset} ) )
74             {
75 8         14 my $cache;
76 8 100 66     102 if( exists( $self->{_cache_value} ) &&
    50 66        
      33        
77             defined( $self->{_cache_value} ) &&
78             length( $self->{_cache_value} ) )
79             {
80 2         11 $cache = $self->{_cache_value};
81             }
82             elsif( defined( $self->{raw} ) && length( "$self->{raw}" ) )
83             {
84 6         64 $cache = $self->{raw};
85             }
86              
87 8         33 my $lines = $self->new_array( $cache->scalar );
88             $self->elements->foreach(sub
89             {
90 10     10   7993 my $this = $_->as_string;
91 10 50       36 if( defined( $this ) )
92             {
93 10         35 $lines->push( $this->scalar );
94             }
95 8         5749 });
96             # my $str = $lines->join( "\n" );
97 8         9147 my $str = $lines->join( '' );
98 8         535 return( $str );
99             }
100 3         16 my $nl = $self->nl;
101 3         5316 my $lines = $self->new_array;
102             # Either bracket or colon
103 3   50     2651 my $type = $self->type // 'bracket';
104 3 50 50     5086 my $grp_str = $self->new_scalar( ( $self->spacer // '' ) . ( $type eq 'bracket' ? '[' : '' ) . ( $self->name // '' ) . ( $type eq 'bracket' ? ']' : ':' ) . ( $nl // '' ) );
    50 50        
      50        
105 3         5174 $lines->push( $grp_str->scalar );
106             $self->changes->foreach(sub
107             {
108 3     3   1076 my $this = $_->as_string;
109 3 50       17 if( defined( $this ) )
110             {
111 3         17 $lines->push( $this->scalar );
112             }
113 3         111 });
114             # my $str = $lines->join( "$nl" );
115 3         2891 my $str = $lines->join( '' );
116 3         1007 $self->{_cache_value} = $str;
117 3         9 CORE::delete( $self->{_reset} );
118 3         18 return( $str );
119             }
120              
121             sub changes
122             {
123 12     12 1 161929 my $self = shift( @_ );
124 12     15   50 my $a = $self->elements->grep(sub{ $self->_is_a( $_ => 'Changes::Change' ) });
  15         9332  
125 12         3487 return( $a );
126             }
127              
128 5     5 1 569 sub defaults { return( shift->_set_get_hash_as_mix_object( { field => 'defaults', undef_ok => 1 }, @_ ) ); }
129              
130             sub delete_change
131             {
132 0     0 1 0 my $self = shift( @_ );
133 0         0 my $elements = $self->elements;
134 0 0       0 if( scalar( @_ ) != 1 )
    0          
135             {
136 0         0 return( $self->error( 'Usage: $group->delete_change( $change_object );' ) );
137             }
138             elsif( $self->_is_a( $_[0] => 'Changes::Change' ) )
139             {
140 0         0 my $change = shift( @_ );
141 0         0 my $pos = $elements->pos( $change );
142 0 0       0 if( !defined( $pos ) )
143             {
144 0         0 $self->_load_class( 'overload' );
145 0         0 return( '' );
146             }
147 0         0 $elements->delete( $pos, 1 );
148 0         0 return( $change );
149             }
150             else
151             {
152 0         0 $self->_load_class( 'overload' );
153 0 0 0     0 return( $self->error( "I was expecting a Changes::Change object, but instead got '", ( $_[0] // '' ), "' (", ( defined( $_[0] ) ? overload::StrVal( $_[0] ) : 'undef' ), ")." ) );
154             }
155             }
156              
157 44     44 1 275 sub elements { return( shift->_set_get_array_as_object( 'elements', @_ ) ); }
158              
159             sub freeze
160             {
161 6     6 0 13 my $self = shift( @_ );
162 6         25 CORE::delete( @$self{qw( _reset )} );
163             $self->elements->foreach(sub
164             {
165 7 50   7   6625 if( $self->_can( $_ => 'freeze' ) )
166             {
167 7         201 $_->freeze;
168             }
169 6         19 });
170 6         2577 return( $self );
171             }
172              
173 6     6 1 8825 sub line { return( shift->reset(@_)->_set_get_number( 'line', @_ ) ); }
174              
175 22     22 1 94776 sub name { return( shift->reset(@_)->_set_get_scalar_as_object( 'name', @_ ) ); }
176              
177             sub new_change
178             {
179 3     3 1 9 my $self = shift( @_ );
180 3         36 my $opts = $self->_get_args_as_hash( @_ );
181 3 50       3276 $self->_load_class( 'Changes::Change' ) || return( $self->pass_error );
182 3         2109 my $defaults = $self->defaults;
183 3 50       2653 if( defined( $defaults ) )
184             {
185 3         11 foreach my $opt ( qw( spacer1 marker spacer2 max_width wrapper ) )
186             {
187 15 100 33     513 $opts->{ $opt } //= $defaults->{ $opt } if( defined( $defaults->{ $opt } ) );
188             }
189             }
190 3   50     78 my $c = Changes::Change->new( $opts ) ||
191             return( $self->pass_error( Changes::Change->error ) );
192 3         39 return( $c );
193             }
194              
195             sub new_line
196             {
197 0     0 1 0 my $self = shift( @_ );
198 0 0       0 $self->_load_class( 'Changes::NewLine' ) || return( $self->pass_error );
199 0   0     0 my $nl = Changes::NewLine->new( @_ ) ||
200             return( $self->pass_error( Changes::NewLine->error ) );
201 0         0 return( $nl );
202             }
203              
204 9     9 1 9568 sub nl { return( shift->reset(@_)->_set_get_scalar_as_object( 'nl', @_ ) ); }
205              
206 7     7 1 10393 sub raw { return( shift->_set_get_scalar_as_object( 'raw', @_ ) ); }
207              
208 0     0 1 0 sub remove_change { return( shift->delete_change( @_ ) ); }
209              
210             sub reset
211             {
212 59     59 0 124 my $self = shift( @_ );
213 59 100 33     525 if( (
      100        
214             !exists( $self->{_reset} ) ||
215             !defined( $self->{_reset} ) ||
216             !CORE::length( $self->{_reset} )
217             ) && scalar( @_ ) )
218             {
219 9         31 $self->{_reset} = scalar( @_ );
220             }
221 59         315 return( $self );
222             }
223              
224 13     13 1 121791 sub spacer { return( shift->reset(@_)->_set_get_scalar_as_object( 'spacer', @_ ) ); }
225              
226 9     9 1 137833 sub type { return( shift->reset(@_)->_set_get_scalar_as_object( 'type', @_ ) ); }
227              
228             sub DESTROY
229             {
230             # <https://perldoc.perl.org/perlobj#Destructors>
231 3     3   9068 CORE::local( $., $@, $!, $^E, $? );
232 3         178 my $self = CORE::shift( @_ );
233 3 50       20 CORE::return if( !CORE::defined( $self ) );
234 3 50       47 CORE::return if( ${^GLOBAL_PHASE} eq 'DESTRUCT' );
235             };
236              
237             1;
238             # NOTE: POD
239             __END__
240              
241             =encoding utf-8
242              
243             =head1 NAME
244              
245             Changes::Group - Group object class
246              
247             =head1 SYNOPSIS
248              
249             use Changes::Group;
250             my $g = Changes::Group->new(
251             line => 12,
252             name => 'Front-end',
253             spacer => "\t",
254             debug => 4,
255             ) || die( Changes::Group->error, "\n" );
256             my $change = $g->add_change( $change_object );
257             # or
258             my $change = $g->add_change( text => 'Some comment here' );
259             $g->delete_change( $change );
260             say $g->as_string;
261              
262             =head1 VERSION
263              
264             v0.2.1
265              
266             =head1 DESCRIPTION
267              
268             This object class represents a C<Changes> file group within a release section. It is completely optional.
269              
270             =head1 METHODS
271              
272             =head2 add_change
273              
274             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 group object.
275              
276             It returns the L<Changes::Change> object, or an L<error|Module::Generic/error> if an error occurred.
277              
278             =head2 as_string
279              
280             Returns a L<scalar object|Module::Generic::Scalar> of the change group. This is a group name enclosed in square brackets:
281              
282             [my group]
283              
284             It returns a L<scalar object|Module::Generic::Scalar>
285              
286             If an error occurred, it returns an L<error|Module::Generic/error>
287              
288             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.
289              
290             =head2 changes
291              
292             Read only. This returns an L<array object|Module::Generic::Array> containing all the L<change objects|Changes::Change> within this group object.
293              
294             =head2 defaults
295              
296             Sets or gets an hash of default values for the L<Changes::Change> object when it is instantiated by the C<new_change> method.
297              
298             Default is C<undef>, which means no default value is set.
299              
300             my $ch = Changes->new(
301             file => '/some/where/Changes',
302             defaults => {
303             spacer1 => "\t",
304             spacer2 => ' ',
305             marker => '-',
306             max_width => 72,
307             wrapper => $code_reference,
308             }
309             );
310              
311             =head2 delete_change
312              
313             This takes a list of change to remove and returns an L<array object|Module::Generic::Array> of those changes thus removed.
314              
315             A change provided can only be a L<Changes::Change> object.
316              
317             If an error occurred, this will return an L<error|Module::Generic/error>
318              
319             =head2 elements
320              
321             Sets or gets an L<array object|Module::Generic::Array> of all the elements within this group object. Those elements can be L<Changes::Change> and C<Changes::NewLine> objects.
322              
323             =for Pod::Coverage freeze
324              
325             =head2 line
326              
327             Sets or gets an integer representing the line number where this line containing this group information was found in the original C<Changes> file. If this object was instantiated separately, then obviously this value will be C<undef>
328              
329             =head2 name
330              
331             Sets or gets the group name as a L<scalar object|Module::Generic::Scalar>
332              
333             =head2 new_change
334              
335             Instantiates and returns a new L<Changes::Change>, passing its constructor any argument provided.
336              
337             my $change = $rel->new_change( text => 'Some change' ) ||
338             die( $rel->error );
339              
340             =head2 new_line
341              
342             Returns a new C<Changes::NewLine> object, passing it any parameters provided.
343              
344             If an error occurred, it returns an L<error object|Module::Generic/error>
345              
346             =head2 nl
347              
348             Sets or gets the new line character, which defaults to C<\n>
349              
350             It returns a L<number object|Module::Generic::Number>
351              
352             =head2 raw
353              
354             Sets or gets the raw version of the group as found in the C<Changes> file. If set and nothing has been changed, this will be returned by L</as_string> instead of computing the formatting of the group.
355              
356             It returns a L<scalar object|Module::Generic::Scalar>
357              
358             =head2 remove_change
359              
360             This is an alias for L</delete_change>
361              
362             =for Pod::Coverage reset
363              
364             =head2 spacer
365              
366             Sets or gets the leading space, if any, found before the group.
367              
368             It returns a L<scalar object|Module::Generic::Scalar>
369              
370             =head2 type
371              
372             Sets or gets the type of group for this object. This can either be C<bracket>, which is the default, or C<colon>:
373              
374             [My group]
375             # or
376             My group:
377              
378             =head1 AUTHOR
379              
380             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
381              
382             =head1 SEE ALSO
383              
384             L<Changes>, L<Changes::Release>, L<Changes::Change>, L<Changes::Version>, L<Changes::NewLine>
385              
386             =head1 COPYRIGHT & LICENSE
387              
388             Copyright(c) 2022 DEGUEST Pte. Ltd.
389              
390             All rights reserved
391              
392             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
393              
394             =cut