File Coverage

lib/Changes/Group.pm
Criterion Covered Total %
statement 100 122 81.9
branch 17 40 42.5
condition 19 42 45.2
subroutine 27 30 90.0
pod 16 18 88.8
total 179 252 71.0


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   105644 use strict;
  20         64  
  20         666  
18 20     20   112 use warnings;
  20         40  
  20         561  
19 20     20   105 use warnings::register;
  20         39  
  20         2132  
20 20     20   570 use parent qw( Module::Generic );
  20         383  
  20         118  
21 20     20   12958641 use vars qw( $VERSION );
  20         48  
  20         897  
22 20     20   398 our $VERSION = 'v0.2.0';
23             };
24              
25 20     20   97 use strict;
  20         40  
  20         452  
26 20     20   94 use warnings;
  20         44  
  20         26400  
27              
28             sub init
29             {
30 9     9 1 898 my $self = shift( @_ );
31 9         285 $self->{defaults} = undef;
32 9         31 $self->{elements} = [];
33 9         23 $self->{line} = undef;
34 9         27 $self->{name} = undef;
35 9         36 $self->{nl} = "\n";
36 9         22 $self->{raw} = undef;
37 9         17 $self->{spacer} = undef;
38 9         35 $self->{type} = 'bracket';
39 9         24 $self->{_init_strict_use_sub} = 1;
40 9 50       78 $self->SUPER::init( @_ ) || return( $self->pass_error );
41 9         49072 $self->{_reset} = 1;
42 9         29 return( $self );
43             }
44              
45             sub add_change
46             {
47 3     3 1 1465 my $self = shift( @_ );
48 3         6 my( $change, $opts );
49 3         18 my $elements = $self->elements;
50 3 50 33     3116 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         15 $opts = $self->_get_args_as_hash( @_ );
62 3   50     391 $change = $self->new_change( %$opts ) || return( $self->pass_error );
63             }
64 3         18 $elements->push( $change );
65 3         26 return( $change );
66             }
67              
68             sub as_string
69             {
70 11     11 1 4123 my $self = shift( @_ );
71 11 50 66     80 if( !exists( $self->{_reset} ) ||
      33        
72             !defined( $self->{_reset} ) ||
73             !CORE::length( $self->{_reset} ) )
74             {
75 8         12 my $cache;
76 8 100 66     83 if( exists( $self->{_cache_value} ) &&
    50 66        
      33        
77             defined( $self->{_cache_value} ) &&
78             length( $self->{_cache_value} ) )
79             {
80 2         18 $cache = $self->{_cache_value};
81             }
82             elsif( defined( $self->{raw} ) && length( "$self->{raw}" ) )
83             {
84 6         62 $cache = $self->{raw};
85             }
86            
87 8         24 my $lines = $self->new_array( $cache->scalar );
88             $self->elements->foreach(sub
89             {
90 10     10   5098 my $this = $_->as_string;
91 10 50       37 if( defined( $this ) )
92             {
93 10         29 $lines->push( $this->scalar );
94             }
95 8         265 });
96             # my $str = $lines->join( "\n" );
97 8         1204 my $str = $lines->join( '' );
98 8         265 return( $str );
99             }
100 3         16 my $nl = $self->nl;
101 3         2699 my $lines = $self->new_array;
102             # Either bracket or colon
103 3   50     85 my $type = $self->type // 'bracket';
104 3 50 50     2749 my $grp_str = $self->new_scalar( ( $self->spacer // '' ) . ( $type eq 'bracket' ? '[' : '' ) . ( $self->name // '' ) . ( $type eq 'bracket' ? ']' : ':' ) . ( $nl // '' ) );
    50 50        
      50        
105 3         2977 $lines->push( $grp_str->scalar );
106             $self->changes->foreach(sub
107             {
108 3     3   268 my $this = $_->as_string;
109 3 50       15 if( defined( $this ) )
110             {
111 3         16 $lines->push( $this->scalar );
112             }
113 3         91 });
114             # my $str = $lines->join( "$nl" );
115 3         476 my $str = $lines->join( '' );
116 3         149 $self->{_cache_value} = $str;
117 3         11 CORE::delete( $self->{_reset} );
118 3         11 return( $str );
119             }
120              
121             sub changes
122             {
123 12     12 1 133266 my $self = shift( @_ );
124 12     15   47 my $a = $self->elements->grep(sub{ $self->_is_a( $_ => 'Changes::Change' ) });
  15         6893  
125 12         1853 return( $a );
126             }
127              
128 5     5 1 2008 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 186 sub elements { return( shift->_set_get_array_as_object( 'elements', @_ ) ); }
158              
159             sub freeze
160             {
161 6     6 0 15 my $self = shift( @_ );
162 6         25 CORE::delete( @$self{qw( _reset )} );
163             $self->elements->foreach(sub
164             {
165 7 50   7   3518 if( $self->_can( $_ => 'freeze' ) )
166             {
167 7         161 $_->freeze;
168             }
169 6         18 });
170 6         190 return( $self );
171             }
172              
173 6     6 1 3960 sub line { return( shift->reset(@_)->_set_get_number( 'line', @_ ) ); }
174              
175 22     22 1 48484 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         8 my $opts = $self->_get_args_as_hash( @_ );
181 3 50       325 $self->_load_class( 'Changes::Change' ) || return( $self->pass_error );
182 3         115 my $defaults = $self->defaults;
183 3 50       2754 if( defined( $defaults ) )
184             {
185 3         11 foreach my $opt ( qw( spacer1 marker spacer2 max_width wrapper ) )
186             {
187 15 100 33     350 $opts->{ $opt } //= $defaults->{ $opt } if( defined( $defaults->{ $opt } ) );
188             }
189             }
190 3   50     61 my $c = Changes::Change->new( $opts ) ||
191             return( $self->pass_error( Changes::Change->error ) );
192 3         30 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 4690 sub nl { return( shift->reset(@_)->_set_get_scalar_as_object( 'nl', @_ ) ); }
205              
206 7     7 1 86267 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     492 if( (
      100        
214             !exists( $self->{_reset} ) ||
215             !defined( $self->{_reset} ) ||
216             !CORE::length( $self->{_reset} )
217             ) && scalar( @_ ) )
218             {
219 9         32 $self->{_reset} = scalar( @_ );
220             }
221 59         262 return( $self );
222             }
223              
224 13     13 1 3292097 sub spacer { return( shift->reset(@_)->_set_get_scalar_as_object( 'spacer', @_ ) ); }
225              
226 9     9 1 76770 sub type { return( shift->reset(@_)->_set_get_scalar_as_object( 'type', @_ ) ); }
227              
228             1;
229             # NOTE: POD
230             __END__
231              
232             =encoding utf-8
233              
234             =head1 NAME
235              
236             Changes::Group - Group object class
237              
238             =head1 SYNOPSIS
239              
240             use Changes::Group;
241             my $g = Changes::Group->new(
242             line => 12,
243             name => 'Front-end',
244             spacer => "\t",
245             debug => 4,
246             ) || die( Changes::Group->error, "\n" );
247             my $change = $g->add_change( $change_object );
248             # or
249             my $change = $g->add_change( text => 'Some comment here' );
250             $g->delete_change( $change );
251             say $g->as_string;
252              
253             =head1 VERSION
254              
255             v0.2.0
256              
257             =head1 DESCRIPTION
258              
259             This object class represents a C<Changes> file group within a release section. It is completely optional.
260              
261             =head1 METHODS
262              
263             =head2 add_change
264              
265             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.
266              
267             It returns the L<Changes::Change> object, or an L<error|Module::Generic/error> if an error occurred.
268              
269             =head2 as_string
270              
271             Returns a L<scalar object|Module::Generic::Scalar> of the change group. This is a group name enclosed in square brackets:
272              
273             [my group]
274              
275             It returns a L<scalar object|Module::Generic::Scalar>
276              
277             If an error occurred, it returns an L<error|Module::Generic/error>
278              
279             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.
280              
281             =head2 changes
282              
283             Read only. This returns an L<array object|Module::Generic::Array> containing all the L<change objects|Changes::Change> within this group object.
284              
285             =head2 defaults
286              
287             Sets or gets an hash of default values for the L<Changes::Change> object when it is instantiated by the C<new_change> method.
288              
289             Default is C<undef>, which means no default value is set.
290              
291             my $ch = Changes->new(
292             file => '/some/where/Changes',
293             defaults => {
294             spacer1 => "\t",
295             spacer2 => ' ',
296             marker => '-',
297             max_width => 72,
298             wrapper => $code_reference,
299             }
300             );
301              
302             =head2 delete_change
303              
304             This takes a list of change to remove and returns an L<array object|Module::Generic::Array> of those changes thus removed.
305              
306             A change provided can only be a L<Changes::Change> object.
307              
308             If an error occurred, this will return an L<error|Module::Generic/error>
309              
310             =head2 elements
311              
312             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.
313              
314             =for Pod::Coverage freeze
315              
316             =head2 line
317              
318             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>
319              
320             =head2 name
321              
322             Sets or gets the group name as a L<scalar object|Module::Generic::Scalar>
323              
324             =head2 new_change
325              
326             Instantiates and returns a new L<Changes::Change>, passing its constructor any argument provided.
327              
328             my $change = $rel->new_change( text => 'Some change' ) ||
329             die( $rel->error );
330              
331             =head2 new_line
332              
333             Returns a new C<Changes::NewLine> object, passing it any parameters provided.
334              
335             If an error occurred, it returns an L<error object|Module::Generic/error>
336              
337             =head2 nl
338              
339             Sets or gets the new line character, which defaults to C<\n>
340              
341             It returns a L<number object|Module::Generic::Number>
342              
343             =head2 raw
344              
345             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.
346              
347             It returns a L<scalar object|Module::Generic::Scalar>
348              
349             =head2 remove_change
350              
351             This is an alias for L</delete_change>
352              
353             =for Pod::Coverage reset
354              
355             =head2 spacer
356              
357             Sets or gets the leading space, if any, found before the group.
358              
359             It returns a L<scalar object|Module::Generic::Scalar>
360              
361             =head2 type
362              
363             Sets or gets the type of group for this object. This can either be C<bracket>, which is the default, or C<colon>:
364              
365             [My group]
366             # or
367             My group:
368              
369             =head1 AUTHOR
370              
371             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
372              
373             =head1 SEE ALSO
374              
375             L<Changes>, L<Changes::Release>, L<Changes::Change>, L<Changes::Version>, L<Changes::NewLine>
376              
377             =head1 COPYRIGHT & LICENSE
378              
379             Copyright(c) 2022 DEGUEST Pte. Ltd.
380              
381             All rights reserved
382              
383             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
384              
385             =cut