line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
##---------------------------------------------------------------------------- |
2
|
|
|
|
|
|
|
## Changes file management - ~/lib/Changes.pm |
3
|
|
|
|
|
|
|
## Version v0.3.1 |
4
|
|
|
|
|
|
|
## Copyright(c) 2023 DEGUEST Pte. Ltd. |
5
|
|
|
|
|
|
|
## Author: Jacques Deguest <jack@deguest.jp> |
6
|
|
|
|
|
|
|
## Created 2022/12/09 |
7
|
|
|
|
|
|
|
## Modified 2023/07/30 |
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; |
15
|
|
|
|
|
|
|
BEGIN |
16
|
|
|
|
|
|
|
{ |
17
|
18
|
|
|
18
|
|
362953823
|
use strict; |
|
18
|
|
|
|
|
196
|
|
|
18
|
|
|
|
|
578
|
|
18
|
18
|
|
|
18
|
|
95
|
use warnings; |
|
18
|
|
|
|
|
31
|
|
|
18
|
|
|
|
|
490
|
|
19
|
18
|
|
|
18
|
|
93
|
use warnings::register; |
|
18
|
|
|
|
|
38
|
|
|
18
|
|
|
|
|
1931
|
|
20
|
18
|
|
|
18
|
|
615
|
use parent qw( Module::Generic ); |
|
18
|
|
|
|
|
338
|
|
|
18
|
|
|
|
|
112
|
|
21
|
18
|
|
|
18
|
|
37411264
|
use vars qw( $VERSION $VERSION_LAX_REGEX $DATE_DISTZILA_RE $DATETIME_RE ); |
|
18
|
|
|
|
|
37
|
|
|
18
|
|
|
|
|
1255
|
|
22
|
18
|
|
|
18
|
|
8160
|
use Changes::Release; |
|
18
|
|
|
|
|
81
|
|
|
18
|
|
|
|
|
327
|
|
23
|
18
|
|
|
18
|
|
7623
|
use Changes::Group; |
|
18
|
|
|
|
|
42
|
|
|
18
|
|
|
|
|
143
|
|
24
|
18
|
|
|
18
|
|
14169
|
use Changes::Change; |
|
18
|
|
|
|
|
68
|
|
|
18
|
|
|
|
|
371
|
|
25
|
18
|
|
|
18
|
|
7422
|
use Nice::Try; |
|
18
|
|
|
|
|
40
|
|
|
18
|
|
|
|
|
166
|
|
26
|
|
|
|
|
|
|
# From version::regex |
27
|
18
|
|
|
18
|
|
37460378
|
our $VERSION_LAX_REGEX = qr/(?^x: (?^x: |
28
|
|
|
|
|
|
|
(?<has_v>v) (?<ver>(?^:[0-9]+) (?: (?^:\.[0-9]+)+ (?^:_[0-9]+)? )?) |
29
|
|
|
|
|
|
|
| |
30
|
|
|
|
|
|
|
(?<ver>(?^:[0-9]+)? (?^:\.[0-9]+){2,} (?^:_[0-9]+)?) |
31
|
|
|
|
|
|
|
) | (?^x: (?<ver>(?^:[0-9]+) (?: (?^:\.[0-9]+) | \. )? (?^:_[0-9]+)?) |
32
|
|
|
|
|
|
|
| |
33
|
|
|
|
|
|
|
(?<ver>(?^:\.[0-9]+) (?^:_[0-9]+)?) |
34
|
|
|
|
|
|
|
) |
35
|
|
|
|
|
|
|
)/; |
36
|
|
|
|
|
|
|
# 2022-12-11 08:07:12 Asia/Tokyo |
37
|
18
|
|
|
|
|
71
|
our $DATE_DISTZILA_RE = qr/ |
38
|
|
|
|
|
|
|
(?<r_year>\d{4}) |
39
|
|
|
|
|
|
|
- |
40
|
|
|
|
|
|
|
(?<r_month>\d{1,2}) |
41
|
|
|
|
|
|
|
- |
42
|
|
|
|
|
|
|
(?<r_day>\d{1,2}) |
43
|
|
|
|
|
|
|
(?<r_dt_space>[[:blank:]\h]+) |
44
|
|
|
|
|
|
|
(?<r_hour>\d{1,2}) |
45
|
|
|
|
|
|
|
: |
46
|
|
|
|
|
|
|
(?<r_minute>\d{1,2}) |
47
|
|
|
|
|
|
|
: |
48
|
|
|
|
|
|
|
(?<r_second>\d{1,2}) |
49
|
|
|
|
|
|
|
(?<r_tz_space>[[:blank:]\h]+) |
50
|
|
|
|
|
|
|
(?<r_tz>\S+) |
51
|
|
|
|
|
|
|
/x; |
52
|
18
|
|
|
|
|
592
|
our $VERSION = 'v0.3.1'; |
53
|
|
|
|
|
|
|
}; |
54
|
|
|
|
|
|
|
|
55
|
18
|
|
|
18
|
|
230
|
use strict; |
|
18
|
|
|
|
|
35
|
|
|
18
|
|
|
|
|
574
|
|
56
|
18
|
|
|
18
|
|
110
|
use warnings; |
|
18
|
|
|
|
|
34
|
|
|
18
|
|
|
|
|
14532
|
|
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
sub init |
59
|
|
|
|
|
|
|
{ |
60
|
22
|
|
|
22
|
1
|
2270
|
my $self = shift( @_ ); |
61
|
22
|
|
|
|
|
1277
|
$self->{defaults} = undef; |
62
|
22
|
|
|
|
|
87
|
$self->{elements} = []; |
63
|
22
|
|
|
|
|
72
|
$self->{epilogue} = undef; |
64
|
22
|
|
|
|
|
56
|
$self->{file} = undef; |
65
|
22
|
|
|
|
|
62
|
$self->{max_width} = 0; |
66
|
22
|
|
|
|
|
86
|
$self->{mode} = '+<'; |
67
|
22
|
|
|
|
|
67
|
$self->{nl} = "\n"; |
68
|
22
|
|
|
|
|
73
|
$self->{preamble} = undef; |
69
|
22
|
|
|
|
|
65
|
$self->{releases} = []; |
70
|
22
|
|
|
|
|
58
|
$self->{time_zone} = undef; |
71
|
22
|
|
|
|
|
59
|
$self->{type} = undef; |
72
|
22
|
|
|
|
|
64
|
$self->{wrapper} = undef; |
73
|
22
|
|
|
|
|
59
|
$self->{_init_strict_use_sub} = 1; |
74
|
22
|
|
|
|
|
79
|
$self->{_init_params_order} = [qw( preset )]; |
75
|
22
|
50
|
|
|
|
174
|
$self->SUPER::init( @_ ) || return( $self->pass_error ); |
76
|
22
|
|
|
|
|
45975
|
return( $self ); |
77
|
|
|
|
|
|
|
} |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
sub add_epilogue |
80
|
|
|
|
|
|
|
{ |
81
|
1
|
|
|
1
|
1
|
25
|
my( $self, $text ) = @_; |
82
|
1
|
50
|
33
|
|
|
11
|
if( !defined( $text ) || !length( "$text" ) ) |
83
|
|
|
|
|
|
|
{ |
84
|
0
|
|
|
|
|
0
|
return( $self->error( "No text was provided to add an epilogue." ) ); |
85
|
|
|
|
|
|
|
} |
86
|
1
|
|
|
|
|
5
|
my $elements = $self->elements; |
87
|
1
|
|
|
|
|
959
|
my $last = $elements->last; |
88
|
1
|
50
|
33
|
|
|
74
|
if( defined( $last ) && !$self->_is_a( $last => 'Changes::NewLine' ) ) |
89
|
|
|
|
|
|
|
{ |
90
|
0
|
|
0
|
|
|
0
|
$elements->push( $self->new_line( nl => ( $self->nl // "\n" ) ) ); |
91
|
|
|
|
|
|
|
} |
92
|
1
|
|
|
|
|
15
|
$self->epilogue( $text ); |
93
|
1
|
|
|
|
|
994
|
return( $self ); |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
sub add_preamble |
97
|
|
|
|
|
|
|
{ |
98
|
1
|
|
|
1
|
1
|
2278
|
my( $self, $text ) = @_; |
99
|
1
|
50
|
33
|
|
|
27
|
if( !defined( $text ) || !length( "$text" ) ) |
100
|
|
|
|
|
|
|
{ |
101
|
0
|
|
|
|
|
0
|
return( $self->error( "No text was provided to add a premable." ) ); |
102
|
|
|
|
|
|
|
} |
103
|
1
|
|
|
|
|
12
|
$self->preamble( $text ); |
104
|
1
|
|
|
|
|
155
|
return( $self ); |
105
|
|
|
|
|
|
|
} |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
sub add_release |
108
|
|
|
|
|
|
|
{ |
109
|
4
|
|
|
4
|
1
|
447
|
my $self = shift( @_ ); |
110
|
4
|
|
|
|
|
40
|
my( $rel, $opts ); |
111
|
4
|
|
|
|
|
15
|
my $elements = $self->elements; |
112
|
4
|
100
|
66
|
|
|
4001
|
if( scalar( @_ ) == 1 && $self->_is_a( $_[0] => 'Changes::Release' ) ) |
113
|
|
|
|
|
|
|
{ |
114
|
2
|
|
|
|
|
87
|
$rel = shift( @_ ); |
115
|
2
|
50
|
|
|
|
15
|
if( $elements->exists( $rel ) ) |
116
|
|
|
|
|
|
|
{ |
117
|
0
|
|
|
|
|
0
|
return( $self->error( "A very same release object with version '", $rel->version, "' is already registered." ) ); |
118
|
|
|
|
|
|
|
} |
119
|
2
|
|
|
|
|
88
|
my $vers = $rel->version; |
120
|
2
|
50
|
|
|
|
509
|
if( length( "$vers" ) ) |
121
|
|
|
|
|
|
|
{ |
122
|
2
|
100
|
|
2
|
|
64
|
my $same = $elements->grep(sub{ $self->_is_a( $_ => 'Changes::Release' ) && $_->version == "$vers" }); |
|
2
|
|
|
|
|
417
|
|
123
|
2
|
50
|
|
|
|
230
|
return( $self->error( "A similar release with version '$vers' is already registered." ) ) if( !$same->is_empty ); |
124
|
|
|
|
|
|
|
} |
125
|
|
|
|
|
|
|
} |
126
|
|
|
|
|
|
|
else |
127
|
|
|
|
|
|
|
{ |
128
|
2
|
|
|
|
|
14
|
$opts = $self->_get_args_as_hash( @_ ); |
129
|
2
|
50
|
33
|
|
|
319
|
if( exists( $opts->{version} ) && defined( $opts->{version} ) && length( "$opts->{version}" ) ) |
|
|
|
33
|
|
|
|
|
130
|
|
|
|
|
|
|
{ |
131
|
2
|
|
|
|
|
6
|
my $vers = $opts->{version}; |
132
|
2
|
100
|
|
2
|
|
18
|
my $same = $elements->grep(sub{ $self->_is_a( $_ => 'Changes::Release' ) && $_->version == "$vers" }); |
|
2
|
|
|
|
|
458
|
|
133
|
2
|
50
|
|
|
|
282
|
return( $self->error( "A similar release with version '$vers' is already registered." ) ) if( !$same->is_empty ); |
134
|
|
|
|
|
|
|
} |
135
|
2
|
|
50
|
|
|
63
|
$rel = $self->new_release( %$opts ) || return( $self->pass_error ); |
136
|
2
|
|
|
|
|
148
|
return( $self->add_release( $rel ) ); |
137
|
|
|
|
|
|
|
} |
138
|
2
|
|
|
|
|
68
|
$elements->unshift( $self->new_line ); |
139
|
2
|
|
|
|
|
20
|
$elements->unshift( $rel ); |
140
|
2
|
|
|
|
|
25
|
return( $rel ); |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
sub as_string |
144
|
|
|
|
|
|
|
{ |
145
|
21
|
|
|
21
|
1
|
217869
|
my $self = shift( @_ ); |
146
|
21
|
|
|
|
|
140
|
my $lines = $self->new_array; |
147
|
21
|
|
|
|
|
560
|
my $preamble = $self->preamble; |
148
|
21
|
|
|
|
|
19889
|
my $epilogue = $self->epilogue; |
149
|
21
|
100
|
66
|
|
|
18687
|
if( defined( $preamble ) && !$preamble->is_empty ) |
150
|
|
|
|
|
|
|
{ |
151
|
4
|
|
|
|
|
61
|
$lines->push( $preamble->scalar ); |
152
|
|
|
|
|
|
|
} |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
$self->elements->foreach(sub |
155
|
|
|
|
|
|
|
{ |
156
|
71
|
|
|
71
|
|
20630
|
my $str; |
157
|
71
|
50
|
|
|
|
298
|
$str = $_->as_string if( $self->_can( $_ => 'as_string' ) ); |
158
|
71
|
50
|
|
|
|
754
|
if( defined( $str ) ) |
159
|
|
|
|
|
|
|
{ |
160
|
71
|
|
|
|
|
265
|
$lines->push( $str->scalar ); |
161
|
|
|
|
|
|
|
} |
162
|
21
|
|
|
|
|
160
|
}); |
163
|
21
|
100
|
66
|
|
|
3429
|
if( defined( $epilogue ) && !$epilogue->is_empty ) |
164
|
|
|
|
|
|
|
{ |
165
|
2
|
|
|
|
|
26
|
$lines->push( $epilogue->scalar ); |
166
|
|
|
|
|
|
|
} |
167
|
21
|
|
|
|
|
114
|
return( $lines->join( '' ) ); |
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
{ |
171
|
18
|
|
|
18
|
|
145
|
no warnings 'once'; |
|
18
|
|
|
|
|
33
|
|
|
18
|
|
|
|
|
48539
|
|
172
|
|
|
|
|
|
|
*serialize = \&as_string; |
173
|
|
|
|
|
|
|
*serialise = \&as_string; |
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
|
176
|
114
|
|
|
114
|
1
|
14825104
|
sub defaults { return( shift->_set_get_hash_as_mix_object( { field => 'defaults', undef_ok => 1 }, @_ ) ); } |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
sub delete_release |
179
|
|
|
|
|
|
|
{ |
180
|
0
|
|
|
0
|
1
|
0
|
my $self = shift( @_ ); |
181
|
0
|
|
|
|
|
0
|
my $elements = $self->elements; |
182
|
0
|
|
|
|
|
0
|
my $removed = $self->new_array; |
183
|
0
|
|
|
|
|
0
|
foreach my $rel ( @_ ) |
184
|
|
|
|
|
|
|
{ |
185
|
0
|
0
|
|
|
|
0
|
if( $self->_is_a( $rel => 'Changes::Release' ) ) |
186
|
|
|
|
|
|
|
{ |
187
|
0
|
|
|
|
|
0
|
my $pos = $elements->pos( $rel ); |
188
|
0
|
|
|
|
|
0
|
my $until = 1; |
189
|
0
|
|
0
|
|
|
0
|
while( defined( $elements->[ $pos + $until ] ) && $self->_is_a( $elements->[ $pos + $until ] => 'Changes::NewLine' ) ) |
190
|
|
|
|
|
|
|
{ |
191
|
0
|
|
|
|
|
0
|
$until++; |
192
|
|
|
|
|
|
|
} |
193
|
0
|
|
|
|
|
0
|
$elements->delete( $pos, $until ); |
194
|
0
|
|
|
|
|
0
|
$removed->push( $rel ); |
195
|
|
|
|
|
|
|
} |
196
|
|
|
|
|
|
|
else |
197
|
|
|
|
|
|
|
{ |
198
|
0
|
|
|
|
|
0
|
my $vers = $rel; |
199
|
0
|
0
|
0
|
|
|
0
|
if( !defined( $vers ) || !length( "$vers" ) ) |
200
|
|
|
|
|
|
|
{ |
201
|
0
|
0
|
|
|
|
0
|
warn( "No version provided to remove its corresponding release object.\n" ) if( $self->_warnings_is_enabled ); |
202
|
0
|
|
|
|
|
0
|
next; |
203
|
|
|
|
|
|
|
} |
204
|
0
|
0
|
|
0
|
|
0
|
my $found = $elements->grep(sub{ $self->_is_a( $_ => 'Changes::Release' ) && $_->version == $vers }); |
|
0
|
|
|
|
|
0
|
|
205
|
0
|
0
|
|
|
|
0
|
if( $found->is_empty ) |
206
|
|
|
|
|
|
|
{ |
207
|
0
|
|
|
|
|
0
|
next; |
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
$found->foreach(sub |
210
|
|
|
|
|
|
|
{ |
211
|
0
|
|
|
0
|
|
0
|
my $deleted = $self->delete_release( $_ ); |
212
|
0
|
0
|
|
|
|
0
|
$removed->push( $deleted->list ) if( !$deleted->is_empty ); |
213
|
0
|
|
|
|
|
0
|
}); |
214
|
|
|
|
|
|
|
} |
215
|
|
|
|
|
|
|
} |
216
|
0
|
|
|
|
|
0
|
return( $removed ); |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
|
219
|
128
|
|
|
128
|
1
|
671
|
sub elements { return( shift->_set_get_array_as_object( 'elements', @_ ) ); } |
220
|
|
|
|
|
|
|
|
221
|
24
|
|
|
24
|
1
|
41740
|
sub epilogue { return( shift->_set_get_scalar_as_object( 'epilogue', @_ ) ); } |
222
|
|
|
|
|
|
|
|
223
|
4
|
|
|
4
|
1
|
8079
|
sub file { return( shift->_set_get_file( 'file', @_ ) ); } |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
sub freeze |
226
|
|
|
|
|
|
|
{ |
227
|
20
|
|
|
20
|
0
|
54
|
my $self = shift( @_ ); |
228
|
|
|
|
|
|
|
$self->elements->foreach(sub |
229
|
|
|
|
|
|
|
{ |
230
|
67
|
100
|
|
67
|
|
13808
|
if( $self->_can( $_ => 'freeze' ) ) |
231
|
|
|
|
|
|
|
{ |
232
|
44
|
|
|
|
|
1271
|
$_->freeze; |
233
|
|
|
|
|
|
|
} |
234
|
20
|
|
|
|
|
76
|
}); |
235
|
20
|
|
|
|
|
499
|
return( $self ); |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
|
238
|
0
|
|
|
0
|
1
|
0
|
sub history { return( shift->releases( @_ ) ); } |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
sub load |
241
|
|
|
|
|
|
|
{ |
242
|
1
|
|
|
1
|
1
|
3685702
|
my $this = shift( @_ ); |
243
|
1
|
|
50
|
|
|
6
|
my $file = shift( @_ ) || |
244
|
|
|
|
|
|
|
return( $this->error( "No changes file was provided to load." ) ); |
245
|
1
|
|
|
|
|
39
|
my $opts = $this->_get_args_as_hash( @_ ); |
246
|
1
|
|
50
|
|
|
148
|
my $self = $this->new( %$opts ) || |
247
|
|
|
|
|
|
|
return( $this->pass_error ); |
248
|
1
|
|
50
|
|
|
18
|
my $f = $self->new_file( $file ) || |
249
|
|
|
|
|
|
|
return( $this->pass_error( $self->error ) ); |
250
|
1
|
|
50
|
|
|
145959
|
my $mode = $self->mode // '+<'; |
251
|
1
|
50
|
|
|
|
154
|
$f->open( "$mode", { binmode => 'utf-8', autoflush => 1 } ) || |
252
|
|
|
|
|
|
|
return( $this->pass_error( $f->error ) ); |
253
|
|
|
|
|
|
|
# my $lines = $f->lines( chomp => 1 ) || |
254
|
1
|
|
50
|
|
|
10000
|
my $lines = $f->lines || |
255
|
|
|
|
|
|
|
return( $this->pass_error( $f->error ) ); |
256
|
1
|
50
|
|
|
|
3185
|
$self->parse( $lines ) || return( $self->pass_error ); |
257
|
1
|
|
|
|
|
44
|
$self->freeze; |
258
|
1
|
|
|
|
|
5
|
return( $self ); |
259
|
|
|
|
|
|
|
} |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
sub load_data |
262
|
|
|
|
|
|
|
{ |
263
|
19
|
|
|
19
|
1
|
40567
|
my $this = shift( @_ ); |
264
|
19
|
|
|
|
|
64
|
my $data = shift( @_ ); |
265
|
19
|
|
|
|
|
212
|
my $opts = $this->_get_args_as_hash( @_ ); |
266
|
19
|
|
50
|
|
|
144229
|
my $self = $this->new( %$opts ) || |
267
|
|
|
|
|
|
|
return( $this->pass_error ); |
268
|
19
|
50
|
33
|
|
|
348
|
return( $self ) if( !defined( $data ) || !length( "$data" ) ); |
269
|
19
|
|
|
|
|
444
|
my $lines = $self->new_array( [split( /(?<=\n)/, $data )] ); |
270
|
|
|
|
|
|
|
# $lines->chomp; |
271
|
19
|
50
|
|
|
|
560
|
$self->parse( $lines ) || return( $self->pass_error ); |
272
|
19
|
|
|
|
|
761
|
$self->freeze; |
273
|
19
|
|
|
|
|
77
|
return( $self ); |
274
|
|
|
|
|
|
|
} |
275
|
|
|
|
|
|
|
|
276
|
22
|
|
|
22
|
1
|
14821866
|
sub max_width { return( shift->_set_get_number( 'max_width', @_ ) ); } |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
sub new_change |
279
|
|
|
|
|
|
|
{ |
280
|
41
|
|
|
41
|
1
|
167
|
my $self = shift( @_ ); |
281
|
41
|
|
|
|
|
314
|
my $opts = $self->_get_args_as_hash( @_ ); |
282
|
41
|
|
|
|
|
7732
|
my $defaults = $self->defaults; |
283
|
41
|
50
|
|
|
|
36204
|
if( defined( $defaults ) ) |
284
|
|
|
|
|
|
|
{ |
285
|
0
|
|
|
|
|
0
|
foreach my $opt ( qw( spacer1 marker spacer2 ) ) |
286
|
|
|
|
|
|
|
{ |
287
|
0
|
0
|
0
|
|
|
0
|
$opts->{ $opt } //= $defaults->{ $opt } if( defined( $defaults->{ $opt } ) ); |
288
|
|
|
|
|
|
|
} |
289
|
|
|
|
|
|
|
} |
290
|
41
|
|
50
|
|
|
380
|
my $c = Changes::Change->new( $opts ) || |
291
|
|
|
|
|
|
|
return( $self->pass_error( Changes::Change->error ) ); |
292
|
41
|
|
|
|
|
818
|
return( $c ); |
293
|
|
|
|
|
|
|
} |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
sub new_group |
296
|
|
|
|
|
|
|
{ |
297
|
6
|
|
|
6
|
1
|
18
|
my $self = shift( @_ ); |
298
|
6
|
|
|
|
|
51
|
my $opts = $self->_get_args_as_hash( @_ ); |
299
|
6
|
|
|
|
|
1109
|
my $defaults = $self->defaults; |
300
|
6
|
50
|
|
|
|
5290
|
if( defined( $defaults ) ) |
301
|
|
|
|
|
|
|
{ |
302
|
0
|
|
|
|
|
0
|
my $def = { %$defaults }; |
303
|
0
|
|
|
|
|
0
|
foreach my $opt ( qw( spacer type ) ) |
304
|
|
|
|
|
|
|
{ |
305
|
0
|
0
|
0
|
|
|
0
|
if( !defined( $opts->{ "group_${opt}" } ) && |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
306
|
|
|
|
|
|
|
exists( $def->{ "group_${opt}" } ) && |
307
|
|
|
|
|
|
|
defined( $def->{ "group_${opt}" } ) && |
308
|
|
|
|
|
|
|
length( $def->{ "group_${opt}" } ) ) |
309
|
|
|
|
|
|
|
{ |
310
|
0
|
|
|
|
|
0
|
$opts->{ $opt } = CORE::delete( $def->{ "group_${opt}" } ); |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
} |
313
|
0
|
|
0
|
|
|
0
|
$opts->{defaults} //= $def; |
314
|
|
|
|
|
|
|
} |
315
|
6
|
|
50
|
|
|
53
|
my $g = Changes::Group->new( $opts ) || |
316
|
|
|
|
|
|
|
return( $self->pass_error( Changes::Group->error ) ); |
317
|
6
|
|
|
|
|
104
|
return( $g ); |
318
|
|
|
|
|
|
|
} |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
sub new_line |
321
|
|
|
|
|
|
|
{ |
322
|
25
|
|
|
25
|
1
|
73
|
my $self = shift( @_ ); |
323
|
25
|
50
|
|
|
|
171
|
$self->_load_class( 'Changes::NewLine' ) || return( $self->pass_error ); |
324
|
25
|
|
50
|
|
|
4955
|
my $nl = Changes::NewLine->new( @_ ) || |
325
|
|
|
|
|
|
|
return( $self->pass_error( Changes::NewLine->error ) ); |
326
|
25
|
|
|
|
|
336
|
return( $nl ); |
327
|
|
|
|
|
|
|
} |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
sub new_release |
330
|
|
|
|
|
|
|
{ |
331
|
46
|
|
|
46
|
1
|
150
|
my $self = shift( @_ ); |
332
|
46
|
|
|
|
|
350
|
my $opts = $self->_get_args_as_hash( @_ ); |
333
|
46
|
|
|
|
|
9000
|
my $defaults = $self->defaults; |
334
|
46
|
100
|
|
|
|
42854
|
if( defined( $defaults ) ) |
335
|
|
|
|
|
|
|
{ |
336
|
2
|
|
|
|
|
14
|
my $def = { %$defaults }; |
337
|
2
|
|
|
|
|
269
|
foreach my $opt ( qw( datetime_formatter format spacer time_zone ) ) |
338
|
|
|
|
|
|
|
{ |
339
|
8
|
50
|
66
|
|
|
58
|
if( !defined( $opts->{ $opt } ) && |
|
|
|
66
|
|
|
|
|
|
|
|
66
|
|
|
|
|
340
|
|
|
|
|
|
|
exists( $def->{ $opt } ) && |
341
|
|
|
|
|
|
|
defined( $def->{ $opt } ) && |
342
|
|
|
|
|
|
|
length( $def->{ $opt } ) ) |
343
|
|
|
|
|
|
|
{ |
344
|
4
|
|
|
|
|
16
|
$opts->{ $opt } = CORE::delete( $def->{ $opt } ); |
345
|
|
|
|
|
|
|
} |
346
|
|
|
|
|
|
|
} |
347
|
2
|
|
33
|
|
|
26
|
$opts->{defaults} //= $def; |
348
|
|
|
|
|
|
|
} |
349
|
46
|
|
50
|
|
|
521
|
my $rel = Changes::Release->new( $opts ) || |
350
|
|
|
|
|
|
|
return( $self->pass_error( Changes::Release->error ) ); |
351
|
46
|
|
|
|
|
829
|
return( $rel ); |
352
|
|
|
|
|
|
|
} |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
sub new_version |
355
|
|
|
|
|
|
|
{ |
356
|
0
|
|
|
0
|
1
|
0
|
my $self = shift( @_ ); |
357
|
0
|
0
|
|
|
|
0
|
$self->_load_class( 'Changes::Version' ) || return( $self->pass_error ); |
358
|
0
|
|
0
|
|
|
0
|
my $v = Changes::Version->new( @_ ) || |
359
|
|
|
|
|
|
|
return( $self->pass_error( Changes::Version->error ) ); |
360
|
0
|
|
|
|
|
0
|
return( $v ); |
361
|
|
|
|
|
|
|
} |
362
|
|
|
|
|
|
|
|
363
|
2
|
|
|
2
|
1
|
20
|
sub nl { return( shift->_set_get_scalar_as_object( 'nl', @_ ) ); } |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
sub parse |
366
|
|
|
|
|
|
|
{ |
367
|
20
|
|
|
20
|
1
|
73
|
my $self = shift( @_ ); |
368
|
20
|
|
50
|
|
|
539
|
my $lines = shift( @_ ) || return( $self->error( "No array reference of lines was provided." ) ); |
369
|
20
|
50
|
|
|
|
164
|
return( $self->error( "Data provided is not an array reference of lines." ) ) if( !$self->_is_array( $lines ) ); |
370
|
20
|
|
|
|
|
379
|
$lines = $self->new_array( $lines ); |
371
|
20
|
|
|
|
|
672
|
my $preamble = $self->new_scalar; |
372
|
20
|
|
|
|
|
46524377
|
my $epilogue; |
373
|
20
|
|
|
|
|
129
|
my $elements = $self->new_array; |
374
|
|
|
|
|
|
|
# Temporary array buffer of new lines found that we store here until we read more of the context in the Changes file and we decide what to do with them. |
375
|
20
|
|
|
|
|
504
|
my $nls = $self->new_array; |
376
|
20
|
|
50
|
|
|
417
|
my $max_width = $self->max_width // 0; |
377
|
20
|
|
|
|
|
3004725
|
my $debug = $self->debug; |
378
|
20
|
|
|
|
|
494
|
my( $group, $release, $change ); |
379
|
|
|
|
|
|
|
# $type is the Changes file type. It contains the value guessed, otherwise it remains undef |
380
|
20
|
|
|
|
|
124
|
my $type = $self->type; |
381
|
20
|
|
|
|
|
17966
|
my $wrapper = $self->wrapper; |
382
|
20
|
|
|
|
|
17428
|
my $tz = $self->time_zone; |
383
|
20
|
|
|
|
|
127
|
my $defaults = $self->defaults; |
384
|
|
|
|
|
|
|
# Cache it |
385
|
20
|
100
|
|
|
|
21099
|
unless( defined( $DATETIME_RE ) ) |
386
|
|
|
|
|
|
|
{ |
387
|
15
|
|
|
|
|
231
|
$DATETIME_RE = $self->_get_datetime_regexp( 'all' ); |
388
|
|
|
|
|
|
|
} |
389
|
20
|
|
|
|
|
26045317
|
for( my $i = 0; $i < scalar( @$lines ); $i++ ) |
390
|
|
|
|
|
|
|
{ |
391
|
127
|
|
|
|
|
70981
|
my $l = $lines->[$i]; |
392
|
|
|
|
|
|
|
# DistZilla release line |
393
|
|
|
|
|
|
|
# 0.01 2022-12-11 08:07:12 Asia/Tokyo |
394
|
127
|
100
|
100
|
|
|
20498
|
if( $l =~ /^ |
|
|
100
|
33
|
|
|
|
|
|
|
100
|
33
|
|
|
|
|
|
|
100
|
33
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
395
|
|
|
|
|
|
|
[[:blank:]\h]* |
396
|
|
|
|
|
|
|
(?<r_vers>$VERSION_LAX_REGEX) |
397
|
|
|
|
|
|
|
(?<v_space>[[:blank:]\h][[:blank:]\h\W]*) |
398
|
|
|
|
|
|
|
(?<r_datetime>$DATE_DISTZILA_RE) |
399
|
|
|
|
|
|
|
[[:blank:]\h]* |
400
|
|
|
|
|
|
|
(?<r_nl>[\015\012]+)?$ |
401
|
|
|
|
|
|
|
/msx ) |
402
|
|
|
|
|
|
|
{ |
403
|
2
|
|
|
|
|
101
|
my $re = { %+ }; |
404
|
|
|
|
|
|
|
# Create the DateTime object |
405
|
2
|
50
|
|
|
|
21
|
$self->_load_class( 'DateTime' ) || return( $self->pass_error ); |
406
|
2
|
50
|
|
|
|
84
|
$self->_load_class( 'DateTime::TimeZone' ) || return( $self->pass_error ); |
407
|
2
|
50
|
|
|
|
58
|
$self->_load_class( 'DateTime::Format::Strptime' ) || return( $self->pass_error ); |
408
|
2
|
|
|
|
|
204523
|
my( $dt, $tz, $fmt ); |
409
|
2
|
50
|
33
|
|
|
11
|
try |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
15
|
|
|
0
|
|
|
|
|
0
|
|
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
12
|
|
|
2
|
|
|
|
|
6
|
|
410
|
2
|
|
|
2
|
|
4
|
{ |
411
|
2
|
|
|
|
|
21
|
$tz = DateTime::TimeZone->new( name => $re->{r_tz} ); |
412
|
|
|
|
|
|
|
} |
413
|
2
|
0
|
50
|
|
|
14
|
catch( $e where { /The[[:blank:]\h]+timezone[[:blank:]\h]+'(?:.*?)'[[:blank:]\h]+could[[:blank:]\h]+not[[:blank:]\h]+be[[:blank:]\h]+loaded/i } ) |
|
2
|
0
|
33
|
|
|
32108
|
|
|
2
|
0
|
|
|
|
10
|
|
|
2
|
0
|
|
|
|
6
|
|
|
2
|
0
|
|
|
|
5
|
|
|
2
|
0
|
|
|
|
4
|
|
|
2
|
0
|
|
|
|
4
|
|
|
2
|
0
|
|
|
|
11
|
|
|
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
|
0
|
|
|
|
0
|
|
|
0
|
50
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
2
|
|
|
|
|
9
|
|
|
0
|
|
|
|
|
0
|
|
|
2
|
|
|
|
|
5
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
2
|
|
|
|
|
15
|
|
|
2
|
|
|
|
|
12
|
|
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
10
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
414
|
0
|
|
|
0
|
|
0
|
{ |
415
|
0
|
0
|
|
|
|
0
|
warn( "Warning only: invalid time zone '$re->{r_tz}' specified in release at line ", ( $i + 1 ), "\n" ) if( $self->_warnings_is_enabled ); |
416
|
0
|
|
|
|
|
0
|
$tz = DateTime::TimeZone->new( name => 'UTC' ); |
417
|
|
|
|
|
|
|
} |
418
|
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
|
|
419
|
0
|
|
|
0
|
|
0
|
{ |
420
|
0
|
0
|
|
|
|
0
|
warn( "Warning only: error trying to instantiate a new DateTime::TimeZone object with time zone '$re->{r_tz}': $e\n" ) if( $self->_warnings_is_enabled ); |
421
|
0
|
|
|
|
|
0
|
$tz = DateTime::TimeZone->new( name => 'UTC' ); |
422
|
18
|
0
|
0
|
18
|
|
160
|
} |
|
18
|
0
|
0
|
|
|
41
|
|
|
18
|
0
|
33
|
|
|
21324
|
|
|
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
|
|
|
2
|
0
|
|
|
|
7
|
|
|
0
|
0
|
|
|
|
0
|
|
|
2
|
0
|
|
|
|
93
|
|
|
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
|
|
|
2
|
|
|
|
|
10
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
2
|
|
|
|
|
8
|
|
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
|
425
|
2
|
50
|
33
|
|
|
5
|
try |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
10
|
|
|
0
|
|
|
|
|
0
|
|
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
5
|
|
426
|
2
|
|
|
2
|
|
4
|
{ |
427
|
2
|
|
|
|
|
32
|
$fmt = DateTime::Format::Strptime->new( |
428
|
|
|
|
|
|
|
pattern => "%F$re->{r_dt_space}%T$re->{r_tz_space}%O", |
429
|
|
|
|
|
|
|
); |
430
|
|
|
|
|
|
|
} |
431
|
2
|
0
|
50
|
|
|
13
|
catch( $e ) |
|
2
|
0
|
33
|
|
|
3914
|
|
|
2
|
0
|
|
|
|
8
|
|
|
2
|
0
|
|
|
|
6
|
|
|
2
|
0
|
|
|
|
4
|
|
|
2
|
0
|
|
|
|
3
|
|
|
2
|
0
|
|
|
|
4
|
|
|
2
|
0
|
|
|
|
10
|
|
|
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
|
|
|
2
|
|
|
|
|
8
|
|
|
0
|
|
|
|
|
0
|
|
|
2
|
|
|
|
|
6
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
2
|
|
|
|
|
11
|
|
|
2
|
|
|
|
|
10
|
|
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
7
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
432
|
0
|
|
|
0
|
|
0
|
{ |
433
|
0
|
0
|
|
|
|
0
|
warn( "Error only: failed to create a DateTime::Format::Strptime with pattern '%F$re->{r_dt_space}%T$re->{r_tz_space}%Z': $e\n" ) if( $self->_warnings_is_enabled ); |
434
|
0
|
|
|
|
|
0
|
$fmt = DateTime::Format::Strptime->new( |
435
|
|
|
|
|
|
|
pattern => "%F %T %O", |
436
|
|
|
|
|
|
|
); |
437
|
18
|
0
|
0
|
18
|
|
144
|
} |
|
18
|
0
|
0
|
|
|
38
|
|
|
18
|
0
|
33
|
|
|
22507
|
|
|
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
|
|
|
2
|
0
|
|
|
|
7
|
|
|
0
|
0
|
|
|
|
0
|
|
|
2
|
0
|
|
|
|
91
|
|
|
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
|
|
|
2
|
|
|
|
|
10
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
2
|
|
|
|
|
8
|
|
438
|
|
|
|
|
|
|
|
439
|
2
|
50
|
33
|
|
|
5
|
try |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
9
|
|
|
0
|
|
|
|
|
0
|
|
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
5
|
|
440
|
2
|
|
|
2
|
|
4
|
{ |
441
|
|
|
|
|
|
|
$dt = DateTime->new( |
442
|
|
|
|
|
|
|
year => $re->{r_year}, |
443
|
|
|
|
|
|
|
month => $re->{r_month}, |
444
|
|
|
|
|
|
|
day => $re->{r_day}, |
445
|
|
|
|
|
|
|
hour => $re->{r_hour}, |
446
|
|
|
|
|
|
|
minute => $re->{r_minute}, |
447
|
|
|
|
|
|
|
second => $re->{r_second}, |
448
|
2
|
|
|
|
|
22
|
time_zone => $tz, |
449
|
|
|
|
|
|
|
); |
450
|
2
|
|
|
|
|
1754
|
$dt->set_formatter( $fmt ); |
451
|
|
|
|
|
|
|
} |
452
|
2
|
0
|
50
|
|
|
12
|
catch( $e ) |
|
2
|
0
|
33
|
|
|
145
|
|
|
2
|
0
|
|
|
|
7
|
|
|
2
|
0
|
|
|
|
5
|
|
|
2
|
0
|
|
|
|
4
|
|
|
2
|
0
|
|
|
|
3
|
|
|
2
|
0
|
|
|
|
5
|
|
|
2
|
0
|
|
|
|
9
|
|
|
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
|
|
|
2
|
|
|
|
|
10
|
|
|
0
|
|
|
|
|
0
|
|
|
2
|
|
|
|
|
4
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
2
|
|
|
|
|
7
|
|
|
2
|
|
|
|
|
11
|
|
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
7
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
453
|
0
|
|
|
0
|
|
0
|
{ |
454
|
0
|
0
|
|
|
|
0
|
warn( "Warning only: error trying to instantiate a DateTime value based on the date and time of the release at line ", ( $i + 1 ), ": $e\n" ) if( $self->_warnings_is_enabled ); |
455
|
0
|
|
|
|
|
0
|
$dt = DateTime->now( time_zone => $tz ); |
456
|
18
|
0
|
0
|
18
|
|
147
|
} |
|
18
|
0
|
0
|
|
|
34
|
|
|
18
|
0
|
33
|
|
|
66437
|
|
|
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
|
|
|
2
|
0
|
|
|
|
7
|
|
|
0
|
0
|
|
|
|
0
|
|
|
2
|
0
|
|
|
|
92
|
|
|
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
|
|
|
2
|
|
|
|
|
10
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
2
|
|
|
|
|
7
|
|
457
|
|
|
|
|
|
|
|
458
|
2
|
100
|
|
|
|
14
|
if( !$nls->is_empty ) |
459
|
|
|
|
|
|
|
{ |
460
|
1
|
|
|
|
|
23
|
$elements->push( $nls->list ); |
461
|
1
|
|
|
|
|
14
|
$nls->reset; |
462
|
|
|
|
|
|
|
} |
463
|
2
|
|
|
|
|
90
|
undef( $group ); |
464
|
|
|
|
|
|
|
$release = $self->new_release( |
465
|
|
|
|
|
|
|
version => $re->{r_vers}, |
466
|
|
|
|
|
|
|
datetime => $dt, |
467
|
|
|
|
|
|
|
spacer => $re->{v_space}, |
468
|
|
|
|
|
|
|
( defined( $re->{r_note} ) ? ( note => $re->{r_note} ) : () ), |
469
|
|
|
|
|
|
|
raw => $l, |
470
|
|
|
|
|
|
|
line => ( $i + 1 ), |
471
|
|
|
|
|
|
|
container => $self, |
472
|
|
|
|
|
|
|
# Could be undef if this is the last line with no trailing crlf |
473
|
|
|
|
|
|
|
nl => $re->{r_nl}, |
474
|
2
|
50
|
|
|
|
27
|
( defined( $tz ) ? ( time_zone => $tz ) : () ), |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
475
|
|
|
|
|
|
|
( defined( $defaults ) ? ( defaults => $defaults ) : () ), |
476
|
|
|
|
|
|
|
debug => $debug, |
477
|
|
|
|
|
|
|
); |
478
|
2
|
|
|
|
|
16
|
$elements->push( $release ); |
479
|
2
|
50
|
33
|
|
|
32
|
if( defined( $preamble ) && !$preamble->is_empty ) |
480
|
|
|
|
|
|
|
{ |
481
|
0
|
|
|
|
|
0
|
$self->preamble( $preamble ); |
482
|
0
|
|
|
|
|
0
|
undef( $preamble ); |
483
|
|
|
|
|
|
|
} |
484
|
2
|
50
|
|
|
|
25
|
unless( defined( $type ) ) |
485
|
|
|
|
|
|
|
{ |
486
|
2
|
|
|
|
|
5
|
$type = 'distzilla'; |
487
|
2
|
|
|
|
|
12
|
$self->type( $type ); |
488
|
|
|
|
|
|
|
} |
489
|
|
|
|
|
|
|
} |
490
|
|
|
|
|
|
|
# Release line |
491
|
|
|
|
|
|
|
# v0.1.0 2022-11-17T08:12:31+0900 |
492
|
|
|
|
|
|
|
# 0.01 - 2022-11-17 |
493
|
|
|
|
|
|
|
elsif( $l =~ /^ |
494
|
|
|
|
|
|
|
[[:blank:]\h]* |
495
|
|
|
|
|
|
|
(?<r_vers>$VERSION_LAX_REGEX) |
496
|
|
|
|
|
|
|
(?<v_space>[[:blank:]\h][[:blank:]\h\W]*) |
497
|
|
|
|
|
|
|
(?<r_date>$DATETIME_RE) |
498
|
|
|
|
|
|
|
(?: |
499
|
|
|
|
|
|
|
(?<d_space>[[:blank:]\h]+) |
500
|
|
|
|
|
|
|
(?<r_note>.+?))?(?<r_nl>[\015\012]+)? |
501
|
|
|
|
|
|
|
$/msx ) |
502
|
|
|
|
|
|
|
{ |
503
|
34
|
|
|
|
|
9743
|
my $re = { %+ }; |
504
|
34
|
|
50
|
|
|
1345
|
my $dt = $self->_parse_timestamp( $re->{r_date} ) || |
505
|
|
|
|
|
|
|
return( $self->pass_error( "Cannot parse datetime timestamp although the regular expression matched: ", $self->error->message ) ); |
506
|
34
|
100
|
|
|
|
34869437
|
if( !$nls->is_empty ) |
507
|
|
|
|
|
|
|
{ |
508
|
16
|
|
|
|
|
406
|
$elements->push( $nls->list ); |
509
|
16
|
|
|
|
|
240
|
$nls->reset; |
510
|
|
|
|
|
|
|
} |
511
|
34
|
|
|
|
|
910
|
undef( $group ); |
512
|
|
|
|
|
|
|
$release = $self->new_release( |
513
|
|
|
|
|
|
|
version => $re->{r_vers}, |
514
|
|
|
|
|
|
|
# datetime => $re->{r_date}, |
515
|
|
|
|
|
|
|
datetime => $dt, |
516
|
|
|
|
|
|
|
spacer => $re->{v_space}, |
517
|
|
|
|
|
|
|
( defined( $re->{r_note} ) ? ( note => $re->{r_note} ) : () ), |
518
|
|
|
|
|
|
|
raw => $l, |
519
|
|
|
|
|
|
|
line => ( $i + 1 ), |
520
|
|
|
|
|
|
|
container => $self, |
521
|
|
|
|
|
|
|
# Could be undef if this is the last line with no trailing crlf |
522
|
|
|
|
|
|
|
nl => $re->{r_nl}, |
523
|
34
|
100
|
|
|
|
594
|
( defined( $tz ) ? ( time_zone => $tz ) : () ), |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
524
|
|
|
|
|
|
|
( defined( $defaults ) ? ( defaults => $defaults ) : () ), |
525
|
|
|
|
|
|
|
debug => $debug, |
526
|
|
|
|
|
|
|
); |
527
|
34
|
|
|
|
|
299
|
$elements->push( $release ); |
528
|
34
|
100
|
100
|
|
|
601
|
if( defined( $preamble ) && !$preamble->is_empty ) |
529
|
|
|
|
|
|
|
{ |
530
|
3
|
|
|
|
|
47
|
$self->preamble( $preamble ); |
531
|
3
|
|
|
|
|
530
|
undef( $preamble ); |
532
|
|
|
|
|
|
|
} |
533
|
|
|
|
|
|
|
} |
534
|
|
|
|
|
|
|
elsif( $l =~ /^ |
535
|
|
|
|
|
|
|
[[:blank:]\h]* |
536
|
|
|
|
|
|
|
(?<r_vers>$VERSION_LAX_REGEX) |
537
|
|
|
|
|
|
|
(?: |
538
|
|
|
|
|
|
|
(?<v_space>[[:blank:]\h][[:blank:]\h\W]*) |
539
|
|
|
|
|
|
|
(?<r_note>[^\015\012]*) |
540
|
|
|
|
|
|
|
)? |
541
|
|
|
|
|
|
|
(?<r_nl>[\015\012]+)? |
542
|
|
|
|
|
|
|
/msx ) |
543
|
|
|
|
|
|
|
{ |
544
|
8
|
|
|
|
|
1797
|
my $re = { %+ }; |
545
|
8
|
100
|
|
|
|
87
|
if( !$nls->is_empty ) |
546
|
|
|
|
|
|
|
{ |
547
|
5
|
|
|
|
|
126
|
$elements->push( $nls->list ); |
548
|
5
|
|
|
|
|
73
|
$nls->reset; |
549
|
|
|
|
|
|
|
} |
550
|
8
|
|
|
|
|
213
|
undef( $group ); |
551
|
|
|
|
|
|
|
$release = $self->new_release( |
552
|
|
|
|
|
|
|
version => $re->{r_vers}, |
553
|
|
|
|
|
|
|
spacer => $re->{v_space}, |
554
|
|
|
|
|
|
|
( defined( $re->{r_note} ) ? ( note => $re->{r_note} ) : () ), |
555
|
|
|
|
|
|
|
raw => $l, |
556
|
|
|
|
|
|
|
line => ( $i + 1 ), |
557
|
|
|
|
|
|
|
container => $self, |
558
|
|
|
|
|
|
|
# Could be undef if this is the last line with no trailing crlf |
559
|
|
|
|
|
|
|
nl => $re->{r_nl}, |
560
|
8
|
100
|
|
|
|
112
|
( defined( $tz ) ? ( time_zone => $tz ) : () ), |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
561
|
|
|
|
|
|
|
( defined( $defaults ) ? ( defaults => $defaults ) : () ), |
562
|
|
|
|
|
|
|
debug => $debug, |
563
|
|
|
|
|
|
|
); |
564
|
8
|
|
|
|
|
76
|
$elements->push( $release ); |
565
|
8
|
50
|
33
|
|
|
132
|
if( defined( $preamble ) && !$preamble->is_empty ) |
566
|
|
|
|
|
|
|
{ |
567
|
0
|
|
|
|
|
0
|
$self->preamble( $preamble ); |
568
|
0
|
|
|
|
|
0
|
undef( $preamble ); |
569
|
|
|
|
|
|
|
} |
570
|
|
|
|
|
|
|
} |
571
|
|
|
|
|
|
|
# Group line |
572
|
|
|
|
|
|
|
elsif( $l =~ /^(?<g_space>[[:blank:]\h]+)(?<data>(?:\[(?<g_name>[^\]]+)\]|(?<g_name_colon>\w[^\:]+)\:))[[:blank:]\h]*(?<g_nl>[\015\012]+)?$/ms ) |
573
|
|
|
|
|
|
|
{ |
574
|
6
|
|
|
|
|
189
|
my $re = { %+ }; |
575
|
|
|
|
|
|
|
# Depending on where we are we treat this either as a group, or as a mere comment of a release change |
576
|
|
|
|
|
|
|
# 1) This is a continuity of the previous change line |
577
|
|
|
|
|
|
|
# We assert this by checking if the space before is longer than the prefix of the change, which would imply an indentation that would put it below the change, and thus not a group |
578
|
6
|
50
|
50
|
|
|
91
|
if( defined( $change ) && length( $re->{g_space} // '' ) > $change->prefix->length ) |
|
|
|
66
|
|
|
|
|
579
|
|
|
|
|
|
|
{ |
580
|
0
|
|
|
|
|
0
|
$change->text->append( $re->{data} ); |
581
|
|
|
|
|
|
|
# Since this is a wrapped line, we remove any excessive leading spaces and replace them by just one space |
582
|
0
|
|
|
|
|
0
|
$l =~ s/^[[:blank:]\h]+/ /g; |
583
|
0
|
|
|
|
|
0
|
$change->raw->push( $l ); |
584
|
|
|
|
|
|
|
} |
585
|
|
|
|
|
|
|
else |
586
|
|
|
|
|
|
|
{ |
587
|
|
|
|
|
|
|
# A group is above a change, so if we already have an ongoing change object, we stop using it |
588
|
6
|
|
|
|
|
123999
|
undef( $change ); |
589
|
|
|
|
|
|
|
$group = $self->new_group( |
590
|
|
|
|
|
|
|
name => ( $re->{g_name} // $re->{g_name_colon} ), |
591
|
|
|
|
|
|
|
spacer => $re->{g_space}, |
592
|
|
|
|
|
|
|
raw => $l, |
593
|
|
|
|
|
|
|
line => ( $i + 1 ), |
594
|
|
|
|
|
|
|
type => ( defined( $re->{g_name_colon} ) ? 'colon' : 'bracket' ), |
595
|
|
|
|
|
|
|
# Could be undef if this is the last line with no trailing crlf |
596
|
|
|
|
|
|
|
nl => $re->{g_nl}, |
597
|
6
|
50
|
33
|
|
|
91
|
( defined( $defaults ) ? ( defaults => $defaults ) : () ), |
|
|
50
|
|
|
|
|
|
598
|
|
|
|
|
|
|
debug => $debug, |
599
|
|
|
|
|
|
|
); |
600
|
6
|
50
|
|
|
|
33
|
if( !defined( $release ) ) |
601
|
|
|
|
|
|
|
{ |
602
|
0
|
0
|
|
|
|
0
|
warn( "Found a group token outside of a release information at line ", ( $i + 1 ), "\n" ) if( $self->_warnings_is_enabled ); |
603
|
0
|
0
|
|
|
|
0
|
if( !$nls->is_empty ) |
604
|
|
|
|
|
|
|
{ |
605
|
0
|
|
|
|
|
0
|
$elements->push( $nls->list ); |
606
|
0
|
|
|
|
|
0
|
$nls->reset; |
607
|
|
|
|
|
|
|
} |
608
|
0
|
|
|
|
|
0
|
$elements->push( $group ); |
609
|
|
|
|
|
|
|
} |
610
|
|
|
|
|
|
|
else |
611
|
|
|
|
|
|
|
{ |
612
|
6
|
50
|
|
|
|
200
|
if( !$nls->is_empty ) |
613
|
|
|
|
|
|
|
{ |
614
|
0
|
|
|
|
|
0
|
$release->elements->push( $nls->list ); |
615
|
0
|
|
|
|
|
0
|
$nls->reset; |
616
|
|
|
|
|
|
|
} |
617
|
6
|
|
|
|
|
177
|
$release->elements->push( $group ); |
618
|
|
|
|
|
|
|
} |
619
|
|
|
|
|
|
|
} |
620
|
|
|
|
|
|
|
} |
621
|
|
|
|
|
|
|
# Change line |
622
|
|
|
|
|
|
|
elsif( defined( $release ) && |
623
|
|
|
|
|
|
|
$l =~ /^(?<c_space1>[[:blank:]\h]*)(?<marker>(?:[^\w[:blank:]\h]|[\_\x{30FC}]))(?<c_space2>[[:blank:]\h]+)(?<c_text>.+?)(?<c_nl>[\015\012]+)?$/ms ) |
624
|
|
|
|
|
|
|
{ |
625
|
41
|
|
|
|
|
2592
|
my $re = { %+ }; |
626
|
|
|
|
|
|
|
$change = $self->new_change( |
627
|
|
|
|
|
|
|
( defined( $re->{c_space1} ) ? ( spacer1 => $re->{c_space1} ) : () ), |
628
|
|
|
|
|
|
|
( defined( $re->{c_space2} ) ? ( spacer2 => $re->{c_space2} ) : () ), |
629
|
|
|
|
|
|
|
marker => $re->{marker}, |
630
|
|
|
|
|
|
|
max_width => $max_width, |
631
|
|
|
|
|
|
|
( defined( $re->{c_text} ) ? ( text => $re->{c_text} ) : () ), |
632
|
|
|
|
|
|
|
# Could be undef if this is the last line with no trailing crlf |
633
|
|
|
|
|
|
|
nl => $re->{c_nl}, |
634
|
|
|
|
|
|
|
# raw => "$l\n", |
635
|
41
|
|
50
|
|
|
801
|
raw => $l, |
636
|
|
|
|
|
|
|
( defined( $wrapper ) ? ( wrapper => $wrapper ) : () ), |
637
|
|
|
|
|
|
|
line => ( $i + 1 ), |
638
|
|
|
|
|
|
|
debug => $debug, |
639
|
|
|
|
|
|
|
) || return( $self->pass_error ); |
640
|
|
|
|
|
|
|
|
641
|
41
|
100
|
|
|
|
250
|
if( defined( $group ) ) |
|
|
50
|
|
|
|
|
|
642
|
|
|
|
|
|
|
{ |
643
|
7
|
50
|
|
|
|
54
|
if( !$nls->is_empty ) |
644
|
|
|
|
|
|
|
{ |
645
|
0
|
|
|
|
|
0
|
$group->elements->push( $nls->list ); |
646
|
0
|
|
|
|
|
0
|
$nls->reset; |
647
|
|
|
|
|
|
|
} |
648
|
7
|
|
|
|
|
202
|
$group->elements->push( $change ); |
649
|
|
|
|
|
|
|
} |
650
|
|
|
|
|
|
|
elsif( defined( $release ) ) |
651
|
|
|
|
|
|
|
{ |
652
|
34
|
50
|
|
|
|
251
|
if( !$nls->is_empty ) |
653
|
|
|
|
|
|
|
{ |
654
|
0
|
|
|
|
|
0
|
$release->elements->push( $nls->list ); |
655
|
0
|
|
|
|
|
0
|
$nls->reset; |
656
|
|
|
|
|
|
|
} |
657
|
34
|
|
|
|
|
973
|
$release->elements->push( $change ); |
658
|
|
|
|
|
|
|
} |
659
|
|
|
|
|
|
|
else |
660
|
|
|
|
|
|
|
{ |
661
|
0
|
0
|
|
|
|
0
|
warn( "Found a change token outside of a release information at line ", ( $i + 1 ), "\n" ) if( $self->_warnings_is_enabled ); |
662
|
0
|
0
|
|
|
|
0
|
if( !$nls->is_empty ) |
663
|
|
|
|
|
|
|
{ |
664
|
0
|
|
|
|
|
0
|
$elements->push( $nls->list ); |
665
|
0
|
|
|
|
|
0
|
$nls->reset; |
666
|
|
|
|
|
|
|
} |
667
|
0
|
|
|
|
|
0
|
$elements->push( $change ); |
668
|
|
|
|
|
|
|
} |
669
|
|
|
|
|
|
|
} |
670
|
|
|
|
|
|
|
# Some previous line continuity |
671
|
|
|
|
|
|
|
elsif( $l =~ /^(?<space>[[:blank:]\h]+)(?<data>\S+.*?)(?<c_nl>[\015\012]+)?$/ms ) |
672
|
|
|
|
|
|
|
{ |
673
|
4
|
|
|
|
|
106
|
my $re = { %+ }; |
674
|
|
|
|
|
|
|
# We have an ongoing change, so this is likely a wrapped line. We append the text |
675
|
4
|
50
|
|
|
|
24
|
if( defined( $change ) ) |
676
|
|
|
|
|
|
|
{ |
677
|
4
|
|
33
|
|
|
41
|
$change->text->append( ( $change->nl // $self->nl ) . ( $re->{space} . $re->{data} ) ); |
678
|
|
|
|
|
|
|
# Which might be undef if, for example, this is the last line and there is no trailing crlf |
679
|
4
|
|
|
|
|
10699
|
$change->nl( $re->{c_nl} ); |
680
|
4
|
|
|
|
|
11516
|
$change->raw->append( $l ); |
681
|
|
|
|
|
|
|
} |
682
|
|
|
|
|
|
|
# Ok, then some weirdly formatted change text |
683
|
|
|
|
|
|
|
else |
684
|
|
|
|
|
|
|
{ |
685
|
|
|
|
|
|
|
$change = $self->new_change( |
686
|
|
|
|
|
|
|
( defined( $re->{c_space1} ) ? ( spacer1 => $re->{c_space1} ) : () ), |
687
|
|
|
|
|
|
|
( defined( $re->{c_space2} ) ? ( spacer2 => $re->{c_space2} ) : () ), |
688
|
|
|
|
|
|
|
marker => $re->{marker}, |
689
|
|
|
|
|
|
|
max_width => $max_width, |
690
|
|
|
|
|
|
|
( defined( $re->{c_text} ) ? ( text => $re->{c_text} ) : () ), |
691
|
|
|
|
|
|
|
nl => $re->{c_nl}, |
692
|
|
|
|
|
|
|
# raw => "$l\n", |
693
|
0
|
|
0
|
|
|
0
|
raw => $l, |
694
|
|
|
|
|
|
|
line => ( $i + 1 ), |
695
|
|
|
|
|
|
|
debug => $debug, |
696
|
|
|
|
|
|
|
) || return( $self->pass_error ); |
697
|
0
|
0
|
|
|
|
0
|
if( defined( $group ) ) |
|
|
0
|
|
|
|
|
|
698
|
|
|
|
|
|
|
{ |
699
|
0
|
0
|
|
|
|
0
|
if( !$nls->is_empty ) |
700
|
|
|
|
|
|
|
{ |
701
|
0
|
|
|
|
|
0
|
$group->elements->push( $nls->list ); |
702
|
0
|
|
|
|
|
0
|
$nls->reset; |
703
|
|
|
|
|
|
|
} |
704
|
0
|
|
|
|
|
0
|
$group->elements->push( $change ); |
705
|
|
|
|
|
|
|
} |
706
|
|
|
|
|
|
|
elsif( defined( $release ) ) |
707
|
|
|
|
|
|
|
{ |
708
|
0
|
0
|
|
|
|
0
|
if( !$nls->is_empty ) |
709
|
|
|
|
|
|
|
{ |
710
|
0
|
|
|
|
|
0
|
$release->elements->push( $nls->list ); |
711
|
0
|
|
|
|
|
0
|
$nls->reset; |
712
|
|
|
|
|
|
|
} |
713
|
0
|
|
|
|
|
0
|
$release->elements->push( $change ); |
714
|
|
|
|
|
|
|
} |
715
|
|
|
|
|
|
|
} |
716
|
|
|
|
|
|
|
} |
717
|
|
|
|
|
|
|
# Blank line |
718
|
|
|
|
|
|
|
elsif( $l =~ /^(?<space>[[:blank:]\h]*)(?<nl>[\015\012]+)?$/ ) |
719
|
|
|
|
|
|
|
{ |
720
|
27
|
|
|
|
|
621
|
my $re = { %+ }; |
721
|
|
|
|
|
|
|
# If we are still in the preamble, this might just be a multi lines preamble |
722
|
27
|
100
|
|
|
|
199
|
if( $elements->is_empty ) |
|
|
50
|
|
|
|
|
|
723
|
|
|
|
|
|
|
{ |
724
|
|
|
|
|
|
|
# $preamble->append( "$l\n" ); |
725
|
4
|
|
|
|
|
61
|
$preamble->append( $l ); |
726
|
|
|
|
|
|
|
} |
727
|
|
|
|
|
|
|
# Otherwise, this is a blank line, which separates elements |
728
|
|
|
|
|
|
|
elsif( defined( $release ) ) |
729
|
|
|
|
|
|
|
{ |
730
|
23
|
|
|
|
|
518
|
undef( $change ); |
731
|
23
|
|
|
|
|
56
|
undef( $group ); |
732
|
|
|
|
|
|
|
# We do not undef the latest release object, because we could have blank lines inside a release section |
733
|
|
|
|
|
|
|
# $release->changes->push( $self->new_line ); |
734
|
|
|
|
|
|
|
$nls->push( $self->new_line( |
735
|
|
|
|
|
|
|
line => ( $i + 1 ), |
736
|
|
|
|
|
|
|
( |
737
|
|
|
|
|
|
|
( defined( $re->{nl} ) && defined( $re->{space} ) ) |
738
|
23
|
50
|
33
|
|
|
408
|
? ( nl => ( $re->{space} // '' ) . ( $re->{nl} // '' ) ) |
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
739
|
|
|
|
|
|
|
: ( nl => undef ) |
740
|
|
|
|
|
|
|
), |
741
|
|
|
|
|
|
|
raw => $l, |
742
|
|
|
|
|
|
|
debug => $debug |
743
|
|
|
|
|
|
|
)); |
744
|
|
|
|
|
|
|
} |
745
|
|
|
|
|
|
|
else |
746
|
|
|
|
|
|
|
{ |
747
|
0
|
0
|
|
|
|
0
|
warn( "I found an empty line outside a release and no release object to associate it to.\n" ) if( $self->_warnings_is_enabled ); |
748
|
|
|
|
|
|
|
# $releases->push( $self->new_line ); |
749
|
0
|
|
|
|
|
0
|
$nls->push( $self->new_line( raw => $l, debug => $debug ) ); |
750
|
|
|
|
|
|
|
} |
751
|
|
|
|
|
|
|
} |
752
|
|
|
|
|
|
|
# Preamble |
753
|
|
|
|
|
|
|
elsif( $elements->is_empty ) |
754
|
|
|
|
|
|
|
{ |
755
|
4
|
|
|
|
|
930
|
$preamble->append( $l ); |
756
|
|
|
|
|
|
|
} |
757
|
|
|
|
|
|
|
# Epilogue |
758
|
|
|
|
|
|
|
# We found a line with no leading space with new blank lines before it and no epilogue yet, or maybe no blank lines, but with epilogue already set. |
759
|
|
|
|
|
|
|
elsif( $l =~ /^(\S+.*?)(?<nl>[\015\012]+)?$/ms && |
760
|
|
|
|
|
|
|
( |
761
|
|
|
|
|
|
|
( !$nls->is_empty && !defined( $epilogue ) ) || |
762
|
|
|
|
|
|
|
( defined( $epilogue ) && !defined( $release ) && !defined( $group ) && !defined( $change ) ) |
763
|
|
|
|
|
|
|
) && |
764
|
|
|
|
|
|
|
# If elements are empty this would rather be part of the preamble |
765
|
|
|
|
|
|
|
!$elements->is_empty ) |
766
|
|
|
|
|
|
|
{ |
767
|
1
|
|
|
|
|
93
|
my $re = { %+ }; |
768
|
1
|
50
|
|
|
|
5
|
if( !$nls->is_empty ) |
769
|
|
|
|
|
|
|
{ |
770
|
1
|
|
|
|
|
13
|
$elements->push( $nls->list ); |
771
|
1
|
|
|
|
|
16
|
$nls->reset; |
772
|
1
|
|
|
|
|
10
|
undef( $release ); |
773
|
1
|
|
|
|
|
2
|
undef( $change ); |
774
|
1
|
|
|
|
|
2
|
undef( $group ); |
775
|
1
|
|
|
|
|
9
|
$epilogue = $self->new_scalar( $l ); |
776
|
1
|
|
|
|
|
37
|
$self->epilogue( $epilogue ); |
777
|
|
|
|
|
|
|
} |
778
|
|
|
|
|
|
|
else |
779
|
|
|
|
|
|
|
{ |
780
|
0
|
|
|
|
|
0
|
$epilogue->append( $l ); |
781
|
|
|
|
|
|
|
} |
782
|
|
|
|
|
|
|
} |
783
|
|
|
|
|
|
|
else |
784
|
|
|
|
|
|
|
{ |
785
|
0
|
|
|
|
|
0
|
chomp( $l ); |
786
|
0
|
0
|
|
|
|
0
|
warn( "Found an unrecognisable line: '$l'\n" ) if( $self->_warnings_is_enabled ); |
787
|
|
|
|
|
|
|
} |
788
|
|
|
|
|
|
|
} |
789
|
20
|
|
|
|
|
34726
|
$self->elements( $elements ); |
790
|
20
|
|
|
|
|
19935
|
return( $self ); |
791
|
|
|
|
|
|
|
} |
792
|
|
|
|
|
|
|
|
793
|
|
|
|
|
|
|
sub preamble { return( shift->_set_get_scalar_as_object( { field => 'preamble', callbacks => |
794
|
|
|
|
|
|
|
{ |
795
|
|
|
|
|
|
|
set => sub |
796
|
|
|
|
|
|
|
{ |
797
|
4
|
|
|
4
|
|
8974
|
my( $self, $text ) = @_; |
798
|
4
|
50
|
33
|
|
|
40
|
if( defined( $text ) && $text->defined ) |
799
|
|
|
|
|
|
|
{ |
800
|
4
|
100
|
|
|
|
52
|
unless( $text =~ /[\015\012]$/ms ) |
801
|
|
|
|
|
|
|
{ |
802
|
1
|
|
50
|
|
|
21
|
$text->append( $self->nl // "\n" ); |
803
|
|
|
|
|
|
|
} |
804
|
4
|
100
|
|
|
|
1042
|
unless( $text =~ /[\015\012]{2,}$/ms ) |
805
|
|
|
|
|
|
|
{ |
806
|
1
|
|
50
|
|
|
10
|
$text->append( $self->nl // "\n" ); |
807
|
|
|
|
|
|
|
} |
808
|
|
|
|
|
|
|
} |
809
|
4
|
|
|
|
|
958
|
return( $text ); |
810
|
|
|
|
|
|
|
}, |
811
|
45
|
|
|
45
|
1
|
33066
|
} }, @_ ) ); } |
812
|
|
|
|
|
|
|
|
813
|
|
|
|
|
|
|
sub preset |
814
|
|
|
|
|
|
|
{ |
815
|
0
|
|
|
0
|
1
|
0
|
my $self = shift( @_ ); |
816
|
0
|
|
0
|
|
|
0
|
my $set = shift( @_ ) || return( $self->error( "No set name was provided." ) ); |
817
|
|
|
|
|
|
|
my $sets = |
818
|
|
|
|
|
|
|
{ |
819
|
|
|
|
|
|
|
standard => |
820
|
|
|
|
|
|
|
{ |
821
|
|
|
|
|
|
|
# for Changes::Release |
822
|
|
|
|
|
|
|
datetime_formatter => sub |
823
|
|
|
|
|
|
|
{ |
824
|
0
|
|
0
|
0
|
|
0
|
my $dt = shift( @_ ) || DateTime->now; |
825
|
0
|
|
|
|
|
0
|
require DateTime::Format::Strptime; |
826
|
0
|
|
|
|
|
0
|
my $fmt = DateTime::Format::Strptime->new( |
827
|
|
|
|
|
|
|
pattern => '%FT%T%z', |
828
|
|
|
|
|
|
|
locale => 'en_GB', |
829
|
|
|
|
|
|
|
); |
830
|
0
|
|
|
|
|
0
|
$dt->set_formatter( $fmt ); |
831
|
0
|
|
|
|
|
0
|
my $tz = $self->time_zone; |
832
|
0
|
0
|
|
|
|
0
|
$dt->set_time_zone( $tz ) if( $tz ); |
833
|
0
|
|
|
|
|
0
|
return( $dt ); |
834
|
|
|
|
|
|
|
}, |
835
|
|
|
|
|
|
|
# No need to provide it if it is just a space though, because it will default to it anyway |
836
|
0
|
|
|
|
|
0
|
spacer => ' ', |
837
|
|
|
|
|
|
|
# for Changes::Change |
838
|
|
|
|
|
|
|
spacer1 => "\t", |
839
|
|
|
|
|
|
|
spacer2 => ' ', |
840
|
|
|
|
|
|
|
marker => '-', |
841
|
|
|
|
|
|
|
max_width => 72, |
842
|
|
|
|
|
|
|
# wrapper => $code_reference, |
843
|
|
|
|
|
|
|
# for Changes::Group |
844
|
|
|
|
|
|
|
group_spacer => "\t", |
845
|
|
|
|
|
|
|
group_type => 'bracket', # [Some group] |
846
|
|
|
|
|
|
|
} |
847
|
|
|
|
|
|
|
}; |
848
|
0
|
0
|
|
|
|
0
|
return( $self->error( "Set requested ($set) is not supported." ) ) if( !exists( $sets->{ $set } ) ); |
849
|
0
|
|
|
|
|
0
|
my $def = $sets->{ $set }; |
850
|
0
|
|
|
|
|
0
|
$self->defaults( $def ); |
851
|
0
|
|
|
|
|
0
|
return( $self ); |
852
|
|
|
|
|
|
|
} |
853
|
|
|
|
|
|
|
|
854
|
|
|
|
|
|
|
sub releases |
855
|
|
|
|
|
|
|
{ |
856
|
62
|
|
|
62
|
1
|
927707
|
my $self = shift( @_ ); |
857
|
62
|
|
|
405
|
|
291
|
my $a = $self->elements->grep(sub{ $self->_is_a( $_ => 'Changes::Release' ) }); |
|
405
|
|
|
|
|
49551
|
|
858
|
62
|
|
|
|
|
6770
|
return( $a ); |
859
|
|
|
|
|
|
|
} |
860
|
|
|
|
|
|
|
|
861
|
0
|
|
|
0
|
1
|
0
|
sub remove_release { return( shift->delete_release( @_ ) ); } |
862
|
|
|
|
|
|
|
|
863
|
|
|
|
|
|
|
sub reset |
864
|
|
|
|
|
|
|
{ |
865
|
0
|
|
|
0
|
0
|
0
|
my $self = shift( @_ ); |
866
|
0
|
0
|
0
|
|
|
0
|
if( ( |
|
|
|
0
|
|
|
|
|
867
|
|
|
|
|
|
|
!exists( $self->{_reset} ) || |
868
|
|
|
|
|
|
|
!defined( $self->{_reset} ) || |
869
|
|
|
|
|
|
|
!CORE::length( $self->{_reset} ) |
870
|
|
|
|
|
|
|
) && scalar( @_ ) ) |
871
|
|
|
|
|
|
|
{ |
872
|
0
|
|
|
|
|
0
|
$self->{_reset} = scalar( @_ ); |
873
|
0
|
|
|
|
|
0
|
$self->{_reset_normalise} = 1; |
874
|
|
|
|
|
|
|
} |
875
|
0
|
|
|
|
|
0
|
return( $self ); |
876
|
|
|
|
|
|
|
} |
877
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
sub time_zone |
879
|
|
|
|
|
|
|
{ |
880
|
20
|
|
|
20
|
1
|
87
|
my $self = shift( @_ ); |
881
|
20
|
50
|
|
|
|
116
|
if( @_ ) |
882
|
|
|
|
|
|
|
{ |
883
|
0
|
|
|
|
|
0
|
my $v = shift( @_ ); |
884
|
0
|
0
|
|
|
|
0
|
if( $self->_is_a( $v => 'DateTime::TimeZone' ) ) |
885
|
|
|
|
|
|
|
{ |
886
|
0
|
|
|
|
|
0
|
$self->{time_zone} = $v; |
887
|
|
|
|
|
|
|
} |
888
|
|
|
|
|
|
|
else |
889
|
|
|
|
|
|
|
{ |
890
|
0
|
0
|
0
|
|
|
0
|
try |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
891
|
0
|
|
|
0
|
|
0
|
{ |
892
|
0
|
0
|
|
|
|
0
|
$self->_load_class( 'DateTime::TimeZone' ) || return( $self->pass_error ); |
893
|
0
|
|
|
|
|
0
|
my $tz = DateTime::TimeZone->new( name => "$v" ); |
894
|
0
|
|
|
|
|
0
|
$self->{time_zone} = $tz; |
895
|
|
|
|
|
|
|
} |
896
|
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
|
|
897
|
0
|
|
|
0
|
|
0
|
{ |
898
|
0
|
|
|
|
|
0
|
return( $self->error( "Error setting time zone for '$v': $e" ) ); |
899
|
18
|
0
|
0
|
18
|
|
153
|
} |
|
18
|
0
|
0
|
|
|
34
|
|
|
18
|
0
|
0
|
|
|
7931
|
|
|
0
|
0
|
0
|
|
|
0
|
|
|
0
|
0
|
0
|
|
|
0
|
|
|
0
|
0
|
0
|
|
|
0
|
|
|
0
|
0
|
0
|
|
|
0
|
|
|
0
|
0
|
0
|
|
|
0
|
|
|
0
|
0
|
0
|
|
|
0
|
|
|
0
|
0
|
0
|
|
|
0
|
|
|
0
|
0
|
0
|
|
|
0
|
|
|
0
|
0
|
0
|
|
|
0
|
|
|
0
|
0
|
0
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
900
|
|
|
|
|
|
|
} |
901
|
|
|
|
|
|
|
# $self->reset(1); |
902
|
|
|
|
|
|
|
} |
903
|
20
|
50
|
|
|
|
110
|
if( !defined( $self->{time_zone} ) ) |
904
|
|
|
|
|
|
|
{ |
905
|
20
|
50
|
|
|
|
97
|
if( Want::want( 'OBJECT' ) ) |
906
|
|
|
|
|
|
|
{ |
907
|
0
|
|
|
|
|
0
|
require Module::Generic::Null; |
908
|
0
|
|
|
|
|
0
|
rreturn( Module::Generic::Null->new( wants => 'OBJECT' ) ); |
909
|
|
|
|
|
|
|
} |
910
|
|
|
|
|
|
|
else |
911
|
|
|
|
|
|
|
{ |
912
|
20
|
|
|
|
|
1027
|
return; |
913
|
|
|
|
|
|
|
} |
914
|
|
|
|
|
|
|
} |
915
|
|
|
|
|
|
|
else |
916
|
|
|
|
|
|
|
{ |
917
|
0
|
|
|
|
|
0
|
return( $self->{time_zone} ); |
918
|
|
|
|
|
|
|
} |
919
|
|
|
|
|
|
|
} |
920
|
|
|
|
|
|
|
|
921
|
23
|
|
|
23
|
1
|
955
|
sub type { return( shift->_set_get_scalar_as_object( 'type', @_ ) ); } |
922
|
|
|
|
|
|
|
|
923
|
20
|
|
|
20
|
1
|
204
|
sub wrapper { return( shift->_set_get_code( 'wrapper', @_ ) ); } |
924
|
|
|
|
|
|
|
|
925
|
|
|
|
|
|
|
sub write |
926
|
|
|
|
|
|
|
{ |
927
|
1
|
|
|
1
|
1
|
1228
|
my $self = shift( @_ ); |
928
|
1
|
|
50
|
|
|
7
|
my $f = $self->file || |
929
|
|
|
|
|
|
|
return( $self->error( "No Changes file has been set to write to." ) ); |
930
|
1
|
|
|
|
|
1044
|
my $str = $self->as_string; |
931
|
1
|
50
|
|
|
|
54
|
return( $self->pass_error ) if( !defined( $str ) ); |
932
|
1
|
50
|
|
|
|
5
|
if( $str->is_empty ) |
933
|
|
|
|
|
|
|
{ |
934
|
0
|
0
|
|
|
|
0
|
warn( "Warning only: nothing to write to change file $f\n" ) if( $self->_warnings_is_enabled ); |
935
|
0
|
|
|
|
|
0
|
return( $self ); |
936
|
|
|
|
|
|
|
} |
937
|
1
|
|
50
|
|
|
29
|
my $fh = $f->open( '>', { binmode => 'utf-8', autoflush => 1 } ) || |
938
|
|
|
|
|
|
|
return( $self->pass_error( $f->error ) ); |
939
|
1
|
50
|
|
|
|
52146
|
$fh->print( $str->scalar ) || return( $self->pass_error( $fh->error ) ); |
940
|
1
|
|
|
|
|
287
|
$fh->close; |
941
|
1
|
|
|
|
|
198
|
return( $self ); |
942
|
|
|
|
|
|
|
} |
943
|
|
|
|
|
|
|
|
944
|
|
|
|
|
|
|
1; |
945
|
|
|
|
|
|
|
# NOTE: POD |
946
|
|
|
|
|
|
|
__END__ |
947
|
|
|
|
|
|
|
|
948
|
|
|
|
|
|
|
=encoding utf-8 |
949
|
|
|
|
|
|
|
|
950
|
|
|
|
|
|
|
=head1 NAME |
951
|
|
|
|
|
|
|
|
952
|
|
|
|
|
|
|
Changes - Changes file management |
953
|
|
|
|
|
|
|
|
954
|
|
|
|
|
|
|
=head1 SYMOPSIS |
955
|
|
|
|
|
|
|
|
956
|
|
|
|
|
|
|
use Changes; |
957
|
|
|
|
|
|
|
my $c = Changes->load( '/some/where/Changes', |
958
|
|
|
|
|
|
|
{ |
959
|
|
|
|
|
|
|
file => '/some/where/else/CHANGES', |
960
|
|
|
|
|
|
|
max_width => 78, |
961
|
|
|
|
|
|
|
type => 'cpan', |
962
|
|
|
|
|
|
|
debug => 4, |
963
|
|
|
|
|
|
|
}) || die( Changes->error ); |
964
|
|
|
|
|
|
|
say "Found ", $c->releases->length, " releases."; |
965
|
|
|
|
|
|
|
my $rel = $c->add_release( |
966
|
|
|
|
|
|
|
version => 'v0.1.1', |
967
|
|
|
|
|
|
|
# Accepts relative time |
968
|
|
|
|
|
|
|
datetime => '+1D', |
969
|
|
|
|
|
|
|
note => 'CPAN update', |
970
|
|
|
|
|
|
|
) || die( $c->error ); |
971
|
|
|
|
|
|
|
$rel->changes->push( $c->new_change( |
972
|
|
|
|
|
|
|
text => 'Minor corrections in unit tests', |
973
|
|
|
|
|
|
|
) ) || die( $rel->error ); |
974
|
|
|
|
|
|
|
# or |
975
|
|
|
|
|
|
|
my $change = $rel->add_change( text => 'Minor corrections in unit tests' ); |
976
|
|
|
|
|
|
|
$rel->delete_change( $change ); |
977
|
|
|
|
|
|
|
my $array_object = $c->delete_release( $rel ) || |
978
|
|
|
|
|
|
|
die( $c->error ); |
979
|
|
|
|
|
|
|
say sprintf( "%d releases removed.", $array_object->length ); |
980
|
|
|
|
|
|
|
# or $c->remove_release( $rel ); |
981
|
|
|
|
|
|
|
# Writing to /some/where/else/CHANGES even though we read from /some/where/Changes |
982
|
|
|
|
|
|
|
$c->write || die( $c->error ); |
983
|
|
|
|
|
|
|
|
984
|
|
|
|
|
|
|
=head1 VERSION |
985
|
|
|
|
|
|
|
|
986
|
|
|
|
|
|
|
v0.3.1 |
987
|
|
|
|
|
|
|
|
988
|
|
|
|
|
|
|
=head1 DESCRIPTION |
989
|
|
|
|
|
|
|
|
990
|
|
|
|
|
|
|
This module is designed to read and update C<Changes> files that are provided as part of change management in software distribution. |
991
|
|
|
|
|
|
|
|
992
|
|
|
|
|
|
|
It is not limited to CPAN, and is versatile and flexible giving you a lot of control. |
993
|
|
|
|
|
|
|
|
994
|
|
|
|
|
|
|
Its distinctive value compared to other modules that handle C<Changes> file is that it does not attempt to reformat release and change information if they have not been modified. This ensure not just speed, but also that existing formatting of C<Changes> file remain unchanged. You can force reformatting of any release section by calling L<Changes::Release/reset> |
995
|
|
|
|
|
|
|
|
996
|
|
|
|
|
|
|
This module does not L<perlfunc/die> upon error, but instead returns an L<error object|Module::Generic/error>, so you need to check for the return value when you call any methods in this package distribution. |
997
|
|
|
|
|
|
|
|
998
|
|
|
|
|
|
|
=head1 CONSTRUCTOR |
999
|
|
|
|
|
|
|
|
1000
|
|
|
|
|
|
|
=head2 new |
1001
|
|
|
|
|
|
|
|
1002
|
|
|
|
|
|
|
Provided with an optional hash or hash reference of properties-values pairs, and this will instantiate a new L<Changes> object and return it. |
1003
|
|
|
|
|
|
|
|
1004
|
|
|
|
|
|
|
Supported properties are the same as the methods listed below. |
1005
|
|
|
|
|
|
|
|
1006
|
|
|
|
|
|
|
If an error occurs, this will return an L<error|Module::Generic/error> |
1007
|
|
|
|
|
|
|
|
1008
|
|
|
|
|
|
|
=head2 load |
1009
|
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
Provided with a file path, and an optional hash or hash reference of parameters, and this will parse the C<Changes> file and return a new object. Thus, this method can be called either using an existing object, or as a class function: |
1011
|
|
|
|
|
|
|
|
1012
|
|
|
|
|
|
|
my $c2 = $c->load( '/some/where/Changes' ) || |
1013
|
|
|
|
|
|
|
die( $c->error ); |
1014
|
|
|
|
|
|
|
# or |
1015
|
|
|
|
|
|
|
my $c = Changes->load( '/some/where/Changes' ) || |
1016
|
|
|
|
|
|
|
die( Changes->error ); |
1017
|
|
|
|
|
|
|
|
1018
|
|
|
|
|
|
|
=head2 load_data |
1019
|
|
|
|
|
|
|
|
1020
|
|
|
|
|
|
|
Provided with some string and an optional hash or hash reference of parameters and this will parse the C<Changes> file data and return a new object. Thus, this method can be called either using an existing object, or as a class function: |
1021
|
|
|
|
|
|
|
|
1022
|
|
|
|
|
|
|
my $c2 = $c->load_data( $changes_data ) || |
1023
|
|
|
|
|
|
|
die( $c->error ); |
1024
|
|
|
|
|
|
|
# or |
1025
|
|
|
|
|
|
|
my $c = Change->load_data( $changes_data ) || |
1026
|
|
|
|
|
|
|
die( Changes->error ); |
1027
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
=head1 METHODS |
1029
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
=head2 add_epilogue |
1031
|
|
|
|
|
|
|
|
1032
|
|
|
|
|
|
|
Provided with a text and this will set it as the Changes file epilogue, i.e. an optional text that will appear at the end of the Changes file. |
1033
|
|
|
|
|
|
|
|
1034
|
|
|
|
|
|
|
If the last element is not a blank line to separate the epilogue from the last release information, then it will be added as necessary. |
1035
|
|
|
|
|
|
|
|
1036
|
|
|
|
|
|
|
It returns the current object upon success, or an L<error|Module::Generic/error> upon error. |
1037
|
|
|
|
|
|
|
|
1038
|
|
|
|
|
|
|
=head2 add_preamble |
1039
|
|
|
|
|
|
|
|
1040
|
|
|
|
|
|
|
Provided with a text and this will set it as the Changes file preamble. |
1041
|
|
|
|
|
|
|
|
1042
|
|
|
|
|
|
|
If the text does not have 2 blank new lines at the end, those will be added in order to separate the preamble from the first release line. |
1043
|
|
|
|
|
|
|
|
1044
|
|
|
|
|
|
|
It returns the current object upon success, or an L<error|Module::Generic/error> upon error. |
1045
|
|
|
|
|
|
|
|
1046
|
|
|
|
|
|
|
=head2 add_release |
1047
|
|
|
|
|
|
|
|
1048
|
|
|
|
|
|
|
This takes either an L<Changes::Release> or an hash or hash reference of options required to create one (for that refer to the L<Changes::Release> class), and returns the newly added release object. |
1049
|
|
|
|
|
|
|
|
1050
|
|
|
|
|
|
|
The new release object will be added on top of the elements stack with a blank new line separating it from the other releases. |
1051
|
|
|
|
|
|
|
|
1052
|
|
|
|
|
|
|
If the same object is found, or an object with the same version number is found, an error is returned, otherwise it returns the release object thus added. |
1053
|
|
|
|
|
|
|
|
1054
|
|
|
|
|
|
|
=head2 as_string |
1055
|
|
|
|
|
|
|
|
1056
|
|
|
|
|
|
|
Returns a L<string object|Module::Generic::Scalar> representing the entire C<Changes> file. It does so by getting the value set with L<preamble>, and by calling C<as_string> on each element stored in L</elements>. Those elements can be L<Changes::Release> and L<Changes::Group> and possibly L<Changes::Change> object. |
1057
|
|
|
|
|
|
|
|
1058
|
|
|
|
|
|
|
If an error occurred, it returns an L<error|Module::Generic/error> |
1059
|
|
|
|
|
|
|
|
1060
|
|
|
|
|
|
|
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. |
1061
|
|
|
|
|
|
|
|
1062
|
|
|
|
|
|
|
=head2 defaults |
1063
|
|
|
|
|
|
|
|
1064
|
|
|
|
|
|
|
Sets or gets an hash of default values for the L<Changes::Release> or L<Changes::Change> object when it is instantiated upon parsing with L</parse> or by the C<new_release> or C<new_change> method found in L<Changes>, L<Changes::Release> and L<Changes::Group> |
1065
|
|
|
|
|
|
|
|
1066
|
|
|
|
|
|
|
Default is C<undef>, which means no default value is set. |
1067
|
|
|
|
|
|
|
|
1068
|
|
|
|
|
|
|
my $ch = Changes->new( |
1069
|
|
|
|
|
|
|
file => '/some/where/Changes', |
1070
|
|
|
|
|
|
|
defaults => { |
1071
|
|
|
|
|
|
|
# for Changes::Release |
1072
|
|
|
|
|
|
|
datetime_formatter => sub |
1073
|
|
|
|
|
|
|
{ |
1074
|
|
|
|
|
|
|
my $dt = shift( @_ ) || DateTime->now; |
1075
|
|
|
|
|
|
|
require DateTime::Format::Strptime; |
1076
|
|
|
|
|
|
|
my $fmt = DateTime::Format::Strptime->new( |
1077
|
|
|
|
|
|
|
pattern => '%FT%T%z', |
1078
|
|
|
|
|
|
|
locale => 'en_GB', |
1079
|
|
|
|
|
|
|
); |
1080
|
|
|
|
|
|
|
$dt->set_formatter( $fmt ); |
1081
|
|
|
|
|
|
|
$dt->set_time_zone( 'Asia/Tokyo' ); |
1082
|
|
|
|
|
|
|
return( $dt ); |
1083
|
|
|
|
|
|
|
}, |
1084
|
|
|
|
|
|
|
# No need to provide it if it is just a space though, because it will default to it anyway |
1085
|
|
|
|
|
|
|
spacer => ' ', |
1086
|
|
|
|
|
|
|
# Not necessary if the custom datetime formatter has already set it |
1087
|
|
|
|
|
|
|
time_zone => 'Asia/Tokyo', |
1088
|
|
|
|
|
|
|
# for Changes::Change |
1089
|
|
|
|
|
|
|
spacer1 => "\t", |
1090
|
|
|
|
|
|
|
spacer2 => ' ', |
1091
|
|
|
|
|
|
|
marker => '-', |
1092
|
|
|
|
|
|
|
max_width => 72, |
1093
|
|
|
|
|
|
|
wrapper => $code_reference, |
1094
|
|
|
|
|
|
|
# for Changes::Group |
1095
|
|
|
|
|
|
|
group_spacer => "\t", |
1096
|
|
|
|
|
|
|
group_type => 'bracket', # [Some group] |
1097
|
|
|
|
|
|
|
} |
1098
|
|
|
|
|
|
|
); |
1099
|
|
|
|
|
|
|
|
1100
|
|
|
|
|
|
|
=head2 delete_release |
1101
|
|
|
|
|
|
|
|
1102
|
|
|
|
|
|
|
This takes a list of release to remove and returns an L<array object|Module::Generic::Array> of those releases thus removed. |
1103
|
|
|
|
|
|
|
|
1104
|
|
|
|
|
|
|
A release provided can either be a L<Changes::Release> object, or a version string. |
1105
|
|
|
|
|
|
|
|
1106
|
|
|
|
|
|
|
When removing a release object, it will also take care of removing following blank new lines that typically separate a release from the rest. |
1107
|
|
|
|
|
|
|
|
1108
|
|
|
|
|
|
|
If an error occurred, this will return an L<error|Module::Generic/error> |
1109
|
|
|
|
|
|
|
|
1110
|
|
|
|
|
|
|
=head2 elements |
1111
|
|
|
|
|
|
|
|
1112
|
|
|
|
|
|
|
Sets or gets an L<array object|Module::Generic::Array> of all the elements within the C<Changes> file. Those elements can be L<Changes::Release>, L<Changes::Group>, L<Changes::Change> and C<Changes::NewLine> objects. |
1113
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
=head2 epilogue |
1115
|
|
|
|
|
|
|
|
1116
|
|
|
|
|
|
|
Sets or gets the text of the epilogue. An epilogue is a chunk of text, possibly multi line, that appears at the bottom of the Changes file after the last release information, separated by a blank line. |
1117
|
|
|
|
|
|
|
|
1118
|
|
|
|
|
|
|
=head2 file |
1119
|
|
|
|
|
|
|
|
1120
|
|
|
|
|
|
|
my $file = $c->file; |
1121
|
|
|
|
|
|
|
$c->file( '/some/where/Changes' ); |
1122
|
|
|
|
|
|
|
|
1123
|
|
|
|
|
|
|
Sets or gets the file path of the Changes file. This returns a L<file object|Module::Generic::File> |
1124
|
|
|
|
|
|
|
|
1125
|
|
|
|
|
|
|
=for Pod::Coverage freeze |
1126
|
|
|
|
|
|
|
|
1127
|
|
|
|
|
|
|
=head2 history |
1128
|
|
|
|
|
|
|
|
1129
|
|
|
|
|
|
|
This is an alias for L</releases> and returns an L<array object|Module::Generic::Array> of L<Changes::Release> objects. |
1130
|
|
|
|
|
|
|
|
1131
|
|
|
|
|
|
|
=head2 max_width |
1132
|
|
|
|
|
|
|
|
1133
|
|
|
|
|
|
|
Sets or gets the maximum line width for a change inside a release. The line width includes an spaces at the beginning of the line and not just the text of the change itself. |
1134
|
|
|
|
|
|
|
|
1135
|
|
|
|
|
|
|
For example: |
1136
|
|
|
|
|
|
|
|
1137
|
|
|
|
|
|
|
v0.1.0 2022-11-17T08:12:42+0900 |
1138
|
|
|
|
|
|
|
- Some very long line of change going here, which can be wrapped here at 78 characters |
1139
|
|
|
|
|
|
|
|
1140
|
|
|
|
|
|
|
wrapped at 78 characters would become: |
1141
|
|
|
|
|
|
|
|
1142
|
|
|
|
|
|
|
v0.1.0 2022-11-17T08:12:42+0900 |
1143
|
|
|
|
|
|
|
- Some very long line of change going here, which can be wrapped here at |
1144
|
|
|
|
|
|
|
78 characters |
1145
|
|
|
|
|
|
|
|
1146
|
|
|
|
|
|
|
=head2 new_change |
1147
|
|
|
|
|
|
|
|
1148
|
|
|
|
|
|
|
Returns a new L<Changes::Change> object, passing it any parameters provided. |
1149
|
|
|
|
|
|
|
|
1150
|
|
|
|
|
|
|
If an error occurred, it returns an L<error object|Module::Generic/error> |
1151
|
|
|
|
|
|
|
|
1152
|
|
|
|
|
|
|
=head2 new_group |
1153
|
|
|
|
|
|
|
|
1154
|
|
|
|
|
|
|
Returns a new L<Changes::Group> object, passing it any parameters provided. |
1155
|
|
|
|
|
|
|
|
1156
|
|
|
|
|
|
|
If an error occurred, it returns an L<error object|Module::Generic/error> |
1157
|
|
|
|
|
|
|
|
1158
|
|
|
|
|
|
|
=head2 new_line |
1159
|
|
|
|
|
|
|
|
1160
|
|
|
|
|
|
|
Returns a new C<Changes::NewLine> object, passing it any parameters provided. |
1161
|
|
|
|
|
|
|
|
1162
|
|
|
|
|
|
|
If an error occurred, it returns an L<error object|Module::Generic/error> |
1163
|
|
|
|
|
|
|
|
1164
|
|
|
|
|
|
|
=head2 new_release |
1165
|
|
|
|
|
|
|
|
1166
|
|
|
|
|
|
|
Returns a new L<Changes::Release> object, passing it any parameters provided. |
1167
|
|
|
|
|
|
|
|
1168
|
|
|
|
|
|
|
If an error occurred, it returns an L<error object|Module::Generic/error> |
1169
|
|
|
|
|
|
|
|
1170
|
|
|
|
|
|
|
=head2 new_version |
1171
|
|
|
|
|
|
|
|
1172
|
|
|
|
|
|
|
Returns a new C<Changes::Version> object, passing it any parameters provided. |
1173
|
|
|
|
|
|
|
|
1174
|
|
|
|
|
|
|
If an error occurred, it returns an L<error object|Module::Generic/error> |
1175
|
|
|
|
|
|
|
|
1176
|
|
|
|
|
|
|
=head2 nl |
1177
|
|
|
|
|
|
|
|
1178
|
|
|
|
|
|
|
Sets or gets the new line character, which defaults to C<\n> |
1179
|
|
|
|
|
|
|
|
1180
|
|
|
|
|
|
|
It returns a L<number object|Module::Generic::Number> |
1181
|
|
|
|
|
|
|
|
1182
|
|
|
|
|
|
|
=head2 parse |
1183
|
|
|
|
|
|
|
|
1184
|
|
|
|
|
|
|
Provided with an array reference of lines to parse and this will parse each line and create all necessary L<release|Changes::Release>, L<group|Changes::Group> and L<change|Changes::Change> objects. |
1185
|
|
|
|
|
|
|
|
1186
|
|
|
|
|
|
|
It returns the current object it was called with upon success, and returns an L<error|Module::Generic/error> upon error. |
1187
|
|
|
|
|
|
|
|
1188
|
|
|
|
|
|
|
=head2 preamble |
1189
|
|
|
|
|
|
|
|
1190
|
|
|
|
|
|
|
Sets or gets the text of the preamble. A preamble is a chunk of text, possibly multi line, that appears at the top of the Changes file before any release information. |
1191
|
|
|
|
|
|
|
|
1192
|
|
|
|
|
|
|
=head2 preset |
1193
|
|
|
|
|
|
|
|
1194
|
|
|
|
|
|
|
Provided with a preset name, and this will set all its defaults. |
1195
|
|
|
|
|
|
|
|
1196
|
|
|
|
|
|
|
Currently, the only preset supported is C<standard> |
1197
|
|
|
|
|
|
|
|
1198
|
|
|
|
|
|
|
Returns the current object upon success, or sets an L<error object|Module::Generic/error> and return C<undef> or empty list, depending on the context, otherwise. |
1199
|
|
|
|
|
|
|
|
1200
|
|
|
|
|
|
|
=head2 releases |
1201
|
|
|
|
|
|
|
|
1202
|
|
|
|
|
|
|
Read only. This returns an L<array object|Module::Generic::Array> containing all the L<release objects|Changes::Release> within the Changes file. |
1203
|
|
|
|
|
|
|
|
1204
|
|
|
|
|
|
|
=head2 remove_release |
1205
|
|
|
|
|
|
|
|
1206
|
|
|
|
|
|
|
This is an alias for L</delete_release> |
1207
|
|
|
|
|
|
|
|
1208
|
|
|
|
|
|
|
=head2 serialise |
1209
|
|
|
|
|
|
|
|
1210
|
|
|
|
|
|
|
This is an alias for L</as_string> |
1211
|
|
|
|
|
|
|
|
1212
|
|
|
|
|
|
|
=head2 serialize |
1213
|
|
|
|
|
|
|
|
1214
|
|
|
|
|
|
|
This is an alias for L</as_string> |
1215
|
|
|
|
|
|
|
|
1216
|
|
|
|
|
|
|
=head2 time_zone |
1217
|
|
|
|
|
|
|
|
1218
|
|
|
|
|
|
|
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. |
1219
|
|
|
|
|
|
|
|
1220
|
|
|
|
|
|
|
If set, it will be passed to all new L<Changes::Release> object upon parsing with L</parse> |
1221
|
|
|
|
|
|
|
|
1222
|
|
|
|
|
|
|
It returns a L<DateTime::TimeZone> object upon success, or an L<error|Module::Generic/error> if an error occurred. |
1223
|
|
|
|
|
|
|
|
1224
|
|
|
|
|
|
|
=head2 type |
1225
|
|
|
|
|
|
|
|
1226
|
|
|
|
|
|
|
Sets or get the type of C<Changes> file format this is. |
1227
|
|
|
|
|
|
|
|
1228
|
|
|
|
|
|
|
=head2 wrapper |
1229
|
|
|
|
|
|
|
|
1230
|
|
|
|
|
|
|
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> by L<Changes::Change>. |
1231
|
|
|
|
|
|
|
|
1232
|
|
|
|
|
|
|
If set, this is passed by L</parse> when creating L<Changes::Change> objects. |
1233
|
|
|
|
|
|
|
|
1234
|
|
|
|
|
|
|
See L<Changes::Change/as_string> for more information. |
1235
|
|
|
|
|
|
|
|
1236
|
|
|
|
|
|
|
=head2 write |
1237
|
|
|
|
|
|
|
|
1238
|
|
|
|
|
|
|
This will open the file set with L</file> in write clobbering mode and print out the result from L</as_string>. |
1239
|
|
|
|
|
|
|
|
1240
|
|
|
|
|
|
|
It returns the current object upon success, and an L<error|Module::Generic/error> if an error occurred. |
1241
|
|
|
|
|
|
|
|
1242
|
|
|
|
|
|
|
=head1 AUTHOR |
1243
|
|
|
|
|
|
|
|
1244
|
|
|
|
|
|
|
Jacques Deguest E<lt>F<jack@deguest.jp>E<gt> |
1245
|
|
|
|
|
|
|
|
1246
|
|
|
|
|
|
|
=head1 SEE ALSO |
1247
|
|
|
|
|
|
|
|
1248
|
|
|
|
|
|
|
L<Changes::Release>, L<Changes::Group>, L<Changes::Change>, L<Changes::Version>, L<Changes::NewLine> |
1249
|
|
|
|
|
|
|
|
1250
|
|
|
|
|
|
|
=head1 COPYRIGHT & LICENSE |
1251
|
|
|
|
|
|
|
|
1252
|
|
|
|
|
|
|
Copyright(c) 2022 DEGUEST Pte. Ltd. |
1253
|
|
|
|
|
|
|
|
1254
|
|
|
|
|
|
|
All rights reserved |
1255
|
|
|
|
|
|
|
|
1256
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. |
1257
|
|
|
|
|
|
|
|
1258
|
|
|
|
|
|
|
=cut |