OLD | NEW |
(Empty) | |
| 1 # Copyright © 2005, 2007 Frank Lichtenheld <frank@lichtenheld.de> |
| 2 # Copyright © 2009 Raphaël Hertzog <hertzog@debian.org> |
| 3 # |
| 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. |
| 8 # |
| 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. |
| 13 # |
| 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/>. |
| 16 |
| 17 =encoding utf8 |
| 18 |
| 19 =head1 NAME |
| 20 |
| 21 Dpkg::Changelog - base class to implement a changelog parser |
| 22 |
| 23 =head1 DESCRIPTION |
| 24 |
| 25 Dpkg::Changelog is a class representing a changelog file |
| 26 as an array of changelog entries (Dpkg::Changelog::Entry). |
| 27 By deriving this object and implementing its parse method, you |
| 28 add the ability to fill this object with changelog entries. |
| 29 |
| 30 =head2 FUNCTIONS |
| 31 |
| 32 =cut |
| 33 |
| 34 package Dpkg::Changelog; |
| 35 |
| 36 use strict; |
| 37 use warnings; |
| 38 |
| 39 our $VERSION = '1.00'; |
| 40 |
| 41 use Dpkg; |
| 42 use Dpkg::Gettext; |
| 43 use Dpkg::ErrorHandling qw(:DEFAULT report); |
| 44 use Dpkg::Control; |
| 45 use Dpkg::Control::Changelog; |
| 46 use Dpkg::Control::Fields; |
| 47 use Dpkg::Index; |
| 48 use Dpkg::Version; |
| 49 use Dpkg::Vendor qw(run_vendor_hook); |
| 50 |
| 51 use parent qw(Dpkg::Interface::Storable); |
| 52 |
| 53 use overload |
| 54 '@{}' => sub { return $_[0]->{data} }; |
| 55 |
| 56 =over 4 |
| 57 |
| 58 =item my $c = Dpkg::Changelog->new(%options) |
| 59 |
| 60 Creates a new changelog object. |
| 61 |
| 62 =cut |
| 63 |
| 64 sub new { |
| 65 my ($this, %opts) = @_; |
| 66 my $class = ref($this) || $this; |
| 67 my $self = { |
| 68 verbose => 1, |
| 69 parse_errors => [] |
| 70 }; |
| 71 bless $self, $class; |
| 72 $self->set_options(%opts); |
| 73 return $self; |
| 74 } |
| 75 |
| 76 =item $c->load($filename) |
| 77 |
| 78 Parse $filename as a changelog. |
| 79 |
| 80 =cut |
| 81 |
| 82 =item $c->set_options(%opts) |
| 83 |
| 84 Change the value of some options. "verbose" (defaults to 1) defines |
| 85 whether parse errors are displayed as warnings by default. "reportfile" |
| 86 is a string to use instead of the name of the file parsed, in particular |
| 87 in error messages. "range" defines the range of entries that we want to |
| 88 parse, the parser will stop as soon as it has parsed enough data to |
| 89 satisfy $c->get_range($opts{range}). |
| 90 |
| 91 =cut |
| 92 |
| 93 sub set_options { |
| 94 my ($self, %opts) = @_; |
| 95 $self->{$_} = $opts{$_} foreach keys %opts; |
| 96 } |
| 97 |
| 98 =item $c->reset_parse_errors() |
| 99 |
| 100 Can be used to delete all information about errors occurred during |
| 101 previous L<parse> runs. |
| 102 |
| 103 =cut |
| 104 |
| 105 sub reset_parse_errors { |
| 106 my ($self) = @_; |
| 107 $self->{parse_errors} = []; |
| 108 } |
| 109 |
| 110 =item $c->parse_error($file, $line_nr, $error, [$line]) |
| 111 |
| 112 Record a new parse error in $file at line $line_nr. The error message is |
| 113 specified with $error and a copy of the line can be recorded in $line. |
| 114 |
| 115 =cut |
| 116 |
| 117 sub parse_error { |
| 118 my ($self, $file, $line_nr, $error, $line) = @_; |
| 119 shift; |
| 120 |
| 121 push @{$self->{parse_errors}}, [ @_ ]; |
| 122 |
| 123 if ($self->{verbose}) { |
| 124 if ($line) { |
| 125 warning("%20s(l$line_nr): $error\nLINE: $line", $file); |
| 126 } else { |
| 127 warning("%20s(l$line_nr): $error", $file); |
| 128 } |
| 129 } |
| 130 } |
| 131 |
| 132 =item $c->get_parse_errors() |
| 133 |
| 134 Returns all error messages from the last L<parse> run. |
| 135 If called in scalar context returns a human readable |
| 136 string representation. If called in list context returns |
| 137 an array of arrays. Each of these arrays contains |
| 138 |
| 139 =over 4 |
| 140 |
| 141 =item 1. |
| 142 |
| 143 a string describing the origin of the data (a filename usually). If the |
| 144 reportfile configuration option was given, its value will be used instead. |
| 145 |
| 146 =item 2. |
| 147 |
| 148 the line number where the error occurred |
| 149 |
| 150 =item 3. |
| 151 |
| 152 an error description |
| 153 |
| 154 =item 4. |
| 155 |
| 156 the original line |
| 157 |
| 158 =back |
| 159 |
| 160 =cut |
| 161 |
| 162 sub get_parse_errors { |
| 163 my ($self) = @_; |
| 164 |
| 165 if (wantarray) { |
| 166 return @{$self->{parse_errors}}; |
| 167 } else { |
| 168 my $res = ''; |
| 169 foreach my $e (@{$self->{parse_errors}}) { |
| 170 if ($e->[3]) { |
| 171 $res .= report(_g('warning'),_g("%s(l%s): %s\nLINE: %s"), @$e ); |
| 172 } else { |
| 173 $res .= report(_g('warning'), _g('%s(l%s): %s'), @$e); |
| 174 } |
| 175 } |
| 176 return $res; |
| 177 } |
| 178 } |
| 179 |
| 180 =item $c->set_unparsed_tail($tail) |
| 181 |
| 182 Add a string representing unparsed lines after the changelog entries. |
| 183 Use undef as $tail to remove the unparsed lines currently set. |
| 184 |
| 185 =item $c->get_unparsed_tail() |
| 186 |
| 187 Return a string representing the unparsed lines after the changelog |
| 188 entries. Returns undef if there's no such thing. |
| 189 |
| 190 =cut |
| 191 |
| 192 sub set_unparsed_tail { |
| 193 my ($self, $tail) = @_; |
| 194 $self->{unparsed_tail} = $tail; |
| 195 } |
| 196 |
| 197 sub get_unparsed_tail { |
| 198 my ($self) = @_; |
| 199 return $self->{unparsed_tail}; |
| 200 } |
| 201 |
| 202 =item @{$c} |
| 203 |
| 204 Returns all the Dpkg::Changelog::Entry objects contained in this changelog |
| 205 in the order in which they have been parsed. |
| 206 |
| 207 =item $c->get_range($range) |
| 208 |
| 209 Returns an array (if called in list context) or a reference to an array of |
| 210 Dpkg::Changelog::Entry objects which each represent one entry of the |
| 211 changelog. $range is a hash reference describing the range of entries |
| 212 to return. See section L<"RANGE SELECTION">. |
| 213 |
| 214 =cut |
| 215 |
| 216 sub __sanity_check_range { |
| 217 my ($self, $r) = @_; |
| 218 my $data = $self->{data}; |
| 219 |
| 220 if (defined($r->{offset}) and not defined($r->{count})) { |
| 221 warning(_g("'offset' without 'count' has no effect")) if $self->{verbose
}; |
| 222 delete $r->{offset}; |
| 223 } |
| 224 |
| 225 ## no critic (ControlStructures::ProhibitUntilBlocks) |
| 226 if ((defined($r->{count}) || defined($r->{offset})) && |
| 227 (defined($r->{from}) || defined($r->{since}) || |
| 228 defined($r->{to}) || defined($r->{until}))) |
| 229 { |
| 230 warning(_g("you can't combine 'count' or 'offset' with any other " . |
| 231 'range option')) if $self->{verbose}; |
| 232 delete $r->{from}; |
| 233 delete $r->{since}; |
| 234 delete $r->{to}; |
| 235 delete $r->{until}; |
| 236 } |
| 237 if (defined($r->{from}) && defined($r->{since})) { |
| 238 warning(_g("you can only specify one of 'from' and 'since', using " . |
| 239 "'since'")) if $self->{verbose}; |
| 240 delete $r->{from}; |
| 241 } |
| 242 if (defined($r->{to}) && defined($r->{until})) { |
| 243 warning(_g("you can only specify one of 'to' and 'until', using " . |
| 244 "'until'")) if $self->{verbose}; |
| 245 delete $r->{to}; |
| 246 } |
| 247 |
| 248 # Handle non-existing versions |
| 249 my (%versions, @versions); |
| 250 foreach my $entry (@{$data}) { |
| 251 $versions{$entry->get_version()->as_string()} = 1; |
| 252 push @versions, $entry->get_version()->as_string(); |
| 253 } |
| 254 if ((defined($r->{since}) and not exists $versions{$r->{since}})) { |
| 255 warning(_g("'%s' option specifies non-existing version"), 'since'); |
| 256 warning(_g('use newest entry that is earlier than the one specified')); |
| 257 foreach my $v (@versions) { |
| 258 if (version_compare_relation($v, REL_LT, $r->{since})) { |
| 259 $r->{since} = $v; |
| 260 last; |
| 261 } |
| 262 } |
| 263 if (not exists $versions{$r->{since}}) { |
| 264 # No version was earlier, include all |
| 265 warning(_g('none found, starting from the oldest entry')); |
| 266 delete $r->{since}; |
| 267 $r->{from} = $versions[-1]; |
| 268 } |
| 269 } |
| 270 if ((defined($r->{from}) and not exists $versions{$r->{from}})) { |
| 271 warning(_g("'%s' option specifies non-existing version"), 'from'); |
| 272 warning(_g('use oldest entry that is later than the one specified')); |
| 273 my $oldest; |
| 274 foreach my $v (@versions) { |
| 275 if (version_compare_relation($v, REL_GT, $r->{from})) { |
| 276 $oldest = $v; |
| 277 } |
| 278 } |
| 279 if (defined($oldest)) { |
| 280 $r->{from} = $oldest; |
| 281 } else { |
| 282 warning(_g("no such entry found, ignoring '%s' parameter"), 'from'); |
| 283 delete $r->{from}; # No version was oldest |
| 284 } |
| 285 } |
| 286 if (defined($r->{until}) and not exists $versions{$r->{until}}) { |
| 287 warning(_g("'%s' option specifies non-existing version"), 'until'); |
| 288 warning(_g('use oldest entry that is later than the one specified')); |
| 289 my $oldest; |
| 290 foreach my $v (@versions) { |
| 291 if (version_compare_relation($v, REL_GT, $r->{until})) { |
| 292 $oldest = $v; |
| 293 } |
| 294 } |
| 295 if (defined($oldest)) { |
| 296 $r->{until} = $oldest; |
| 297 } else { |
| 298 warning(_g("no such entry found, ignoring '%s' parameter"), 'until')
; |
| 299 delete $r->{until}; # No version was oldest |
| 300 } |
| 301 } |
| 302 if (defined($r->{to}) and not exists $versions{$r->{to}}) { |
| 303 warning(_g("'%s' option specifies non-existing version"), 'to'); |
| 304 warning(_g('use newest entry that is earlier than the one specified')); |
| 305 foreach my $v (@versions) { |
| 306 if (version_compare_relation($v, REL_LT, $r->{to})) { |
| 307 $r->{to} = $v; |
| 308 last; |
| 309 } |
| 310 } |
| 311 if (not exists $versions{$r->{to}}) { |
| 312 # No version was earlier |
| 313 warning(_g("no such entry found, ignoring '%s' parameter"), 'to'); |
| 314 delete $r->{to}; |
| 315 } |
| 316 } |
| 317 |
| 318 if (defined($r->{since}) and $data->[0]->get_version() eq $r->{since}) { |
| 319 warning(_g("'since' option specifies most recent version, ignoring")); |
| 320 delete $r->{since}; |
| 321 } |
| 322 if (defined($r->{until}) and $data->[-1]->get_version() eq $r->{until}) { |
| 323 warning(_g("'until' option specifies oldest version, ignoring")); |
| 324 delete $r->{until}; |
| 325 } |
| 326 ## use critic |
| 327 } |
| 328 |
| 329 sub get_range { |
| 330 my ($self, $range) = @_; |
| 331 $range //= {}; |
| 332 my $res = $self->_data_range($range); |
| 333 if (defined $res) { |
| 334 return @$res if wantarray; |
| 335 return $res; |
| 336 } else { |
| 337 return; |
| 338 } |
| 339 } |
| 340 |
| 341 sub _is_full_range { |
| 342 my ($self, $range) = @_; |
| 343 |
| 344 return 1 if $range->{all}; |
| 345 |
| 346 # If no range delimiter is specified, we want everything. |
| 347 foreach (qw(since until from to count offset)) { |
| 348 return 0 if exists $range->{$_}; |
| 349 } |
| 350 |
| 351 return 1; |
| 352 } |
| 353 |
| 354 sub _data_range { |
| 355 my ($self, $range) = @_; |
| 356 |
| 357 my $data = $self->{data} or return; |
| 358 |
| 359 return [ @$data ] if $self->_is_full_range($range); |
| 360 |
| 361 $self->__sanity_check_range($range); |
| 362 |
| 363 my ($start, $end); |
| 364 if (defined($range->{count})) { |
| 365 my $offset = $range->{offset} || 0; |
| 366 my $count = $range->{count}; |
| 367 # Convert count/offset in start/end |
| 368 if ($offset > 0) { |
| 369 $offset -= ($count < 0); |
| 370 } elsif ($offset < 0) { |
| 371 $offset = $#$data + ($count > 0) + $offset; |
| 372 } else { |
| 373 $offset = $#$data if $count < 0; |
| 374 } |
| 375 $start = $end = $offset; |
| 376 $start += $count+1 if $count < 0; |
| 377 $end += $count-1 if $count > 0; |
| 378 # Check limits |
| 379 $start = 0 if $start < 0; |
| 380 return if $start > $#$data; |
| 381 $end = $#$data if $end > $#$data; |
| 382 return if $end < 0; |
| 383 $end = $start if $end < $start; |
| 384 return [ @{$data}[$start .. $end] ]; |
| 385 } |
| 386 |
| 387 ## no critic (ControlStructures::ProhibitUntilBlocks) |
| 388 my @result; |
| 389 my $include = 1; |
| 390 $include = 0 if defined($range->{to}) or defined($range->{until}); |
| 391 foreach (@$data) { |
| 392 my $v = $_->get_version(); |
| 393 $include = 1 if defined($range->{to}) and $v eq $range->{to}; |
| 394 last if defined($range->{since}) and $v eq $range->{since}; |
| 395 |
| 396 push @result, $_ if $include; |
| 397 |
| 398 $include = 1 if defined($range->{until}) and $v eq $range->{until}; |
| 399 last if defined($range->{from}) and $v eq $range->{from}; |
| 400 } |
| 401 ## use critic |
| 402 |
| 403 return \@result if scalar(@result); |
| 404 return; |
| 405 } |
| 406 |
| 407 =item $c->abort_early() |
| 408 |
| 409 Returns true if enough data have been parsed to be able to return all |
| 410 entries selected by the range set at creation (or with set_options). |
| 411 |
| 412 =cut |
| 413 |
| 414 sub abort_early { |
| 415 my ($self) = @_; |
| 416 |
| 417 my $data = $self->{data} or return; |
| 418 my $r = $self->{range} or return; |
| 419 my $count = $r->{count} || 0; |
| 420 my $offset = $r->{offset} || 0; |
| 421 |
| 422 return if $self->_is_full_range($r); |
| 423 return if $offset < 0 or $count < 0; |
| 424 if (defined($r->{count})) { |
| 425 if ($offset > 0) { |
| 426 $offset -= ($count < 0); |
| 427 } |
| 428 my $start = my $end = $offset; |
| 429 $end += $count-1 if $count > 0; |
| 430 return ($start < @$data and $end < @$data); |
| 431 } |
| 432 |
| 433 return unless defined($r->{since}) or defined($r->{from}); |
| 434 foreach (@$data) { |
| 435 my $v = $_->get_version(); |
| 436 return 1 if defined($r->{since}) and $v eq $r->{since}; |
| 437 return 1 if defined($r->{from}) and $v eq $r->{from}; |
| 438 } |
| 439 |
| 440 return; |
| 441 } |
| 442 |
| 443 =item $c->save($filename) |
| 444 |
| 445 Save the changelog in the given file. |
| 446 |
| 447 =item $c->output() |
| 448 |
| 449 =item "$c" |
| 450 |
| 451 Returns a string representation of the changelog (it's a concatenation of |
| 452 the string representation of the individual changelog entries). |
| 453 |
| 454 =item $c->output($fh) |
| 455 |
| 456 Output the changelog to the given filehandle. |
| 457 |
| 458 =cut |
| 459 |
| 460 sub output { |
| 461 my ($self, $fh) = @_; |
| 462 my $str = ''; |
| 463 foreach my $entry (@{$self}) { |
| 464 my $text = $entry->output(); |
| 465 print { $fh } $text if defined $fh; |
| 466 $str .= $text if defined wantarray; |
| 467 } |
| 468 my $text = $self->get_unparsed_tail(); |
| 469 if (defined $text) { |
| 470 print { $fh } $text if defined $fh; |
| 471 $str .= $text if defined wantarray; |
| 472 } |
| 473 return $str; |
| 474 } |
| 475 |
| 476 =item my $control = $c->dpkg($range) |
| 477 |
| 478 Returns a Dpkg::Control::Changelog object representing the entries selected |
| 479 by the optional range specifier (see L<"RANGE SELECTION"> for details). |
| 480 Returns undef in no entries are matched. |
| 481 |
| 482 The following fields are contained in the object: |
| 483 |
| 484 =over 4 |
| 485 |
| 486 =item Source |
| 487 |
| 488 package name (in the first entry) |
| 489 |
| 490 =item Version |
| 491 |
| 492 packages' version (from first entry) |
| 493 |
| 494 =item Distribution |
| 495 |
| 496 target distribution (from first entry) |
| 497 |
| 498 =item Urgency |
| 499 |
| 500 urgency (highest of all printed entries) |
| 501 |
| 502 =item Maintainer |
| 503 |
| 504 person that created the (first) entry |
| 505 |
| 506 =item Date |
| 507 |
| 508 date of the (first) entry |
| 509 |
| 510 =item Closes |
| 511 |
| 512 bugs closed by the entry/entries, sorted by bug number |
| 513 |
| 514 =item Changes |
| 515 |
| 516 content of the the entry/entries |
| 517 |
| 518 =back |
| 519 |
| 520 =cut |
| 521 |
| 522 our ( @URGENCIES, %URGENCIES ); |
| 523 BEGIN { |
| 524 @URGENCIES = qw(low medium high critical emergency); |
| 525 my $i = 1; |
| 526 %URGENCIES = map { $_ => $i++ } @URGENCIES; |
| 527 } |
| 528 |
| 529 sub dpkg { |
| 530 my ($self, $range) = @_; |
| 531 |
| 532 my @data = $self->get_range($range) or return; |
| 533 my $src = shift @data; |
| 534 |
| 535 my $f = Dpkg::Control::Changelog->new(); |
| 536 $f->{Urgency} = $src->get_urgency() || 'unknown'; |
| 537 $f->{Source} = $src->get_source() || 'unknown'; |
| 538 $f->{Version} = $src->get_version() // 'unknown'; |
| 539 $f->{Distribution} = join(' ', $src->get_distributions()); |
| 540 $f->{Maintainer} = $src->get_maintainer() || ''; |
| 541 $f->{Date} = $src->get_timestamp() || ''; |
| 542 $f->{Changes} = $src->get_dpkg_changes(); |
| 543 |
| 544 # handle optional fields |
| 545 my $opts = $src->get_optional_fields(); |
| 546 my %closes; |
| 547 foreach (keys %$opts) { |
| 548 if (/^Urgency$/i) { # Already dealt |
| 549 } elsif (/^Closes$/i) { |
| 550 $closes{$_} = 1 foreach (split(/\s+/, $opts->{Closes})); |
| 551 } else { |
| 552 field_transfer_single($opts, $f); |
| 553 } |
| 554 } |
| 555 |
| 556 foreach my $bin (@data) { |
| 557 my $oldurg = $f->{Urgency} || ''; |
| 558 my $oldurgn = $URGENCIES{$f->{Urgency}} || -1; |
| 559 my $newurg = $bin->get_urgency() || ''; |
| 560 my $newurgn = $URGENCIES{$newurg} || -1; |
| 561 $f->{Urgency} = ($newurgn > $oldurgn) ? $newurg : $oldurg; |
| 562 $f->{Changes} .= "\n" . $bin->get_dpkg_changes(); |
| 563 |
| 564 # handle optional fields |
| 565 $opts = $bin->get_optional_fields(); |
| 566 foreach (keys %$opts) { |
| 567 if (/^Closes$/i) { |
| 568 $closes{$_} = 1 foreach (split(/\s+/, $opts->{Closes})); |
| 569 } elsif (not exists $f->{$_}) { # Don't overwrite an existing field |
| 570 field_transfer_single($opts, $f); |
| 571 } |
| 572 } |
| 573 } |
| 574 |
| 575 if (scalar keys %closes) { |
| 576 $f->{Closes} = join ' ', sort { $a <=> $b } keys %closes; |
| 577 } |
| 578 run_vendor_hook('post-process-changelog-entry', $f); |
| 579 |
| 580 return $f; |
| 581 } |
| 582 |
| 583 =item my @controls = $c->rfc822($range) |
| 584 |
| 585 Returns a Dpkg::Index containing Dpkg::Control::Changelog objects where |
| 586 each object represents one entry in the changelog that is part of the |
| 587 range requested (see L<"RANGE SELECTION"> for details). For the format of |
| 588 such an object see the description of the L<"dpkg"> method (while ignoring |
| 589 the remarks about which values are taken from the first entry). |
| 590 |
| 591 =cut |
| 592 |
| 593 sub rfc822 { |
| 594 my ($self, $range) = @_; |
| 595 |
| 596 my @data = $self->get_range($range) or return; |
| 597 my $index = Dpkg::Index->new(type => CTRL_CHANGELOG); |
| 598 |
| 599 foreach my $entry (@data) { |
| 600 my $f = Dpkg::Control::Changelog->new(); |
| 601 $f->{Urgency} = $entry->get_urgency() || 'unknown'; |
| 602 $f->{Source} = $entry->get_source() || 'unknown'; |
| 603 $f->{Version} = $entry->get_version() // 'unknown'; |
| 604 $f->{Distribution} = join(' ', $entry->get_distributions()); |
| 605 $f->{Maintainer} = $entry->get_maintainer() || ''; |
| 606 $f->{Date} = $entry->get_timestamp() || ''; |
| 607 $f->{Changes} = $entry->get_dpkg_changes(); |
| 608 |
| 609 # handle optional fields |
| 610 my $opts = $entry->get_optional_fields(); |
| 611 foreach (keys %$opts) { |
| 612 field_transfer_single($opts, $f) unless exists $f->{$_}; |
| 613 } |
| 614 |
| 615 run_vendor_hook('post-process-changelog-entry', $f); |
| 616 |
| 617 $index->add($f); |
| 618 } |
| 619 return $index; |
| 620 } |
| 621 |
| 622 =back |
| 623 |
| 624 =head1 RANGE SELECTION |
| 625 |
| 626 A range selection is described by a hash reference where |
| 627 the allowed keys and values are described below. |
| 628 |
| 629 The following options take a version number as value. |
| 630 |
| 631 =over 4 |
| 632 |
| 633 =item since |
| 634 |
| 635 Causes changelog information from all versions strictly |
| 636 later than B<version> to be used. |
| 637 |
| 638 =item until |
| 639 |
| 640 Causes changelog information from all versions strictly |
| 641 earlier than B<version> to be used. |
| 642 |
| 643 =item from |
| 644 |
| 645 Similar to C<since> but also includes the information for the |
| 646 specified B<version> itself. |
| 647 |
| 648 =item to |
| 649 |
| 650 Similar to C<until> but also includes the information for the |
| 651 specified B<version> itself. |
| 652 |
| 653 =back |
| 654 |
| 655 The following options don't take version numbers as values: |
| 656 |
| 657 =over 4 |
| 658 |
| 659 =item all |
| 660 |
| 661 If set to a true value, all entries of the changelog are returned, |
| 662 this overrides all other options. |
| 663 |
| 664 =item count |
| 665 |
| 666 Expects a signed integer as value. Returns C<value> entries from the |
| 667 top of the changelog if set to a positive integer, and C<abs(value)> |
| 668 entries from the tail if set to a negative integer. |
| 669 |
| 670 =item offset |
| 671 |
| 672 Expects a signed integer as value. Changes the starting point for |
| 673 C<count>, either counted from the top (positive integer) or from |
| 674 the tail (negative integer). C<offset> has no effect if C<count> |
| 675 wasn't given as well. |
| 676 |
| 677 =back |
| 678 |
| 679 Some examples for the above options. Imagine an example changelog with |
| 680 entries for the versions 1.2, 1.3, 2.0, 2.1, 2.2, 3.0 and 3.1. |
| 681 |
| 682 Range Included entries |
| 683 C<{ since =E<gt> '2.0' }> 3.1, 3.0, 2.2 |
| 684 C<{ until =E<gt> '2.0' }> 1.3, 1.2 |
| 685 C<{ from =E<gt> '2.0' }> 3.1, 3.0, 2.2, 2.1, 2.0 |
| 686 C<{ to =E<gt> '2.0' }> 2.0, 1.3, 1.2 |
| 687 C<{ count =E<gt> 2 }> 3.1, 3.0 |
| 688 C<{ count =E<gt> -2 }> 1.3, 1.2 |
| 689 C<{ count =E<gt> 3, offset=E<gt> 2 }> 2.2, 2.1, 2.0 |
| 690 C<{ count =E<gt> 2, offset=E<gt> -3 }> 2.0, 1.3 |
| 691 C<{ count =E<gt> -2, offset=E<gt> 3 }> 3.0, 2.2 |
| 692 C<{ count =E<gt> -2, offset=E<gt> -3 }> 2.2, 2.1 |
| 693 |
| 694 Any combination of one option of C<since> and C<from> and one of |
| 695 C<until> and C<to> returns the intersection of the two results |
| 696 with only one of the options specified. |
| 697 |
| 698 =head1 AUTHOR |
| 699 |
| 700 Frank Lichtenheld, E<lt>frank@lichtenheld.deE<gt> |
| 701 Raphaël Hertzog, E<lt>hertzog@debian.orgE<gt> |
| 702 |
| 703 =cut |
| 704 1; |
OLD | NEW |