1 # Copyright © 2005, 2007 Frank Lichtenheld <frank@lichtenheld.de>
2 # Copyright © 2009 Raphaël Hertzog <hertzog@debian.org>
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <https://www.gnu.org/licenses/>.
21 Dpkg::Changelog - base class to implement a changelog parser
25 Dpkg::Changelog is a class representing a changelog file
26 as an array of changelog entries (Dpkg::Changelog::Entry).
27 By deriving this class and implementing its parse method, you
28 add the ability to fill this object with changelog entries.
32 package Dpkg
::Changelog
2.00;
40 use Dpkg
::ErrorHandling
qw(:DEFAULT report REPORT_WARN);
42 use Dpkg
::Control
::Changelog
;
43 use Dpkg
::Control
::Fields
;
46 use Dpkg
::Vendor
qw(run_vendor_hook);
48 use parent
qw(Dpkg::Interface::Storable);
51 '@{}' => sub { return $_[0]->{data
} };
57 =item $c = Dpkg::Changelog->new(%options)
59 Creates a new changelog object.
64 my ($this, %opts) = @_;
65 my $class = ref($this) || $this;
71 $self->set_options(%opts);
75 =item $c->set_options(%opts)
77 Change the value of some options. "verbose" (defaults to 1) defines
78 whether parse errors are displayed as warnings by default. "reportfile"
79 is a string to use instead of the name of the file parsed, in particular
80 in error messages. "range" defines the range of entries that we want to
81 parse, the parser will stop as soon as it has parsed enough data to
82 satisfy $c->get_range($opts{range}).
87 my ($self, %opts) = @_;
88 $self->{$_} = $opts{$_} foreach keys %opts;
91 =item $count = $c->parse($fh, $description)
93 Read the filehandle and parse a changelog in it. The data in the object is
94 reset before parsing new data.
96 Returns the number of changelog entries that have been parsed with success.
98 This method needs to be implemented by one of the specialized changelog
101 =item $count = $c->load($filename)
103 Parse $filename contents for a changelog.
105 Returns the number of changelog entries that have been parsed with success.
107 =item $c->reset_parse_errors()
109 Can be used to delete all information about errors occurred during
110 previous L<parse> runs.
114 sub reset_parse_errors
{
116 $self->{parse_errors
} = [];
119 =item $c->parse_error($file, $line_nr, $error, [$line])
121 Record a new parse error in $file at line $line_nr. The error message is
122 specified with $error and a copy of the line can be recorded in $line.
127 my ($self, $file, $line_nr, $error, $line) = @_;
129 push @
{$self->{parse_errors
}}, [ $file, $line_nr, $error, $line ];
131 if ($self->{verbose
}) {
133 warning
("%20s(l$line_nr): $error\nLINE: $line", $file);
135 warning
("%20s(l$line_nr): $error", $file);
140 =item $c->get_parse_errors()
142 Returns all error messages from the last L<parse> run.
143 If called in scalar context returns a human readable
144 string representation. If called in list context returns
145 an array of arrays. Each of these arrays contains
151 a string describing the origin of the data (a filename usually). If the
152 reportfile configuration option was given, its value will be used instead.
156 the line number where the error occurred
170 sub get_parse_errors
{
174 return @
{$self->{parse_errors
}};
177 foreach my $e (@
{$self->{parse_errors
}}) {
179 $res .= report
(REPORT_WARN
, g_
("%s(l%s): %s\nLINE: %s"), @
$e);
181 $res .= report
(REPORT_WARN
, g_
('%s(l%s): %s'), @
$e);
188 =item $c->set_unparsed_tail($tail)
190 Add a string representing unparsed lines after the changelog entries.
191 Use undef as $tail to remove the unparsed lines currently set.
193 =item $c->get_unparsed_tail()
195 Return a string representing the unparsed lines after the changelog
196 entries. Returns undef if there's no such thing.
200 sub set_unparsed_tail
{
201 my ($self, $tail) = @_;
202 $self->{unparsed_tail
} = $tail;
205 sub get_unparsed_tail
{
207 return $self->{unparsed_tail
};
212 Returns all the Dpkg::Changelog::Entry objects contained in this changelog
213 in the order in which they have been parsed.
215 =item $c->get_range($range)
217 Returns an array (if called in list context) or a reference to an array of
218 Dpkg::Changelog::Entry objects which each represent one entry of the
219 changelog. $range is a hash reference describing the range of entries
220 to return. See section L<"RANGE SELECTION">.
224 sub _sanitize_range
{
226 my $data = $self->{data
};
228 if (defined($r->{offset
}) and not defined($r->{count
})) {
229 warning
(g_
("'offset' without 'count' has no effect")) if $self->{verbose
};
233 ## no critic (ControlStructures::ProhibitUntilBlocks)
234 if ((defined($r->{count
}) || defined($r->{offset
})) &&
235 (defined($r->{from
}) || defined($r->{since
}) ||
236 defined($r->{to
}) || defined($r->{until})))
238 warning
(g_
("you can't combine 'count' or 'offset' with any other " .
239 'range option')) if $self->{verbose
};
245 if (defined($r->{from
}) && defined($r->{since
})) {
246 warning
(g_
("you can only specify one of 'from' and 'since', using " .
247 "'since'")) if $self->{verbose
};
250 if (defined($r->{to
}) && defined($r->{until})) {
251 warning
(g_
("you can only specify one of 'to' and 'until', using " .
252 "'until'")) if $self->{verbose
};
256 # Handle non-existing versions
257 my (%versions, @versions);
258 foreach my $entry (@
{$data}) {
259 my $version = $entry->get_version();
260 next unless defined $version;
261 $versions{$version->as_string()} = 1;
262 push @versions, $version->as_string();
264 if ((defined($r->{since
}) and not exists $versions{$r->{since
}})) {
265 warning
(g_
("'%s' option specifies non-existing version '%s'"), 'since', $r->{since
});
266 warning
(g_
('use newest entry that is earlier than the one specified'));
267 foreach my $v (@versions) {
268 if (version_compare_relation
($v, REL_LT
, $r->{since
})) {
273 if (not exists $versions{$r->{since
}}) {
274 # No version was earlier, include all
275 warning
(g_
('none found, starting from the oldest entry'));
277 $r->{from
} = $versions[-1];
280 if ((defined($r->{from
}) and not exists $versions{$r->{from
}})) {
281 warning
(g_
("'%s' option specifies non-existing version '%s'"), 'from', $r->{from
});
282 warning
(g_
('use oldest entry that is later than the one specified'));
284 foreach my $v (@versions) {
285 if (version_compare_relation
($v, REL_GT
, $r->{from
})) {
289 if (defined($oldest)) {
290 $r->{from
} = $oldest;
292 warning
(g_
("no such entry found, ignoring '%s' parameter '%s'"), 'from', $r->{from
});
293 delete $r->{from
}; # No version was oldest
296 if (defined($r->{until}) and not exists $versions{$r->{until}}) {
297 warning
(g_
("'%s' option specifies non-existing version '%s'"), 'until', $r->{until});
298 warning
(g_
('use oldest entry that is later than the one specified'));
300 foreach my $v (@versions) {
301 if (version_compare_relation
($v, REL_GT
, $r->{until})) {
305 if (defined($oldest)) {
306 $r->{until} = $oldest;
308 warning
(g_
("no such entry found, ignoring '%s' parameter '%s'"), 'until', $r->{until});
309 delete $r->{until}; # No version was oldest
312 if (defined($r->{to
}) and not exists $versions{$r->{to
}}) {
313 warning
(g_
("'%s' option specifies non-existing version '%s'"), 'to', $r->{to
});
314 warning
(g_
('use newest entry that is earlier than the one specified'));
315 foreach my $v (@versions) {
316 if (version_compare_relation
($v, REL_LT
, $r->{to
})) {
321 if (not exists $versions{$r->{to
}}) {
322 # No version was earlier
323 warning
(g_
("no such entry found, ignoring '%s' parameter '%s'"), 'to', $r->{to
});
328 if (defined($r->{since
}) and $data->[0]->get_version() eq $r->{since
}) {
329 warning
(g_
("'since' option specifies most recent version '%s', ignoring"), $r->{since
});
332 if (defined($r->{until}) and $data->[-1]->get_version() eq $r->{until}) {
333 warning
(g_
("'until' option specifies oldest version '%s', ignoring"), $r->{until});
340 my ($self, $range) = @_;
342 my $res = $self->_data_range($range);
343 return unless defined $res;
345 return reverse @
{$res} if $range->{reverse};
353 my ($self, $range) = @_;
355 return 1 if $range->{all
};
357 # If no range delimiter is specified, we want everything.
358 foreach my $delim (qw(since until from to count offset)) {
359 return 0 if exists $range->{$delim};
366 my ($self, $range) = @_;
368 my $data = $self->{data
} or return;
370 return [ @
$data ] if $self->_is_full_range($range);
372 $self->_sanitize_range($range);
375 if (defined($range->{count
})) {
376 my $offset = $range->{offset
} // 0;
377 my $count = $range->{count
};
378 # Convert count/offset in start/end
380 $offset -= ($count < 0);
381 } elsif ($offset < 0) {
382 $offset = $#$data + ($count > 0) + $offset;
384 $offset = $#$data if $count < 0;
386 $start = $end = $offset;
387 $start += $count+1 if $count < 0;
388 $end += $count-1 if $count > 0;
390 $start = 0 if $start < 0;
391 return if $start > $#$data;
392 $end = $#$data if $end > $#$data;
394 $end = $start if $end < $start;
395 return [ @
{$data}[$start .. $end] ];
398 ## no critic (ControlStructures::ProhibitUntilBlocks)
401 $include = 0 if defined($range->{to
}) or defined($range->{until});
402 foreach my $entry (@
{$data}) {
403 my $v = $entry->get_version();
404 $include = 1 if defined($range->{to
}) and $v eq $range->{to
};
405 last if defined($range->{since
}) and $v eq $range->{since
};
407 push @result, $entry if $include;
409 $include = 1 if defined($range->{until}) and $v eq $range->{until};
410 last if defined($range->{from
}) and $v eq $range->{from
};
414 return \
@result if scalar(@result);
418 =item $c->abort_early()
420 Returns true if enough data have been parsed to be able to return all
421 entries selected by the range set at creation (or with set_options).
428 my $data = $self->{data
} or return;
429 my $r = $self->{range
} or return;
430 my $count = $r->{count
} // 0;
431 my $offset = $r->{offset
} // 0;
433 return if $self->_is_full_range($r);
434 return if $offset < 0 or $count < 0;
435 if (defined($r->{count
})) {
437 $offset -= ($count < 0);
439 my $start = my $end = $offset;
440 $end += $count-1 if $count > 0;
441 return $start < @
{$data} > $end;
444 return unless defined($r->{since
}) or defined($r->{from
});
445 foreach my $entry (@
{$data}) {
446 my $v = $entry->get_version();
447 return 1 if defined($r->{since
}) and $v eq $r->{since
};
448 return 1 if defined($r->{from
}) and $v eq $r->{from
};
454 =item $str = $c->output()
458 Returns a string representation of the changelog (it's a concatenation of
459 the string representation of the individual changelog entries).
461 =item $c->output($fh)
463 Output the changelog to the given filehandle.
468 my ($self, $fh) = @_;
470 foreach my $entry (@
{$self}) {
471 my $text = $entry->output();
472 print { $fh } $text if defined $fh;
473 $str .= $text if defined wantarray;
475 my $text = $self->get_unparsed_tail();
477 print { $fh } $text if defined $fh;
478 $str .= $text if defined wantarray;
483 =item $c->save($filename)
485 Save the changelog in the given file.
489 our ( @URGENCIES, %URGENCIES );
499 %URGENCIES = map { $_ => $i++ } @URGENCIES;
503 my ($self, $range) = @_;
505 my @data = $self->get_range($range) or return;
506 my $src = shift @data;
508 my $c = Dpkg
::Control
::Changelog
->new();
509 $c->{Urgency
} = $src->get_urgency() || 'unknown';
510 $c->{Source
} = $src->get_source() || 'unknown';
511 $c->{Version
} = $src->get_version() // 'unknown';
512 $c->{Distribution
} = join ' ', $src->get_distributions();
513 $c->{Maintainer
} = $src->get_maintainer() // '';
514 $c->{Date
} = $src->get_timestamp() // '';
515 $c->{Timestamp
} = $src->get_timepiece && $src->get_timepiece->epoch // '';
516 $c->{Changes
} = $src->get_dpkg_changes();
518 # handle optional fields
519 my $opts = $src->get_optional_fields();
521 foreach my $f (keys %{$opts}) {
522 if ($f eq 'Urgency') {
524 } elsif ($f eq 'Closes') {
525 $closes{$_} = 1 foreach (split(/\s+/, $opts->{Closes
}));
527 field_transfer_single
($opts, $c, $f);
531 foreach my $bin (@data) {
532 my $oldurg = $c->{Urgency
} // '';
533 my $oldurgn = $URGENCIES{$c->{Urgency
}} // -1;
534 my $newurg = $bin->get_urgency() // '';
535 my $newurgn = $URGENCIES{$newurg} // -1;
536 $c->{Urgency
} = ($newurgn > $oldurgn) ?
$newurg : $oldurg;
537 $c->{Changes
} .= "\n" . $bin->get_dpkg_changes();
539 # handle optional fields
540 $opts = $bin->get_optional_fields();
541 foreach my $f (keys %{$opts}) {
542 if ($f eq 'Closes') {
543 $closes{$_} = 1 foreach (split(/\s+/, $opts->{Closes
}));
544 } elsif (not exists $c->{$f}) {
545 # Don't overwrite an existing field
546 field_transfer_single
($opts, $c, $f);
551 if (scalar keys %closes) {
552 $c->{Closes
} = join ' ', sort { $a <=> $b } keys %closes;
554 run_vendor_hook
('post-process-changelog-entry', $c);
560 my ($self, $range) = @_;
562 my @data = $self->get_range($range) or return;
565 foreach my $entry (@data) {
566 my $c = Dpkg
::Control
::Changelog
->new();
567 $c->{Urgency
} = $entry->get_urgency() || 'unknown';
568 $c->{Source
} = $entry->get_source() || 'unknown';
569 $c->{Version
} = $entry->get_version() // 'unknown';
570 $c->{Distribution
} = join ' ', $entry->get_distributions();
571 $c->{Maintainer
} = $entry->get_maintainer() // '';
572 $c->{Date
} = $entry->get_timestamp() // '';
573 $c->{Timestamp
} = $entry->get_timepiece && $entry->get_timepiece->epoch // '';
574 $c->{Changes
} = $entry->get_dpkg_changes();
576 # handle optional fields
577 my $opts = $entry->get_optional_fields();
578 foreach my $f (keys %{$opts}) {
579 field_transfer_single
($opts, $c, $f) unless exists $c->{$f};
582 run_vendor_hook
('post-process-changelog-entry', $c);
590 =item $control = $c->format_range($format, $range)
592 Formats the changelog into Dpkg::Control::Changelog objects representing the
593 entries selected by the optional range specifier (see L<"RANGE SELECTION">
594 for details). In scalar context returns a Dpkg::Index object containing the
595 selected entries, in list context returns an array of Dpkg::Control::Changelog
598 With format B<dpkg> the returned Dpkg::Control::Changelog object is coalesced
599 from the entries in the changelog that are part of the range requested,
600 with the fields described below, but considering that "selected entry"
601 means the first entry of the selected range.
603 With format B<rfc822> each returned Dpkg::Control::Changelog objects
604 represents one entry in the changelog that is part of the range requested,
605 with the fields described below, but considering that "selected entry"
606 means for each entry.
608 The different formats return undef if no entries are matched. The following
609 fields are contained in the object(s) returned:
615 package name (selected entry)
619 packages' version (selected entry)
623 target distribution (selected entry)
627 urgency (highest of all entries in range)
631 person that created the (selected) entry
635 date of the (selected) entry
639 date of the (selected) entry as a timestamp in seconds since the epoch
643 bugs closed by the (selected) entry/entries, sorted by bug number
647 content of the (selected) entry/entries
654 my ($self, $format, $range) = @_;
658 if ($format eq 'dpkg') {
659 @ctrl = $self->_format_dpkg($range);
660 } elsif ($format eq 'rfc822') {
661 @ctrl = $self->_format_rfc822($range);
663 croak
"unknown changelog output format $format";
669 my $index = Dpkg
::Index
->new(type
=> CTRL_CHANGELOG
);
671 foreach my $c (@ctrl) {
681 =head1 RANGE SELECTION
683 A range selection is described by a hash reference where
684 the allowed keys and values are described below.
686 The following options take a version number as value.
692 Causes changelog information from all versions strictly
693 later than B<version> to be used.
697 Causes changelog information from all versions strictly
698 earlier than B<version> to be used.
702 Similar to C<since> but also includes the information for the
703 specified B<version> itself.
707 Similar to C<until> but also includes the information for the
708 specified B<version> itself.
712 The following options don't take version numbers as values:
718 If set to a true value, all entries of the changelog are returned,
719 this overrides all other options.
723 Expects a signed integer as value. Returns C<value> entries from the
724 top of the changelog if set to a positive integer, and C<abs(value)>
725 entries from the tail if set to a negative integer.
729 Expects a signed integer as value. Changes the starting point for
730 C<count>, either counted from the top (positive integer) or from
731 the tail (negative integer). C<offset> has no effect if C<count>
732 wasn't given as well.
736 Some examples for the above options. Imagine an example changelog with
737 entries for the versions 1.2, 1.3, 2.0, 2.1, 2.2, 3.0 and 3.1.
739 Range Included entries
740 ----- ----------------
741 since => '2.0' 3.1, 3.0, 2.2
742 until => '2.0' 1.3, 1.2
743 from => '2.0' 3.1, 3.0, 2.2, 2.1, 2.0
744 to => '2.0' 2.0, 1.3, 1.2
747 count => 3, offset => 2 2.2, 2.1, 2.0
748 count => 2, offset => -3 2.0, 1.3
749 count => -2, offset => 3 3.0, 2.2
750 count => -2, offset => -3 2.2, 2.1
752 Any combination of one option of C<since> and C<from> and one of
753 C<until> and C<to> returns the intersection of the two results
754 with only one of the options specified.
758 =head2 Version 2.00 (dpkg 1.20.0)
760 Remove methods: $c->dpkg(), $c->rfc822().
762 =head2 Version 1.01 (dpkg 1.18.8)
764 New method: $c->format_range().
766 Deprecated methods: $c->dpkg(), $c->rfc822().
768 New field Timestamp in output formats.
770 =head2 Version 1.00 (dpkg 1.15.6)
772 Mark the module as public.