File Coverage

blib/lib/Dpkg/Changelog/Debian.pm
Criterion Covered Total %
statement 75 96 78.1
branch 34 50 68.0
condition 6 16 37.5
subroutine 9 9 100.0
pod 1 1 100.0
total 125 172 72.6


line stmt bran cond sub pod time code
1             # Copyright © 1996 Ian Jackson
2             # Copyright © 2005 Frank Lichtenheld
3             # Copyright © 2009 Raphaël Hertzog
4             # Copyright © 2012-2017 Guillem Jover
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 .
18              
19             =encoding utf8
20              
21             =head1 NAME
22              
23             Dpkg::Changelog::Debian - parse Debian changelogs
24              
25             =head1 DESCRIPTION
26              
27             This class represents a Debian changelog file as an array of changelog
28             entries (Dpkg::Changelog::Entry::Debian).
29             It implements the generic interface Dpkg::Changelog.
30             Only methods specific to this implementation are described below,
31             the rest are inherited.
32              
33             Dpkg::Changelog::Debian parses Debian changelogs as described in
34             deb-changelog(5).
35              
36             The parser tries to ignore most cruft like # or /* */ style comments,
37             RCS keywords, Vim modelines, Emacs local variables and stuff from
38             older changelogs with other formats at the end of the file.
39             NOTE: most of these are ignored silently currently, there is no
40             parser error issued for them. This should become configurable in the
41             future.
42              
43             =cut
44              
45             package Dpkg::Changelog::Debian;
46              
47 2     2   2955 use strict;
  2         5  
  2         63  
48 2     2   13 use warnings;
  2         4  
  2         85  
49              
50             our $VERSION = '1.00';
51              
52 2     2   10 use Dpkg::Gettext;
  2         5  
  2         161  
53 2     2   13 use Dpkg::File;
  2         4  
  2         120  
54 2     2   13 use Dpkg::Changelog qw(:util);
  2         4  
  2         61  
55 2     2   1996 use Dpkg::Changelog::Entry::Debian qw(match_header match_trailer);
  2         8  
  2         146  
56              
57 2     2   17 use parent qw(Dpkg::Changelog);
  2         4  
  2         11  
58              
59             use constant {
60 2         10 FIRST_HEADING => g_('first heading'),
61             NEXT_OR_EOF => g_('next heading or end of file'),
62             START_CHANGES => g_('start of change data'),
63             CHANGES_OR_TRAILER => g_('more change data or trailer'),
64 2     2   165 };
  2         7  
65              
66             my $ancient_delimiter_re = qr{
67             ^
68             (?: # Ancient GNU style changelog entry with expanded date
69             (?:
70             \w+\s+ # Day of week (abbreviated)
71             \w+\s+ # Month name (abbreviated)
72             \d{1,2} # Day of month
73             \Q \E
74             \d{1,2}:\d{1,2}:\d{1,2}\s+ # Time
75             [\w\s]* # Timezone
76             \d{4} # Year
77             )
78             \s+
79             (?:.*) # Maintainer name
80             \s+
81             [<\(]
82             (?:.*) # Maintainer email
83             [\)>]
84             | # Old GNU style changelog entry with expanded date
85             (?:
86             \w+\s+ # Day of week (abbreviated)
87             \w+\s+ # Month name (abbreviated)
88             \d{1,2},?\s* # Day of month
89             \d{4} # Year
90             )
91             \s+
92             (?:.*) # Maintainer name
93             \s+
94             [<\(]
95             (?:.*) # Maintainer email
96             [\)>]
97             | # Ancient changelog header w/o key=value options
98             (?:\w[-+0-9a-z.]*) # Package name
99             \Q \E
100             \(
101             (?:[^\(\) \t]+) # Package version
102             \)
103             \;?
104             | # Ancient changelog header
105             (?:[\w.+-]+) # Package name
106             [- ]
107             (?:\S+) # Package version
108             \ Debian
109             \ (?:\S+) # Package revision
110             |
111             Changes\ from\ version\ (?:.*)\ to\ (?:.*):
112             |
113             Changes\ for\ [\w.+-]+-[\w.+-]+:?\s*$
114             |
115             Old\ Changelog:\s*$
116             |
117             (?:\d+:)?
118             \w[\w.+~-]*:?
119             \s*$
120             )
121             }xi;
122              
123             =head1 METHODS
124              
125             =over 4
126              
127             =item $count = $c->parse($fh, $description)
128              
129             Read the filehandle and parse a Debian changelog in it, to store the entries
130             as an array of Dpkg::Changelog::Entry::Debian objects.
131             Any previous entries in the object are reset before parsing new data.
132              
133             Returns the number of changelog entries that have been parsed with success.
134              
135             =cut
136              
137             sub parse {
138 16     16 1 48 my ($self, $fh, $file) = @_;
139 16 50       75 $file = $self->{reportfile} if exists $self->{reportfile};
140              
141 16         112 $self->reset_parse_errors;
142              
143 16         41 $self->{data} = [];
144 16         70 $self->set_unparsed_tail(undef);
145              
146 16         35 my $expect = FIRST_HEADING;
147 16         140 my $entry = Dpkg::Changelog::Entry::Debian->new();
148 16         33 my @blanklines = ();
149 16         29 my $unknowncounter = 1; # to make version unique, e.g. for using as id
150 16         29 local $_;
151              
152 16         72 while (<$fh>) {
153 3780         88630 chomp;
154 3780 100       7792 if (match_header($_)) {
    50          
    100          
    50          
    50          
    50          
    100          
    100          
    100          
    100          
    100          
    50          
155 250 100 100     1087 unless ($expect eq FIRST_HEADING || $expect eq NEXT_OR_EOF) {
156 2         10 $self->parse_error($file, $.,
157             sprintf(g_('found start of entry where expected %s'),
158             $expect), "$_");
159             }
160 250 100       669 unless ($entry->is_empty) {
161 234         359 push @{$self->{data}}, $entry;
  234         659  
162 234         673 $entry = Dpkg::Changelog::Entry::Debian->new();
163 234 50       761 last if $self->abort_early();
164             }
165 250         740 $entry->set_part('header', $_);
166 250         610 foreach my $error ($entry->parse_header()) {
167 0         0 $self->parse_error($file, $., $error, $_);
168             }
169 250         412 $expect= START_CHANGES;
170 250         1049 @blanklines = ();
171             } elsif (m/^(?:;;\s*)?Local variables:/io) {
172             # Save any trailing Emacs variables at end of file.
173 0   0     0 $self->set_unparsed_tail("$_\n" . (file_slurp($fh) // ''));
174 0         0 last;
175             } elsif (m/^vim:/io) {
176             # Save any trailing Vim modelines at end of file.
177 2   50     12 $self->set_unparsed_tail("$_\n" . (file_slurp($fh) // ''));
178 2         4 last;
179             } elsif (m/^\$\w+:.*\$/o) {
180 0         0 next; # skip stuff that look like a RCS keyword
181             } elsif (m/^\# /o) {
182 0         0 next; # skip comments, even that's not supported
183             } elsif (m{^/\*.*\*/}o) {
184 0         0 next; # more comments
185             } elsif (m/$ancient_delimiter_re/) {
186             # save entries on old changelog format verbatim
187             # we assume the rest of the file will be in old format once we
188             # hit it for the first time
189 2         17 $self->set_unparsed_tail("$_\n" . file_slurp($fh));
190             } elsif (m/^\S/) {
191 2         12 $self->parse_error($file, $., g_('badly formatted heading line'), "$_");
192             } elsif (match_trailer($_)) {
193 248 50       604 unless ($expect eq CHANGES_OR_TRAILER) {
194 0         0 $self->parse_error($file, $.,
195             sprintf(g_('found trailer where expected %s'), $expect), "$_");
196             }
197 248         755 $entry->set_part('trailer', $_);
198 248         794 $entry->extend_part('blank_after_changes', [ @blanklines ]);
199 248         541 @blanklines = ();
200 248         692 foreach my $error ($entry->parse_trailer()) {
201 0         0 $self->parse_error($file, $., $error, $_);
202             }
203 248         1120 $expect = NEXT_OR_EOF;
204             } elsif (m/^ \-\-/) {
205 2         11 $self->parse_error($file, $., g_('badly formatted trailer line'), "$_");
206             } elsif (m/^\s{2,}(?:\S)/) {
207 2534 50 66     9104 unless ($expect eq START_CHANGES or $expect eq CHANGES_OR_TRAILER) {
208 0         0 $self->parse_error($file, $., sprintf(g_('found change data' .
209             ' where expected %s'), $expect), "$_");
210 0 0 0     0 if ($expect eq NEXT_OR_EOF and not $entry->is_empty) {
211             # lets assume we have missed the actual header line
212 0         0 push @{$self->{data}}, $entry;
  0         0  
213 0         0 $entry = Dpkg::Changelog::Entry::Debian->new();
214 0         0 $entry->set_part('header', 'unknown (unknown' . ($unknowncounter++) . ') unknown; urgency=unknown');
215             }
216             }
217             # Keep raw changes
218 2534         8421 $entry->extend_part('changes', [ @blanklines, $_ ]);
219 2534         4480 @blanklines = ();
220 2534         7438 $expect = CHANGES_OR_TRAILER;
221             } elsif (!m/\S/) {
222 740 100       1851 if ($expect eq START_CHANGES) {
    100          
    50          
223 246         795 $entry->extend_part('blank_after_header', $_);
224 246         1317 next;
225             } elsif ($expect eq NEXT_OR_EOF) {
226 238         742 $entry->extend_part('blank_after_trailer', $_);
227 238         813 next;
228             } elsif ($expect ne CHANGES_OR_TRAILER) {
229 0         0 $self->parse_error($file, $.,
230             sprintf(g_('found blank line where expected %s'), $expect));
231             }
232 256         860 push @blanklines, $_;
233             } else {
234 0         0 $self->parse_error($file, $., g_('unrecognized line'), "$_");
235 0 0 0     0 unless ($expect eq START_CHANGES or $expect eq CHANGES_OR_TRAILER) {
236             # lets assume change data if we expected it
237 0         0 $entry->extend_part('changes', [ @blanklines, $_]);
238 0         0 @blanklines = ();
239 0         0 $expect = CHANGES_OR_TRAILER;
240             }
241             }
242             }
243              
244 16 50       517 unless ($expect eq NEXT_OR_EOF) {
245 0         0 $self->parse_error($file, $.,
246             sprintf(g_('found end of file where expected %s'),
247             $expect));
248             }
249 16 50       64 unless ($entry->is_empty) {
250 16         28 push @{$self->{data}}, $entry;
  16         53  
251             }
252              
253 16         37 return scalar @{$self->{data}};
  16         104  
254             }
255              
256             1;
257             __END__