line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Module::Metadata::Changes; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
52332
|
use strict; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
22
|
|
4
|
1
|
|
|
1
|
|
3
|
use warnings; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
23
|
|
5
|
|
|
|
|
|
|
|
6
|
1
|
|
|
1
|
|
571
|
use Config::IniFiles; |
|
1
|
|
|
|
|
22842
|
|
|
1
|
|
|
|
|
32
|
|
7
|
|
|
|
|
|
|
|
8
|
1
|
|
|
1
|
|
463
|
use DateTime::Format::W3CDTF; |
|
1
|
|
|
|
|
302784
|
|
|
1
|
|
|
|
|
36
|
|
9
|
|
|
|
|
|
|
|
10
|
1
|
|
|
1
|
|
467
|
use File::Slurper 'read_lines'; |
|
1
|
|
|
|
|
9830
|
|
|
1
|
|
|
|
|
46
|
|
11
|
|
|
|
|
|
|
|
12
|
1
|
|
|
1
|
|
415
|
use HTML::Entities::Interpolate; |
|
1
|
|
|
|
|
4819
|
|
|
1
|
|
|
|
|
4
|
|
13
|
1
|
|
|
1
|
|
832
|
use HTML::Template; |
|
1
|
|
|
|
|
9226
|
|
|
1
|
|
|
|
|
30
|
|
14
|
|
|
|
|
|
|
|
15
|
1
|
|
|
1
|
|
478
|
use Moo; |
|
1
|
|
|
|
|
6881
|
|
|
1
|
|
|
|
|
3
|
|
16
|
|
|
|
|
|
|
|
17
|
1
|
|
|
1
|
|
955
|
use Try::Tiny; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
46
|
|
18
|
|
|
|
|
|
|
|
19
|
1
|
|
|
1
|
|
449
|
use Types::Standard qw/Any ArrayRef Bool Str/; |
|
1
|
|
|
|
|
51755
|
|
|
1
|
|
|
|
|
10
|
|
20
|
|
|
|
|
|
|
|
21
|
1
|
|
|
1
|
|
1260
|
use version; |
|
1
|
|
|
|
|
1573
|
|
|
1
|
|
|
|
|
5
|
|
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
has changes => |
24
|
|
|
|
|
|
|
( |
25
|
|
|
|
|
|
|
default => sub{return []}, |
26
|
|
|
|
|
|
|
is => 'rw', |
27
|
|
|
|
|
|
|
isa => ArrayRef, |
28
|
|
|
|
|
|
|
required => 0, |
29
|
|
|
|
|
|
|
); |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
has config => |
32
|
|
|
|
|
|
|
( |
33
|
|
|
|
|
|
|
default => sub{return ''}, |
34
|
|
|
|
|
|
|
is => 'rw', |
35
|
|
|
|
|
|
|
isa => Any, |
36
|
|
|
|
|
|
|
required => 0, |
37
|
|
|
|
|
|
|
); |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
has convert => |
40
|
|
|
|
|
|
|
( |
41
|
|
|
|
|
|
|
default => sub{return 0}, |
42
|
|
|
|
|
|
|
is => 'rw', |
43
|
|
|
|
|
|
|
isa => Bool, |
44
|
|
|
|
|
|
|
required => 0, |
45
|
|
|
|
|
|
|
); |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
has errstr => |
48
|
|
|
|
|
|
|
( |
49
|
|
|
|
|
|
|
default => sub{return ''}, |
50
|
|
|
|
|
|
|
is => 'rw', |
51
|
|
|
|
|
|
|
isa => Str, |
52
|
|
|
|
|
|
|
required => 0, |
53
|
|
|
|
|
|
|
); |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
has inFileName => |
56
|
|
|
|
|
|
|
( |
57
|
|
|
|
|
|
|
default => sub{return ''}, |
58
|
|
|
|
|
|
|
is => 'rw', |
59
|
|
|
|
|
|
|
isa => Str, |
60
|
|
|
|
|
|
|
required => 0, |
61
|
|
|
|
|
|
|
); |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
has module_name => |
64
|
|
|
|
|
|
|
( |
65
|
|
|
|
|
|
|
default => sub{return ''}, |
66
|
|
|
|
|
|
|
is => 'rw', |
67
|
|
|
|
|
|
|
isa => Str, |
68
|
|
|
|
|
|
|
required => 0, |
69
|
|
|
|
|
|
|
); |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
has outFileName => |
72
|
|
|
|
|
|
|
( |
73
|
|
|
|
|
|
|
default => sub{return 'Changelog.ini'}, |
74
|
|
|
|
|
|
|
is => 'rw', |
75
|
|
|
|
|
|
|
isa => Str, |
76
|
|
|
|
|
|
|
required => 0, |
77
|
|
|
|
|
|
|
); |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
has pathForHTML => |
80
|
|
|
|
|
|
|
( |
81
|
|
|
|
|
|
|
default => sub{return '/dev/run/html/assets/templates/module/metadata/changes'}, |
82
|
|
|
|
|
|
|
is => 'rw', |
83
|
|
|
|
|
|
|
isa => Str, |
84
|
|
|
|
|
|
|
required => 0, |
85
|
|
|
|
|
|
|
); |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
has release => |
88
|
|
|
|
|
|
|
( |
89
|
|
|
|
|
|
|
default => sub{return ''}, |
90
|
|
|
|
|
|
|
is => 'rw', |
91
|
|
|
|
|
|
|
isa => Str, |
92
|
|
|
|
|
|
|
required => 0, |
93
|
|
|
|
|
|
|
); |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
has table => |
96
|
|
|
|
|
|
|
( |
97
|
|
|
|
|
|
|
default => sub{return 0}, |
98
|
|
|
|
|
|
|
is => 'rw', |
99
|
|
|
|
|
|
|
isa => Bool, |
100
|
|
|
|
|
|
|
required => 0, |
101
|
|
|
|
|
|
|
); |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
has urlForCSS => |
104
|
|
|
|
|
|
|
( |
105
|
|
|
|
|
|
|
default => sub{return '/assets/css/module/metadata/changes/ini.css'}, |
106
|
|
|
|
|
|
|
is => 'rw', |
107
|
|
|
|
|
|
|
isa => Str, |
108
|
|
|
|
|
|
|
required => 0, |
109
|
|
|
|
|
|
|
); |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
has verbose => |
112
|
|
|
|
|
|
|
( |
113
|
|
|
|
|
|
|
default => sub{return 0}, |
114
|
|
|
|
|
|
|
is => 'rw', |
115
|
|
|
|
|
|
|
isa => Bool, |
116
|
|
|
|
|
|
|
required => 0, |
117
|
|
|
|
|
|
|
); |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
has webPage => |
120
|
|
|
|
|
|
|
( |
121
|
|
|
|
|
|
|
default => sub{return 0}, |
122
|
|
|
|
|
|
|
is => 'rw', |
123
|
|
|
|
|
|
|
isa => Bool, |
124
|
|
|
|
|
|
|
required => 0, |
125
|
|
|
|
|
|
|
); |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
our $VERSION = '2.12'; |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
# ----------------------------------------------- |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
sub BUILD |
132
|
|
|
|
|
|
|
{ |
133
|
1
|
|
|
1
|
0
|
14
|
my($self) = @_; |
134
|
|
|
|
|
|
|
|
135
|
1
|
50
|
|
|
|
14
|
if ($self -> webPage) |
136
|
|
|
|
|
|
|
{ |
137
|
0
|
|
|
|
|
0
|
$self -> table(1); |
138
|
|
|
|
|
|
|
} |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
} # End of BUILD. |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
# ------------------------------------------------ |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
sub get_latest_release |
145
|
|
|
|
|
|
|
{ |
146
|
1
|
|
|
1
|
1
|
644
|
my($self) = @_; |
147
|
1
|
|
|
|
|
22
|
my(@release) = $self -> config -> GroupMembers('V'); |
148
|
|
|
|
|
|
|
|
149
|
1
|
|
|
|
|
30
|
my(@output); |
150
|
|
|
|
|
|
|
my($release); |
151
|
0
|
|
|
|
|
0
|
my($version); |
152
|
|
|
|
|
|
|
|
153
|
1
|
|
|
|
|
2
|
for $release (@release) |
154
|
|
|
|
|
|
|
{ |
155
|
29
|
|
|
|
|
49
|
($version = $release) =~ s/^V //; |
156
|
|
|
|
|
|
|
|
157
|
29
|
|
|
|
|
85
|
push @output, version -> new($version); |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
1
|
|
|
|
|
4
|
@output = reverse sort{$a cmp $b} @output; |
|
28
|
|
|
|
|
43
|
|
161
|
|
|
|
|
|
|
|
162
|
1
|
|
|
|
|
3
|
my($result) = {}; |
163
|
|
|
|
|
|
|
|
164
|
1
|
50
|
|
|
|
3
|
if ($#output >= 0) |
165
|
|
|
|
|
|
|
{ |
166
|
1
|
|
|
|
|
5
|
my($section) = "V $output[0]"; |
167
|
|
|
|
|
|
|
|
168
|
1
|
|
|
|
|
1
|
my($token); |
169
|
|
|
|
|
|
|
|
170
|
1
|
|
|
|
|
18
|
for $token ($self -> config -> Parameters($section) ) |
171
|
|
|
|
|
|
|
{ |
172
|
2
|
|
|
|
|
64
|
$$result{$token} = $self -> config -> val($section, $token); |
173
|
|
|
|
|
|
|
} |
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
|
176
|
1
|
|
|
|
|
30
|
return $result; |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
} # End of get_latest_release. |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
# ------------------------------------------------ |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
sub get_latest_version |
183
|
|
|
|
|
|
|
{ |
184
|
1
|
|
|
1
|
1
|
6
|
my($self) = @_; |
185
|
1
|
|
|
|
|
15
|
my(@release) = $self -> config -> GroupMembers('V'); |
186
|
|
|
|
|
|
|
|
187
|
1
|
|
|
|
|
18
|
my(@output); |
188
|
|
|
|
|
|
|
my($release); |
189
|
0
|
|
|
|
|
0
|
my($version); |
190
|
|
|
|
|
|
|
|
191
|
1
|
|
|
|
|
2
|
for $release (@release) |
192
|
|
|
|
|
|
|
{ |
193
|
29
|
|
|
|
|
41
|
($version = $release) =~ s/^V //; |
194
|
|
|
|
|
|
|
|
195
|
29
|
|
|
|
|
76
|
push @output, version -> new($version); |
196
|
|
|
|
|
|
|
} |
197
|
|
|
|
|
|
|
|
198
|
1
|
|
|
|
|
3
|
@output = reverse sort{$a cmp $b} @output; |
|
28
|
|
|
|
|
42
|
|
199
|
|
|
|
|
|
|
|
200
|
1
|
50
|
|
|
|
16
|
return $#output >= 0 ? $output[0] : ''; |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
} # End of get_latest_version. |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
# ----------------------------------------------- |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
sub log |
207
|
|
|
|
|
|
|
{ |
208
|
34
|
|
|
34
|
0
|
425
|
my($self, $s) = @_; |
209
|
34
|
|
50
|
|
|
88
|
$s ||= ''; |
210
|
|
|
|
|
|
|
|
211
|
34
|
50
|
|
|
|
748
|
if ($self -> verbose) |
212
|
|
|
|
|
|
|
{ |
213
|
0
|
|
|
|
|
0
|
print STDERR "$s\n"; |
214
|
|
|
|
|
|
|
} |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
} # End of log. |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
# ----------------------------------------------- |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
sub parse_datetime |
221
|
|
|
|
|
|
|
{ |
222
|
242
|
|
|
242
|
1
|
285
|
my($self, $candidate) = @_; |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
# One of the modules DateTime::Format::HTTP or DateTime::Format::Strptime |
225
|
|
|
|
|
|
|
# can return 'No input string', so we use it as well. |
226
|
|
|
|
|
|
|
|
227
|
242
|
50
|
|
|
|
584
|
if (length($candidate) == 0) |
228
|
|
|
|
|
|
|
{ |
229
|
0
|
|
|
|
|
0
|
return 'No input string'; |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
|
232
|
242
|
|
|
|
|
308
|
my($date) = $self -> parse_datetime_1($candidate); |
233
|
|
|
|
|
|
|
|
234
|
242
|
50
|
|
|
|
469
|
if ($date eq 'Could not parse date') |
235
|
|
|
|
|
|
|
{ |
236
|
242
|
|
|
|
|
349
|
$date = $self -> parse_datetime_2('%A%n%B%n%d%n%Y', $candidate); |
237
|
|
|
|
|
|
|
|
238
|
242
|
100
|
|
|
|
38671
|
if ($date eq 'Could not parse date') |
239
|
|
|
|
|
|
|
{ |
240
|
215
|
|
|
|
|
767
|
$candidate =~ s/(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)\s*//; |
241
|
215
|
|
|
|
|
347
|
$date = $self -> parse_datetime_2('%B%n%d%n%Y', $candidate); |
242
|
|
|
|
|
|
|
} |
243
|
|
|
|
|
|
|
} |
244
|
|
|
|
|
|
|
|
245
|
242
|
|
33
|
|
|
14214
|
return $@ || $date; |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
} # End of parse_datetime. |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
# ----------------------------------------------- |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
sub parse_datetime_1 |
252
|
|
|
|
|
|
|
{ |
253
|
242
|
|
|
242
|
1
|
225
|
my($self, $candidate) = @_; |
254
|
|
|
|
|
|
|
|
255
|
242
|
|
|
|
|
200
|
my($date); |
256
|
|
|
|
|
|
|
|
257
|
242
|
|
|
|
|
1483
|
require 'DateTime/Format/HTTP.pm'; |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
try |
260
|
|
|
|
|
|
|
{ |
261
|
242
|
|
|
242
|
|
5726
|
$date = DateTime::Format::HTTP -> parse_datetime($candidate); |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
catch |
264
|
|
|
|
|
|
|
{ |
265
|
242
|
|
|
242
|
|
8522
|
$date = 'Could not parse date'; |
266
|
242
|
|
|
|
|
4825
|
}; |
267
|
|
|
|
|
|
|
|
268
|
242
|
|
|
|
|
999
|
return $date; |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
} # End of parse_datetime_1. |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
# ----------------------------------------------- |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
sub parse_datetime_2 |
275
|
|
|
|
|
|
|
{ |
276
|
457
|
|
|
457
|
1
|
566
|
my($self, $pattern, $candidate) = @_; |
277
|
457
|
|
|
|
|
1024
|
$candidate =~ s/([0-9]+)(st|nd|rd|th)/$1/; # Zap st from 1st, etc. |
278
|
|
|
|
|
|
|
|
279
|
457
|
|
|
|
|
2324
|
require 'DateTime/Format/Strptime.pm'; |
280
|
|
|
|
|
|
|
|
281
|
457
|
|
|
|
|
39368
|
my($parser) = DateTime::Format::Strptime -> new(pattern => $pattern); |
282
|
|
|
|
|
|
|
|
283
|
457
|
|
100
|
|
|
398861
|
return $parser -> parse_datetime($candidate) || 'Could not parse date'; |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
} # End of parse_datetime_2. |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
# ----------------------------------------------- |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
sub read |
290
|
|
|
|
|
|
|
{ |
291
|
0
|
|
|
0
|
1
|
0
|
my($self, $in_file_name) = @_; |
292
|
0
|
|
0
|
|
|
0
|
$in_file_name ||= $self -> inFileName || 'Changelog.ini'; |
|
|
|
0
|
|
|
|
|
293
|
|
|
|
|
|
|
|
294
|
0
|
|
|
|
|
0
|
$self -> config(Config::IniFiles -> new(-file => $in_file_name) ); |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
# Return object for method chaining. |
297
|
|
|
|
|
|
|
|
298
|
0
|
|
|
|
|
0
|
return $self -> validate($in_file_name); |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
} # End of read. |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
# ----------------------------------------------- |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
sub reader |
305
|
|
|
|
|
|
|
{ |
306
|
1
|
|
|
1
|
1
|
5
|
my($self, $in_file_name) = @_; |
307
|
1
|
|
0
|
|
|
2
|
$in_file_name ||= $self -> inFileName || (-e 'Changes' ? 'Changes' : 'CHANGES'); |
|
|
|
33
|
|
|
|
|
308
|
1
|
|
|
|
|
7
|
my(@line) = read_lines $in_file_name; |
309
|
|
|
|
|
|
|
|
310
|
1
|
|
|
|
|
410
|
$self -> log("Input file: $in_file_name"); |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
# Get module name from the first line. |
313
|
|
|
|
|
|
|
# 1st guess at format: /Revision history for Perl extension Local::Wine./. |
314
|
|
|
|
|
|
|
|
315
|
1
|
|
|
|
|
9
|
my($line) = shift @line; |
316
|
1
|
|
|
|
|
10
|
$line =~ s/\s+$//; |
317
|
1
|
|
|
|
|
4
|
$line =~ s/\s*\.\s*$//; |
318
|
1
|
|
|
|
|
10
|
my(@field) = split(/\s+/, $line); |
319
|
1
|
|
|
|
|
4
|
my($module_name) = $field[$#field]; |
320
|
1
|
50
|
|
|
|
6
|
my($ok) = $module_name ? 1 : 0; |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
# 2nd guess at format: X::Y somewhere in the first line. This overrides the first guess. |
323
|
|
|
|
|
|
|
|
324
|
1
|
50
|
|
|
|
5
|
if (! $ok) |
325
|
|
|
|
|
|
|
{ |
326
|
0
|
|
|
|
|
0
|
@field = split(/\s+/, $line); |
327
|
|
|
|
|
|
|
|
328
|
0
|
|
|
|
|
0
|
my($field); |
329
|
|
|
|
|
|
|
|
330
|
0
|
|
|
|
|
0
|
for $field (@field) |
331
|
|
|
|
|
|
|
{ |
332
|
0
|
0
|
|
|
|
0
|
if ($field =~ /^.+::.+$/) |
333
|
|
|
|
|
|
|
{ |
334
|
0
|
|
|
|
|
0
|
$module_name = $field; |
335
|
|
|
|
|
|
|
|
336
|
0
|
|
|
|
|
0
|
last; |
337
|
|
|
|
|
|
|
} |
338
|
|
|
|
|
|
|
} |
339
|
|
|
|
|
|
|
} |
340
|
|
|
|
|
|
|
|
341
|
1
|
|
|
|
|
25
|
$self -> module_name($module_name); |
342
|
1
|
|
|
|
|
26
|
$self -> log("Module: $module_name"); |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
# Return object for method chaining. |
345
|
|
|
|
|
|
|
|
346
|
1
|
|
|
|
|
9
|
return $self -> transform(@line); |
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
} # End of reader. |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
# ----------------------------------------------- |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
sub report |
353
|
|
|
|
|
|
|
{ |
354
|
0
|
|
|
0
|
1
|
0
|
my($self) = @_; |
355
|
0
|
|
|
|
|
0
|
my($module_name) = $self -> config -> val('Module', 'Name'); |
356
|
0
|
|
|
|
|
0
|
my($width) = 15; |
357
|
|
|
|
|
|
|
|
358
|
0
|
|
|
|
|
0
|
my(@output); |
359
|
|
|
|
|
|
|
|
360
|
0
|
|
|
|
|
0
|
push @output, ['Module', $module_name]; |
361
|
0
|
|
|
|
|
0
|
push @output, ['-' x $width, '-' x $width]; |
362
|
|
|
|
|
|
|
|
363
|
0
|
|
|
|
|
0
|
my($found) = 0; |
364
|
0
|
|
|
|
|
0
|
my(@release) = $self -> config -> GroupMembers('V'); |
365
|
|
|
|
|
|
|
|
366
|
0
|
|
|
|
|
0
|
my($date, $deploy_action, $deploy_reason); |
367
|
0
|
|
|
|
|
0
|
my($release); |
368
|
0
|
|
|
|
|
0
|
my($version); |
369
|
|
|
|
|
|
|
|
370
|
0
|
|
|
|
|
0
|
for $release (@release) |
371
|
|
|
|
|
|
|
{ |
372
|
0
|
|
|
|
|
0
|
($version = $release) =~ s/^V //; |
373
|
|
|
|
|
|
|
|
374
|
0
|
0
|
0
|
|
|
0
|
next if ($self -> release && ($version ne $self -> release) ); |
375
|
|
|
|
|
|
|
|
376
|
0
|
|
|
|
|
0
|
$date = $self -> config -> val($release, 'Date'); |
377
|
0
|
|
|
|
|
0
|
$deploy_action = $self -> config -> val($release, 'Deploy.Action'); |
378
|
0
|
|
|
|
|
0
|
$deploy_reason = $self -> config -> val($release, 'Deploy.Reason'); |
379
|
0
|
|
|
|
|
0
|
$found = 1; |
380
|
|
|
|
|
|
|
|
381
|
0
|
|
|
|
|
0
|
push @output, ['Version', $version]; |
382
|
0
|
|
|
|
|
0
|
push @output, ['Date', $date]; |
383
|
|
|
|
|
|
|
|
384
|
0
|
0
|
|
|
|
0
|
if ($deploy_action) |
385
|
|
|
|
|
|
|
{ |
386
|
0
|
|
|
|
|
0
|
push @output, ['Deploy.Action', $deploy_action]; |
387
|
0
|
|
|
|
|
0
|
push @output, ['Deploy.Reason', $deploy_reason]; |
388
|
|
|
|
|
|
|
} |
389
|
|
|
|
|
|
|
|
390
|
0
|
|
|
|
|
0
|
push @output, ['-' x $width, '-' x $width]; |
391
|
|
|
|
|
|
|
} |
392
|
|
|
|
|
|
|
|
393
|
0
|
0
|
|
|
|
0
|
if (! $found) |
394
|
|
|
|
|
|
|
{ |
395
|
0
|
|
|
|
|
0
|
push @output, ['Warning', "V @{[$self -> release]} not found"]; |
|
0
|
|
|
|
|
0
|
|
396
|
|
|
|
|
|
|
} |
397
|
|
|
|
|
|
|
|
398
|
0
|
0
|
|
|
|
0
|
if ($self -> table) |
399
|
|
|
|
|
|
|
{ |
400
|
0
|
|
|
|
|
0
|
$self -> report_as_html(@output); |
401
|
|
|
|
|
|
|
} |
402
|
|
|
|
|
|
|
else |
403
|
|
|
|
|
|
|
{ |
404
|
|
|
|
|
|
|
# Report as text. |
405
|
|
|
|
|
|
|
|
406
|
0
|
|
|
|
|
0
|
for (@output) |
407
|
|
|
|
|
|
|
{ |
408
|
0
|
|
|
|
|
0
|
printf "%-${width}s %s\n", $$_[0], $$_[1]; |
409
|
|
|
|
|
|
|
} |
410
|
|
|
|
|
|
|
} |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
} # End of report. |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
# ----------------------------------------------- |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
sub report_as_html |
417
|
|
|
|
|
|
|
{ |
418
|
0
|
|
|
0
|
1
|
0
|
my($self, @output) = @_; |
419
|
0
|
|
|
|
|
0
|
my($template) = HTML::Template -> new(path => $self -> pathForHTML, filename => 'ini.table.tmpl'); |
420
|
|
|
|
|
|
|
@output = map |
421
|
|
|
|
|
|
|
{ |
422
|
0
|
|
|
|
|
0
|
{ |
423
|
|
|
|
|
|
|
th => $Entitize{$$_[0]}, |
424
|
0
|
0
|
|
|
|
0
|
td => $Entitize{$$_[1]}, |
425
|
|
|
|
|
|
|
td_class => $$_[0] =~ /Deploy/ ? 'ini_deploy' : 'ini_td', |
426
|
|
|
|
|
|
|
} |
427
|
|
|
|
|
|
|
} @output; |
428
|
|
|
|
|
|
|
|
429
|
0
|
|
|
|
|
0
|
$template -> param(tr_loop => [@output]); |
430
|
|
|
|
|
|
|
|
431
|
0
|
|
|
|
|
0
|
my($content) = $template -> output(); |
432
|
|
|
|
|
|
|
|
433
|
0
|
0
|
|
|
|
0
|
if ($self -> webPage) |
434
|
|
|
|
|
|
|
{ |
435
|
0
|
|
|
|
|
0
|
$template = HTML::Template -> new(path => $self -> pathForHTML, filename => 'ini.page.tmpl'); |
436
|
|
|
|
|
|
|
|
437
|
0
|
|
|
|
|
0
|
$template -> param(content => $content); |
438
|
0
|
|
|
|
|
0
|
$template -> param(url_for_css => $self -> urlForCSS); |
439
|
|
|
|
|
|
|
|
440
|
0
|
|
|
|
|
0
|
$content = $template -> output(); |
441
|
|
|
|
|
|
|
} |
442
|
|
|
|
|
|
|
|
443
|
0
|
|
|
|
|
0
|
print $content; |
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
} # End of report_as_html. |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
# ----------------------------------------------- |
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
sub run |
450
|
|
|
|
|
|
|
{ |
451
|
1
|
|
|
1
|
1
|
706
|
my($self) = @_; |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
# If converting, inFileName is the name of an old-style Changes/CHANGES file, |
454
|
|
|
|
|
|
|
# and outFileName is the name of a new-style Changelog.ini file. |
455
|
|
|
|
|
|
|
# If reporting on a specific release, inFileName is the name of |
456
|
|
|
|
|
|
|
# a new-style Changelog.ini file. |
457
|
|
|
|
|
|
|
|
458
|
1
|
50
|
|
|
|
15
|
if ($self -> convert) |
459
|
|
|
|
|
|
|
{ |
460
|
1
|
50
|
|
|
|
16
|
$self -> inFileName('Changes') if (! $self -> inFileName); |
461
|
1
|
|
|
|
|
18
|
$self -> reader($self -> inFileName) -> writer($self -> outFileName); |
462
|
|
|
|
|
|
|
} |
463
|
|
|
|
|
|
|
else |
464
|
|
|
|
|
|
|
{ |
465
|
0
|
0
|
|
|
|
0
|
$self -> inFileName('Changelog.ini') if (! $self -> inFileName); |
466
|
0
|
|
|
|
|
0
|
$self -> read($self -> inFileName); |
467
|
0
|
|
|
|
|
0
|
$self -> report; |
468
|
|
|
|
|
|
|
} |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
# Return 0 for success in case someone wants to know. |
471
|
|
|
|
|
|
|
|
472
|
1
|
|
|
|
|
5
|
return 0; |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
} # End of run. |
475
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
# ----------------------------------------------- |
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
sub transform |
479
|
|
|
|
|
|
|
{ |
480
|
1
|
|
|
1
|
1
|
26
|
my($self, @line) = @_; |
481
|
1
|
|
|
|
|
2
|
my($count) = 0; |
482
|
|
|
|
|
|
|
|
483
|
1
|
|
|
|
|
2
|
my($current_version, $current_date, @comment); |
484
|
0
|
|
|
|
|
0
|
my($date); |
485
|
0
|
|
|
|
|
0
|
my(@field); |
486
|
0
|
|
|
|
|
0
|
my($line); |
487
|
0
|
|
|
|
|
0
|
my($release, @release); |
488
|
0
|
|
|
|
|
0
|
my($version); |
489
|
|
|
|
|
|
|
|
490
|
1
|
|
|
|
|
2
|
for $line (@line) |
491
|
|
|
|
|
|
|
{ |
492
|
322
|
|
|
|
|
284
|
$count++; |
493
|
|
|
|
|
|
|
|
494
|
322
|
|
|
|
|
1178
|
$line =~ s/^\s+//; |
495
|
322
|
|
|
|
|
1304
|
$line =~ s/\s+$//; |
496
|
|
|
|
|
|
|
|
497
|
322
|
100
|
|
|
|
750
|
next if (length($line) == 0); |
498
|
253
|
50
|
|
|
|
456
|
next if ($line =~ /^#/); |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
# Try to get the version number and date. |
501
|
|
|
|
|
|
|
# Each release is expected to start with one of: |
502
|
|
|
|
|
|
|
# o 1.05 Fri Jan 25 10:08:00 2008 |
503
|
|
|
|
|
|
|
# o 4.30 - Friday, April 25, 2008 |
504
|
|
|
|
|
|
|
# o 4.08 - Thursday, March 15th, 2006 |
505
|
|
|
|
|
|
|
# Squash spaces. |
506
|
|
|
|
|
|
|
|
507
|
253
|
|
|
|
|
703
|
$line =~ tr/ / /s; |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
# Remove commas (from dates) if the line starts with a digit (which is assumed to be a version #). |
510
|
|
|
|
|
|
|
|
511
|
253
|
100
|
|
|
|
698
|
$line =~ s/,//g if ($line =~ /^v?\d/); |
512
|
253
|
|
|
|
|
904
|
@field = split(/\s(?:-\s)?/, $line, 2); |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
# The "" keeps version happy. |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
try |
517
|
|
|
|
|
|
|
{ |
518
|
253
|
|
|
253
|
|
6199
|
$version = version -> new("$field[0]"); |
519
|
253
|
|
|
|
|
1445
|
}; |
520
|
|
|
|
|
|
|
|
521
|
253
|
100
|
|
|
|
2847
|
$date = defined $field[1] ? $self -> parse_datetime($field[1]) : 'No input string'; |
522
|
|
|
|
|
|
|
|
523
|
253
|
100
|
66
|
|
|
3364
|
if (! defined $version || ($version eq '0') || ($date eq 'Could not parse date') || ($date =~ /No input string/) ) |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
524
|
|
|
|
|
|
|
{ |
525
|
|
|
|
|
|
|
# We got an error. So assume it's commentary on the current release. |
526
|
|
|
|
|
|
|
# If the line starts with EOT, jam a '-' in front of it to escape it, |
527
|
|
|
|
|
|
|
# since Config::IniFiles uses EOT to terminate multi-line comments. |
528
|
|
|
|
|
|
|
|
529
|
223
|
50
|
|
|
|
684
|
$line = "-$line" if (substr($line, 0, 3) eq 'EOT'); |
530
|
|
|
|
|
|
|
|
531
|
223
|
|
|
|
|
639
|
push @comment, $line; |
532
|
|
|
|
|
|
|
} |
533
|
|
|
|
|
|
|
else |
534
|
|
|
|
|
|
|
{ |
535
|
|
|
|
|
|
|
# We got a version and a date. Assume it's a new release. |
536
|
|
|
|
|
|
|
# Step 1: Wrap up the last version, if any. |
537
|
|
|
|
|
|
|
|
538
|
30
|
50
|
33
|
|
|
1213
|
if ($version && $date) |
539
|
|
|
|
|
|
|
{ |
540
|
30
|
|
|
|
|
1038
|
$self -> log("Processing: V $version $date"); |
541
|
|
|
|
|
|
|
} |
542
|
|
|
|
|
|
|
|
543
|
30
|
100
|
|
|
|
411
|
if ($current_version) |
544
|
|
|
|
|
|
|
{ |
545
|
29
|
|
|
|
|
140
|
$release = {Version => $current_version, Date => $current_date, Comments => [@comment]}; |
546
|
|
|
|
|
|
|
|
547
|
29
|
|
|
|
|
48
|
push @release, $release; |
548
|
|
|
|
|
|
|
} |
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
# Step 2: Start the new version. |
551
|
|
|
|
|
|
|
|
552
|
30
|
100
|
100
|
|
|
310
|
if ($current_version && ($version eq $current_version) ) |
553
|
|
|
|
|
|
|
{ |
554
|
1
|
|
|
|
|
5
|
$self -> errstr("V $version found with dates $current_date and $date"); |
555
|
|
|
|
|
|
|
|
556
|
1
|
|
|
|
|
73
|
$self -> log($self -> errstr); |
557
|
|
|
|
|
|
|
} |
558
|
|
|
|
|
|
|
|
559
|
30
|
|
|
|
|
86
|
@comment = (); |
560
|
30
|
|
|
|
|
28
|
$current_date = $date; |
561
|
30
|
|
|
|
|
61
|
$current_version = $version; |
562
|
|
|
|
|
|
|
} |
563
|
|
|
|
|
|
|
} |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
# Step 3: Wrap up the last version, if any. |
566
|
|
|
|
|
|
|
|
567
|
1
|
50
|
|
|
|
10
|
if ($current_version) |
568
|
|
|
|
|
|
|
{ |
569
|
1
|
|
|
|
|
10
|
$release = {Version => $current_version, Date => $current_date, Comments => [@comment]}; |
570
|
|
|
|
|
|
|
|
571
|
1
|
|
|
|
|
2
|
push @release, $release; |
572
|
|
|
|
|
|
|
} |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
# Scan the releases looking for security advisories. |
575
|
|
|
|
|
|
|
|
576
|
1
|
|
|
|
|
3
|
my($security); |
577
|
|
|
|
|
|
|
|
578
|
1
|
|
|
|
|
4
|
for $release (0 .. $#release) |
579
|
|
|
|
|
|
|
{ |
580
|
30
|
|
|
|
|
19
|
$security = 0; |
581
|
|
|
|
|
|
|
|
582
|
30
|
|
|
|
|
17
|
for $line (@{$release[$release]{'Comments'} }) |
|
30
|
|
|
|
|
44
|
|
583
|
|
|
|
|
|
|
{ |
584
|
204
|
100
|
|
|
|
584
|
if ($line =~ /Security/i) |
585
|
|
|
|
|
|
|
{ |
586
|
5
|
|
|
|
|
4
|
$security = 1; |
587
|
|
|
|
|
|
|
|
588
|
5
|
|
|
|
|
4
|
last; |
589
|
|
|
|
|
|
|
} |
590
|
|
|
|
|
|
|
} |
591
|
|
|
|
|
|
|
|
592
|
30
|
100
|
|
|
|
36
|
if ($security) |
593
|
|
|
|
|
|
|
{ |
594
|
5
|
|
|
|
|
6
|
$release[$release]{'Deploy.Action'} = 'Upgrade'; |
595
|
5
|
|
|
|
|
6
|
$release[$release]{'Deploy.Reason'} = 'Security'; |
596
|
|
|
|
|
|
|
} |
597
|
|
|
|
|
|
|
} |
598
|
|
|
|
|
|
|
|
599
|
1
|
|
|
|
|
31
|
$self -> changes([@release]); |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
# Return object for method chaining. |
602
|
|
|
|
|
|
|
|
603
|
1
|
|
|
|
|
131
|
return $self; |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
} # End of transform. |
606
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
# ----------------------------------------------- |
608
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
sub validate |
610
|
|
|
|
|
|
|
{ |
611
|
0
|
|
|
0
|
1
|
0
|
my($self, $in_file_name) = @_; |
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
# Validate existence of Module section. |
614
|
|
|
|
|
|
|
|
615
|
0
|
0
|
|
|
|
0
|
if (! $self -> config -> SectionExists('Module') ) |
616
|
|
|
|
|
|
|
{ |
617
|
0
|
|
|
|
|
0
|
die "Error: Section 'Module' is missing from $in_file_name"; |
618
|
|
|
|
|
|
|
} |
619
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
# Validate existence of Name within Module section. |
621
|
|
|
|
|
|
|
|
622
|
0
|
|
|
|
|
0
|
my($module_name) = $self -> config -> val('Module', 'Name'); |
623
|
|
|
|
|
|
|
|
624
|
0
|
0
|
|
|
|
0
|
if (! defined $module_name) |
625
|
|
|
|
|
|
|
{ |
626
|
0
|
|
|
|
|
0
|
die "Error: Section 'Module' is missing a 'Name' token in $in_file_name"; |
627
|
|
|
|
|
|
|
} |
628
|
|
|
|
|
|
|
|
629
|
|
|
|
|
|
|
# Validate existence of Releases. |
630
|
|
|
|
|
|
|
|
631
|
0
|
|
|
|
|
0
|
my(@release) = $self -> config -> GroupMembers('V'); |
632
|
|
|
|
|
|
|
|
633
|
0
|
0
|
|
|
|
0
|
if ($#release < 0) |
634
|
|
|
|
|
|
|
{ |
635
|
0
|
|
|
|
|
0
|
die "Error: No releases (sections like [V \$version]) found in $in_file_name"; |
636
|
|
|
|
|
|
|
} |
637
|
|
|
|
|
|
|
|
638
|
0
|
|
|
|
|
0
|
my($parser) = DateTime::Format::W3CDTF -> new; |
639
|
|
|
|
|
|
|
|
640
|
0
|
|
|
|
|
0
|
my($candidate); |
641
|
|
|
|
|
|
|
my($date); |
642
|
0
|
|
|
|
|
0
|
my($release); |
643
|
0
|
|
|
|
|
0
|
my($version); |
644
|
|
|
|
|
|
|
|
645
|
0
|
|
|
|
|
0
|
for $release (@release) |
646
|
|
|
|
|
|
|
{ |
647
|
0
|
|
|
|
|
0
|
($version = $release) =~ s/^V //; |
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
# Validate Date within each Release. |
650
|
|
|
|
|
|
|
|
651
|
0
|
|
|
|
|
0
|
$candidate = $self -> config -> val($release, 'Date'); |
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
try |
654
|
|
|
|
|
|
|
{ |
655
|
0
|
|
|
0
|
|
0
|
$date = $parser -> parse_datetime($candidate); |
656
|
|
|
|
|
|
|
} |
657
|
|
|
|
|
|
|
catch |
658
|
|
|
|
|
|
|
{ |
659
|
0
|
|
|
0
|
|
0
|
die "Error: Date $candidate is not in W3CDTF format"; |
660
|
|
|
|
|
|
|
} |
661
|
0
|
|
|
|
|
0
|
} |
662
|
|
|
|
|
|
|
|
663
|
0
|
|
|
|
|
0
|
$self -> log("Successful validation of file: $in_file_name"); |
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
# Return object for method chaining. |
666
|
|
|
|
|
|
|
|
667
|
0
|
|
|
|
|
0
|
return $self; |
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
} # End of validate. |
670
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
# ----------------------------------------------- |
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
sub writer |
674
|
|
|
|
|
|
|
{ |
675
|
1
|
|
|
1
|
1
|
9
|
my($self, $output_file_name) = @_; |
676
|
1
|
|
0
|
|
|
3
|
$output_file_name ||= $self -> outFileName || 'Changelog.ini'; |
|
|
|
33
|
|
|
|
|
677
|
|
|
|
|
|
|
|
678
|
1
|
|
|
|
|
11
|
$self -> config(Config::IniFiles -> new); |
679
|
1
|
|
|
|
|
145
|
$self -> config -> AddSection('Module'); |
680
|
1
|
|
|
|
|
72
|
$self -> config -> newval('Module', 'Name', $self -> module_name); |
681
|
1
|
|
|
|
|
92
|
$self -> config -> newval('Module', 'Changelog.Creator', __PACKAGE__ . " V $VERSION"); |
682
|
1
|
|
|
|
|
55
|
$self -> config -> newval('Module', 'Changelog.Parser', "Config::IniFiles V $Config::IniFiles::VERSION"); |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
# Sort by version number to put the latest version at the top of the file. |
685
|
|
|
|
|
|
|
|
686
|
1
|
|
|
|
|
40
|
my($section); |
687
|
|
|
|
|
|
|
|
688
|
1
|
|
|
|
|
2
|
for my $r (reverse sort{$$a{'Version'} cmp $$b{'Version'} } @{$self -> changes}) |
|
69
|
|
|
|
|
137
|
|
|
1
|
|
|
|
|
43
|
|
689
|
|
|
|
|
|
|
{ |
690
|
30
|
|
|
|
|
1150
|
$section = "V $$r{'Version'}"; |
691
|
|
|
|
|
|
|
|
692
|
30
|
|
|
|
|
411
|
$self -> config -> AddSection($section); |
693
|
30
|
|
|
|
|
1735
|
$self -> config -> newval($section, 'Date', $$r{'Date'}); |
694
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
# Put these near the top of this release's notes. |
696
|
|
|
|
|
|
|
|
697
|
30
|
100
|
|
|
|
1192
|
if ($$r{'Deploy.Action'}) |
698
|
|
|
|
|
|
|
{ |
699
|
5
|
|
|
|
|
68
|
$self -> config -> newval($section, 'Deploy.Action', $$r{'Deploy.Action'}); |
700
|
5
|
|
50
|
|
|
261
|
$self -> config -> newval($section, 'Deploy.Reason', $$r{'Deploy.Reason'} || ''); |
701
|
|
|
|
|
|
|
} |
702
|
|
|
|
|
|
|
|
703
|
30
|
|
|
|
|
595
|
$self -> config -> newval($section, 'Comments', @{$$r{'Comments'} }); |
|
30
|
|
|
|
|
103
|
|
704
|
|
|
|
|
|
|
} |
705
|
|
|
|
|
|
|
|
706
|
1
|
|
|
|
|
51
|
$self -> config -> WriteConfig($output_file_name); |
707
|
|
|
|
|
|
|
|
708
|
1
|
|
|
|
|
4246
|
$self -> log("Output file: $output_file_name"); |
709
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
# Return object for method chaining. |
711
|
|
|
|
|
|
|
|
712
|
1
|
|
|
|
|
7
|
return $self; |
713
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
} # End of writer. |
715
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
# ----------------------------------------------- |
717
|
|
|
|
|
|
|
|
718
|
|
|
|
|
|
|
1; |
719
|
|
|
|
|
|
|
|
720
|
|
|
|
|
|
|
=head1 NAME |
721
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
Module::Metadata::Changes - Manage machine-readable Changes/CHANGES/Changelog.ini files |
723
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
=head1 Synopsis |
725
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
=head2 One-liners |
727
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
These examples use Changes/CHANGES and Changelog.ini in the 'current' directory. |
729
|
|
|
|
|
|
|
|
730
|
|
|
|
|
|
|
The command line options (except for -h) correspond to the options documented under L</Constructor and initialization>, below. |
731
|
|
|
|
|
|
|
|
732
|
|
|
|
|
|
|
shell>ini.report.pl -h |
733
|
|
|
|
|
|
|
shell>ini.report.pl -c |
734
|
|
|
|
|
|
|
shell>ini.report.pl -r 1.23 |
735
|
|
|
|
|
|
|
shell>sudo ini.report.pl -w > /var/www/Changelog.html |
736
|
|
|
|
|
|
|
shell>perl -MModule::Metadata::Changes -e 'Module::Metadata::Changes->new(convert => 1)->run' |
737
|
|
|
|
|
|
|
shell>perl -MModule::Metadata::Changes -e 'print Module::Metadata::Changes->new->read->get_latest_version' |
738
|
|
|
|
|
|
|
shell>perl -MModule::Metadata::Changes -e 'print Module::Metadata::Changes->new->read->report' |
739
|
|
|
|
|
|
|
shell>perl -MModule::Metadata::Changes -e 'print Module::Metadata::Changes->new(release=>"2.00")->read->report' |
740
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
L<Module::Metadata::Changes> ships with C<ini.report.pl> in the bin/ directory. It is installed along with the module. |
742
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
Also, L<Module::Metadata::Changes> uses L<Config::IniFiles> to read and write Changelog.ini files. |
744
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
=head2 Reporters |
746
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
With a script like this: |
748
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
#!/usr/bin/env perl |
750
|
|
|
|
|
|
|
|
751
|
|
|
|
|
|
|
use feature 'say'; |
752
|
|
|
|
|
|
|
use strict; |
753
|
|
|
|
|
|
|
use warnings; |
754
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
use File::chdir; # For magic $CWD. |
756
|
|
|
|
|
|
|
|
757
|
|
|
|
|
|
|
use Module::Metadata::Changes; |
758
|
|
|
|
|
|
|
|
759
|
|
|
|
|
|
|
# ------------------------------------------------ |
760
|
|
|
|
|
|
|
|
761
|
|
|
|
|
|
|
my($work) = "$ENV{HOME}/perl.modules"; |
762
|
|
|
|
|
|
|
my($m) = Module::Metadata::Changes -> new; |
763
|
|
|
|
|
|
|
|
764
|
|
|
|
|
|
|
opendir(INX, $work) || die "Can't opendir($work)"; |
765
|
|
|
|
|
|
|
my(@name) = sort grep{! /^\.\.?$/} readdir INX; |
766
|
|
|
|
|
|
|
closedir INX; |
767
|
|
|
|
|
|
|
|
768
|
|
|
|
|
|
|
my($config); |
769
|
|
|
|
|
|
|
my($version); |
770
|
|
|
|
|
|
|
|
771
|
|
|
|
|
|
|
for my $name (@name) |
772
|
|
|
|
|
|
|
{ |
773
|
|
|
|
|
|
|
$CWD = "$work/$name"; # Does a chdir. |
774
|
|
|
|
|
|
|
$version = $m -> read -> get_latest_version; |
775
|
|
|
|
|
|
|
$config = $m -> config; # Must call read() before config(). |
776
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
say $config -> val('Module', 'Name'), " V $version ", $config -> val("V $version", 'Date'); |
778
|
|
|
|
|
|
|
} |
779
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
you can get a report of the latest version number, from Changelog.ini, for each module in your vast library. |
781
|
|
|
|
|
|
|
|
782
|
|
|
|
|
|
|
=head1 Description |
783
|
|
|
|
|
|
|
|
784
|
|
|
|
|
|
|
L<Module::Metadata::Changes> is a pure Perl module. |
785
|
|
|
|
|
|
|
|
786
|
|
|
|
|
|
|
It allows you to convert old-style Changes/CHANGES files, and to read and write Changelog.ini files. |
787
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
=head1 Distributions |
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
This module is available as a Unix-style distro (*.tgz). |
791
|
|
|
|
|
|
|
|
792
|
|
|
|
|
|
|
See http://savage.net.au/Perl-modules.html for details. |
793
|
|
|
|
|
|
|
|
794
|
|
|
|
|
|
|
See http://savage.net.au/Perl-modules/html/installing-a-module.html for |
795
|
|
|
|
|
|
|
help on unpacking and installing. |
796
|
|
|
|
|
|
|
|
797
|
|
|
|
|
|
|
=head1 Constructor and initialization |
798
|
|
|
|
|
|
|
|
799
|
|
|
|
|
|
|
new(...) returns an object of type L<Module::Metadata::Changes>. |
800
|
|
|
|
|
|
|
|
801
|
|
|
|
|
|
|
This is the class contructor. |
802
|
|
|
|
|
|
|
|
803
|
|
|
|
|
|
|
Usage: C<< Module::Metadata::Changes -> new() >>. |
804
|
|
|
|
|
|
|
|
805
|
|
|
|
|
|
|
This method takes a hash of options. There are no mandatory options. |
806
|
|
|
|
|
|
|
|
807
|
|
|
|
|
|
|
Call C<new()> as C<< new(option_1 => value_1, option_2 => value_2, ...) >>. |
808
|
|
|
|
|
|
|
|
809
|
|
|
|
|
|
|
Available options: |
810
|
|
|
|
|
|
|
|
811
|
|
|
|
|
|
|
=over 4 |
812
|
|
|
|
|
|
|
|
813
|
|
|
|
|
|
|
=item o convert |
814
|
|
|
|
|
|
|
|
815
|
|
|
|
|
|
|
This takes the value 0 or 1. |
816
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
The default is 0. |
818
|
|
|
|
|
|
|
|
819
|
|
|
|
|
|
|
If the value is 0, calling C<run()> calls C<read()> and C<report()>. |
820
|
|
|
|
|
|
|
|
821
|
|
|
|
|
|
|
If the value is 1, calling C<run()> calls C<writer(reader() )>. |
822
|
|
|
|
|
|
|
|
823
|
|
|
|
|
|
|
=item o inFileName |
824
|
|
|
|
|
|
|
|
825
|
|
|
|
|
|
|
The default is 'Changes' (or, if absent, 'CHANGES') when calling C<reader()>, and |
826
|
|
|
|
|
|
|
'Changelog.ini' when calling C<read()>. |
827
|
|
|
|
|
|
|
|
828
|
|
|
|
|
|
|
=item o outFileName |
829
|
|
|
|
|
|
|
|
830
|
|
|
|
|
|
|
The default is 'Changelog.ini'. |
831
|
|
|
|
|
|
|
|
832
|
|
|
|
|
|
|
=item o pathForHTML |
833
|
|
|
|
|
|
|
|
834
|
|
|
|
|
|
|
This is path to the HTML::Template-style templates used by the 'table' and 'webPage' options. |
835
|
|
|
|
|
|
|
|
836
|
|
|
|
|
|
|
The default is '/dev/shm/html/assets/templates/module/metadata/changes'. |
837
|
|
|
|
|
|
|
|
838
|
|
|
|
|
|
|
=item o release |
839
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
The default is ''. |
841
|
|
|
|
|
|
|
|
842
|
|
|
|
|
|
|
If this option has a non-empty value, the value is assumed to be a release/version number. |
843
|
|
|
|
|
|
|
|
844
|
|
|
|
|
|
|
In that case, reports (text, HTML) are restricted to only the given version. |
845
|
|
|
|
|
|
|
|
846
|
|
|
|
|
|
|
The default ('') means reports contain all versions. |
847
|
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
'release' was chosen, rather than 'version', in order to avoid a clash with 'verbose', |
849
|
|
|
|
|
|
|
since all options could then be abbreviated to 1 letter (when running ini.report.pl). |
850
|
|
|
|
|
|
|
|
851
|
|
|
|
|
|
|
Also, a lot of other software uses -r to refer to release/version. |
852
|
|
|
|
|
|
|
|
853
|
|
|
|
|
|
|
=item o table |
854
|
|
|
|
|
|
|
|
855
|
|
|
|
|
|
|
This takes the value 0 or 1. |
856
|
|
|
|
|
|
|
|
857
|
|
|
|
|
|
|
The default is 0. |
858
|
|
|
|
|
|
|
|
859
|
|
|
|
|
|
|
This option is only used when C<report()> is called. |
860
|
|
|
|
|
|
|
|
861
|
|
|
|
|
|
|
If the value is 0, calling C<report()> outputs a text report. |
862
|
|
|
|
|
|
|
|
863
|
|
|
|
|
|
|
If the value is 1, calling C<report()> outputs a HTML report. |
864
|
|
|
|
|
|
|
|
865
|
|
|
|
|
|
|
By default, the HTML report will just be a HTML table. |
866
|
|
|
|
|
|
|
|
867
|
|
|
|
|
|
|
However, if the 'webPage' option is 1, the HTML will be a complete web page. |
868
|
|
|
|
|
|
|
|
869
|
|
|
|
|
|
|
=item o urlForCSS |
870
|
|
|
|
|
|
|
|
871
|
|
|
|
|
|
|
The default is '/assets/css/module/metadata/changes/ini.css'. |
872
|
|
|
|
|
|
|
|
873
|
|
|
|
|
|
|
This is only used if the 'webPage' option is 1. |
874
|
|
|
|
|
|
|
|
875
|
|
|
|
|
|
|
=item o verbose |
876
|
|
|
|
|
|
|
|
877
|
|
|
|
|
|
|
This takes the value 0 or 1. |
878
|
|
|
|
|
|
|
|
879
|
|
|
|
|
|
|
The default is 0. |
880
|
|
|
|
|
|
|
|
881
|
|
|
|
|
|
|
If the value is 1, write progress reports to STDERR. |
882
|
|
|
|
|
|
|
|
883
|
|
|
|
|
|
|
=item o webPage |
884
|
|
|
|
|
|
|
|
885
|
|
|
|
|
|
|
This takes the value 0 or 1. |
886
|
|
|
|
|
|
|
|
887
|
|
|
|
|
|
|
The default is 0. |
888
|
|
|
|
|
|
|
|
889
|
|
|
|
|
|
|
A value of 1 automatically sets 'table' to 1. |
890
|
|
|
|
|
|
|
|
891
|
|
|
|
|
|
|
If the value is 0, the 'table' option outputs just a HTML table. |
892
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
If the value is 1, the 'table' option outputs a complete web page. |
894
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
=back |
896
|
|
|
|
|
|
|
|
897
|
|
|
|
|
|
|
=head1 Methods |
898
|
|
|
|
|
|
|
|
899
|
|
|
|
|
|
|
=head2 o config() |
900
|
|
|
|
|
|
|
|
901
|
|
|
|
|
|
|
Returns the L<Config::IniFiles> object, from which you can extract all the data. |
902
|
|
|
|
|
|
|
|
903
|
|
|
|
|
|
|
This method I<must> be called after calling C<read()>. |
904
|
|
|
|
|
|
|
|
905
|
|
|
|
|
|
|
See C<scripts/report.names.pl> for sample code. |
906
|
|
|
|
|
|
|
|
907
|
|
|
|
|
|
|
The names of the sections, [Module] and [V 1.23], and the keys under each, are documented in the FAQ. |
908
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
=head2 o errstr() |
910
|
|
|
|
|
|
|
|
911
|
|
|
|
|
|
|
Returns the last error message, or ''. |
912
|
|
|
|
|
|
|
|
913
|
|
|
|
|
|
|
=head2 o get_latest_release() |
914
|
|
|
|
|
|
|
|
915
|
|
|
|
|
|
|
Returns an hash ref of details for the latest release. |
916
|
|
|
|
|
|
|
|
917
|
|
|
|
|
|
|
Returns {} if there is no such release. |
918
|
|
|
|
|
|
|
|
919
|
|
|
|
|
|
|
The hash keys are (most of) the reserved tokens, as discussed below in the FAQ. |
920
|
|
|
|
|
|
|
|
921
|
|
|
|
|
|
|
Some reserved tokens, such as EOT, make no sense as hash keys. |
922
|
|
|
|
|
|
|
|
923
|
|
|
|
|
|
|
=head2 o get_latest_version() |
924
|
|
|
|
|
|
|
|
925
|
|
|
|
|
|
|
Returns the version number of the latest version. |
926
|
|
|
|
|
|
|
|
927
|
|
|
|
|
|
|
Returns '' if there is no such version. |
928
|
|
|
|
|
|
|
|
929
|
|
|
|
|
|
|
=head2 o parse_datetime() |
930
|
|
|
|
|
|
|
|
931
|
|
|
|
|
|
|
Used by C<transform()>. |
932
|
|
|
|
|
|
|
|
933
|
|
|
|
|
|
|
=head2 o parse_datetime_1() |
934
|
|
|
|
|
|
|
|
935
|
|
|
|
|
|
|
Used by C<transform()>. |
936
|
|
|
|
|
|
|
|
937
|
|
|
|
|
|
|
=head2 o parse_datetime_2() |
938
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
Used by C<transform()>. |
940
|
|
|
|
|
|
|
|
941
|
|
|
|
|
|
|
=head2 o read([$input_file_name]) |
942
|
|
|
|
|
|
|
|
943
|
|
|
|
|
|
|
This method reads the given file, using L<Config::IniFiles>. |
944
|
|
|
|
|
|
|
|
945
|
|
|
|
|
|
|
The $input_file_name is optional. It defaults to 'Changelog.ini'. |
946
|
|
|
|
|
|
|
|
947
|
|
|
|
|
|
|
See config(). |
948
|
|
|
|
|
|
|
|
949
|
|
|
|
|
|
|
Return value: The object, for method chaining. |
950
|
|
|
|
|
|
|
|
951
|
|
|
|
|
|
|
=head2 o reader([$input_file_name]) |
952
|
|
|
|
|
|
|
|
953
|
|
|
|
|
|
|
This method parses the given file, assuming it is format is the common-or-garden Changes/CHANGES style. |
954
|
|
|
|
|
|
|
|
955
|
|
|
|
|
|
|
The $input_file_name is optional. It defaults to 'Changes' (or, if absent, 'CHANGES'). |
956
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
C<reader()> calls C<module_name()> to save the module name for use by other methods. |
958
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
C<reader()> calls C<transform()>. |
960
|
|
|
|
|
|
|
|
961
|
|
|
|
|
|
|
Return value: An arrayref of hashrefs, i.e. the return value of C<transform()>. |
962
|
|
|
|
|
|
|
|
963
|
|
|
|
|
|
|
This value is suitable for passing to C<writer()>. |
964
|
|
|
|
|
|
|
|
965
|
|
|
|
|
|
|
=head2 o report() |
966
|
|
|
|
|
|
|
|
967
|
|
|
|
|
|
|
Displays various items for one or all releases. |
968
|
|
|
|
|
|
|
|
969
|
|
|
|
|
|
|
If the 'release' option to C<new()> was not used, displays items for all releases. |
970
|
|
|
|
|
|
|
|
971
|
|
|
|
|
|
|
If 'release' was used, restrict the report to just that release/version. |
972
|
|
|
|
|
|
|
|
973
|
|
|
|
|
|
|
If either the 'table' or 'webPage' options to C<new()> were used, output HTML by calling C<report_as_html()>. |
974
|
|
|
|
|
|
|
|
975
|
|
|
|
|
|
|
If these latter 2 options were not used, output text. |
976
|
|
|
|
|
|
|
|
977
|
|
|
|
|
|
|
HTML is escaped using L<HTML::Entities::Interpolate>. |
978
|
|
|
|
|
|
|
|
979
|
|
|
|
|
|
|
Output is to STDOUT. |
980
|
|
|
|
|
|
|
|
981
|
|
|
|
|
|
|
Clearly, you should not use -v to get logging output when using text or HTML output. |
982
|
|
|
|
|
|
|
|
983
|
|
|
|
|
|
|
=head2 o report_as_html() |
984
|
|
|
|
|
|
|
|
985
|
|
|
|
|
|
|
Displays various items as HTML for one or all releases. |
986
|
|
|
|
|
|
|
|
987
|
|
|
|
|
|
|
If the 'release' option to C<new()> was not used, displays items for all releases. |
988
|
|
|
|
|
|
|
|
989
|
|
|
|
|
|
|
If 'release' was used, restrict the report to just that release/version. |
990
|
|
|
|
|
|
|
|
991
|
|
|
|
|
|
|
Warning: This method must be called via the C<report()> method. |
992
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
Output is to STDOUT. |
994
|
|
|
|
|
|
|
|
995
|
|
|
|
|
|
|
=head2 o run() |
996
|
|
|
|
|
|
|
|
997
|
|
|
|
|
|
|
Use the options passed to C<new()> to determine what to do. |
998
|
|
|
|
|
|
|
|
999
|
|
|
|
|
|
|
Calling C<< new(convert => 1) >> and then C<run()> will cause C<writer(reader() )> to be called. |
1000
|
|
|
|
|
|
|
|
1001
|
|
|
|
|
|
|
If you do not set 'convert' to 1 (i.e. use 0 - the default), C<run()> will call C<read()> and C<report()>. |
1002
|
|
|
|
|
|
|
|
1003
|
|
|
|
|
|
|
Return value: 0. |
1004
|
|
|
|
|
|
|
|
1005
|
|
|
|
|
|
|
=head2 o transform(@line) |
1006
|
|
|
|
|
|
|
|
1007
|
|
|
|
|
|
|
Transform the memory-based version of Changes/CHANGES into an arrayref of hashrefs, where each array element |
1008
|
|
|
|
|
|
|
holds data for 1 version. |
1009
|
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
Must be called by C<reader()>. |
1011
|
|
|
|
|
|
|
|
1012
|
|
|
|
|
|
|
The array is the text read in from Changes/CHANGES. |
1013
|
|
|
|
|
|
|
|
1014
|
|
|
|
|
|
|
C<transform()> stores the arrayref of hashrefs in $obj -> changes(), for use by C<writer()>. |
1015
|
|
|
|
|
|
|
|
1016
|
|
|
|
|
|
|
Return value: The object, for method chaining. |
1017
|
|
|
|
|
|
|
|
1018
|
|
|
|
|
|
|
=head2 o validate($file_name) |
1019
|
|
|
|
|
|
|
|
1020
|
|
|
|
|
|
|
This method is used by C<read()> to validate the contents of the file read in. |
1021
|
|
|
|
|
|
|
|
1022
|
|
|
|
|
|
|
C<validate()> does not read the file. |
1023
|
|
|
|
|
|
|
|
1024
|
|
|
|
|
|
|
C<validate()> calls die when a validation test fails. |
1025
|
|
|
|
|
|
|
|
1026
|
|
|
|
|
|
|
The file name is just used for reporting. |
1027
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
Return value: The object, for method chaining. |
1029
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
=head2 o writer([$output_file_name]) |
1031
|
|
|
|
|
|
|
|
1032
|
|
|
|
|
|
|
This method writes the arrayref stored in $obj -> changes(), using L<Config::IniFiles>, to the given file. |
1033
|
|
|
|
|
|
|
|
1034
|
|
|
|
|
|
|
See C<transform()>. |
1035
|
|
|
|
|
|
|
|
1036
|
|
|
|
|
|
|
The $output_file_name is optional. It defaults to 'Changelog.ini'. |
1037
|
|
|
|
|
|
|
|
1038
|
|
|
|
|
|
|
Return value: The object, for method chaining. |
1039
|
|
|
|
|
|
|
|
1040
|
|
|
|
|
|
|
=head1 FAQ |
1041
|
|
|
|
|
|
|
|
1042
|
|
|
|
|
|
|
=over 4 |
1043
|
|
|
|
|
|
|
|
1044
|
|
|
|
|
|
|
=item o Are there any things I should look out for? |
1045
|
|
|
|
|
|
|
|
1046
|
|
|
|
|
|
|
=over 4 |
1047
|
|
|
|
|
|
|
|
1048
|
|
|
|
|
|
|
=item o Invalid dates |
1049
|
|
|
|
|
|
|
|
1050
|
|
|
|
|
|
|
Invalid dates in Changes/CHANGES cannot be distinguished from comments. That means that if the output file is |
1051
|
|
|
|
|
|
|
missing one or more versions, it is because of those invalid dates. |
1052
|
|
|
|
|
|
|
|
1053
|
|
|
|
|
|
|
=item o Invalid day-of-week (dow) |
1054
|
|
|
|
|
|
|
|
1055
|
|
|
|
|
|
|
If Changes/CHANGES includes the dow, it is not cross-checked with the date, so if the dow is wrong, |
1056
|
|
|
|
|
|
|
you will not get an error generated. |
1057
|
|
|
|
|
|
|
|
1058
|
|
|
|
|
|
|
=back |
1059
|
|
|
|
|
|
|
|
1060
|
|
|
|
|
|
|
=item o How do I display Changelog.ini? |
1061
|
|
|
|
|
|
|
|
1062
|
|
|
|
|
|
|
See C<bin/ini.report.pl>. It outputs text or HTML. |
1063
|
|
|
|
|
|
|
|
1064
|
|
|
|
|
|
|
=item o What is the format of Changelog.ini? |
1065
|
|
|
|
|
|
|
|
1066
|
|
|
|
|
|
|
See also the next question. |
1067
|
|
|
|
|
|
|
|
1068
|
|
|
|
|
|
|
See C<scripts/report.names.pl> for sample code. |
1069
|
|
|
|
|
|
|
|
1070
|
|
|
|
|
|
|
Here is a sample: |
1071
|
|
|
|
|
|
|
|
1072
|
|
|
|
|
|
|
[Module] |
1073
|
|
|
|
|
|
|
Name=CGI::Session |
1074
|
|
|
|
|
|
|
Changelog.Creator=Module::Metadata::Changes V 1.00 |
1075
|
|
|
|
|
|
|
Changelog.Parser=Config::IniFiles V 2.39 |
1076
|
|
|
|
|
|
|
|
1077
|
|
|
|
|
|
|
[V 4.30] |
1078
|
|
|
|
|
|
|
Date=2008-04-25T00:00:00 |
1079
|
|
|
|
|
|
|
Comments= <<EOT |
1080
|
|
|
|
|
|
|
* FIX: Patch POD for CGI::Session in various places, to emphasize even more that auto-flushing is |
1081
|
|
|
|
|
|
|
unreliable, and that flush() should always be called explicitly before the program exits. |
1082
|
|
|
|
|
|
|
The changes are a new section just after SYNOPSIS and DESCRIPTION, and the PODs for flush(), |
1083
|
|
|
|
|
|
|
and delete(). See RT#17299 and RT#34668 |
1084
|
|
|
|
|
|
|
* NEW: Add t/new_with_undef.t and t/load_with_undef.t to explicitly demonstrate the effects of |
1085
|
|
|
|
|
|
|
calling new() and load() with various types of undefined or fake parameters. See RT#34668 |
1086
|
|
|
|
|
|
|
EOT |
1087
|
|
|
|
|
|
|
|
1088
|
|
|
|
|
|
|
[V 4.10] |
1089
|
|
|
|
|
|
|
Date=2006-03-28T00:00:00 |
1090
|
|
|
|
|
|
|
Deploy.Action=Upgrade |
1091
|
|
|
|
|
|
|
Deploy.Reason=Security |
1092
|
|
|
|
|
|
|
Comments= <<EOT |
1093
|
|
|
|
|
|
|
* SECURITY: Hopefully this settles all of the problems with symlinks. Both the file |
1094
|
|
|
|
|
|
|
and db_file drivers now use O_NOFOLLOW with open when the file should exist and |
1095
|
|
|
|
|
|
|
O_EXCL|O_CREAT when creating the file. Tests added for symlinks. (Matt LeBlanc) |
1096
|
|
|
|
|
|
|
* SECURITY: sqlite driver no longer attempts to use /tmp/sessions.sqlt when no |
1097
|
|
|
|
|
|
|
Handle or DataSource is specified. This was a mistake from a security standpoint |
1098
|
|
|
|
|
|
|
as anyone on the machine would then be able to create and therefore insert data |
1099
|
|
|
|
|
|
|
into your sessions. (Matt LeBlanc) |
1100
|
|
|
|
|
|
|
* NEW: name is now an instance method (RT#17979) (Matt LeBlanc) |
1101
|
|
|
|
|
|
|
EOT |
1102
|
|
|
|
|
|
|
|
1103
|
|
|
|
|
|
|
=item o What are the reserved tokens in this format? |
1104
|
|
|
|
|
|
|
|
1105
|
|
|
|
|
|
|
I am using tokens to refer to both things in [] such as Module, and things on the left hand side |
1106
|
|
|
|
|
|
|
of the = signs, such as Date. |
1107
|
|
|
|
|
|
|
|
1108
|
|
|
|
|
|
|
And yes, these tokens are case-sensitive. |
1109
|
|
|
|
|
|
|
|
1110
|
|
|
|
|
|
|
Under the [Module] section, the tokens are: |
1111
|
|
|
|
|
|
|
|
1112
|
|
|
|
|
|
|
=over 4 |
1113
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
=item o Changelog.Creator |
1115
|
|
|
|
|
|
|
|
1116
|
|
|
|
|
|
|
sample: Changelog.Creator=Module::Metadata::Changes V 2.00 |
1117
|
|
|
|
|
|
|
|
1118
|
|
|
|
|
|
|
=item o Changelog.Parser |
1119
|
|
|
|
|
|
|
|
1120
|
|
|
|
|
|
|
Sample: Changelog.Parser=Config::IniFiles V 2.66 |
1121
|
|
|
|
|
|
|
|
1122
|
|
|
|
|
|
|
=item o Name |
1123
|
|
|
|
|
|
|
|
1124
|
|
|
|
|
|
|
Sample: Name=Manage::Module::Changes |
1125
|
|
|
|
|
|
|
|
1126
|
|
|
|
|
|
|
=back |
1127
|
|
|
|
|
|
|
|
1128
|
|
|
|
|
|
|
Under each version (section), whose name is like [V 1.23], the token are as follows. |
1129
|
|
|
|
|
|
|
|
1130
|
|
|
|
|
|
|
L<Config::IniFiles> calls the V in [V 1.23] a Group Name. |
1131
|
|
|
|
|
|
|
|
1132
|
|
|
|
|
|
|
=over 4 |
1133
|
|
|
|
|
|
|
|
1134
|
|
|
|
|
|
|
=item o Comments |
1135
|
|
|
|
|
|
|
|
1136
|
|
|
|
|
|
|
Sample: Comments=- Original version |
1137
|
|
|
|
|
|
|
|
1138
|
|
|
|
|
|
|
=item o Date |
1139
|
|
|
|
|
|
|
|
1140
|
|
|
|
|
|
|
The datetime of the release, in W3CDTF format. |
1141
|
|
|
|
|
|
|
|
1142
|
|
|
|
|
|
|
Sample: Date=2008-05-02T15:15:45 |
1143
|
|
|
|
|
|
|
|
1144
|
|
|
|
|
|
|
I know the embedded 'T' makes this format a bit harder to read, but the idea is that such files |
1145
|
|
|
|
|
|
|
will normally be processed by a program. |
1146
|
|
|
|
|
|
|
|
1147
|
|
|
|
|
|
|
=item o Deploy.Action |
1148
|
|
|
|
|
|
|
|
1149
|
|
|
|
|
|
|
The module author makes this recommendation to the end user. |
1150
|
|
|
|
|
|
|
|
1151
|
|
|
|
|
|
|
This enables the end user to quickly grep the Changelog.ini, or the output of C<ini.report.pl>, |
1152
|
|
|
|
|
|
|
for things like security fixes and API changes. |
1153
|
|
|
|
|
|
|
|
1154
|
|
|
|
|
|
|
Run 'bin/ini.report.pl -h' for help. |
1155
|
|
|
|
|
|
|
|
1156
|
|
|
|
|
|
|
Suggestions: |
1157
|
|
|
|
|
|
|
|
1158
|
|
|
|
|
|
|
Deploy.Action=Upgrade |
1159
|
|
|
|
|
|
|
Deploy.Reason=(Security|Major bug fix) |
1160
|
|
|
|
|
|
|
|
1161
|
|
|
|
|
|
|
Deploy.Action=Upgrade with caution |
1162
|
|
|
|
|
|
|
Deploy.Reason=(Major|Minor) API change/Development version |
1163
|
|
|
|
|
|
|
|
1164
|
|
|
|
|
|
|
Alternately, the classic syslog tokens could perhaps be used: |
1165
|
|
|
|
|
|
|
|
1166
|
|
|
|
|
|
|
Debug/Info/Notice/Warning/Error/Critical/Alert/Emergency. |
1167
|
|
|
|
|
|
|
|
1168
|
|
|
|
|
|
|
I think the values for these 2 tokens (Deploy.*) should be kept terse, and the Comments section used |
1169
|
|
|
|
|
|
|
for an expanded explanation, if necessary. |
1170
|
|
|
|
|
|
|
|
1171
|
|
|
|
|
|
|
Omitting Deploy.Action simply means the module author leaves it up to the end user to |
1172
|
|
|
|
|
|
|
read the comments and make up their own mind. |
1173
|
|
|
|
|
|
|
|
1174
|
|
|
|
|
|
|
C<reader()> called directly, or via C<ini.report.pl -c> (i.e. old format to ini format converter), |
1175
|
|
|
|
|
|
|
inserts these 2 tokens if it sees the word /Security/i in the Comments. It is a crude but automatic warning |
1176
|
|
|
|
|
|
|
to end users. The HTML output options (C<-t> and C<-w>) use red text via CSS to highlight these 2 tokens. |
1177
|
|
|
|
|
|
|
|
1178
|
|
|
|
|
|
|
Of course security is best handled by the module author explicitly inserting a suitable note. |
1179
|
|
|
|
|
|
|
|
1180
|
|
|
|
|
|
|
And, lastly, any such note is purely up to the judgement of the author, which means differences in |
1181
|
|
|
|
|
|
|
opinion are inevitable. |
1182
|
|
|
|
|
|
|
|
1183
|
|
|
|
|
|
|
=item o Deploy.Reason |
1184
|
|
|
|
|
|
|
|
1185
|
|
|
|
|
|
|
The module author gives this reason for their recommended action. |
1186
|
|
|
|
|
|
|
|
1187
|
|
|
|
|
|
|
=item o EOT |
1188
|
|
|
|
|
|
|
|
1189
|
|
|
|
|
|
|
Config::IniFiles uses EOT to terminate multi-line comments. |
1190
|
|
|
|
|
|
|
|
1191
|
|
|
|
|
|
|
If C<transform()> finds a line beginning with EOT, it jams a '-' in front of it. |
1192
|
|
|
|
|
|
|
|
1193
|
|
|
|
|
|
|
=back |
1194
|
|
|
|
|
|
|
|
1195
|
|
|
|
|
|
|
=item o Why are there not more reserved tokens? |
1196
|
|
|
|
|
|
|
|
1197
|
|
|
|
|
|
|
Various reasons: |
1198
|
|
|
|
|
|
|
|
1199
|
|
|
|
|
|
|
=over 4 |
1200
|
|
|
|
|
|
|
|
1201
|
|
|
|
|
|
|
=item o Any one person, or any group, can standardize on their own tokens |
1202
|
|
|
|
|
|
|
|
1203
|
|
|
|
|
|
|
Obviously, it would help if they advertised their choice, firstly so as to get as |
1204
|
|
|
|
|
|
|
many people as possible using the same tokens, and secondly to get agreement on the |
1205
|
|
|
|
|
|
|
interpretation of those choices. |
1206
|
|
|
|
|
|
|
|
1207
|
|
|
|
|
|
|
Truely, there is no point in any particular token if it is not given a consistent meaning. |
1208
|
|
|
|
|
|
|
|
1209
|
|
|
|
|
|
|
=item o You can simply add your own to your Changelog.ini file |
1210
|
|
|
|
|
|
|
|
1211
|
|
|
|
|
|
|
They will then live on as part of the file. |
1212
|
|
|
|
|
|
|
|
1213
|
|
|
|
|
|
|
=back |
1214
|
|
|
|
|
|
|
|
1215
|
|
|
|
|
|
|
Special processing is normally only relevant when converting an old-style Changes/CHANGES file |
1216
|
|
|
|
|
|
|
to a new-style Changelog.ini file. |
1217
|
|
|
|
|
|
|
|
1218
|
|
|
|
|
|
|
However, if you think the new tokens are important enough to be displayed as part of the text |
1219
|
|
|
|
|
|
|
and HTML format reports, let me know. |
1220
|
|
|
|
|
|
|
|
1221
|
|
|
|
|
|
|
I have deliberately not included the Comments in reports since you can always just examine the |
1222
|
|
|
|
|
|
|
Changelog.ini file itself for such items. But that too could be changed. |
1223
|
|
|
|
|
|
|
|
1224
|
|
|
|
|
|
|
=item o Are single-line comments acceptable? |
1225
|
|
|
|
|
|
|
|
1226
|
|
|
|
|
|
|
Sure. Here is one: |
1227
|
|
|
|
|
|
|
|
1228
|
|
|
|
|
|
|
Comments=* INTERNAL: No Changes since 4.20_1. Declaring stable. |
1229
|
|
|
|
|
|
|
|
1230
|
|
|
|
|
|
|
The '*' is not special, it is just part of the comment. |
1231
|
|
|
|
|
|
|
|
1232
|
|
|
|
|
|
|
=item o What is with the datetime format? |
1233
|
|
|
|
|
|
|
|
1234
|
|
|
|
|
|
|
It is called W3CDTF format. See: |
1235
|
|
|
|
|
|
|
|
1236
|
|
|
|
|
|
|
http://search.cpan.org/dist/DateTime-Format-W3CDTF/ |
1237
|
|
|
|
|
|
|
|
1238
|
|
|
|
|
|
|
See also ISO8601 format: |
1239
|
|
|
|
|
|
|
|
1240
|
|
|
|
|
|
|
http://search.cpan.org/dist/DateTime-Format-ISO8601/ |
1241
|
|
|
|
|
|
|
|
1242
|
|
|
|
|
|
|
=item o Why this file format? |
1243
|
|
|
|
|
|
|
|
1244
|
|
|
|
|
|
|
Various reasons: |
1245
|
|
|
|
|
|
|
|
1246
|
|
|
|
|
|
|
=over 4 |
1247
|
|
|
|
|
|
|
|
1248
|
|
|
|
|
|
|
=item o [Module] allows for [Script], [Library], and so on. |
1249
|
|
|
|
|
|
|
|
1250
|
|
|
|
|
|
|
=item o *.ini files are easy for beginners to comprehend |
1251
|
|
|
|
|
|
|
|
1252
|
|
|
|
|
|
|
=item o Other formats were considered. I made a decision |
1253
|
|
|
|
|
|
|
|
1254
|
|
|
|
|
|
|
There is no perfect format which will please everyone. |
1255
|
|
|
|
|
|
|
|
1256
|
|
|
|
|
|
|
Various references, in no particular order: |
1257
|
|
|
|
|
|
|
|
1258
|
|
|
|
|
|
|
http://use.perl.org/~miyagawa/journal/34850 |
1259
|
|
|
|
|
|
|
|
1260
|
|
|
|
|
|
|
http://use.perl.org/~hex/journal/34864 |
1261
|
|
|
|
|
|
|
|
1262
|
|
|
|
|
|
|
http://redhanded.hobix.com/inspect/yamlIsJson.html |
1263
|
|
|
|
|
|
|
|
1264
|
|
|
|
|
|
|
http://use.perl.org/article.pl?sid=07/09/06/0324215 |
1265
|
|
|
|
|
|
|
|
1266
|
|
|
|
|
|
|
http://use.perl.org/comments.pl?sid=36862&cid=57590 |
1267
|
|
|
|
|
|
|
|
1268
|
|
|
|
|
|
|
http://use.perl.org/~RGiersig/journal/34370/ |
1269
|
|
|
|
|
|
|
|
1270
|
|
|
|
|
|
|
=item o The module L<Config::IniFiles> already existed, for reading and writing this format |
1271
|
|
|
|
|
|
|
|
1272
|
|
|
|
|
|
|
Specifically, L<Config::IniFiles> allows for here documents, which I use to hold the comments |
1273
|
|
|
|
|
|
|
authors produce for most of their releases. |
1274
|
|
|
|
|
|
|
|
1275
|
|
|
|
|
|
|
=back |
1276
|
|
|
|
|
|
|
|
1277
|
|
|
|
|
|
|
=item o What is the difference between release and version? |
1278
|
|
|
|
|
|
|
|
1279
|
|
|
|
|
|
|
I am using release to refer not just to the version number, but also to all the notes |
1280
|
|
|
|
|
|
|
relating to that version. |
1281
|
|
|
|
|
|
|
|
1282
|
|
|
|
|
|
|
And by notes I mean everything in one section under the name [V $version]. |
1283
|
|
|
|
|
|
|
|
1284
|
|
|
|
|
|
|
=item o Will you switch to YAML or XML format? |
1285
|
|
|
|
|
|
|
|
1286
|
|
|
|
|
|
|
YAML? No, never. It is targetted at other situations, and while it can be used for simple |
1287
|
|
|
|
|
|
|
applications like this, it can't be hand-written I<by beginners>. |
1288
|
|
|
|
|
|
|
|
1289
|
|
|
|
|
|
|
And it's unreasonable to force people to write a simple program to write a simple YAML file. |
1290
|
|
|
|
|
|
|
|
1291
|
|
|
|
|
|
|
XML? Nope. It is great in I<some> situations, but too visually dense and slow to write for this one. |
1292
|
|
|
|
|
|
|
|
1293
|
|
|
|
|
|
|
=item o What about adding Changed Requirements to the file? |
1294
|
|
|
|
|
|
|
|
1295
|
|
|
|
|
|
|
No. That info will be in the changed C<Build.PL> or C<Makefile.PL> files. |
1296
|
|
|
|
|
|
|
|
1297
|
|
|
|
|
|
|
It is a pointless burden to make the module author I<also> add that to Changelog.ini. |
1298
|
|
|
|
|
|
|
|
1299
|
|
|
|
|
|
|
=item o Who said you had the power to decide on this format? |
1300
|
|
|
|
|
|
|
|
1301
|
|
|
|
|
|
|
No-one. But I do have the time and the inclination to maintain L<Module::Metadata::Changes> |
1302
|
|
|
|
|
|
|
indefinitely. |
1303
|
|
|
|
|
|
|
|
1304
|
|
|
|
|
|
|
Also, I had a pressing need for a better way to manage metadata pertaining my own modules, |
1305
|
|
|
|
|
|
|
for use in my database of modules. |
1306
|
|
|
|
|
|
|
|
1307
|
|
|
|
|
|
|
One of the reports I produce from this database is visible here: |
1308
|
|
|
|
|
|
|
|
1309
|
|
|
|
|
|
|
http://savage.net.au/Perl-modules.html |
1310
|
|
|
|
|
|
|
|
1311
|
|
|
|
|
|
|
Ideally, there will come a time when all of your modules, if not the whole of CPAN, |
1312
|
|
|
|
|
|
|
will have Changelog.ini files, so producing such a report will be easy, and hence will be |
1313
|
|
|
|
|
|
|
that much more likely to happen. |
1314
|
|
|
|
|
|
|
|
1315
|
|
|
|
|
|
|
=item o Why not use, say, L<Config::Tiny> to process Changelog.ini files? |
1316
|
|
|
|
|
|
|
|
1317
|
|
|
|
|
|
|
Because L<Config::Tiny> contains this line, 's/\s\;\s.+$//g;', so it will mangle |
1318
|
|
|
|
|
|
|
text containing English semi-colons. |
1319
|
|
|
|
|
|
|
|
1320
|
|
|
|
|
|
|
Also, authors add comments per release, and most C<Config::*> modules only handle lines |
1321
|
|
|
|
|
|
|
of the type X=Y. |
1322
|
|
|
|
|
|
|
|
1323
|
|
|
|
|
|
|
=item o How are the old Changes/CHANGES files parsed? |
1324
|
|
|
|
|
|
|
|
1325
|
|
|
|
|
|
|
The first line is scanned looking for /X::Y/ or /X\.$/. And yes, it fails for modules |
1326
|
|
|
|
|
|
|
which identify themselves like Fuse-PDF not at the end of the line. |
1327
|
|
|
|
|
|
|
|
1328
|
|
|
|
|
|
|
Then lines looking something like /$a_version_number ... $a_datetime/ are searched for. |
1329
|
|
|
|
|
|
|
This is deemed to be the start of information pertaining to a specific release. |
1330
|
|
|
|
|
|
|
|
1331
|
|
|
|
|
|
|
Everything up to the next release, or EOF, is deemed to belong to the release just |
1332
|
|
|
|
|
|
|
identified. |
1333
|
|
|
|
|
|
|
|
1334
|
|
|
|
|
|
|
This means a line containing a version number without a date is not recognized as a new release, |
1335
|
|
|
|
|
|
|
so that that line and the following comments are added to the 'current' release info. |
1336
|
|
|
|
|
|
|
|
1337
|
|
|
|
|
|
|
For an example of this, process the C<Changes> file from CGI::Session (t/Changes), and scan the |
1338
|
|
|
|
|
|
|
output for '[4.00_01]', which you will see contains stuff for V 3.12, 3.8 and 3.x. |
1339
|
|
|
|
|
|
|
|
1340
|
|
|
|
|
|
|
See above, under the list of reserved tokens, for how security advisories are inserted in the output |
1341
|
|
|
|
|
|
|
stream. |
1342
|
|
|
|
|
|
|
|
1343
|
|
|
|
|
|
|
=item o Is this conversion process perfect? |
1344
|
|
|
|
|
|
|
|
1345
|
|
|
|
|
|
|
Well, no, actually, but it will be as good as I can make it. |
1346
|
|
|
|
|
|
|
|
1347
|
|
|
|
|
|
|
For example, version numbers like '3.x' are turned into '3.'. |
1348
|
|
|
|
|
|
|
|
1349
|
|
|
|
|
|
|
You will simply have to scrutinize (which means 'read I<carefully>') the output of this conversion process. |
1350
|
|
|
|
|
|
|
|
1351
|
|
|
|
|
|
|
If a Changes/CHANGES file is not handled by the current version, log a bug report on Request Tracker: |
1352
|
|
|
|
|
|
|
http://rt.cpan.org/Public/ |
1353
|
|
|
|
|
|
|
|
1354
|
|
|
|
|
|
|
=item o How are datetimes in old-style files parsed? |
1355
|
|
|
|
|
|
|
|
1356
|
|
|
|
|
|
|
Firstly try L<DateTime::Format::HTTP>, and if that fails, try these steps: |
1357
|
|
|
|
|
|
|
|
1358
|
|
|
|
|
|
|
=over 4 |
1359
|
|
|
|
|
|
|
|
1360
|
|
|
|
|
|
|
=item o Strip 'st' from 1st, 'nd' from 2nd, etc |
1361
|
|
|
|
|
|
|
|
1362
|
|
|
|
|
|
|
=item o Try L<DateTime::Format::Strptime> |
1363
|
|
|
|
|
|
|
|
1364
|
|
|
|
|
|
|
=item o If that fails, strip Monday, etc, and retry L<DateTime::Format::Strptime> |
1365
|
|
|
|
|
|
|
|
1366
|
|
|
|
|
|
|
I noticed some dates were invalid because the day of the week did not match |
1367
|
|
|
|
|
|
|
the day of the month. So, I arbitrarily chop the day of the week, and retry. |
1368
|
|
|
|
|
|
|
|
1369
|
|
|
|
|
|
|
=back |
1370
|
|
|
|
|
|
|
|
1371
|
|
|
|
|
|
|
Other date parsing modules are L<Date::Manip>, L<Date::Parse> and L<Regexp::Common::time>. |
1372
|
|
|
|
|
|
|
|
1373
|
|
|
|
|
|
|
=item o Why did you choose these 2 modules? |
1374
|
|
|
|
|
|
|
|
1375
|
|
|
|
|
|
|
I had a look at a few Changes/CHANGES files, and these made sense. |
1376
|
|
|
|
|
|
|
|
1377
|
|
|
|
|
|
|
If appropriate, other modules can be added to the algorithm. |
1378
|
|
|
|
|
|
|
|
1379
|
|
|
|
|
|
|
See the discussion on this page (search for 'parse multiple formats'): |
1380
|
|
|
|
|
|
|
|
1381
|
|
|
|
|
|
|
http://datetime.perl.org/index.cgi?FAQBasicUsage |
1382
|
|
|
|
|
|
|
|
1383
|
|
|
|
|
|
|
If things get more complicated, I will reconsider using L<DateTime::Format::Builder>. |
1384
|
|
|
|
|
|
|
|
1385
|
|
|
|
|
|
|
=item o What happens for 2 releases on the same day? |
1386
|
|
|
|
|
|
|
|
1387
|
|
|
|
|
|
|
It depends whether or not the version numbers are different. |
1388
|
|
|
|
|
|
|
|
1389
|
|
|
|
|
|
|
The C<Changes> file for L<CGI::Session> contains 2 references to version 4.06 :-(. |
1390
|
|
|
|
|
|
|
|
1391
|
|
|
|
|
|
|
As long as the version numbers are different, the date does not actually matter. |
1392
|
|
|
|
|
|
|
|
1393
|
|
|
|
|
|
|
=item o Will a new file format mean more work for those who maintain CPAN? |
1394
|
|
|
|
|
|
|
|
1395
|
|
|
|
|
|
|
Yes, I am afraid so, unless they completely ignore me! |
1396
|
|
|
|
|
|
|
|
1397
|
|
|
|
|
|
|
But I am hopeful this will lead to less work overall. |
1398
|
|
|
|
|
|
|
|
1399
|
|
|
|
|
|
|
=item o Why did you not use the C<Template Toolkit> for the HTML? |
1400
|
|
|
|
|
|
|
|
1401
|
|
|
|
|
|
|
It is too complex for this tiny project. |
1402
|
|
|
|
|
|
|
|
1403
|
|
|
|
|
|
|
=item o Where do I go for support? |
1404
|
|
|
|
|
|
|
|
1405
|
|
|
|
|
|
|
Log a bug report on Request Tracker: http://rt.cpan.org/Public/ |
1406
|
|
|
|
|
|
|
|
1407
|
|
|
|
|
|
|
If it concerns failure to convert a specific Changes/CHANGES file, just provide the name of |
1408
|
|
|
|
|
|
|
the module and the version number. |
1409
|
|
|
|
|
|
|
|
1410
|
|
|
|
|
|
|
It would help - if the problem is failure to parse a specific datetime format - if you could |
1411
|
|
|
|
|
|
|
advise me on a suitable C<DateTime::Format::*> module to use. |
1412
|
|
|
|
|
|
|
|
1413
|
|
|
|
|
|
|
=back |
1414
|
|
|
|
|
|
|
|
1415
|
|
|
|
|
|
|
=head1 Machine-Readable Change Log |
1416
|
|
|
|
|
|
|
|
1417
|
|
|
|
|
|
|
The file Changes was converted into Changelog.ini by L<Module::Metadata::Changes>. |
1418
|
|
|
|
|
|
|
|
1419
|
|
|
|
|
|
|
=head1 Version Numbers |
1420
|
|
|
|
|
|
|
|
1421
|
|
|
|
|
|
|
Version numbers < 1.00 represent development versions. From 1.00 up, they are production versions. |
1422
|
|
|
|
|
|
|
|
1423
|
|
|
|
|
|
|
=head1 Repository |
1424
|
|
|
|
|
|
|
|
1425
|
|
|
|
|
|
|
L<https://github.com/ronsavage/Module-Metadata-Changes>. |
1426
|
|
|
|
|
|
|
|
1427
|
|
|
|
|
|
|
=head1 Support |
1428
|
|
|
|
|
|
|
|
1429
|
|
|
|
|
|
|
Email the author, or log a bug on RT: |
1430
|
|
|
|
|
|
|
|
1431
|
|
|
|
|
|
|
L<https://rt.cpan.org/Public/Dist/Display.html?Name=Module::Metadata::Changes>. |
1432
|
|
|
|
|
|
|
|
1433
|
|
|
|
|
|
|
=head1 See Also |
1434
|
|
|
|
|
|
|
|
1435
|
|
|
|
|
|
|
L<App::ParseCPANChanges>. |
1436
|
|
|
|
|
|
|
|
1437
|
|
|
|
|
|
|
L<CPAN::Changes> |
1438
|
|
|
|
|
|
|
|
1439
|
|
|
|
|
|
|
L<Module::Changes> |
1440
|
|
|
|
|
|
|
|
1441
|
|
|
|
|
|
|
=head1 Author |
1442
|
|
|
|
|
|
|
|
1443
|
|
|
|
|
|
|
L<Module::Metadata::Changes> was written by Ron Savage I<E<lt>ron@savage.net.auE<gt>> in 2008. |
1444
|
|
|
|
|
|
|
|
1445
|
|
|
|
|
|
|
Home page: http://savage.net.au/index.html |
1446
|
|
|
|
|
|
|
|
1447
|
|
|
|
|
|
|
=head1 Copyright |
1448
|
|
|
|
|
|
|
|
1449
|
|
|
|
|
|
|
Australian copyright (c) 2008, Ron Savage. |
1450
|
|
|
|
|
|
|
All Programs of mine are 'OSI Certified Open Source Software'; |
1451
|
|
|
|
|
|
|
you can redistribute them and/or modify them under the terms of |
1452
|
|
|
|
|
|
|
The Perl License, a copy of which is available at: |
1453
|
|
|
|
|
|
|
http://dev.perl.org/licenses/ |
1454
|
|
|
|
|
|
|
|
1455
|
|
|
|
|
|
|
=cut |