dpkg (1.18.25) stretch; urgency=medium
[dpkg] / scripts / Dpkg / Changelog / Debian.pm
CommitLineData
1479465f
GJ
1# Copyright © 1996 Ian Jackson
2# Copyright © 2005 Frank Lichtenheld <frank@lichtenheld.de>
3# Copyright © 2009 Raphaël Hertzog <hertzog@debian.org>
4# Copyright © 2012-2015 Guillem Jover <guillem@debian.org>
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19=encoding utf8
20
21=head1 NAME
22
23Dpkg::Changelog::Debian - parse Debian changelogs
24
25=head1 DESCRIPTION
26
27Dpkg::Changelog::Debian parses Debian changelogs as described in
28deb-changelog(5).
29
30The parser tries to ignore most cruft like # or /* */ style comments,
31CVS comments, vim variables, emacs local variables and stuff from
32older changelogs with other formats at the end of the file.
33NOTE: most of these are ignored silently currently, there is no
34parser error issued for them. This should become configurable in the
35future.
36
37=cut
38
39package Dpkg::Changelog::Debian;
40
41use strict;
42use warnings;
43
44our $VERSION = '1.00';
45
46use Dpkg::Gettext;
47use Dpkg::File;
48use Dpkg::Changelog qw(:util);
49use Dpkg::Changelog::Entry::Debian qw(match_header match_trailer);
50
51use parent qw(Dpkg::Changelog);
52
53use constant {
54 FIRST_HEADING => g_('first heading'),
55 NEXT_OR_EOF => g_('next heading or end of file'),
56 START_CHANGES => g_('start of change data'),
57 CHANGES_OR_TRAILER => g_('more change data or trailer'),
58};
59
60my $ancient_delimiter_re = qr{
61 ^
62 (?: # Ancient GNU style changelog entry with expanded date
63 (?:
64 \w+\s+ # Day of week (abbreviated)
65 \w+\s+ # Month name (abbreviated)
66 \d{1,2} # Day of month
67 \Q \E
68 \d{1,2}:\d{1,2}:\d{1,2}\s+ # Time
69 [\w\s]* # Timezone
70 \d{4} # Year
71 )
72 \s+
73 (?:.*) # Maintainer name
74 \s+
75 [<\(]
76 (?:.*) # Maintainer email
77 [\)>]
78 | # Old GNU style changelog entry with expanded date
79 (?:
80 \w+\s+ # Day of week (abbreviated)
81 \w+\s+ # Month name (abbreviated)
82 \d{1,2},?\s* # Day of month
83 \d{4} # Year
84 )
85 \s+
86 (?:.*) # Maintainer name
87 \s+
88 [<\(]
89 (?:.*) # Maintainer email
90 [\)>]
91 | # Ancient changelog header w/o key=value options
92 (?:\w[-+0-9a-z.]*) # Package name
93 \Q \E
94 \(
95 (?:[^\(\) \t]+) # Package version
96 \)
97 \;?
98 | # Ancient changelog header
99 (?:[\w.+-]+) # Package name
100 [- ]
101 (?:\S+) # Package version
102 \ Debian
103 \ (?:\S+) # Package revision
104 |
105 Changes\ from\ version\ (?:.*)\ to\ (?:.*):
106 |
107 Changes\ for\ [\w.+-]+-[\w.+-]+:?\s*$
108 |
109 Old\ Changelog:\s*$
110 |
111 (?:\d+:)?
112 \w[\w.+~-]*:?
113 \s*$
114 )
115}xi;
116
117=head1 METHODS
118
119=over 4
120
121=item $c->parse($fh, $description)
122
123Read the filehandle and parse a Debian changelog in it. The data in the
124object is reset before parsing new data.
125
126Returns the number of changelog entries that have been parsed with success.
127
128=cut
129
130sub parse {
131 my ($self, $fh, $file) = @_;
132 $file = $self->{reportfile} if exists $self->{reportfile};
133
134 $self->reset_parse_errors;
135
136 $self->{data} = [];
137 $self->set_unparsed_tail(undef);
138
139 my $expect = FIRST_HEADING;
140 my $entry = Dpkg::Changelog::Entry::Debian->new();
141 my @blanklines = ();
142 my $unknowncounter = 1; # to make version unique, e.g. for using as id
143 local $_;
144
145 while (<$fh>) {
146 chomp;
147 if (match_header($_)) {
148 unless ($expect eq FIRST_HEADING || $expect eq NEXT_OR_EOF) {
149 $self->parse_error($file, $.,
150 sprintf(g_('found start of entry where expected %s'),
151 $expect), "$_");
152 }
153 unless ($entry->is_empty) {
154 push @{$self->{data}}, $entry;
155 $entry = Dpkg::Changelog::Entry::Debian->new();
156 last if $self->abort_early();
157 }
158 $entry->set_part('header', $_);
159 foreach my $error ($entry->parse_header()) {
160 $self->parse_error($file, $., $error, $_);
161 }
162 $expect= START_CHANGES;
163 @blanklines = ();
164 } elsif (m/^(?:;;\s*)?Local variables:/io) {
165 last; # skip Emacs variables at end of file
166 } elsif (m/^vim:/io) {
167 last; # skip vim variables at end of file
168 } elsif (m/^\$\w+:.*\$/o) {
169 next; # skip stuff that look like a CVS keyword
170 } elsif (m/^\# /o) {
171 next; # skip comments, even that's not supported
172 } elsif (m{^/\*.*\*/}o) {
173 next; # more comments
174 } elsif (m/$ancient_delimiter_re/) {
175 # save entries on old changelog format verbatim
176 # we assume the rest of the file will be in old format once we
177 # hit it for the first time
178 $self->set_unparsed_tail("$_\n" . file_slurp($fh));
179 } elsif (m/^\S/) {
180 $self->parse_error($file, $., g_('badly formatted heading line'), "$_");
181 } elsif (match_trailer($_)) {
182 unless ($expect eq CHANGES_OR_TRAILER) {
183 $self->parse_error($file, $.,
184 sprintf(g_('found trailer where expected %s'), $expect), "$_");
185 }
186 $entry->set_part('trailer', $_);
187 $entry->extend_part('blank_after_changes', [ @blanklines ]);
188 @blanklines = ();
189 foreach my $error ($entry->parse_trailer()) {
190 $self->parse_error($file, $., $error, $_);
191 }
192 $expect = NEXT_OR_EOF;
193 } elsif (m/^ \-\-/) {
194 $self->parse_error($file, $., g_('badly formatted trailer line'), "$_");
195 } elsif (m/^\s{2,}(?:\S)/) {
196 unless ($expect eq START_CHANGES or $expect eq CHANGES_OR_TRAILER) {
197 $self->parse_error($file, $., sprintf(g_('found change data' .
198 ' where expected %s'), $expect), "$_");
199 if ($expect eq NEXT_OR_EOF and not $entry->is_empty) {
200 # lets assume we have missed the actual header line
201 push @{$self->{data}}, $entry;
202 $entry = Dpkg::Changelog::Entry::Debian->new();
203 $entry->set_part('header', 'unknown (unknown' . ($unknowncounter++) . ') unknown; urgency=unknown');
204 }
205 }
206 # Keep raw changes
207 $entry->extend_part('changes', [ @blanklines, $_ ]);
208 @blanklines = ();
209 $expect = CHANGES_OR_TRAILER;
210 } elsif (!m/\S/) {
211 if ($expect eq START_CHANGES) {
212 $entry->extend_part('blank_after_header', $_);
213 next;
214 } elsif ($expect eq NEXT_OR_EOF) {
215 $entry->extend_part('blank_after_trailer', $_);
216 next;
217 } elsif ($expect ne CHANGES_OR_TRAILER) {
218 $self->parse_error($file, $.,
219 sprintf(g_('found blank line where expected %s'), $expect));
220 }
221 push @blanklines, $_;
222 } else {
223 $self->parse_error($file, $., g_('unrecognized line'), "$_");
224 unless ($expect eq START_CHANGES or $expect eq CHANGES_OR_TRAILER) {
225 # lets assume change data if we expected it
226 $entry->extend_part('changes', [ @blanklines, $_]);
227 @blanklines = ();
228 $expect = CHANGES_OR_TRAILER;
229 }
230 }
231 }
232
233 unless ($expect eq NEXT_OR_EOF) {
234 $self->parse_error($file, $.,
235 sprintf(g_('found end of file where expected %s'),
236 $expect));
237 }
238 unless ($entry->is_empty) {
239 push @{$self->{data}}, $entry;
240 }
241
242 return scalar @{$self->{data}};
243}
244
2451;
246__END__
247
248=back
249
250=head1 CHANGES
251
252=head2 Version 1.00 (dpkg 1.15.6)
253
254Mark the module as public.
255
256=head1 SEE ALSO
257
258Dpkg::Changelog
259
260=cut