The following commit has been merged in the master branch:
commit bb4611eb5cb1571af9886f33c61dc6305284c6cc
Author: Raphaël Hertzog <[email protected]>
Date: Sun Oct 25 23:19:42 2009 +0100
Dpkg::Changelog: completely update the API
The previous API was too different from the other modules inside dpkg-dev.
Dpkg::Changelog now behaves as an array of Dpkg::Changelog::Entry
and the documentation explains how it can be derived to create a new
changelog parser.
The non-regression tests as well as the official Debian
changelog parser script had to be updated to cope with the new API.
diff --git a/scripts/Dpkg/Changelog.pm b/scripts/Dpkg/Changelog.pm
index 03b43d9..3fdb87e 100644
--- a/scripts/Dpkg/Changelog.pm
+++ b/scripts/Dpkg/Changelog.pm
@@ -1,6 +1,3 @@
-#
-# Dpkg::Changelog
-#
# Copyright © 2005, 2007 Frank Lichtenheld <[email protected]>
# Copyright © 2009 Raphaël Hertzog <[email protected]>
#
@@ -21,13 +18,16 @@
=head1 NAME
-Dpkg::Changelog
+Dpkg::Changelog - base class to implement a changelog parser
=head1 DESCRIPTION
-FIXME: to be written
+Dpkg::Changelog is a class representing a changelog file
+as an array of changelog entries (Dpkg::Changelog::Entry).
+By deriving this object and implementing its parse method, you
+add the ability to fill this object with changelog entries.
-=head2 Functions
+=head2 FUNCTIONS
=cut
@@ -39,90 +39,102 @@ use warnings;
use Dpkg;
use Dpkg::Gettext;
use Dpkg::ErrorHandling qw(:DEFAULT report);
+use Dpkg::Control;
use Dpkg::Control::Changelog;
use Dpkg::Control::Fields;
+use Dpkg::Index;
use Dpkg::Version;
use Dpkg::Vendor qw(run_vendor_hook);
-use base qw(Exporter);
+use overload
+ '""' => sub { return $_[0]->output() },
+ '@{}' => sub { return $_[0]->{data} };
+
+=over 4
+
+=item my $c = Dpkg::Changelog->new(%options)
-our %EXPORT_TAGS = ( 'util' => [ qw(
- data2rfc822
- data2rfc822_mult
- get_dpkg_changes
-) ] );
-our @EXPORT_OK = @{$EXPORT_TAGS{util}};
+Creates a new changelog object.
-=pod
+=cut
-=head3 init
+sub new {
+ my ($this, %opts) = @_;
+ my $class = ref($this) || $this;
+ my $self = {
+ verbose => 1,
+ parse_errors => []
+ };
+ bless $self, $class;
+ $self->set_options(%opts);
+ return $self;
+}
-Creates a new object instance. Takes a reference to a hash as
-optional argument, which is interpreted as configuration options.
-There are currently no supported general configuration options, but
-see the other methods for more specific configuration options which
-can also specified to C<init>.
+=item $c->load($filename)
-If C<infile>, C<inhandle>, or C<instring> are specified, C<parse()>
-is called from C<init>. If a fatal error is encountered during parsing
-(e.g. the file can't be opened), C<init> will not return a
-valid object but C<undef>!
+Parse $filename as a changelog.
=cut
-sub init {
- my $classname = shift;
- my $config = shift || {};
- my $self = {};
- bless( $self, $classname );
+sub load {
+ my ($self, $file) = @_;
+ open(my $fh, "<", $file) or syserr(_g("cannot read %s"), $file);
+ my $ret = $self->parse($fh, $file);
+ close($fh);
+ return $ret;
+}
- $config->{verbose} = 1 if $config->{debug};
- $self->{config} = $config;
+=item $c->set_options(%opts)
- $self->reset_parse_errors;
+Change the value of some options. "verbose" (defaults to 1) defines
+whether parse errors are displayed as warnings by default. "reportfile"
+is a string to use instead of the name of the file parsed, in particular
+in error messages. "range" defines the range of entries that we want to
+parse, the parser will stop as soon as it has parsed enough data to
+satisfy $c->get_range($opts{'range'}).
- if ($self->{config}{infile}
- || $self->{config}{inhandle}
- || $self->{config}{instring}) {
- defined($self->parse) or return undef;
- }
+=cut
- return $self;
+sub set_options {
+ my ($self, %opts) = @_;
+ $self->{$_} = $opts{$_} foreach keys %opts;
}
-=pod
-
-=head3 reset_parse_errors
+=item $c->reset_parse_errors()
Can be used to delete all information about errors ocurred during
-previous L<parse> runs. Note that C<parse()> also calls this method.
+previous L<parse> runs.
=cut
sub reset_parse_errors {
my ($self) = @_;
-
- $self->{errors}{parser} = [];
+ $self->{parse_errors} = [];
}
-sub _do_parse_error {
+=item $c->parse_error($line_nr, $error, [$line])
+
+Record a new parse error at line $line_nr. The error message is specified
+with $error and a copy of the line can be recorded in $line.
+
+=cut
+
+sub parse_error {
my ($self, $file, $line_nr, $error, $line) = @_;
shift;
- push @{$self->{errors}{parser}}, [ @_ ];
+ push @{$self->{parse_errors}}, [ @_ ];
- unless ($self->{config}{quiet}) {
+ if ($self->{verbose}) {
if ($line) {
- warning("%20s(l$.): $error\nLINE: $line", $file);
+ warning("%20s(l$line_nr): $error\nLINE: $line", $file);
} else {
- warning("%20s(l$.): $error", $file);
+ warning("%20s(l$line_nr): $error", $file);
}
}
}
-=pod
-
-=head3 get_parse_errors
+=item $c->get_parse_errors()
Returns all error messages from the last L<parse> run.
If called in scalar context returns a human readable
@@ -133,10 +145,8 @@ an array of arrays. Each of these arrays contains
=item 1.
-the filename of the parsed file or C<FileHandle> or C<String>
-if the input came from a file handle or a string. If the
-reportfile configuration option was given, its value will be
-used instead
+a string describing the origin of the data (a filename usually). If the
+reportfile configuration option was given, its value will be used instead.
=item 2.
@@ -152,19 +162,16 @@ the original line
=back
-NOTE: This format isn't stable yet and may change in later versions
-of this module.
-
=cut
sub get_parse_errors {
my ($self) = @_;
if (wantarray) {
- return @{$self->{errors}{parser}};
+ return @{$self->{parse_errors}};
} else {
my $res = "";
- foreach my $e (@{$self->{errors}{parser}}) {
+ foreach my $e (@{$self->{parse_errors}}) {
if ($e->[3]) {
$res .= report(_g('warning'),_g("%s(l%s): %s\nLINE: %s"), @$e );
} else {
@@ -175,74 +182,72 @@ sub get_parse_errors {
}
}
-sub _do_fatal_error {
- my ($self, $msg, @msg) = @_;
-
- $self->{errors}{fatal} = report(_g('fatal error'), $msg, @msg);
- warning($msg, @msg) unless $self->{config}{quiet};
-}
+=item $c->set_unparsed_tail($tail)
-=pod
+Add a string representing unparsed lines after the changelog entries.
+Use undef as $tail to remove the unparsed lines currently set.
-=head3 get_error
+=item $c->get_unparsed_tail()
-Get the last non-parser error (e.g. the file to parse couldn't be opened).
+Return a string representing the unparsed lines after the changelog
+entries. Returns undef if there's no such thing.
=cut
-sub get_error {
- my ($self) = @_;
+sub set_unparsed_tail {
+ my ($self, $tail) = @_;
+ $self->{'unparsed_tail'} = $tail;
+}
- return $self->{errors}{fatal};
+sub get_unparsed_tail {
+ my ($self) = @_;
+ return $self->{'unparsed_tail'};
}
-=pod
+=item @{$c}
-=head3 data
+Returns all the Dpkg::Changelog::Entry objects contained in this changelog
+in the order in which they have been parsed.
-C<data> returns an array (if called in list context) or a reference
-to an array of Dpkg::Changelog::Entry objects which each
-represent one entry of the changelog.
+=item $c->get_range($range)
-This method supports the common output options described in
-section L<"COMMON OUTPUT OPTIONS">.
+Returns an array (if called in list context) or a reference to an array of
+Dpkg::Changelog::Entry objects which each represent one entry of the
+changelog. $range is a hash reference describing the range of entries
+to return. See section L<"RANGE SELECTION">.
=cut
-sub data {
- my ($self, $config) = @_;
-
+sub __sanity_check_range {
+ my ($self, $r) = @_;
my $data = $self->{data};
- if ($config) {
- $self->{config}{DATA} = $config if $config;
- $data = $self->_data_range( $config ) or return undef;
- }
- return @$data if wantarray;
- return $data;
-}
-sub __sanity_check_range {
- my ( $data, $from, $to, $since, $until, $start, $end ) = @_;
+ if (defined($r->{offset}) and not defined($r->{count})) {
+ warning(_g("'offset' without 'count' has no effect")) if
$self->{verbose};
+ delete $r->{offset};
+ }
- if (($$start || $$end) &&
- (length($$from) || length($$since) || length($$to) || length($$until)))
+ if ((defined($r->{count}) || defined($r->{offset})) &&
+ (defined($r->{from}) || defined($r->{since}) ||
+ defined($r->{to}) || defined($r->{'until'})))
{
- warning(_g( "you can't combine 'count' or 'offset' with any other range
option" ));
- $$from = $$since = $$to = $$until = '';
+ warning(_g("you can't combine 'count' or 'offset' with any other " .
+ "range option")) if $self->{verbose};
+ delete $r->{from};
+ delete $r->{since};
+ delete $r->{to};
+ delete $r->{'until'};
}
- if (length($$from) && length($$since)) {
- warning(_g( "you can only specify one of 'from' and 'since', using
'since'" ));
- $$from = '';
+ if (defined($r->{from}) && defined($r->{since})) {
+ warning(_g("you can only specify one of 'from' and 'since', using " .
+ "'since'")) if $self->{verbose};
+ delete $r->{from};
}
- if (length($$to) && length($$until)) {
- warning(_g( "you can only specify one of 'to' and 'until', using
'until'" ));
- $$to = '';
+ if (defined($r->{to}) && defined($r->{'until'})) {
+ warning(_g("you can only specify one of 'to' and 'until', using " .
+ "'until'")) if $self->{verbose};
+ delete $r->{to};
}
- $$start = 0 if $$start < 0;
- return if $$start > $#$data;
- $$end = $#$data if $$end > $#$data;
- return if $$end < 0;
- $$end = $$start if $$end < $$start;
# Handle non-existing versions
my (%versions, @versions);
@@ -250,198 +255,217 @@ sub __sanity_check_range {
$versions{$entry->get_version()->as_string()} = 1;
push @versions, $entry->get_version()->as_string();
}
- if ((length($$since) and not exists $versions{$$since})) {
+ if ((defined($r->{since}) and not exists $versions{$r->{since}})) {
warning(_g("'%s' option specifies non-existing version"), "since");
warning(_g("use newest entry that is smaller than the one specified"));
foreach my $v (@versions) {
- if (version_compare_relation($v, REL_LT, $$since)) {
- $$since = $v;
+ if (version_compare_relation($v, REL_LT, $r->{since})) {
+ $r->{since} = $v;
last;
}
}
- if (not exists $versions{$$since}) {
+ if (not exists $versions{$r->{since}}) {
# No version was smaller, include all
warning(_g("none found, starting from the oldest entry"));
- $$since = '';
- $$from = $versions[-1];
+ delete $r->{since};
+ $r->{from} = $versions[-1];
}
}
- if ((length($$from) and not exists $versions{$$from})) {
+ if ((defined($r->{from}) and not exists $versions{$r->{from}})) {
warning(_g("'%s' option specifies non-existing version"), "from");
warning(_g("use oldest entry that is bigger than the one specified"));
my $oldest;
foreach my $v (@versions) {
- if (version_compare_relation($v, REL_GT, $$from)) {
+ if (version_compare_relation($v, REL_GT, $r->{from})) {
$oldest = $v;
}
}
if (defined($oldest)) {
- $$from = $oldest;
+ $r->{from} = $oldest;
} else {
warning(_g("no such entry found, ignoring '%s' parameter"),
"from");
- $$from = ''; # No version was bigger
+ delete $r->{from}; # No version was bigger
}
}
- if ((length($$until) and not exists $versions{$$until})) {
+ if (defined($r->{'until'}) and not exists $versions{$r->{'until'}}) {
warning(_g("'%s' option specifies non-existing version"), "until");
warning(_g("use oldest entry that is bigger than the one specified"));
my $oldest;
foreach my $v (@versions) {
- if (version_compare_relation($v, REL_GT, $$until)) {
+ if (version_compare_relation($v, REL_GT, $r->{'until'})) {
$oldest = $v;
}
}
if (defined($oldest)) {
- $$until = $oldest;
+ $r->{'until'} = $oldest;
} else {
warning(_g("no such entry found, ignoring '%s' parameter"),
"until");
- $$until = ''; # No version was bigger
+ delete $r->{'until'}; # No version was bigger
}
}
- if ((length($$to) and not exists $versions{$$to})) {
+ if (defined($r->{to}) and not exists $versions{$r->{to}}) {
warning(_g("'%s' option specifies non-existing version"), "to");
warning(_g("use newest entry that is smaller than the one specified"));
foreach my $v (@versions) {
- if (version_compare_relation($v, REL_LT, $$to)) {
- $$to = $v;
+ if (version_compare_relation($v, REL_LT, $r->{to})) {
+ $r->{to} = $v;
last;
}
}
- if (not exists $versions{$$to}) {
+ if (not exists $versions{$r->{to}}) {
# No version was smaller
warning(_g("no such entry found, ignoring '%s' parameter"), "to");
- $$to = '';
+ delete $r->{to};
}
}
- if (length($$since) && ($data->[0]->get_version() eq $$since)) {
- warning(_g( "'since' option specifies most recent version, ignoring" ));
- $$since = '';
+ if (defined($r->{since}) and $data->[0]->get_version() eq $r->{since}) {
+ warning(_g("'since' option specifies most recent version, ignoring"));
+ delete $r->{since};
}
- if (length($$until) && ($data->[$#{$data}]->get_version() eq $$until)) {
- warning(_g( "'until' option specifies oldest version, ignoring" ));
- $$until = '';
+ if (defined($r->{'until'}) and $data->[-1]->get_version() eq
$r->{'until'}) {
+ warning(_g("'until' option specifies oldest version, ignoring"));
+ delete $r->{'until'};
}
- return 1;
}
-sub _data_range {
- my ($self, $config) = @_;
+sub get_range {
+ my ($self, $range) = @_;
+ $range = {} unless defined $range;
+ my $res = $self->_data_range($range);
+ return undef unless defined $res;
+ return @$res if wantarray;
+ return $res;
+}
- my $data = $self->data or return undef;
+sub _data_range {
+ my ($self, $range) = @_;
- return [ @$data ] if $config->{all};
+ my $data = $self->{data} or return undef;
- my ($since, $until, $from, $to, $count, $offset) = ('', '', '', '', 0, 0);
- $since = $config->{since} if defined($config->{since});
- $until = $config->{until} if defined($config->{until});
- $from = $config->{from} if defined($config->{from});
- $to = $config->{to} if defined($config->{to});
- $count = $config->{count} if defined($config->{count});
- $offset = $config->{offset} if defined($config->{offset});
+ return [ @$data ] if $range->{all};
- return if $offset and not $count;
- if ($offset > 0) {
- $offset -= ($count < 0);
- } elsif ($offset < 0) {
- $offset = $#$data + ($count > 0) + $offset;
- } else {
- $offset = $#$data if $count < 0;
+ unless (grep { m/^(since|until|from|to|count|offset)$/ } keys %$range) {
+ return [ @$data ];
}
- my $start = my $end = $offset;
- $start += $count+1 if $count < 0;
- $end += $count-1 if $count > 0;
-
- return unless __sanity_check_range( $data, \$from, \$to,
- \$since, \$until,
- \$start, \$end );
-
- unless (length($from) or length($to) or length($since) or length($until)
- or $start or $end)
- {
- return [ @$data ] if $config->{default_all} and not $count;
- return [ $data->[0] ];
+ $self->__sanity_check_range($range);
+
+ my ($start, $end);
+ if (defined($range->{count})) {
+ my $offset = $range->{offset} || 0;
+ my $count = $range->{count};
+ # Convert count/offset in start/end
+ if ($offset > 0) {
+ $offset -= ($count < 0);
+ } elsif ($offset < 0) {
+ $offset = $#$data + ($count > 0) + $offset;
+ } else {
+ $offset = $#$data if $count < 0;
+ }
+ $start = $end = $offset;
+ $start += $count+1 if $count < 0;
+ $end += $count-1 if $count > 0;
+ # Check limits
+ $start = 0 if $start < 0;
+ return if $start > $#$data;
+ $end = $#$data if $end > $#$data;
+ return if $end < 0;
+ $end = $start if $end < $start;
+ return [ @{$data}[$start .. $end] ];
}
- return [ @{$data}[$start .. $end] ] if $start or $end;
-
my @result;
-
my $include = 1;
- $include = 0 if length($to) or length($until);
+ $include = 0 if defined($range->{to}) or defined($range->{'until'});
foreach (@$data) {
my $v = $_->get_version();
- $include = 1 if $to and $v eq $to;
- last if $since and $v eq $since;
+ $include = 1 if defined($range->{to}) and $v eq $range->{to};
+ last if defined($range->{since}) and $v eq $range->{since};
push @result, $_ if $include;
- $include = 1 if $until and $v eq $until;
- last if $from and $v eq $from;
+ $include = 1 if defined($range->{'until'}) and $v eq $range->{'until'};
+ last if defined($range->{from}) and $v eq $range->{from};
}
return \...@result if scalar(@result);
return undef;
}
-sub _abort_early {
- my ($self) = @_;
+=item $c->abort_early()
- my $data = $self->data or return;
- my $config = $self->{config} or return;
+Returns true if enough data have been parsed to be able to return all
+entries selected by the range set at creation (or with set_options).
-# use Data::Dumper;
-# warn "Abort early? (\$# = $#$data)\n".Dumper($config);
+=cut
- return if $config->{all};
+sub abort_early {
+ my ($self) = @_;
- my ($since, $until, $from, $to, $count, $offset) = ('', '', '', '', 0, 0);
- $since = $config->{since} if defined($config->{since});
- $until = $config->{until} if defined($config->{until});
- $from = $config->{from} if defined($config->{from});
- $to = $config->{to} if defined($config->{to});
- $count = $config->{count} if defined($config->{count});
- $offset = $config->{offset} if defined($config->{offset});
+ my $data = $self->{data} or return;
+ my $r = $self->{range} or return;
+ my $count = $r->{count} || 0;
+ my $offset = $r->{offset} || 0;
- return if $offset and not $count;
+ return if $r->{all};
+ return unless grep { m/^(since|until|from|to|count|offset)$/ } keys %$r;
return if $offset < 0 or $count < 0;
- if ($offset > 0) {
- $offset -= ($count < 0);
- }
- my $start = my $end = $offset;
- $end += $count-1 if $count > 0;
-
- unless (length($from) or length($to) or length($since) or length($until)
- or $start or $end)
- {
- return if not $count;
- return 1 if @$data;
+ if (defined($r->{count})) {
+ if ($offset > 0) {
+ $offset -= ($count < 0);
+ }
+ my $start = my $end = $offset;
+ $end += $count-1 if $count > 0;
+ return ($start < @$data and $end < @$data);
}
- return 1 if ($start or $end)
- and $start < @$data and $end < @$data;
-
- return unless length($since) or length($from);
+ return unless defined($r->{since}) or defined($r->{from});
foreach (@$data) {
my $v = $_->get_version();
-
- return 1 if $v eq $since;
- return 1 if $v eq $from;
+ return 1 if defined($r->{since}) and $v eq $r->{since};
+ return 1 if defined($r->{from}) and $v eq $r->{from};
}
return;
}
-=pod
+=item $c->output()
+
+=item "$c"
+
+Returns a string representation of the changelog (it's a concatenation of
+the string representation of the individual changelog entries).
+
+=item $c->output($fh)
+
+Output the changelog to the given filehandle.
+
+=cut
+
+sub output {
+ my ($self, $fh) = @_;
+ my $str = "";
+ foreach my $entry (@{$self}) {
+ my $text = $entry->output();
+ print $fh $text if defined $fh;
+ $str .= $text if defined wantarray;
+ }
+ my $text = $self->get_unparsed_tail();
+ if (defined $text) {
+ print $fh $text if defined $fh;
+ $str .= $text if defined wantarray;
+ }
+ return $str;
+}
-=head3 dpkg
+=item my $control = $c->dpkg($range)
-(and B<dpkg_str>)
+Returns a Dpkg::Control::Changelog object representing the entries selected
+by the optional range specifier (see L<"RANGE SELECTION"> for details).
+Returns undef in no entries are matched.
-C<dpkg> returns a hash (in list context) or a hash reference
-(in scalar context) where the keys are field names and the values are
-field values. The following fields are given:
+The following fields are contained in the object:
=over 4
@@ -479,16 +503,6 @@ content of the the entry/entries
=back
-C<dpkg_str> returns a stringified version of this hash. The fields are
-ordered like in the list above.
-
-Both methods support the common output options described in
-section L<"COMMON OUTPUT OPTIONS">.
-
-=head3 dpkg_str
-
-See L<dpkg>.
-
=cut
our ( @URGENCIES, %URGENCIES );
@@ -499,24 +513,22 @@ BEGIN {
}
sub dpkg {
- my ($self, $config) = @_;
+ my ($self, $range) = @_;
- $self->{config}{DPKG} = $config if $config;
-
- $config = $self->{config}{DPKG} || {};
- my $data = $self->_data_range( $config ) or return undef;
+ my @data = $self->get_range($range) or return undef;
+ my $entry = shift @data;
my $f = Dpkg::Control::Changelog->new();
- $f->{Urgency} = $data->[0]->get_urgency() || "unknown";
- $f->{Source} = $data->[0]->get_source() || "unknown";
- $f->{Version} = $data->[0]->get_version() || "unknown";
- $f->{Distribution} = join(" ", $data->[0]->get_distributions());
- $f->{Maintainer} = $data->[0]->get_maintainer() || '';
- $f->{Date} = $data->[0]->get_timestamp() || '';
- $f->{Changes} = get_dpkg_changes($data->[0]);
+ $f->{Urgency} = $entry->get_urgency() || "unknown";
+ $f->{Source} = $entry->get_source() || "unknown";
+ $f->{Version} = $entry->get_version() || "unknown";
+ $f->{Distribution} = join(" ", $entry->get_distributions());
+ $f->{Maintainer} = $entry->get_maintainer() || '';
+ $f->{Date} = $entry->get_timestamp() || '';
+ $f->{Changes} = $entry->get_dpkg_changes();
# handle optional fields
- my $opts = $data->[0]->get_optional_fields();
+ my $opts = $entry->get_optional_fields();
my %closes;
foreach (keys %$opts) {
if (/^Urgency$/i) { # Already dealt
@@ -527,17 +539,13 @@ sub dpkg {
}
}
- my $first = 1; my $urg_comment = '';
- foreach my $entry (@$data) {
- $first = 0, next if $first;
-
+ foreach $entry (@data) {
my $oldurg = $f->{Urgency} || '';
my $oldurgn = $URGENCIES{$f->{Urgency}} || -1;
my $newurg = $entry->get_urgency() || '';
my $newurgn = $URGENCIES{$newurg} || -1;
$f->{Urgency} = ($newurgn > $oldurgn) ? $newurg : $oldurg;
-
- $f->{Changes} .= "\n ." . get_dpkg_changes($entry);
+ $f->{Changes} .= "\n ." . $entry->get_dpkg_changes();
# handle optional fields
$opts = $entry->get_optional_fields();
@@ -555,47 +563,26 @@ sub dpkg {
}
run_vendor_hook("post-process-changelog-entry", $f);
- return %$f if wantarray;
return $f;
}
-sub dpkg_str {
- return data2rfc822(scalar dpkg(@_));
-}
-
-=pod
-
-=head3 rfc822
-
-(and B<rfc822_str>)
+=item my @controls = $c->rfc822($range)
-C<rfc822> returns an array of hashes (in list context) or a reference
-to this array (in scalar context) where each hash represents one entry
-in the changelog. For the format of such a hash see the description
-of the L<"dpkg"> method (while ignoring the remarks about which
-values are taken from the first entry).
-
-C<rfc822_str> returns a stringified version of this array.
-
-Both methods support the common output options described in
-section L<"COMMON OUTPUT OPTIONS">.
-
-=head3 rfc822_str
-
-See L<rfc822>.
+Returns a Dpkg::Index containing Dpkg::Control::Changelog objects where
+each object represents one entry in the changelog that is part of the
+range requested (see L<"RANGE SELECTION"> for details). For the format of
+such an object see the description of the L<"dpkg"> method (while ignoring
+the remarks about which values are taken from the first entry).
=cut
sub rfc822 {
- my ($self, $config) = @_;
+ my ($self, $range) = @_;
- $self->{config}{RFC822} = $config if $config;
+ my @data = $self->get_range($range) or return undef;
+ my $index = Dpkg::Index->new(type => CTRL_CHANGELOG);
- $config = $self->{config}{RFC822} || {};
- my $data = $self->_data_range( $config ) or return undef;
- my @out_data;
-
- foreach my $entry (@$data) {
+ foreach my $entry (@data) {
my $f = Dpkg::Control::Changelog->new();
$f->{Urgency} = $entry->get_urgency() || "unknown";
$f->{Source} = $entry->get_source() || "unknown";
@@ -603,7 +590,7 @@ sub rfc822 {
$f->{Distribution} = join(" ", $entry->get_distributions());
$f->{Maintainer} = $entry->get_maintainer() || "";
$f->{Date} = $entry->get_timestamp() || "";
- $f->{Changes} = get_dpkg_changes($entry);
+ $f->{Changes} = $entry->get_dpkg_changes();
# handle optional fields
my $opts = $entry->get_optional_fields();
@@ -613,23 +600,19 @@ sub rfc822 {
run_vendor_hook("post-process-changelog-entry", $f);
- push @out_data, $f;
+ $index->add($f);
}
-
- return @out_data if wantarray;
- return \...@out_data;
+ return $index;
}
-sub rfc822_str {
- return data2rfc822(scalar rfc822(@_));
-}
+=back
-=pod
+=head1 RANGE SELECTION
-=head1 COMMON OUTPUT OPTIONS
+A range selection is described by a hash reference where
+the allowed keys and values are described below.
-The following options are supported by all output methods,
-all take a version number as value:
+The following options take a version number as value.
=over 4
@@ -655,8 +638,7 @@ specified B<version> itself.
=back
-The following options are also supported by all output methods but
-don't take version numbers as values:
+The following options don't take version numbers as values:
=over 4
@@ -683,109 +665,26 @@ wasn't given as well.
Some examples for the above options. Imagine an example changelog with
entries for the versions 1.2, 1.3, 2.0, 2.1, 2.2, 3.0 and 3.1.
- Call Included entries
- C<E<lt>formatE<gt>({ since =E<gt> '2.0' })> 3.1, 3.0, 2.2
- C<E<lt>formatE<gt>({ until =E<gt> '2.0' })> 1.3, 1.2
- C<E<lt>formatE<gt>({ from =E<gt> '2.0' })> 3.1, 3.0, 2.2, 2.1, 2.0
- C<E<lt>formatE<gt>({ to =E<gt> '2.0' })> 2.0, 1.3, 1.2
- C<E<lt>formatE<gt>({ count =E<gt> 2 }>> 3.1, 3.0
- C<E<lt>formatE<gt>({ count =E<gt> -2 }>> 1.3, 1.2
- C<E<lt>formatE<gt>({ count =E<gt> 3,
- offset=E<gt> 2 }>> 2.2, 2.1, 2.0
- C<E<lt>formatE<gt>({ count =E<gt> 2,
- offset=E<gt> -3 }>> 2.0, 1.3
- C<E<lt>formatE<gt>({ count =E<gt> -2,
- offset=E<gt> 3 }>> 3.0, 2.2
- C<E<lt>formatE<gt>({ count =E<gt> -2,
- offset=E<gt> -3 }>> 2.2, 2.1
+ Range Included entries
+ C<{ since =E<gt> '2.0' }> 3.1, 3.0, 2.2
+ C<{ until =E<gt> '2.0' }> 1.3, 1.2
+ C<{ from =E<gt> '2.0' }> 3.1, 3.0, 2.2, 2.1, 2.0
+ C<{ to =E<gt> '2.0' }> 2.0, 1.3, 1.2
+ C<{ count =E<gt> 2 }> 3.1, 3.0
+ C<{ count =E<gt> -2 }> 1.3, 1.2
+ C<{ count =E<gt> 3, offset=E<gt> 2 }> 2.2, 2.1, 2.0
+ C<{ count =E<gt> 2, offset=E<gt> -3 }> 2.0, 1.3
+ C<{ count =E<gt> -2, offset=E<gt> 3 }> 3.0, 2.2
+ C<{ count =E<gt> -2, offset=E<gt> -3 }> 2.2, 2.1
Any combination of one option of C<since> and C<from> and one of
C<until> and C<to> returns the intersection of the two results
with only one of the options specified.
-=head1 UTILITY FUNCTIONS
-
-=head3 data2rfc822
-
-Takes a single argument, either a Dpkg::Changelog::Entry object
-or a reference to an array of such objects.
-
-Returns the data in RFC822 format as string.
-
-=cut
-
-sub data2rfc822 {
- my ($data) = @_;
-
- if (ref($data) eq "ARRAY") {
- my @rfc822 = ();
-
- foreach my $entry (@$data) {
- push @rfc822, data2rfc822($entry);
- }
-
- return join "\n", @rfc822;
- } elsif (ref($data)) {
- my $rfc822_str = $data->output;
-
- return $rfc822_str;
- } else {
- return;
- }
-}
-
-=pod
-
-=head3 get_dpkg_changes
-
-Takes a Dpkg::Changelog::Entry object as first argument.
-
-Returns a string that is suitable for using it in a C<Changes> field
-in the output format of C<dpkg-parsechangelog>.
-
-=cut
-
-sub get_dpkg_changes {
- my $entry = shift;
- my $header = $entry->get_part("header") || "";
- $header =~ s/\s+$//;
- my $changes = "\n $header\n .\n";
- foreach my $line (@{$entry->get_part("changes")}) {
- $line =~ s/\s+$//;
- if ($line eq "") {
- $changes .= " .\n";
- } else {
- $changes .= " $line\n";
- }
- }
- chomp $changes;
- return $changes;
-}
-
-1;
-__END__
-
=head1 AUTHOR
Frank Lichtenheld, E<lt>[email protected]<gt>
-
-=head1 COPYRIGHT AND LICENSE
-
-Copyright E<copy> 2005, 2007 by Frank Lichtenheld
-Copyright E<copy> 2009 by Raphael Hertzog
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+Raphael Hertzog, E<lt>[email protected]<gt>
=cut
+1;
diff --git a/scripts/Dpkg/Changelog/Debian.pm b/scripts/Dpkg/Changelog/Debian.pm
index d6a35c0..1e3acf2 100644
--- a/scripts/Dpkg/Changelog/Debian.pm
+++ b/scripts/Dpkg/Changelog/Debian.pm
@@ -57,58 +57,21 @@ use constant {
=pod
-=head3 parse
+=head3 $c->parse($fh, $description)
-Parses either the file named in configuration item C<infile>, the content
-of the filehandle in configuration item C<inhandle>, or the string
-saved in configuration item C<instring> (the latter requires IO::String).
-You can set a filename to use for reporting errors with configuration
-item C<reportfile>.
-Accepts a hash ref as optional argument which can contain configuration
-items.
-
-Returns C<undef> in case of error (e.g. "file not found", B<not> parse
-errors) and the object if successful. If C<undef> was returned, you
-can get the reason for the failure by calling the L<get_error> method.
+Read the filehandle and parse a Debian changelog in it. Returns the number
+of changelog entries that have been parsed with success.
=cut
sub parse {
- my ($self, $config) = @_;
-
- foreach my $c (keys %$config) {
- $self->{config}{$c} = $config->{$c};
- }
-
- my ($fh, $file);
- if ($file = $self->{config}{infile}) {
- open $fh, '<', $file or do {
- $self->_do_fatal_error( _g("can't open file %s: %s"),
- $file, $! );
- return undef;
- };
- } elsif ($fh = $self->{config}{inhandle}) {
- $file = 'FileHandle';
- } elsif (my $string = $self->{config}{instring}) {
- eval { require IO::String };
- if ($@) {
- $self->_do_fatal_error( _g("can't load IO::String: %s"),
- $@ );
- return undef;
- }
- $fh = IO::String->new( $string );
- $file = 'String';
- } else {
- $self->_do_fatal_error(_g('no changelog file specified'));
- return undef;
- }
- if (defined($self->{config}{reportfile})) {
- $file = $self->{config}{reportfile};
- }
+ my ($self, $fh, $file) = @_;
+ $file = $self->{reportfile} if exists $self->{reportfile};
$self->reset_parse_errors;
$self->{data} = [];
+ $self->set_unparsed_tail(undef);
my $expect = FIRST_HEADING;
my $entry = Dpkg::Changelog::Entry::Debian->new();
@@ -120,18 +83,18 @@ sub parse {
if ($_ =~ $regex_header) {
(my $options = $4) =~ s/^\s+//;
unless ($expect eq FIRST_HEADING || $expect eq NEXT_OR_EOF) {
- $self->_do_parse_error($file, $.,
+ $self->parse_error($file, $.,
sprintf(_g("found start of entry where expected %s"),
$expect), "$_");
}
unless ($entry->is_empty) {
push @{$self->{data}}, $entry;
$entry = Dpkg::Changelog::Entry::Debian->new();
- last if $self->_abort_early;
+ last if $self->abort_early();
}
$entry->set_part('header', $_);
foreach my $error ($entry->check_header()) {
- $self->_do_parse_error($file, $., $error, $_);
+ $self->parse_error($file, $., $error, $_);
}
$expect= START_CHANGES;
@blanklines = ();
@@ -156,29 +119,26 @@ sub parse {
# save entries on old changelog format verbatim
# we assume the rest of the file will be in old format once we
# hit it for the first time
- $self->{oldformat} = "$_\n";
- $self->{oldformat} .= join "", <$fh>;
+ $self->set_unparsed_tail("$_\n" . join("", <$fh>));
} elsif (m/^\S/) {
- $self->_do_parse_error($file, $.,
- _g("badly formatted heading line"), "$_");
+ $self->parse_error($file, $., _g("badly formatted heading line"),
"$_");
} elsif ($_ =~ $regex_trailer) {
unless ($expect eq CHANGES_OR_TRAILER) {
- $self->_do_parse_error($file, $.,
+ $self->parse_error($file, $.,
sprintf(_g("found trailer where expected %s"), $expect),
"$_");
}
$entry->set_part("trailer", $_);
$entry->extend_part("blank_after_changes", [ @blanklines ]);
@blanklines = ();
foreach my $error ($entry->check_header()) {
- $self->_do_parse_error($file, $., $error, $_);
+ $self->parse_error($file, $., $error, $_);
}
$expect = NEXT_OR_EOF;
} elsif (m/^ \-\-/) {
- $self->_do_parse_error($file, $.,
- _g( "badly formatted trailer line" ), "$_");
+ $self->parse_error($file, $., _g("badly formatted trailer line"),
"$_");
} elsif (m/^\s{2,}(\S)/) {
unless ($expect eq START_CHANGES or $expect eq CHANGES_OR_TRAILER) {
- $self->_do_parse_error($file, $., sprintf(_g("found change
data" .
+ $self->parse_error($file, $., sprintf(_g("found change data" .
" where expected %s"), $expect), "$_");
if ($expect eq NEXT_OR_EOF and not $entry->is_empty) {
# lets assume we have missed the actual header line
@@ -199,12 +159,12 @@ sub parse {
$entry->extend_part("blank_after_trailer", $_);
next;
} elsif ($expect ne CHANGES_OR_TRAILER) {
- $self->_do_parse_error($file, $.,
- sprintf(_g("found blank line where expected %s"),
$expect));
+ $self->parse_error($file, $.,
+ sprintf(_g("found blank line where expected %s"), $expect));
}
push @blanklines, $_;
} else {
- $self->_do_parse_error($file, $., _g("unrecognised line"), "$_");
+ $self->parse_error($file, $., _g("unrecognised line"), "$_");
unless ($expect eq START_CHANGES or $expect eq CHANGES_OR_TRAILER) {
# lets assume change data if we expected it
$entry->extend_part("changes", [ @blanklines, $_]);
@@ -215,22 +175,14 @@ sub parse {
}
unless ($expect eq NEXT_OR_EOF) {
- $self->_do_parse_error($file, $.,
- sprintf(_g("found eof where expected %s"), $expect));
+ $self->parse_error($file, $., sprintf(_g("found eof where expected %s"),
+ $expect));
}
unless ($entry->is_empty) {
push @{$self->{data}}, $entry;
}
- if ($self->{config}{infile}) {
- close $fh or do {
- $self->_do_fatal_error( _g("can't close file %s: %s"),
- $file, $!);
- return undef;
- };
- }
-
- return $self;
+ return scalar @{$self->{data}};
}
1;
diff --git a/scripts/Dpkg/Changelog/Entry.pm b/scripts/Dpkg/Changelog/Entry.pm
index c67d456..29ecd0f 100644
--- a/scripts/Dpkg/Changelog/Entry.pm
+++ b/scripts/Dpkg/Changelog/Entry.pm
@@ -24,7 +24,9 @@ use Dpkg::ErrorHandling;
use Dpkg::Control::Changelog;
use overload
- '""' => \&output;
+ '""' => \&output,
+ 'eq' => sub { defined($_[1]) and "$_[0]" eq "$_[1]" },
+ fallback => 1;
=head1 NAME
diff --git a/scripts/changelog/debian.pl b/scripts/changelog/debian.pl
index cedcb7b..c5ec4ae 100755
--- a/scripts/changelog/debian.pl
+++ b/scripts/changelog/debian.pl
@@ -99,8 +99,6 @@ if (@ARGV) {
$file = $ARGV[0];
}
-my $changes = Dpkg::Changelog::Debian->init();
-
$file ||= $default_file;
$label ||= $file;
unless (defined($since) or defined($until) or defined($from) or
@@ -109,22 +107,25 @@ unless (defined($since) or defined($until) or
defined($from) or
{
$count = 1;
}
-my @all = $all ? ( all => $all ) : ();
-my $opts = { since => $since, until => $until,
- from => $from, to => $to,
- count => $count, offset => $offset,
- @all, reportfile => $label };
+my %all = $all ? ( all => $all ) : ();
+my $range = {
+ since => $since, until => $until, from => $from, to => $to,
+ count => $count, offset => $offset,
+ %all
+};
+
+my $changes = Dpkg::Changelog::Debian->new(reportfile => $label, range =>
$range);
if ($file eq '-') {
- $changes->parse({ inhandle => \*STDIN, %$opts })
+ $changes->parse(\*STDIN, _g("standard input"))
or error(_g('fatal error occured while parsing input'));
} else {
- $changes->parse({ infile => $file, %$opts })
+ $changes->load($file)
or error(_g('fatal error occured while parsing %s'), $file);
}
-eval("print \$changes->${format}_str(\$opts)");
+eval("print \$changes->${format}(\$range)");
if ($@) {
error("%s", $@);
}
diff --git a/scripts/t/600_Dpkg_Changelog.t b/scripts/t/600_Dpkg_Changelog.t
index 16731ee..67d26ed 100644
--- a/scripts/t/600_Dpkg_Changelog.t
+++ b/scripts/t/600_Dpkg_Changelog.t
@@ -12,7 +12,7 @@ BEGIN {
+ $no_err_examples * 2
+ 26 # countme
+ 2 # fields
- + 24;
+ + 21;
require Test::More;
import Test::More tests => $no_tests;
@@ -27,16 +27,11 @@ $srcdir .= '/t/600_Dpkg_Changelog';
#########################
-my $test = Dpkg::Changelog::Debian->init( { infile => '/nonexistant',
- quiet => 1 } );
-ok( !defined($test), "fatal parse errors lead to init() returning undef");
-
-my $save_data;
foreach my $file ("$srcdir/countme", "$srcdir/shadow", "$srcdir/fields",
"$srcdir/regressions") {
- my $changes = Dpkg::Changelog::Debian->init( { infile => $file,
- quiet => 1 } );
+ my $changes = Dpkg::Changelog::Debian->new(verbose => 0);
+ $changes->load($file);
my $errors = $changes->get_parse_errors();
my $basename = basename( $file );
@@ -45,18 +40,16 @@ foreach my $file ("$srcdir/countme", "$srcdir/shadow",
"$srcdir/fields",
is($errors, '', "Parse example changelog $file without errors" );
- my @data = $changes->data;
+ my @data = @$changes;
ok( @data, "data is not empty" );
- my $str = $changes->dpkg_str();
+ my $str = $changes->dpkg();
# is( $str, `dpkg-parsechangelog -l$file`,
# 'Output of dpkg_str equal to output of dpkg-parsechangelog' );
if ($file eq "$srcdir/countme") {
- $save_data = $changes->rfc822_str({ all => 1 });
-
# test range options
cmp_ok( @data, '==', 7, "no options -> count" );
my $all_versions = join( '/', map { $_->get_version() } @data);
@@ -65,7 +58,7 @@ foreach my $file ("$srcdir/countme", "$srcdir/shadow",
"$srcdir/fields",
my ($changes, $data, $options, $count, $versions,
$check_name) = @_;
- my @cnt = $changes->data( $options );
+ my @cnt = $changes->get_range($options);
cmp_ok( @cnt, '==', $count, "$check_name -> count" );
if ($count == @$data) {
is_deeply( \...@cnt, $data, "$check_name -> returns all" );
@@ -78,7 +71,7 @@ foreach my $file ("$srcdir/countme", "$srcdir/shadow",
"$srcdir/fields",
check_options( $changes, \...@data,
{ count => 3 }, 3, '2:2.0-1/1:2.0~rc2-3/1:2.0~rc2-2',
- 'positve count' );
+ 'positive count' );
check_options( $changes, \...@data,
{ count => -3 }, 3,
'1:2.0~rc2-1sarge2/1:2.0~rc2-1sarge1/1.5-1',
@@ -166,7 +159,7 @@ foreach my $file ("$srcdir/countme", "$srcdir/shadow",
"$srcdir/fields",
#TODO: test combinations
}
if ($file eq "$srcdir/fields") {
- my $str = $changes->dpkg_str({ all => 1 });
+ my $str = $changes->dpkg({ all => 1 });
my $expected = 'Source: fields
Version: 2.0-0etch1
Distribution: stable
@@ -194,7 +187,7 @@ Xc-Userfield: foobar
';
cmp_ok($str,'eq',$expected,"fields handling");
- $str = $changes->dpkg_str({ offset => 1, count => 2 });
+ $str = $changes->dpkg({ offset => 1, count => 2 });
$expected = 'Source: fields
Version: 2.0-1
Distribution: unstable
@@ -224,43 +217,29 @@ Xc-Userfield: foobar
# }
SKIP: {
- skip("avoid spurios warning with only one entry", 2)
+ skip("avoid spurious warning with only one entry", 2)
if @data == 1;
my $oldest_version = $data[-1]->{Version};
- $str = $changes->dpkg_str({ since => $oldest_version });
+ $str = $changes->dpkg({ since => $oldest_version });
- $str = $changes->rfc822_str();
+ $str = $changes->rfc822();
ok( 1 );
- $str = $changes->rfc822_str({ since => $oldest_version });
+ $str = $changes->rfc822({ since => $oldest_version });
ok( 1 );
}
}
-open CHANGES, '<', "$srcdir/countme";
-my $string = join('',<CHANGES>);
-
-my $str_changes = Dpkg::Changelog::Debian->init( { instring => $string,
- quiet => 1 } );
-my $errors = $str_changes->get_parse_errors();
-ok( !$errors,
- "Parse example changelog $srcdir/countme without errors from string" );
-
-my $str_data = $str_changes->rfc822_str({ all => 1 });
-is( $str_data, $save_data,
- "Compare result of parse from string with result of parse from file" );
-
-
foreach my $test (( [ "$srcdir/misplaced-tz", 6 ])) {
my $file = shift @$test;
- my $changes = Dpkg::Changelog::Debian->init( { infile => $file,
- quiet => 1 } );
+ my $changes = Dpkg::Changelog::Debian->new(verbose => 0);
+ $changes->load($file);
my @errors = $changes->get_parse_errors();
- ok( @errors, 'errors occoured' );
+ ok(@errors, 'errors occured');
is_deeply( [ map { $_->[1] } @errors ], $test, 'check line numbers' );
}
--
dpkg's main repository
--
To UNSUBSCRIBE, email to [email protected]
with a subject of "unsubscribe". Trouble? Contact [email protected]