File Coverage

lib/Changes/Group.pm
Criterion Covered Total %
statement 106 129 82.1
branch 19 42 45.2
condition 24 48 50.0
subroutine 27 30 90.0
pod 16 18 88.8
total 192 267 71.9


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