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>
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.
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.
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/>.
23 Dpkg::Changelog::Debian - parse Debian changelogs
27 Dpkg::Changelog::Debian parses Debian changelogs as described in
30 The parser tries to ignore most cruft like # or /* */ style comments,
31 CVS comments, vim variables, emacs local variables and stuff from
32 older changelogs with other formats at the end of the file.
33 NOTE: most of these are ignored silently currently, there is no
34 parser error issued for them. This should become configurable in the
39 package Dpkg::Changelog::Debian;
44 our $VERSION = '1.00';
48 use Dpkg::Changelog qw(:util);
49 use Dpkg::Changelog::Entry::Debian qw(match_header match_trailer);
51 use parent qw(Dpkg::Changelog);
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'),
60 my $ancient_delimiter_re = qr{
62 (?: # Ancient GNU style changelog entry with expanded date
64 \w+\s+ # Day of week (abbreviated)
65 \w+\s+ # Month name (abbreviated)
66 \d{1,2} # Day of month
68 \d{1,2}:\d{1,2}:\d{1,2}\s+ # Time
73 (?:.*) # Maintainer name
76 (?:.*) # Maintainer email
78 | # Old GNU style changelog entry with expanded date
80 \w+\s+ # Day of week (abbreviated)
81 \w+\s+ # Month name (abbreviated)
82 \d{1,2},?\s* # Day of month
86 (?:.*) # Maintainer name
89 (?:.*) # Maintainer email
91 | # Ancient changelog header w/o key=value options
92 (?:\w[-+0-9a-z.]*) # Package name
95 (?:[^\(\) \t]+) # Package version
98 | # Ancient changelog header
99 (?:[\w.+-]+) # Package name
101 (?:\S+) # Package version
103 \ (?:\S+) # Package revision
105 Changes\ from\ version\ (?:.*)\ to\ (?:.*):
107 Changes\ for\ [\w.+-]+-[\w.+-]+:?\s*$
121 =item $c->parse($fh, $description)
123 Read the filehandle and parse a Debian changelog in it. The data in the
124 object is reset before parsing new data.
126 Returns the number of changelog entries that have been parsed with success.
131 my ($self, $fh, $file) = @_;
132 $file = $self->{reportfile} if exists $self->{reportfile};
134 $self->reset_parse_errors;
137 $self->set_unparsed_tail(undef);
139 my $expect = FIRST_HEADING;
140 my $entry = Dpkg::Changelog::Entry::Debian->new();
142 my $unknowncounter = 1; # to make version unique, e.g. for using as id
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'),
153 unless ($entry->is_empty) {
154 push @{$self->{data}}, $entry;
155 $entry = Dpkg::Changelog::Entry::Debian->new();
156 last if $self->abort_early();
158 $entry->set_part('header', $_);
159 foreach my $error ($entry->parse_header()) {
160 $self->parse_error($file, $., $error, $_);
162 $expect= START_CHANGES;
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
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));
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), "$_");
186 $entry->set_part('trailer', $_);
187 $entry->extend_part('blank_after_changes', [ @blanklines ]);
189 foreach my $error ($entry->parse_trailer()) {
190 $self->parse_error($file, $., $error, $_);
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');
207 $entry->extend_part('changes', [ @blanklines, $_ ]);
209 $expect = CHANGES_OR_TRAILER;
211 if ($expect eq START_CHANGES) {
212 $entry->extend_part('blank_after_header', $_);
214 } elsif ($expect eq NEXT_OR_EOF) {
215 $entry->extend_part('blank_after_trailer', $_);
217 } elsif ($expect ne CHANGES_OR_TRAILER) {
218 $self->parse_error($file, $.,
219 sprintf(g_('found blank line where expected %s'), $expect));
221 push @blanklines, $_;
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, $_]);
228 $expect = CHANGES_OR_TRAILER;
233 unless ($expect eq NEXT_OR_EOF) {
234 $self->parse_error($file, $.,
235 sprintf(g_('found end of file where expected %s'),
238 unless ($entry->is_empty) {
239 push @{$self->{data}}, $entry;
242 return scalar @{$self->{data}};
252 =head2 Version 1.00 (dpkg 1.15.6)
254 Mark the module as public.