2 # Parse::DebianChangelog
4 # Copyright 1996 Ian Jackson
5 # Copyright 2005 Frank Lichtenheld <frank@lichtenheld.de>
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24 Parse::DebianChangelog - parse Debian changelogs and output them in other formats
28 use Parse::DebianChangelog;
30 my $chglog = Parse::DebianChangelog->init( { infile => 'debian/changelog',
31 HTML => { outfile => 'changelog.html' } );
34 # the following is semantically equivalent
35 my $chglog = Parse::DebianChangelog->init();
36 $chglog->parse( { infile => 'debian/changelog' } );
37 $chglog->html( { outfile => 'changelog.html' } );
39 my $changes = $chglog->dpkg_str( { since => '1.0-1' } );
44 Parse::DebianChangelog parses Debian changelogs as described in the Debian
45 policy (version 3.6.2.1 at the time of this writing). See section
46 L<"SEE ALSO"> for locations where to find this definition.
48 The parser tries to ignore most cruft like # or /* */ style comments,
49 CVS comments, vim variables, emacs local variables and stuff from
50 older changelogs with other formats at the end of the file.
51 NOTE: most of these are ignored silently currently, there is no
52 parser error issued for them. This should become configurable in the
55 Beside giving access to the details of the parsed file via the
56 L<"data"> method, Parse::DebianChangelog also supports converting these
57 changelogs to various other formats. These are currently:
63 Format as known from L<dpkg-parsechangelog(1)>. All requested entries
64 (see L<"METHODS"> for an explanation what this means) are returned in
65 the usual Debian control format, merged in one stanza, ready to be used
66 in a F<.changes> file.
70 Similar to the C<dpkg> format, but the requested entries are returned
71 as one stanza each, i.e. they are not merged. This is probably the format
72 to use if you want a machine-usable representation of the changelog.
76 Just a simple XML dump of the changelog data. Without any schema or
77 DTD currently, just some made up XML. The actual format might still
78 change. Comments and Improvements welcome.
82 The changelog is converted to a somewhat nice looking HTML file with
83 some nice features as a quick-link bar with direct links to every entry.
84 NOTE: This is not very configurable yet and was specifically designed
85 to be used on L<http://packages.debian.org/>. This is planned to be
86 changed until version 1.0.
94 package Parse::DebianChangelog;
99 use Fcntl qw( :flock );
102 use Parse::DebianChangelog::Util qw( :all );
103 use Parse::DebianChangelog::Entry;
105 our $VERSION = '1.0';
111 Creates a new object instance. Takes a reference to a hash as
112 optional argument, which is interpreted as configuration options.
113 There are currently no supported general configuration options, but
114 see the other methods for more specific configuration options which
115 can also specified to C<init>.
117 If C<infile> or C<instring> are specified (see L<parse>), C<parse()>
118 is called from C<init>. If a fatal error is encountered during parsing
119 (e.g. the file can't be opened), C<init> will not return a
120 valid object but C<undef>!
125 my $classname = shift;
126 my $config = shift || {};
128 bless( $self, $classname );
130 $config->{verbose} = 1 if $config->{debug};
131 $self->{config} = $config;
134 $self->reset_parse_errors;
136 if ($self->{config}{infile} || $self->{config}{instring}) {
137 defined($self->parse) or return undef;
145 =head3 reset_parse_errors
147 Can be used to delete all information about errors ocurred during
148 previous L<parse> runs. Note that C<parse()> also calls this method.
152 sub reset_parse_errors {
155 $self->{errors}{parser} = [];
158 sub _do_parse_error {
159 my ($self, $file, $line_nr, $error, $line) = @_;
162 push @{$self->{errors}{parser}}, [ @_ ];
164 $file = substr $file, 0, 20;
165 unless ($self->{config}{quiet}) {
167 warn "WARN: $file(l$NR): $error\nLINE: $line\n";
169 warn "WARN: $file(l$NR): $error\n";
176 =head3 get_parse_errors
178 Returns all error messages from the last L<parse> run.
179 If called in scalar context returns a human readable
180 string representation. If called in list context returns
181 an array of arrays. Each of these arrays contains
187 the filename of the parsed file or C<String> if a string was
192 the line number where the error occurred
204 NOTE: This format isn't stable yet and may change in later versions
209 sub get_parse_errors {
213 return @{$self->{errors}{parser}};
216 foreach my $e (@{$self->{errors}{parser}}) {
218 $res .= "WARN: $e->[0](l$e->[1]): $e->[2]\nLINE: $e->[3]\n";
220 $res .= "WARN: $e->[0](l$e->[1]): $e->[2]\n";
227 sub _do_fatal_error {
228 my ($self, @msg) = @_;
230 $self->{errors}{fatal} = "@msg";
231 warn "FATAL: @msg\n" unless $self->{config}{quiet};
238 Get the last non-parser error (e.g. the file to parse couldn't be opened).
245 return $self->{errors}{fatal};
252 Parses either the file named in configuration item C<infile> or the string
253 saved in configuration item C<instring>.
254 Accepts a hash ref as optional argument which can contain configuration
257 Returns C<undef> in case of error (e.g. "file not found", B<not> parse
258 errors) and the object if successful. If C<undef> was returned, you
259 can get the reason for the failure by calling the L<get_error> method.
264 my ($self, $config) = @_;
266 foreach my $c (keys %$config) {
267 $self->{config}{$c} = $config->{$c};
271 if ($file = $self->{config}{infile}) {
272 open $fh, '<', $file or do {
273 $self->_do_fatal_error( "can't open file $file: $!" );
276 flock $fh, LOCK_SH or do {
277 $self->_do_fatal_error( "can't lock file $file: $!" );
280 } elsif (my $string = $self->{config}{instring}) {
281 eval { require IO::String };
283 $self->_do_fatal_error( "can't load IO::String: $@" );
286 $fh = IO::String->new( $string );
289 $self->_do_fatal_error( 'no changelog file specified' );
293 $self->reset_parse_errors;
297 # based on /usr/lib/dpkg/parsechangelog/debian
298 my $expect='first heading';
299 my $entry = Parse::DebianChangelog::Entry->init();
301 my $unknowncounter = 1; # to make version unique, e.g. for using as id
305 # printf(STDERR "%-39.39s %-39.39s\n",$expect,$_);
306 if (m/^(\w[-+0-9a-z.]*) \(([^\(\) \t]+)\)((\s+[-0-9a-z]+)+)\;/i) {
307 unless ($expect eq 'first heading'
308 || $expect eq 'next heading or eof') {
309 $entry->{ERROR} = [ $file, $NR,
310 "found start of entry where expected $expect", "$_" ];
311 $self->_do_parse_error(@{$entry->{ERROR}});
313 unless ($entry->is_empty) {
314 $entry->{'Closes'} = find_closes( $entry->{Changes} );
315 # print STDERR, Dumper($entry);
316 push @{$self->{data}}, $entry;
317 $entry = Parse::DebianChangelog::Entry->init();
320 $entry->{'Source'} = $1;
321 $entry->{'Version'} = $2;
322 $entry->{'Header'} = $_;
323 ($entry->{'Distribution'} = $3) =~ s/^\s+//;
324 $entry->{'Changes'} = $entry->{'Urgency_Comment'} = '';
325 $entry->{'Urgency'} = $entry->{'Urgency_LC'} = 'unknown';
327 (my $rhs = $POSTMATCH) =~ s/^\s+//;
329 # print STDERR "RHS: $rhs\n";
330 for my $kv (split(/\s*,\s*/,$rhs)) {
331 $kv =~ m/^([-0-9a-z]+)\=\s*(.*\S)$/i ||
332 $self->_do_parse_error($file, $NR, "bad key-value after \`;': \`$kv'");
335 $kvdone{$k}++ && $self->_do_parse_error($file, $NR,
336 "repeated key-value $k");
337 if ($k eq 'Urgency') {
338 $v =~ m/^([-0-9a-z]+)((\s+.*)?)$/i ||
339 $self->_do_parse_error($file, $NR,
340 "badly formatted urgency value",
342 $entry->{'Urgency'} = $1;
343 $entry->{'Urgency_LC'} = lc($1);
344 $entry->{'Urgency_Comment'} = $2 || '';
345 } elsif ($k =~ m/^X[BCS]+-/i) {
346 # Extensions - XB for putting in Binary,
347 # XC for putting in Control, XS for putting in Source
350 $self->_do_parse_error($file, $NR,
351 "unknown key-value key $k - copying to XS-$k");
352 $entry->{ExtraFields}{"XS-$k"} = $v;
355 $expect= 'start of change data';
357 } elsif (m/^(;;\s*)?Local variables:/io) {
358 last; # skip Emacs variables at end of file
359 } elsif (m/^vim:/io) {
360 last; # skip vim variables at end of file
361 } elsif (m/^\$\w+:.*\$/o) {
362 next; # skip stuff that look like a CVS keyword
364 next; # skip comments, even that's not supported
365 } elsif (m,^/\*.*\*/,o) {
366 next; # more comments
367 } elsif (m/^(\w+\s+\w+\s+\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}\s+[\w\s]*\d{4})\s+(.*)\s+(<|\()(.*)(\)|>)/o
368 || m/^(\w+\s+\w+\s+\d{1,2},?\s*\d{4})\s+(.*)\s+(<|\()(.*)(\)|>)/o
369 || m/^(\w[-+0-9a-z.]*) \(([^\(\) \t]+)\)\;?/io
370 || m/^([\w.+-]+)(-| )(\S+) Debian (\S+)/io
371 || m/^Changes from version (.*) to (.*):/io
372 || m/^Changes for [\w.+-]+-[\w.+-]+:?$/io
373 || m/^Old Changelog:$/io
374 || m/^(?:\d+:)?[\w.+~-]+:?$/o) {
375 # save entries on old changelog format verbatim
376 # we assume the rest of the file will be in old format once we
377 # hit it for the first time
378 $self->{oldformat} = "$_\n";
379 $self->{oldformat} .= join "", <$fh>;
381 $self->_do_parse_error($file, $NR,
382 "badly formatted heading line", "$_");
383 } elsif (m/^ \-\- (.*) <(.*)>( ?)((\w+\,\s*)?\d{1,2}\s+\w+\s+\d{4}\s+\d{1,2}:\d\d:\d\d\s+[-+]\d{4}(\s+\([^\\\(\)]\))?)$/o) {
384 $expect eq 'more change data or trailer' ||
385 $self->_do_parse_error($file, $NR,
386 "found trailer where expected $expect", "$_");
388 $self->_do_parse_error($file, $NR,
389 "badly formatted trailer line", "$_");
391 $entry->{'Trailer'} = $_;
392 $entry->{'Maintainer'} = "$1 <$2>" unless $entry->{'Maintainer'};
393 unless($entry->{'Date'} && $entry->{'Timestamp'}) {
394 $entry->{'Date'} = $4;
395 $entry->{'Timestamp'} = str2time($4)
396 or $self->_do_parse_error( $file, $NR, "couldn't parse date $4" );
398 $expect = 'next heading or eof';
399 } elsif (m/^ \-\-/) {
400 $entry->{ERROR} = [ $file, $NR,
401 "badly formatted trailer line", "$_" ];
402 $self->_do_parse_error(@{$entry->{ERROR}});
403 # $expect = 'next heading or eof'
404 # if $expect eq 'more change data or trailer';
405 } elsif (m/^\s{2,}(\S)/) {
406 $expect eq 'start of change data'
407 || $expect eq 'more change data or trailer'
409 $self->_do_parse_error($file, $NR,
410 "found change data where expected $expect", "$_");
411 if (($expect eq 'next heading or eof')
412 && !$entry->is_empty) {
413 # lets assume we have missed the actual header line
414 $entry->{'Closes'} = find_closes( $entry->{Changes} );
415 # print STDERR, Dumper($entry);
416 push @{$self->{data}}, $entry;
417 $entry = Parse::DebianChangelog::Entry->init();
419 $entry->{Distribution} = $entry->{Urgency} =
420 $entry->{Urgency_LC} = 'unknown';
421 $entry->{Version} = 'unknown'.($unknowncounter++);
422 $entry->{Urgency_Comment} = '';
423 $entry->{ERROR} = [ $file, $NR,
424 "found change data where expected $expect", "$_" ];
427 $entry->{'Changes'} .= (" \n" x $blanklines)." $_\n";
428 if (!$entry->{'Items'} || ($1 eq '*')) {
429 $entry->{'Items'} ||= [];
430 push @{$entry->{'Items'}}, "$_\n";
432 $entry->{'Items'}[-1] .= (" \n" x $blanklines)." $_\n";
435 $expect = 'more change data or trailer';
437 next if $expect eq 'start of change data'
438 || $expect eq 'next heading or eof';
439 $expect eq 'more change data or trailer'
440 || $self->_do_parse_error($file, $NR,
441 "found blank line where expected $expect");
444 $self->_do_parse_error($file, $NR, "unrecognised line", "$_");
445 ($expect eq 'start of change data'
446 || $expect eq 'more change data or trailer')
448 # lets assume change data if we expected it
449 $entry->{'Changes'} .= (" \n" x $blanklines)." $_\n";
450 if (!$entry->{'Items'}) {
451 $entry->{'Items'} ||= [];
452 push @{$entry->{'Items'}}, "$_\n";
454 $entry->{'Items'}[-1] .= (" \n" x $blanklines)." $_\n";
457 $expect = 'more change data or trailer';
458 $entry->{ERROR} = [ $file, $NR, "unrecognised line", "$_" ];
463 $expect eq 'next heading or eof'
465 $entry->{ERROR} = [ $file, $NR, "found eof where expected $expect" ];
466 $self->_do_parse_error( @{$entry->{ERROR}} );
468 unless ($entry->is_empty) {
469 $entry->{'Closes'} = find_closes( $entry->{Changes} );
470 push @{$self->{data}}, $entry;
473 if ($self->{config}{infile}) {
475 $self->_do_fatal_error( "can't close file $file: $!" );
481 # print Dumper( $self );
490 C<data> returns an array (if called in list context) or a reference
491 to an array of Parse::DebianChangelog::Entry objects which each
492 represent one entry of the changelog.
494 This is currently merely a placeholder to enable users to get to the
495 raw data, expect changes to this API in the near future.
497 This method supports the common output options described in
498 section L<"COMMON OUTPUT OPTIONS">.
503 my ($self, $config) = @_;
505 my $data = $self->{data};
507 $self->{config}{DATA} = $config if $config;
508 $data = $self->_data_range( $config ) or return undef;
510 return @$data if wantarray;
514 sub __sanity_check_range {
515 my ( $data, $from, $to, $since, $until, $start, $end ) = @_;
517 if (($$start || $$end) && ($$from || $$since || $$to || $$until)) {
518 warn( "you can't combine 'count' or 'offset' with any other range option\n" );
519 $$from = $$since = $$to = $$until = '';
521 if ($$from && $$since) {
522 warn( "you can only specify one of 'from' and 'since'\n" );
525 if ($$to && $$until) {
526 warn( "you can only specify one of 'to' and 'until'\n" );
529 if ($data->[0]{Version} eq $$since) {
530 warn( "'since' option specifies most recent version\n" );
533 if ($data->[$#{$data}]{Version} eq $$until) {
534 warn( "'until' option specifies oldest version\n" );
537 $$start = 0 if $$start < 0;
538 return if $$start > $#$data;
539 $$end = $#$data if $$end > $#$data;
541 $$end = $$start if $$end < $$start;
542 #TODO: compare versions
547 my ($self, $config) = @_;
549 my $data = $self->data or return undef;
551 return [ @$data ] if $config->{all};
553 my $since = $config->{since} || '';
554 my $until = $config->{until} || '';
555 my $from = $config->{from} || '';
556 my $to = $config->{to} || '';
557 my $count = $config->{count} || 0;
558 my $offset = $config->{offset} || 0;
560 return if $offset and not $count;
562 $offset -= ($count < 0);
563 } elsif ($offset < 0) {
564 $offset = $#$data + ($count > 0) + $offset;
566 $offset = $#$data if $count < 0;
568 my $start = my $end = $offset;
569 $start += $count+1 if $count < 0;
570 $end += $count-1 if $count > 0;
572 return unless __sanity_check_range( $data, \$from, \$to,
577 unless ($from or $to or $since or $until or $start or $end) {
578 return [ @$data ] if $config->{default_all} and not $count;
579 return [ $data->[0] ];
582 return [ @{$data}[$start .. $end] ] if $start or $end;
587 $include = 0 if $to or $until;
589 my $v = $_->{Version};
590 $include = 1 if $v eq $to;
591 last if $v eq $since;
593 push @result, $_ if $include;
595 $include = 1 if $v eq $until;
608 C<dpkg> returns a hash (in list context) or a hash reference
609 (in scalar context) where the keys are field names and the values are
610 field values. The following fields are given:
616 package name (in the first entry)
620 packages' version (from first entry)
624 target distribution (from first entry)
628 urgency (highest of all printed entries)
632 person that created the (first) entry
636 date of the (first) entry
640 bugs closed by the entry/entries, sorted by bug number
644 content of the the entry/entries
648 C<dpkg_str> returns a stringified version of this hash which should look
649 exactly like the output of L<dpkg-parsechangelog(1)>. The fields are
650 ordered like in the list above.
652 Both methods only support the common output options described in
653 section L<"COMMON OUTPUT OPTIONS">.
661 our ( %FIELDIMPS, %URGENCIES );
664 grep($FIELDIMPS{$_}=$i--,
665 qw(Source Version Distribution Urgency Maintainer Date Closes
668 grep($URGENCIES{$_}=$i++,
669 qw(low medium high critical emergency));
673 my ($self, $config) = @_;
675 $self->{config}{DPKG} = $config if $config;
677 $config = $self->{config}{DPKG} || {};
678 my $data = $self->_data_range( $config ) or return undef;
681 foreach my $field (qw( Urgency Source Version
682 Distribution Maintainer Date )) {
683 $f{$field} = $data->[0]{$field};
686 $f{Changes} = get_dpkg_changes( $data->[0] );
687 $f{Closes} = [ @{$data->[0]{Closes}} ];
689 my $first = 1; my $urg_comment = '';
690 foreach my $entry (@$data) {
691 $first = 0, next if $first;
693 my $oldurg = $f{Urgency} || '';
694 my $oldurgn = $URGENCIES{$f{Urgency}} || -1;
695 my $newurg = $entry->{Urgency_LC} || '';
696 my $newurgn = $URGENCIES{$entry->{Urgency_LC}} || -1;
697 $f{Urgency} = ($newurgn > $oldurgn) ? $newurg : $oldurg;
698 $urg_comment .= $entry->{Urgency_Comment};
700 $f{Changes} .= "\n .".get_dpkg_changes( $entry );
701 push @{$f{Closes}}, @{$entry->{Closes}};
704 $f{Closes} = join " ", sort { $a <=> $b } @{$f{Closes}};
705 $f{Urgency} .= $urg_comment;
707 return %f if wantarray;
712 return data2rfc822( scalar dpkg(@_), \%FIELDIMPS );
721 C<rfc822> returns an array of hashes (in list context) or a reference
722 to this array (in scalar context) where each hash represents one entry
723 in the changelog. For the format of such a hash see the description
724 of the L<"dpkg"> method (while ignoring the remarks about which
725 values are taken from the first entry).
727 C<rfc822_str> returns a stringified version of this hash which looks
728 similar to the output of dpkg-parsechangelog but instead of one
729 stanza the output contains one stanza for each entry.
731 Both methods only support the common output options described in
732 section L<"COMMON OUTPUT OPTIONS">.
741 my ($self, $config) = @_;
743 $self->{config}{RFC822} = $config if $config;
745 $config = $self->{config}{RFC822} || {};
746 my $data = $self->_data_range( $config ) or return undef;
749 foreach my $entry (@$data) {
751 foreach my $field (qw( Urgency Source Version
752 Distribution Maintainer Date )) {
753 $f{$field} = $entry->{$field};
756 $f{Urgency} .= $entry->{Urgency_Comment};
757 $f{Changes} = get_dpkg_changes( $entry );
758 $f{Closes} = join " ", sort { $a <=> $b } @{$entry->{Closes}};
762 return @out_data if wantarray;
767 return data2rfc822_mult( scalar rfc822(@_), \%FIELDIMPS );
772 $version =~ s/[^\w.:-]/_/go;
773 return "version$version";
782 C<xml> converts the changelog to some free-form (i.e. there is neither
783 a DTD or a schema for it) XML.
785 The method C<xml_str> is an alias for C<xml>.
787 Both methods support the common output options described in
788 section L<"COMMON OUTPUT OPTIONS"> and additionally the following
789 configuration options (as usual to give
790 in a hash reference as parameter to the method call):
796 directly write the output to the file specified
807 my ($self, $config) = @_;
809 $self->{config}{XML} = $config if $config;
810 $config = $self->{config}{XML} || {};
811 $config->{default_all} = 1 unless exists $config->{all};
812 my $data = $self->_data_range( $config ) or return undef;
814 $out_data{Entry} = [];
817 import XML::Simple qw( :strict );
819 foreach my $entry (@$data) {
821 foreach my $field (qw( Urgency Source Version
822 Distribution Closes )) {
823 $f{$field} = $entry->{$field};
825 foreach my $field (qw( Maintainer Changes )) {
826 $f{$field} = [ $entry->{$field} ];
829 $f{Urgency} .= $entry->{Urgency_Comment};
830 $f{Date} = { timestamp => $entry->{Timestamp},
831 content => $entry->{Date} };
832 push @{$out_data{Entry}}, \%f;
836 my %xml_opts = ( SuppressEmpty => 1, KeyAttr => {},
837 RootName => 'Changelog' );
838 $xml_str = XMLout( \%out_data, %xml_opts );
839 if ($config->{outfile}) {
840 open my $fh, '>', $config->{outfile} or return undef;
841 flock $fh, LOCK_EX or return undef;
845 close $fh or return undef;
861 C<html> converts the changelog to a HTML file with some nice features
862 such as a quick-link bar with direct links to every entry. The HTML
863 is generated with the help of HTML::Template. If you want to change
864 the output you should use the default template provided with this module
865 as a base and read the documentation of HTML::Template to understand
868 The method C<html_str> is an alias for C<html>.
870 Both methods support the common output options described in
871 section L<"COMMON OUTPUT OPTIONS"> and additionally the following
872 configuration options (as usual to give
873 in a hash reference as parameter to the method call):
879 directly write the output to the file specified
883 template file to use, defaults to
884 /usr/share/libparse-debianchangelog-perl/default.tmpl.
885 NOTE: The plan is to provide a configuration file for the module
886 later to be able to use sane defaults here.
890 path to the CSS stylesheet to use (a default might be specified
891 in the template and will be honoured, see the default template
896 path to the CSS stylesheet to use for printing (see the notes for
897 C<style> about default values)
908 my ($self, $config) = @_;
910 $self->{config}{HTML} = $config if $config;
911 $config = $self->{config}{HTML} || {};
912 $config->{default_all} = 1 unless exists $config->{all};
913 my $data = $self->_data_range( $config ) or return undef;
916 import CGI qw( -no_xhtml -no_debug );
917 require HTML::Template;
919 my $template = HTML::Template->new(filename => $config->{template}
920 || '/usr/share/libparse-debianchangelog-perl/default.tmpl',
921 die_on_bad_params => 0);
922 $template->param( MODULE_NAME => ref($self),
923 MODULE_VERSION => $VERSION,
924 GENERATED_DATE => gmtime()." UTC",
925 SOURCE_NEWEST => $data->[0]{Source},
926 VERSION_NEWEST => $data->[0]{Version},
927 MAINTAINER_NEWEST => $data->[0]{Maintainer},
930 $template->param( CSS_FILE_SCREEN => $config->{style} )
932 $template->param( CSS_FILE_PRINT => $config->{print_style} )
933 if $config->{print_style};
940 foreach my $entry (@$data) {
941 my $year = $last_year; # try to deal gracefully with unparsable dates
942 if ($entry->{Timestamp}) {
943 $year = (gmtime($entry->{Timestamp}))[5] + 1900;
947 $year ||= (($entry->{Date} =~ /\s(\d{4})\s/) ? $1 : (gmtime)[5] + 1900);
948 $navigation{$year}{NAV_VERSIONS} ||= [];
949 $navigation{$year}{NAV_YEAR} ||= $year;
951 $entry->{Maintainer} ||= 'unknown';
952 $entry->{Date} ||= 'unknown';
953 push @{$navigation{$year}{NAV_VERSIONS}},
954 { NAV_VERSION_ID => __version2id($entry->{Version}),
955 NAV_VERSION => $entry->{Version},
956 NAV_MAINTAINER => $entry->{Maintainer},
957 NAV_DATE => $entry->{Date} };
960 foreach my $y (reverse sort keys %navigation) {
961 push @nav_years, $navigation{$y};
963 $template->param( OLDFORMAT => (($self->{oldformat}||'') ne ''),
964 NAV_YEARS => \@nav_years );
969 foreach my $entry (@$data) {
970 my $year = $last_year; # try to deal gracefully with unparsable dates
971 if ($entry->{Timestamp}) {
972 $year = (gmtime($entry->{Timestamp}))[5] + 1900;
974 $year ||= (($entry->{Date} =~ /\s(\d{4})\s/) ? $1 : (gmtime)[5] + 1900);
976 if (!$last_year || ($year < $last_year)) {
980 $years{$last_year}{CONTENT_VERSIONS} ||= [];
981 $years{$last_year}{CONTENT_YEAR} ||= $last_year;
983 my $text = $self->apply_filters( 'html::changes',
984 $entry->{Changes}, $cgi );
986 (my $maint_name = $entry->{Maintainer} ) =~ s|<([a-zA-Z0-9_\+\-\.]+\@([a-zA-Z0-9][\w\.+\-]+\.[a-zA-Z]{2,}))>||o;
990 $parse_error = $cgi->p( { -class=>'parse_error' },
991 "(There has been a parse error in the entry above, if some values don't make sense please check the original changelog)" )
994 push @{$years{$last_year}{CONTENT_VERSIONS}}, {
995 CONTENT_VERSION => $entry->{Version},
996 CONTENT_VERSION_ID => __version2id($entry->{Version}),
997 CONTENT_URGENCY => $entry->{Urgency}.$entry->{Urgency_Comment},
998 CONTENT_URGENCY_NORM => $entry->{Urgency_LC},
999 CONTENT_DISTRIBUTION => $entry->{Distribution},
1000 CONTENT_DISTRIBUTION_NORM => lc($entry->{Distribution}),
1001 CONTENT_SOURCE => $entry->{Source},
1002 CONTENT_CHANGES => $text,
1003 CONTENT_CHANGES_UNFILTERED => $entry->{Changes},
1004 CONTENT_DATE => $entry->{Date},
1005 CONTENT_MAINTAINER_NAME => $maint_name,
1006 CONTENT_MAINTAINER_EMAIL => $maint_mail,
1007 CONTENT_PARSE_ERROR => $parse_error,
1011 foreach my $y (reverse sort keys %years) {
1012 push @content_years, $years{$y};
1014 $template->param( OLDFORMAT_CHANGES => $self->{oldformat},
1015 CONTENT_YEARS => \@content_years );
1017 my $html_str = $template->output;
1019 if ($config->{outfile}) {
1020 open my $fh, '>', $config->{outfile} or return undef;
1021 flock $fh, LOCK_EX or return undef;
1023 print $fh $html_str;
1025 close $fh or return undef;
1047 require Parse::DebianChangelog::ChangesFilters;
1049 $self->{filters} = {};
1051 $self->{filters}{'html::changes'} =
1052 [ @Parse::DebianChangelog::ChangesFilters::all_filters ];
1057 =head3 apply_filters
1064 my ($self, $filter_class, $text, $data) = @_;
1066 foreach my $f (@{$self->{filters}{$filter_class}}) {
1067 $text = &$f( $text, $data );
1074 =head3 add_filter, delete_filter, replace_filter
1081 my ($self, $filter_class, $filter, $pos) = @_;
1083 $self->{filters}{$filter_class} ||= [];
1085 push @{$self->{filters}{$filter_class}}, $filter;
1086 } elsif ($pos == 1) {
1087 unshift @{$self->{filters}{$filter_class}}, $filter;
1088 } elsif ($pos > 1) {
1089 my $length = @{$self->{filters}{$filter_class}};
1090 $self->{filters}{$filter_class} =
1091 [ @{$self->{filters}{$filter_class}[0 .. ($pos-2)]}, $filter,
1092 @{$self->{filters}{$filter_class}[($pos-1) .. ($length-1)]} ];
1099 my ($self, $filter_class, $filter) = @_;
1102 unless (ref $filter) {
1105 return delete $self->{filters}{$filter_class}[$pos];
1108 $self->{filters}{$filter_class} ||= [];
1110 for my $i (0 .. $#{$self->{filters}{$filter_class}}) {
1111 push @deleted, delete $self->{filters}{$filter_class}[$i]
1112 if $self->{filters}{$filter_class}[$i] == $filter;
1118 sub replace_filter {
1119 my ($self, $filter_class, $filter, @new_filters) = @_;
1122 unless (ref $filter) {
1125 $self->{filters}{$filter_class} ||= [];
1126 for my $i (0 .. $#{$self->{filters}{$filter_class}}) {
1128 if $self->{filters}{$filter_class}[$i] == $filter;
1132 foreach my $p (@pos) {
1133 $self->delete_filter( $filter_class, $p );
1135 foreach my $f (@new_filters) {
1136 $self->add_filter( $filter_class, $f, $p++);
1146 =head1 COMMON OUTPUT OPTIONS
1148 The following options are supported by all output methods,
1149 all take a version number as value:
1155 Causes changelog information from all versions strictly
1156 later than B<version> to be used.
1158 (works exactly like the C<-v> option of dpkg-parsechangelog).
1162 Causes changelog information from all versions strictly
1163 earlier than B<version> to be used.
1167 Similar to C<since> but also includes the information for the
1168 specified B<version> itself.
1172 Similar to C<until> but also includes the information for the
1173 specified B<version> itself.
1177 The following options also supported by all output methods but
1178 don't take version numbers as values:
1184 If set to a true value, all entries of the changelog are returned,
1185 this overrides all other options. While the XML and HTML formats
1186 default to all == true, this does of course not overwrite other
1187 options unless it is set explicitly with the call.
1191 Expects a signed integer as value. Returns C<value> entries from the
1192 top of the changelog if set to a positive integer, and C<abs(value)>
1193 entries from the tail if set to a negative integer.
1197 Expects a signed integer as value. Changes the starting point for
1198 C<count>, either counted from the top (positive integer) or from
1199 the tail (negative integer). C<offset> has no effect if C<count>
1200 wasn't given as well.
1204 Some examples for the above options. Imagine an example changelog with
1205 entries for the versions 1.2, 1.3, 2.0, 2.1, 2.2, 3.0 and 3.1.
1207 Call Included entries
1208 C<E<lt>formatE<gt>({ since =E<gt> '2.0' })> 3.1, 3.0, 2.2
1209 C<E<lt>formatE<gt>({ until =E<gt> '2.0' })> 1.3, 1.2
1210 C<E<lt>formatE<gt>({ from =E<gt> '2.0' })> 3.1, 3.0, 2.2, 2.1, 2.0
1211 C<E<lt>formatE<gt>({ to =E<gt> '2.0' })> 2.0, 1.3, 1.2
1212 C<E<lt>formatE<gt>({ count =E<gt> 2 }>> 3.1, 3.0
1213 C<E<lt>formatE<gt>({ count =E<gt> -2 }>> 1.3, 1.2
1214 C<E<lt>formatE<gt>({ count =E<gt> 3,
1215 offset=E<gt> 2 }>> 2.2, 2.1, 2.0
1216 C<E<lt>formatE<gt>({ count =E<gt> 2,
1217 offset=E<gt> -3 }>> 2.0, 1.3
1218 C<E<lt>formatE<gt>({ count =E<gt> -2,
1219 offset=E<gt> 3 }>> 3.0, 2.2
1220 C<E<lt>formatE<gt>({ count =E<gt> -2,
1221 offset=E<gt> -3 }>> 2.2, 2.1
1223 Any combination of one option of C<since> and C<from> and one of
1224 C<until> and C<to> returns the intersection of the two results
1225 with only one of the options specified.
1229 Parse::DebianChangelog::Entry, Parse::DebianChangelog::ChangesFilters
1231 Description of the Debian changelog format in the Debian policy:
1232 L<http://www.debian.org/doc/debian-policy/ch-source.html#s-dpkgchangelog>.
1236 Frank Lichtenheld, E<lt>frank@lichtenheld.deE<gt>
1238 =head1 COPYRIGHT AND LICENSE
1240 Copyright (C) 2005 by Frank Lichtenheld
1242 This program is free software; you can redistribute it and/or modify
1243 it under the terms of the GNU General Public License as published by
1244 the Free Software Foundation; either version 2 of the License, or
1245 (at your option) any later version.
1247 This program is distributed in the hope that it will be useful,
1248 but WITHOUT ANY WARRANTY; without even the implied warranty of
1249 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1250 GNU General Public License for more details.
1252 You should have received a copy of the GNU General Public License
1253 along with this program; if not, write to the Free Software
1254 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA