File Coverage

lib/Changes/Change.pm
Criterion Covered Total %
statement 107 212 50.4
branch 21 158 13.2
condition 37 97 38.1
subroutine 26 28 92.8
pod 13 15 86.6
total 204 510 40.0


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## Changes file management - ~/lib/Changes/Change.pm
3             ## Version v0.1.0
4             ## Copyright(c) 2022 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <jack@deguest.jp>
6             ## Created 2022/11/23
7             ## Modified 2022/11/23
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::Change;
15             BEGIN
16             {
17 19     19   109586 use strict;
  19         54  
  19         704  
18 19     19   120 use warnings;
  19         52  
  19         554  
19 19     19   120 use warnings::register;
  19         38  
  19         3020  
20 19     19   649 use parent qw( Module::Generic );
  19         351  
  19         178  
21 19     19   11416734 use vars qw( $VERSION );
  19         48  
  19         983  
22 19     19   113 use Nice::Try;
  19         37  
  19         221  
23 19     19   7536244 our $VERSION = 'v0.1.0';
24             };
25              
26 19     19   269 use strict;
  19         40  
  19         590  
27 19     19   122 use warnings;
  19         48  
  19         31043  
28              
29             sub init
30             {
31 47     47 1 9644 my $self = shift( @_ );
32 47         1037 $self->{line} = undef;
33 47         164 $self->{marker} = undef;
34 47         125 $self->{max_width} = 0;
35 47         143 $self->{nl} = "\n";
36 47         124 $self->{raw} = undef;
37 47         249 $self->{spacer1} = undef;
38 47         121 $self->{spacer2} = undef;
39 47         591 $self->{text} = undef;
40 47         124 $self->{wrapper} = undef;
41 47         170 $self->{_init_strict_use_sub} = 1;
42 47 50       259 $self->SUPER::init( @_ ) || return( $self->pass_error );
43 47         83487 $self->{_reset} = 1;
44 47         157 $self->{_reset_normalise} = 1;
45 47         137 return( $self );
46             }
47              
48             sub as_string
49             {
50 49     49 1 5568 my $self = shift( @_ );
51 49 100 100     551 $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        
52 49 50 66     1423 if( !exists( $self->{_reset} ) ||
      33        
53             !defined( $self->{_reset} ) ||
54             !CORE::length( $self->{_reset} ) )
55             {
56 43   100     242 $self->message( 5, "Reset is disabled, checking for cache value '", ( $self->{_cache_value} // '' ), "' and raw cache '", ( $self->{raw} // '' ), "'" );
      100        
57 43 100 66     1204 if( exists( $self->{_cache_value} ) &&
    50 66        
      33        
58             defined( $self->{_cache_value} ) &&
59             length( $self->{_cache_value} ) )
60             {
61 4         30 return( $self->{_cache_value} );
62             }
63             elsif( defined( $self->{raw} ) && length( "$self->{raw}" ) )
64             {
65 39         375 $self->message( 5, "Re-using the raw cache." );
66 39         647 return( $self->{raw} );
67             }
68             }
69 6         40 my $nl = $self->nl;
70 6   50     1033 my $str = $self->new_scalar( ( $self->spacer1 // '' ) . ( $self->marker // '-' ) . ( $self->spacer2 // '' ) );
      100        
      50        
71 6         1253 $self->message( 4, "Prefix is '$str'" );
72 6         178 my $max = $self->max_width;
73 6         38826 $self->message( 4, "Max width is '$max' and change text + prefix is '", ( $self->normalise->length + $str->length ), "' characters long." );
74 6 100 100     294702 if( $max > 0 && ( $self->normalise->length + $str->length ) > $max )
75             {
76 1         20452 my $text;
77 1 100       5 my @spaces = map{ $_ eq "\t" ? "\t" : ' ' } split( //, "$str" );
  3         17  
78 1         5 my $sep = join( '', @spaces );
79 1         6 my $wrapper = $self->wrapper;
80 1 50       200 if( $self->_is_code( $wrapper ) )
    50          
    0          
81             {
82 0 0 0     0 try
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
83 0     0   0 {
84 0         0 $text = $wrapper->( $self->normalise->scalar, ( $max - $str->length ) );
85             }
86 0 0 0     0 catch( $e )
  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  
  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         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
87 0     0   0 {
88 0 0       0 warn( "Warning only: an error occurred while calling the wrapper calback with ", $self->normalise->length, " bytes of change text and a maximum width of ", ( $max - $str->length ), " characters: $e\n" ) if( $self->_warnings_is_enabled );
89 19 0 0 19   195 }
  19 0 0     46  
  19 0 0     1598  
  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       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  
  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 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  
  0         0  
  0         0  
  0         0  
90             }
91             elsif( $self->_load_class( 'Text::Wrap' ) )
92             {
93             # Silence the use of $Text::Wrap::columns used once
94 19     19   138 no warnings 'once';
  19         39  
  19         17644  
95             # We need to reduce $max by as much indentation there is
96 1         4001 local $Text::Wrap::columns = ( $max - $str->length );
97 1         10427 $text = Text::Wrap::wrap( '', '', $self->normalise->scalar );
98             }
99             elsif( $self->_load_class( 'Text::Format' ) )
100             {
101 0         0 my $fmt = Text::Format->new({
102             columns => ( $max - $str->length ),
103             extraSpace => 0,
104             firstIndent => 0,
105             });
106 0         0 $text = $fmt->format( $self->normalise->scalar );
107             }
108            
109 1 50 33     5513 if( defined( $text ) && length( "$text" ) )
110             {
111 1         16 $str->append( join( "\n$sep", split( /\r?\n/, "$text" ) ) );
112             }
113             }
114             else
115             {
116 5         21638 $str->append( $self->normalise );
117             }
118 6         110 $str->append( $nl );
119 6         735 $self->message( 4, "Setting change string to '$str'" );
120 6         187 $self->{_cache_value} = $str;
121 6         21 CORE::delete( $self->{_reset} );
122 6         46 return( $str );
123             }
124              
125             sub freeze
126             {
127 41     41 0 79 my $self = shift( @_ );
128 41   50     304 $self->message( 5, "Removing the reset marker -> '", ( $self->{_reset} // '' ), "'" );
129 41         1024 CORE::delete( @$self{qw( _reset _reset_normalise )} );
130 41         180 return( $self );
131             }
132              
133 41     41 1 34847 sub line { return( shift->reset(@_)->_set_get_number( 'line', @_ ) ); }
134              
135 55     55 1 95383 sub marker { return( shift->reset(@_)->_set_get_scalar_as_object( 'marker', @_ ) ); }
136              
137 51     51 1 3421749 sub max_width { return( shift->_set_get_number( 'max_width', @_ ) ); }
138              
139 55     55 1 56259 sub nl { return( shift->reset(@_)->_set_get_scalar_as_object( 'nl', @_ ) ); }
140              
141             sub normalise
142             {
143 16     16 1 2461 my $self = shift( @_ );
144 16 100 33     331 if( (
      100        
      66        
145             !exists( $self->{_reset_normalise} ) ||
146             !defined( $self->{_reset_normalise} ) ||
147             !CORE::length( $self->{_reset_normalise} )
148             ) && exists( $self->{_normalised} ) &&
149             $self->_is_a( $self->{_normalised} => 'Module::Generic::Scalar' ) )
150             {
151 8         523 return( $self->{_normalised} );
152             }
153 8         32 my $str = $self->text->clone;
154 8 50       1726 return( $str ) if( $str->is_empty );
155 8 100       104 if( $str->index( "\n" ) != -1 )
156             {
157 2         18399 $str->replace( qr/[[:blank:]\h]*\n[[:blank:]\h]*/ => ' ' );
158             }
159 8         57469 $self->{_normalised} = $str;
160 8         1070 CORE::delete( $self->{_reset_normalise} );
161 8         63 return( $str );
162             }
163              
164             sub prefix
165             {
166 4     4 1 922 my $self = shift( @_ );
167 4   50     24 my $s = ( $self->spacer1 // '' ) . ( $self->marker // '' ) . ( $self->spacer2 // '' );
      50        
      50        
168 4         752 return( $self->new_scalar( \$s ) );
169             }
170              
171 46     46 1 82200 sub raw { return( shift->_set_get_scalar_as_object( 'raw', @_ ) ); }
172              
173             sub reset
174             {
175 366     366 0 685 my $self = shift( @_ );
176 366 100 33     2936 if( (
      100        
177             !exists( $self->{_reset} ) ||
178             !defined( $self->{_reset} ) ||
179             !CORE::length( $self->{_reset} )
180             ) && scalar( @_ ) )
181             {
182 47         146 $self->{_reset} = scalar( @_ );
183 47         201 $self->{_reset_normalise} = 1;
184             }
185 366         1641 return( $self );
186             }
187              
188             # space before the marker
189 58     58 1 274799 sub spacer1 { return( shift->reset(@_)->_set_get_scalar_as_object( 'spacer1', @_ ) ); }
190              
191             # space after the marker
192 58     58 1 99069 sub spacer2 { return( shift->reset(@_)->_set_get_scalar_as_object( 'spacer2', @_ ) ); }
193              
194 99     99 1 276733 sub text { return( shift->reset(@_)->_set_get_scalar_as_object( 'text', @_ ) ); }
195              
196             # We do not use the reset here, because just setting a wrap callback has no direct impact on the output
197 1     1 1 28 sub wrapper { return( shift->_set_get_code( 'wrapper', @_ ) ); }
198              
199             1;
200             # NOTE: POD
201             __END__
202              
203             =encoding utf-8
204              
205             =head1 NAME
206              
207             Changes::Change - Changes object class
208              
209             =head1 SYNOPSIS
210              
211             use Changes::Change;
212             my $this = Changes::Change->new(
213             line => 12,
214             marker => '-',
215             max_width => 68,
216             spacer1 => "\t",
217             # Defaults to just one space
218             spacer2 => undef,
219             text => "This is a change note",
220             wrapper => sub
221             {
222             my( $text, $width ) = @_;
223             require Text::Wrap;
224             local $Text::Wrap::columns = $width;
225             my $result = Text::Wrap::wrap( '', '', "$text" );
226             return( $result );
227             }
228             ) || die( Changes::Change->error, "\n" );
229              
230             =head1 VERSION
231              
232             v0.1.0
233              
234             =head1 DESCRIPTION
235              
236             This represents a change line within a release. A change line is usually represented by some indentation spaces, followed by a marker such as a dash, a space and a text:
237              
238             - This is a change note
239              
240             A change text can be written on a very long line or broken into lines of C<max_width>. You can change this value with L</max_width> and by default it is 0, which means it will be all on one line.
241              
242             =head1 METHODS
243              
244             =head2 as_string
245              
246             Returns a L<scalar object|Module::Generic::Scalar> of the change line. This information is cached unless other information has been changed.
247              
248             Also, if nothing was changed and L</raw> is set with a value, that value will be returned instead.
249              
250             If L</wrapper> is defined, the perl code reference set will be called by providing it the text of the change and the adjusted width to use. The actual width is the width of the change text with any leading spaces and characters as specified with L</spacer1>, L</spacer2> and L</marker>.
251              
252             If the callback dies, this exception will be caught and displayed as a warning if C<use warnings> is enabled.
253              
254             If no callback is specified, it will attempt to load L<Text::Wrap> (a perl core module) and L<Text::Format> in this order.
255              
256             If none of it is possible, the change text will simply not be wrapped.
257              
258             If an error occurred, it returns an L<error|Module::Generic/error>
259              
260             The resulting string is terminated by the carriage return sequence defined with L</nl>
261              
262             It returns a L<scalar object|Module::Generic::Scalar>
263              
264             =for Pod::Coverage freeze
265              
266             =head2 line
267              
268             Sets or gets an integer representing the line number where this line containing the change information was found in the original C<Changes> file. If this object was instantiated separately, then obviously this value will be C<undef>
269              
270             =head2 marker
271              
272             Sets or gets the character representing the marker preceding the text of the change. This is usually a dash.
273              
274             It returns a L<scalar object|Module::Generic::Scalar>
275              
276             =head2 max_width
277              
278             Sets or gets the change line maximum width. The line width includes any spaces and characters at the beginning of the line, as set with L</spacer1>, L</spacer2> and L</marker> and not just the text of the change itself.
279              
280             It returns a L<number object|Module::Generic::Number>
281              
282             =head2 nl
283              
284             Sets or gets the new line character, which defaults to C<\n>
285              
286             It returns a L<number object|Module::Generic::Number>
287              
288             =head2 normalise
289              
290             This returns a "normalised" version of the change text, which means that if the change text is wrapped and has new lines with possibly preceding and trailing spaces, those will be replaced by a single space.
291              
292             It does not modify the original change text.
293              
294             It returns a L<scalar object|Module::Generic::Scalar>
295              
296             =head2 prefix
297              
298             Read-only. This returns what precedes the text of the change, which is an optional leading space, and a marker such as a dash.
299              
300             It returns a L<scalar object|Module::Generic::Scalar>
301              
302             =head2 raw
303              
304             Sets or gets the raw version of the line 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 change.
305              
306             It returns a L<scalar object|Module::Generic::Scalar>
307              
308             =for Pod::Coverage reset
309              
310             =head2 spacer1
311              
312             Sets or gets the leading space, if any, found before the marker.
313              
314             It returns a L<scalar object|Module::Generic::Scalar>
315              
316             =head2 spacer2
317              
318             Sets or gets the space found after the marker and before the text of the change.
319              
320             It returns a L<scalar object|Module::Generic::Scalar>
321              
322             =head2 text
323              
324             Sets or gets the text o the change. If the text is broken into multiple lines in the C<Changes> file, it will be collected as on L<scalar object|Module::Generic::Scalar> here.
325              
326             It returns a L<scalar object|Module::Generic::Scalar>
327              
328             =head2 wrapper
329              
330             Sets or gets a code reference as a callback mechanism to return a properly wrapped change text. This allows flexibility beyond the default use of L<Text::Wrap> and L<Text::Format>
331              
332             =head1 AUTHOR
333              
334             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
335              
336             =head1 SEE ALSO
337              
338             L<Changes>, L<Changes::Release>, L<Changes::Group>, L<Changes::Version>, L<Changes::NewLine>
339              
340             =head1 COPYRIGHT & LICENSE
341              
342             Copyright(c) 2022 DEGUEST Pte. Ltd.
343              
344             All rights reserved
345              
346             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
347              
348             =cut