File: | Dpkg/Changelog/Debian.pm |
Coverage: | 73.3% |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
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-2017 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 - 43 | =head1 NAME Dpkg::Changelog::Debian - parse Debian changelogs =head1 DESCRIPTION This class represents a Debian changelog file as an array of changelog entries (L<Dpkg::Changelog::Entry::Debian>). It implements the generic interface L<Dpkg::Changelog>. Only methods specific to this implementation are described below, the rest are inherited. Dpkg::Changelog::Debian parses Debian changelogs as described in L<deb-changelog(5)>. The parser tries to ignore most cruft like # or /* */ style comments, RCS keywords, Vim modelines, Emacs local variables and stuff from older changelogs with other formats at the end of the file. NOTE: most of these are ignored silently currently, there is no parser error issued for them. This should become configurable in the future. =cut | ||||||
44 | |||||||
45 | package Dpkg::Changelog::Debian 1.00; | ||||||
46 | |||||||
47 | 18 18 18 | 42 13 333 | use strict; | ||||
48 | 18 18 18 | 31 15 454 | use warnings; | ||||
49 | |||||||
50 | 18 18 18 | 1306 21 698 | use Dpkg::Gettext; | ||||
51 | 18 18 18 | 1979 21 632 | use Dpkg::File; | ||||
52 | 18 18 18 | 2788 23 403 | use Dpkg::Changelog; | ||||
53 | 18 18 18 | 3037 28 876 | use Dpkg::Changelog::Entry::Debian qw(match_header match_trailer); | ||||
54 | |||||||
55 | 18 18 18 | 76 17 42 | use parent qw(Dpkg::Changelog); | ||||
56 | |||||||
57 | use constant { | ||||||
58 | 18 | 43 | FIRST_HEADING => g_('first heading'), | ||||
59 | NEXT_OR_EOF => g_('next heading or end of file'), | ||||||
60 | START_CHANGES => g_('start of change data'), | ||||||
61 | CHANGES_OR_TRAILER => g_('more change data or trailer'), | ||||||
62 | 18 18 | 983 14 | }; | ||||
63 | |||||||
64 | my $ancient_delimiter_re = qr{ | ||||||
65 | ^ | ||||||
66 | (?: # Ancient GNU style changelog entry with expanded date | ||||||
67 | (?: | ||||||
68 | \w+\s+ # Day of week (abbreviated) | ||||||
69 | \w+\s+ # Month name (abbreviated) | ||||||
70 | \d{1,2} # Day of month | ||||||
71 | \Q \E | ||||||
72 | \d{1,2}:\d{1,2}:\d{1,2}\s+ # Time | ||||||
73 | [\w\s]* # Timezone | ||||||
74 | \d{4} # Year | ||||||
75 | ) | ||||||
76 | \s+ | ||||||
77 | (?:.*) # Maintainer name | ||||||
78 | \s+ | ||||||
79 | [<\(] | ||||||
80 | (?:.*) # Maintainer email | ||||||
81 | [\)>] | ||||||
82 | | # Old GNU style changelog entry with expanded date | ||||||
83 | (?: | ||||||
84 | \w+\s+ # Day of week (abbreviated) | ||||||
85 | \w+\s+ # Month name (abbreviated) | ||||||
86 | \d{1,2},?\s* # Day of month | ||||||
87 | \d{4} # Year | ||||||
88 | ) | ||||||
89 | \s+ | ||||||
90 | (?:.*) # Maintainer name | ||||||
91 | \s+ | ||||||
92 | [<\(] | ||||||
93 | (?:.*) # Maintainer email | ||||||
94 | [\)>] | ||||||
95 | | # Ancient changelog header w/o key=value options | ||||||
96 | (?:\w[-+0-9a-z.]*) # Package name | ||||||
97 | \Q \E | ||||||
98 | \( | ||||||
99 | (?:[^\(\) \t]+) # Package version | ||||||
100 | \) | ||||||
101 | \;? | ||||||
102 | | # Ancient changelog header | ||||||
103 | (?:[\w.+-]+) # Package name | ||||||
104 | [- ] | ||||||
105 | (?:\S+) # Package version | ||||||
106 | \ Debian | ||||||
107 | \ (?:\S+) # Package revision | ||||||
108 | | | ||||||
109 | Changes\ from\ version\ (?:.*)\ to\ (?:.*): | ||||||
110 | | | ||||||
111 | Changes\ for\ [\w.+-]+-[\w.+-]+:?\s*$ | ||||||
112 | | | ||||||
113 | Old\ Changelog:\s*$ | ||||||
114 | | | ||||||
115 | (?:\d+:)? | ||||||
116 | \w[\w.+~-]*:? | ||||||
117 | \s*$ | ||||||
118 | ) | ||||||
119 | }xi; | ||||||
120 | |||||||
121 - 133 | =head1 METHODS =over 4 =item $count = $c->parse($fh, $description) Read the filehandle and parse a Debian changelog in it, to store the entries as an array of L<Dpkg::Changelog::Entry::Debian> objects. Any previous entries in the object are reset before parsing new data. Returns the number of changelog entries that have been parsed with success. =cut | ||||||
134 | |||||||
135 | sub parse { | ||||||
136 | 84 | 1 | 111 | my ($self, $fh, $file) = @_; | |||
137 | 84 | 445 | $file = $self->{reportfile} if exists $self->{reportfile}; | ||||
138 | |||||||
139 | 84 | 204 | $self->reset_parse_errors; | ||||
140 | |||||||
141 | 84 | 112 | $self->{data} = []; | ||||
142 | 84 | 155 | $self->set_unparsed_tail(undef); | ||||
143 | |||||||
144 | 84 | 84 | my $expect = FIRST_HEADING; | ||||
145 | 84 | 375 | my $entry = Dpkg::Changelog::Entry::Debian->new(); | ||||
146 | 84 | 85 | my @blanklines = (); | ||||
147 | # To make version unique, for example for using as id. | ||||||
148 | 84 | 65 | my $unknowncounter = 1; | ||||
149 | 84 | 79 | local $_; | ||||
150 | |||||||
151 | 84 | 176 | while (<$fh>) { | ||||
152 | 13110 | 9200 | chomp; | ||||
153 | 13110 | 10208 | if (match_header($_)) { | ||||
154 | 957 | 2306 | unless ($expect eq FIRST_HEADING || $expect eq NEXT_OR_EOF) { | ||||
155 | 6 | 11 | $self->parse_error($file, $., | ||||
156 | sprintf(g_('found start of entry where expected %s'), | ||||||
157 | $expect), "$_"); | ||||||
158 | } | ||||||
159 | 957 | 1170 | unless ($entry->is_empty) { | ||||
160 | 873 873 | 513 1107 | push @{$self->{data}}, $entry; | ||||
161 | 873 | 1159 | $entry = Dpkg::Changelog::Entry::Debian->new(); | ||||
162 | 873 | 1291 | last if $self->abort_early(); | ||||
163 | } | ||||||
164 | 957 | 1266 | $entry->set_part('header', $_); | ||||
165 | 957 | 1126 | foreach my $error ($entry->parse_header()) { | ||||
166 | 9 | 21 | $self->parse_error($file, $., $error, $_); | ||||
167 | } | ||||||
168 | 957 | 738 | $expect = START_CHANGES; | ||||
169 | 957 | 1498 | @blanklines = (); | ||||
170 | } elsif (m/^(?:;;\s*)?Local variables:/io) { | ||||||
171 | # Save any trailing Emacs variables at end of file. | ||||||
172 | 0 | 0 | $self->set_unparsed_tail("$_\n" . (file_slurp($fh) // '')); | ||||
173 | 0 | 0 | last; | ||||
174 | } elsif (m/^vim:/io) { | ||||||
175 | # Save any trailing Vim modelines at end of file. | ||||||
176 | 24 | 65 | $self->set_unparsed_tail("$_\n" . (file_slurp($fh) // '')); | ||||
177 | 24 | 29 | last; | ||||
178 | } elsif (m/^\$\w+:.*\$/o) { | ||||||
179 | 0 | 0 | next; # skip stuff that look like a RCS keyword | ||||
180 | } elsif (m/^\# /o) { | ||||||
181 | 0 | 0 | next; # skip comments, even that's not supported | ||||
182 | } elsif (m{^/\*.*\*/}o) { | ||||||
183 | 0 | 0 | next; # more comments | ||||
184 | } elsif (m/$ancient_delimiter_re/) { | ||||||
185 | # save entries on old changelog format verbatim | ||||||
186 | # we assume the rest of the file will be in old format once we | ||||||
187 | # hit it for the first time | ||||||
188 | 6 | 18 | $self->set_unparsed_tail("$_\n" . file_slurp($fh)); | ||||
189 | } elsif (m/^\S/) { | ||||||
190 | 6 | 19 | $self->parse_error($file, $., g_('badly formatted heading line'), "$_"); | ||||
191 | } elsif (match_trailer($_)) { | ||||||
192 | 951 | 913 | unless ($expect eq CHANGES_OR_TRAILER) { | ||||
193 | 0 | 0 | $self->parse_error($file, $., | ||||
194 | sprintf(g_('found trailer where expected %s'), $expect), "$_"); | ||||||
195 | } | ||||||
196 | 951 | 1327 | $entry->set_part('trailer', $_); | ||||
197 | 951 | 1289 | $entry->extend_part('blank_after_changes', [ @blanklines ]); | ||||
198 | 951 | 913 | @blanklines = (); | ||||
199 | 951 | 1079 | foreach my $error ($entry->parse_trailer()) { | ||||
200 | 0 | 0 | $self->parse_error($file, $., $error, $_); | ||||
201 | } | ||||||
202 | 951 | 1605 | $expect = NEXT_OR_EOF; | ||||
203 | } elsif (m/^ \-\-/) { | ||||||
204 | 6 | 13 | $self->parse_error($file, $., g_('badly formatted trailer line'), "$_"); | ||||
205 | } elsif (m/^\s{2,}(?:\S)/) { | ||||||
206 | 8337 | 14099 | unless ($expect eq START_CHANGES or $expect eq CHANGES_OR_TRAILER) { | ||||
207 | 0 | 0 | $self->parse_error($file, $., sprintf(g_('found change data' . | ||||
208 | ' where expected %s'), $expect), "$_"); | ||||||
209 | 0 | 0 | if ($expect eq NEXT_OR_EOF and not $entry->is_empty) { | ||||
210 | # lets assume we have missed the actual header line | ||||||
211 | 0 0 | 0 0 | push @{$self->{data}}, $entry; | ||||
212 | 0 | 0 | $entry = Dpkg::Changelog::Entry::Debian->new(); | ||||
213 | 0 | 0 | $entry->set_part('header', 'unknown (unknown' . ($unknowncounter++) . ') unknown; urgency=unknown'); | ||||
214 | } | ||||||
215 | } | ||||||
216 | # Keep raw changes | ||||||
217 | 8337 | 10577 | $entry->extend_part('changes', [ @blanklines, $_ ]); | ||||
218 | 8337 | 5819 | @blanklines = (); | ||||
219 | 8337 | 8694 | $expect = CHANGES_OR_TRAILER; | ||||
220 | } elsif (!m/\S/) { | ||||||
221 | 2823 | 3185 | if ($expect eq START_CHANGES) { | ||||
222 | 945 | 1274 | $entry->extend_part('blank_after_header', $_); | ||||
223 | 945 | 1290 | next; | ||||
224 | } elsif ($expect eq NEXT_OR_EOF) { | ||||||
225 | 903 | 1131 | $entry->extend_part('blank_after_trailer', $_); | ||||
226 | 903 | 1119 | next; | ||||
227 | } elsif ($expect ne CHANGES_OR_TRAILER) { | ||||||
228 | 0 | 0 | $self->parse_error($file, $., | ||||
229 | sprintf(g_('found blank line where expected %s'), $expect)); | ||||||
230 | } | ||||||
231 | 975 | 1315 | push @blanklines, $_; | ||||
232 | } else { | ||||||
233 | 0 | 0 | $self->parse_error($file, $., g_('unrecognized line'), "$_"); | ||||
234 | 0 | 0 | unless ($expect eq START_CHANGES or $expect eq CHANGES_OR_TRAILER) { | ||||
235 | # lets assume change data if we expected it | ||||||
236 | 0 | 0 | $entry->extend_part('changes', [ @blanklines, $_]); | ||||
237 | 0 | 0 | @blanklines = (); | ||||
238 | 0 | 0 | $expect = CHANGES_OR_TRAILER; | ||||
239 | } | ||||||
240 | } | ||||||
241 | } | ||||||
242 | |||||||
243 | 84 | 131 | unless ($expect eq NEXT_OR_EOF) { | ||||
244 | 0 | 0 | $self->parse_error($file, $., | ||||
245 | sprintf(g_('found end of file where expected %s'), | ||||||
246 | $expect)); | ||||||
247 | } | ||||||
248 | 84 | 112 | unless ($entry->is_empty) { | ||||
249 | 84 84 | 56 133 | push @{$self->{data}}, $entry; | ||||
250 | } | ||||||
251 | |||||||
252 | 84 84 | 69 196 | return scalar @{$self->{data}}; | ||||
253 | } | ||||||
254 | |||||||
255 | 1; | ||||||
256 | |||||||
257 | =back | ||||||
258 | |||||||
259 - 269 | =head1 CHANGES =head2 Version 1.00 (dpkg 1.15.6) Mark the module as public. =head1 SEE ALSO L<Dpkg::Changelog>. =cut |