Changelog.pm 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866
  1. #
  2. # Dpkg::Changelog
  3. #
  4. # Copyright © 2005, 2007 Frank Lichtenheld <frank@lichtenheld.de>
  5. #
  6. # This program is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 2 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program; if not, write to the Free Software
  18. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  19. #
  20. =head1 NAME
  21. Dpkg::Changelog
  22. =head1 DESCRIPTION
  23. to be written
  24. =head2 Functions
  25. =cut
  26. package Dpkg::Changelog;
  27. use strict;
  28. use warnings;
  29. use English;
  30. use Dpkg;
  31. use Dpkg::Gettext;
  32. use Dpkg::ErrorHandling qw(warning report syserr subprocerr);
  33. use Dpkg::Cdata;
  34. use base qw(Exporter);
  35. our %EXPORT_TAGS = ( 'util' => [ qw(
  36. find_closes
  37. data2rfc822
  38. data2rfc822_mult
  39. get_dpkg_changes
  40. parse_changelog
  41. ) ] );
  42. our @EXPORT_OK = @{$EXPORT_TAGS{util}};
  43. =pod
  44. =head3 init
  45. Creates a new object instance. Takes a reference to a hash as
  46. optional argument, which is interpreted as configuration options.
  47. There are currently no supported general configuration options, but
  48. see the other methods for more specific configuration options which
  49. can also specified to C<init>.
  50. If C<infile> or C<instring> are specified (see L<parse>), C<parse()>
  51. is called from C<init>. If a fatal error is encountered during parsing
  52. (e.g. the file can't be opened), C<init> will not return a
  53. valid object but C<undef>!
  54. =cut
  55. sub init {
  56. my $classname = shift;
  57. my $config = shift || {};
  58. my $self = {};
  59. bless( $self, $classname );
  60. $config->{verbose} = 1 if $config->{debug};
  61. $self->{config} = $config;
  62. $self->reset_parse_errors;
  63. if ($self->{config}{infile} || $self->{config}{instring}) {
  64. defined($self->parse) or return undef;
  65. }
  66. return $self;
  67. }
  68. =pod
  69. =head3 reset_parse_errors
  70. Can be used to delete all information about errors ocurred during
  71. previous L<parse> runs. Note that C<parse()> also calls this method.
  72. =cut
  73. sub reset_parse_errors {
  74. my ($self) = @_;
  75. $self->{errors}{parser} = [];
  76. }
  77. sub _do_parse_error {
  78. my ($self, $file, $line_nr, $error, $line) = @_;
  79. shift;
  80. push @{$self->{errors}{parser}}, [ @_ ];
  81. unless ($self->{config}{quiet}) {
  82. if ($line) {
  83. warning("%20s(l$NR): $error\nLINE: $line", $file);
  84. } else {
  85. warning("%20s(l$NR): $error", $file);
  86. }
  87. }
  88. }
  89. =pod
  90. =head3 get_parse_errors
  91. Returns all error messages from the last L<parse> run.
  92. If called in scalar context returns a human readable
  93. string representation. If called in list context returns
  94. an array of arrays. Each of these arrays contains
  95. =over 4
  96. =item 1.
  97. the filename of the parsed file or C<String> if a string was
  98. parsed directly
  99. =item 2.
  100. the line number where the error occurred
  101. =item 3.
  102. an error description
  103. =item 4.
  104. the original line
  105. =back
  106. NOTE: This format isn't stable yet and may change in later versions
  107. of this module.
  108. =cut
  109. sub get_parse_errors {
  110. my ($self) = @_;
  111. if (wantarray) {
  112. return @{$self->{errors}{parser}};
  113. } else {
  114. my $res = "";
  115. foreach my $e (@{$self->{errors}{parser}}) {
  116. if ($e->[3]) {
  117. $res .= report(_g('warning'),_g("%s(l%s): %s\nLINE: %s"), @$e );
  118. } else {
  119. $res .= report(_g('warning'),_g("%s(l%s): %s"), @$e );
  120. }
  121. }
  122. return $res;
  123. }
  124. }
  125. sub _do_fatal_error {
  126. my ($self, $msg, @msg) = @_;
  127. $self->{errors}{fatal} = report(_g('fatal error'), $msg, @msg);
  128. warning($msg, @msg) unless $self->{config}{quiet};
  129. }
  130. =pod
  131. =head3 get_error
  132. Get the last non-parser error (e.g. the file to parse couldn't be opened).
  133. =cut
  134. sub get_error {
  135. my ($self) = @_;
  136. return $self->{errors}{fatal};
  137. }
  138. =pod
  139. =head3 data
  140. C<data> returns an array (if called in list context) or a reference
  141. to an array of Dpkg::Changelog::Entry objects which each
  142. represent one entry of the changelog.
  143. This method supports the common output options described in
  144. section L<"COMMON OUTPUT OPTIONS">.
  145. =cut
  146. sub data {
  147. my ($self, $config) = @_;
  148. my $data = $self->{data};
  149. if ($config) {
  150. $self->{config}{DATA} = $config if $config;
  151. $data = $self->_data_range( $config ) or return undef;
  152. }
  153. return @$data if wantarray;
  154. return $data;
  155. }
  156. sub __sanity_check_range {
  157. my ( $data, $from, $to, $since, $until, $start, $end ) = @_;
  158. if (($$start || $$end) && ($$from || $$since || $$to || $$until)) {
  159. warning(_g( "you can't combine 'count' or 'offset' with any other range option" ));
  160. $$from = $$since = $$to = $$until = '';
  161. }
  162. if ($$from && $$since) {
  163. warning(_g( "you can only specify one of 'from' and 'since', using 'since'" ));
  164. $$from = '';
  165. }
  166. if ($$to && $$until) {
  167. warning(_g( "you can only specify one of 'to' and 'until', using 'until'" ));
  168. $$to = '';
  169. }
  170. if ($$since && ($data->[0]{Version} eq $$since)) {
  171. warning(_g( "'since' option specifies most recent version, ignoring" ));
  172. $$since = '';
  173. }
  174. if ($$until && ($data->[$#{$data}]{Version} eq $$until)) {
  175. warning(_g( "'until' option specifies oldest version, ignoring" ));
  176. $$until = '';
  177. }
  178. $$start = 0 if $$start < 0;
  179. return if $$start > $#$data;
  180. $$end = $#$data if $$end > $#$data;
  181. return if $$end < 0;
  182. $$end = $$start if $$end < $$start;
  183. #TODO: compare versions
  184. return 1;
  185. }
  186. sub _data_range {
  187. my ($self, $config) = @_;
  188. my $data = $self->data or return undef;
  189. return [ @$data ] if $config->{all};
  190. my $since = $config->{since} || '';
  191. my $until = $config->{until} || '';
  192. my $from = $config->{from} || '';
  193. my $to = $config->{to} || '';
  194. my $count = $config->{count} || 0;
  195. my $offset = $config->{offset} || 0;
  196. return if $offset and not $count;
  197. if ($offset > 0) {
  198. $offset -= ($count < 0);
  199. } elsif ($offset < 0) {
  200. $offset = $#$data + ($count > 0) + $offset;
  201. } else {
  202. $offset = $#$data if $count < 0;
  203. }
  204. my $start = my $end = $offset;
  205. $start += $count+1 if $count < 0;
  206. $end += $count-1 if $count > 0;
  207. return unless __sanity_check_range( $data, \$from, \$to,
  208. \$since, \$until,
  209. \$start, \$end );
  210. unless ($from or $to or $since or $until or $start or $end) {
  211. return [ @$data ] if $config->{default_all} and not $count;
  212. return [ $data->[0] ];
  213. }
  214. return [ @{$data}[$start .. $end] ] if $start or $end;
  215. my @result;
  216. my $include = 1;
  217. $include = 0 if $to or $until;
  218. foreach (@$data) {
  219. my $v = $_->{Version};
  220. $include = 1 if $v eq $to;
  221. last if $v eq $since;
  222. push @result, $_ if $include;
  223. $include = 1 if $v eq $until;
  224. last if $v eq $from;
  225. }
  226. return \@result;
  227. }
  228. sub _abort_early {
  229. my ($self) = @_;
  230. my $data = $self->data or return;
  231. my $config = $self->{config} or return;
  232. # use Data::Dumper;
  233. # warn "Abort early? (\$# = $#$data)\n".Dumper($config);
  234. return if $config->{all};
  235. my $since = $config->{since} || '';
  236. my $until = $config->{until} || '';
  237. my $from = $config->{from} || '';
  238. my $to = $config->{to} || '';
  239. my $count = $config->{count} || 0;
  240. my $offset = $config->{offset} || 0;
  241. return if $offset and not $count;
  242. return if $offset < 0 or $count < 0;
  243. if ($offset > 0) {
  244. $offset -= ($count < 0);
  245. }
  246. my $start = my $end = $offset;
  247. $end += $count-1 if $count > 0;
  248. unless ($from or $to or $since or $until or $start or $end) {
  249. return if not $count;
  250. return 1 if @$data;
  251. }
  252. return 1 if ($start or $end)
  253. and $start < @$data and $end < @$data;
  254. return unless $since or $from;
  255. foreach (@$data) {
  256. my $v = $_->{Version};
  257. return 1 if $v eq $since;
  258. return 1 if $v eq $from;
  259. }
  260. return;
  261. }
  262. =pod
  263. =head3 dpkg
  264. (and B<dpkg_str>)
  265. C<dpkg> returns a hash (in list context) or a hash reference
  266. (in scalar context) where the keys are field names and the values are
  267. field values. The following fields are given:
  268. =over 4
  269. =item Source
  270. package name (in the first entry)
  271. =item Version
  272. packages' version (from first entry)
  273. =item Distribution
  274. target distribution (from first entry)
  275. =item Urgency
  276. urgency (highest of all printed entries)
  277. =item Maintainer
  278. person that created the (first) entry
  279. =item Date
  280. date of the (first) entry
  281. =item Closes
  282. bugs closed by the entry/entries, sorted by bug number
  283. =item Changes
  284. content of the the entry/entries
  285. =back
  286. C<dpkg_str> returns a stringified version of this hash. The fields are
  287. ordered like in the list above.
  288. Both methods support the common output options described in
  289. section L<"COMMON OUTPUT OPTIONS">.
  290. =head3 dpkg_str
  291. See L<dpkg>.
  292. =cut
  293. our ( %FIELDIMPS, %URGENCIES );
  294. BEGIN {
  295. my $i=100;
  296. grep($FIELDIMPS{$_}=$i--,
  297. qw(Source Version Distribution Urgency Maintainer Date Closes
  298. Changes));
  299. $i=1;
  300. grep($URGENCIES{$_}=$i++,
  301. qw(low medium high critical emergency));
  302. }
  303. sub dpkg {
  304. my ($self, $config) = @_;
  305. $self->{config}{DPKG} = $config if $config;
  306. $config = $self->{config}{DPKG} || {};
  307. my $data = $self->_data_range( $config ) or return undef;
  308. my %f;
  309. foreach my $field (qw( Urgency Source Version
  310. Distribution Maintainer Date )) {
  311. $f{$field} = $data->[0]{$field};
  312. }
  313. $f{Changes} = get_dpkg_changes( $data->[0] );
  314. $f{Closes} = [ @{$data->[0]{Closes}} ];
  315. my $first = 1; my $urg_comment = '';
  316. foreach my $entry (@$data) {
  317. $first = 0, next if $first;
  318. my $oldurg = $f{Urgency} || '';
  319. my $oldurgn = $URGENCIES{$f{Urgency}} || -1;
  320. my $newurg = $entry->{Urgency_LC} || '';
  321. my $newurgn = $URGENCIES{$entry->{Urgency_LC}} || -1;
  322. $f{Urgency} = ($newurgn > $oldurgn) ? $newurg : $oldurg;
  323. $urg_comment .= $entry->{Urgency_Comment};
  324. $f{Changes} .= "\n .".get_dpkg_changes( $entry );
  325. push @{$f{Closes}}, @{$entry->{Closes}};
  326. }
  327. $f{Closes} = join " ", sort { $a <=> $b } @{$f{Closes}};
  328. $f{Urgency} .= $urg_comment;
  329. return %f if wantarray;
  330. return \%f;
  331. }
  332. sub dpkg_str {
  333. return data2rfc822( scalar dpkg(@_), \%FIELDIMPS );
  334. }
  335. =pod
  336. =head3 rfc822
  337. (and B<rfc822_str>)
  338. C<rfc822> returns an array of hashes (in list context) or a reference
  339. to this array (in scalar context) where each hash represents one entry
  340. in the changelog. For the format of such a hash see the description
  341. of the L<"dpkg"> method (while ignoring the remarks about which
  342. values are taken from the first entry).
  343. C<rfc822_str> returns a stringified version of this array.
  344. Both methods support the common output options described in
  345. section L<"COMMON OUTPUT OPTIONS">.
  346. =head3 rfc822_str
  347. See L<rfc822>.
  348. =cut
  349. sub rfc822 {
  350. my ($self, $config) = @_;
  351. $self->{config}{RFC822} = $config if $config;
  352. $config = $self->{config}{RFC822} || {};
  353. my $data = $self->_data_range( $config ) or return undef;
  354. my @out_data;
  355. foreach my $entry (@$data) {
  356. my %f;
  357. foreach my $field (qw( Urgency Source Version
  358. Distribution Maintainer Date )) {
  359. $f{$field} = $entry->{$field};
  360. }
  361. $f{Urgency} .= $entry->{Urgency_Comment};
  362. $f{Changes} = get_dpkg_changes( $entry );
  363. $f{Closes} = join " ", sort { $a <=> $b } @{$entry->{Closes}};
  364. push @out_data, \%f;
  365. }
  366. return @out_data if wantarray;
  367. return \@out_data;
  368. }
  369. sub rfc822_str {
  370. return data2rfc822_mult( scalar rfc822(@_), \%FIELDIMPS );
  371. }
  372. =pod
  373. =head1 COMMON OUTPUT OPTIONS
  374. The following options are supported by all output methods,
  375. all take a version number as value:
  376. =over 4
  377. =item since
  378. Causes changelog information from all versions strictly
  379. later than B<version> to be used.
  380. =item until
  381. Causes changelog information from all versions strictly
  382. earlier than B<version> to be used.
  383. =item from
  384. Similar to C<since> but also includes the information for the
  385. specified B<version> itself.
  386. =item to
  387. Similar to C<until> but also includes the information for the
  388. specified B<version> itself.
  389. =back
  390. The following options also supported by all output methods but
  391. don't take version numbers as values:
  392. =over 4
  393. =item all
  394. If set to a true value, all entries of the changelog are returned,
  395. this overrides all other options.
  396. =item count
  397. Expects a signed integer as value. Returns C<value> entries from the
  398. top of the changelog if set to a positive integer, and C<abs(value)>
  399. entries from the tail if set to a negative integer.
  400. =item offset
  401. Expects a signed integer as value. Changes the starting point for
  402. C<count>, either counted from the top (positive integer) or from
  403. the tail (negative integer). C<offset> has no effect if C<count>
  404. wasn't given as well.
  405. =back
  406. Some examples for the above options. Imagine an example changelog with
  407. entries for the versions 1.2, 1.3, 2.0, 2.1, 2.2, 3.0 and 3.1.
  408. Call Included entries
  409. C<E<lt>formatE<gt>({ since =E<gt> '2.0' })> 3.1, 3.0, 2.2
  410. C<E<lt>formatE<gt>({ until =E<gt> '2.0' })> 1.3, 1.2
  411. C<E<lt>formatE<gt>({ from =E<gt> '2.0' })> 3.1, 3.0, 2.2, 2.1, 2.0
  412. C<E<lt>formatE<gt>({ to =E<gt> '2.0' })> 2.0, 1.3, 1.2
  413. C<E<lt>formatE<gt>({ count =E<gt> 2 }>> 3.1, 3.0
  414. C<E<lt>formatE<gt>({ count =E<gt> -2 }>> 1.3, 1.2
  415. C<E<lt>formatE<gt>({ count =E<gt> 3,
  416. offset=E<gt> 2 }>> 2.2, 2.1, 2.0
  417. C<E<lt>formatE<gt>({ count =E<gt> 2,
  418. offset=E<gt> -3 }>> 2.0, 1.3
  419. C<E<lt>formatE<gt>({ count =E<gt> -2,
  420. offset=E<gt> 3 }>> 3.0, 2.2
  421. C<E<lt>formatE<gt>({ count =E<gt> -2,
  422. offset=E<gt> -3 }>> 2.2, 2.1
  423. Any combination of one option of C<since> and C<from> and one of
  424. C<until> and C<to> returns the intersection of the two results
  425. with only one of the options specified.
  426. =head1 UTILITY FUNCTIONS
  427. =head3 find_closes
  428. Takes one string as argument and finds "Closes: #123456, #654321" statements
  429. as supported by the Debian Archive software in it. Returns all closed bug
  430. numbers in an array reference.
  431. =cut
  432. sub find_closes {
  433. my $changes = shift;
  434. my @closes = ();
  435. while ($changes &&
  436. ($changes =~ /closes:\s*(?:bug)?\#?\s?\d+(?:,\s*(?:bug)?\#?\s?\d+)*/ig)) {
  437. push(@closes, $& =~ /\#?\s?(\d+)/g);
  438. }
  439. @closes = sort { $a <=> $b } @closes;
  440. return \@closes;
  441. }
  442. =pod
  443. =head3 data2rfc822
  444. Takes two hash references as arguments. The first should contain the
  445. data to output in RFC822 format. The second can contain a sorting order
  446. for the fields. The higher the numerical value of the hash value, the
  447. earlier the field is printed if it exists.
  448. Return the data in RFC822 format as string.
  449. =cut
  450. sub data2rfc822 {
  451. my ($data, $fieldimps) = @_;
  452. my $rfc822_str = '';
  453. # based on /usr/lib/dpkg/controllib.pl
  454. for my $f (sort { $fieldimps->{$b} <=> $fieldimps->{$a} } keys %$data) {
  455. my $v= $data->{$f} or next;
  456. $v =~ m/\S/o || next; # delete whitespace-only fields
  457. $v =~ m/\n\S/o
  458. && warning(_g("field %s has newline then non whitespace >%s<"),
  459. $f, $v);
  460. $v =~ m/\n[ \t]*\n/o && warning(_g("field %s has blank lines >%s<"),
  461. $f, $v);
  462. $v =~ m/\n$/o && warning(_g("field %s has trailing newline >%s<"),
  463. $f, $v);
  464. $v =~ s/\$\{\}/\$/go;
  465. $rfc822_str .= "$f: $v\n";
  466. }
  467. return $rfc822_str;
  468. }
  469. =pod
  470. =head3 data2rfc822_mult
  471. The first argument should be an array ref to an array of hash references.
  472. The second argument is a hash reference and has the same meaning as
  473. the second argument of L<data2rfc822>.
  474. Calls L<data2rfc822> for each element of the array given as first
  475. argument and returns the concatenated results.
  476. =cut
  477. sub data2rfc822_mult {
  478. my ($data, $fieldimps) = @_;
  479. my @rfc822 = ();
  480. foreach my $entry (@$data) {
  481. push @rfc822, data2rfc822($entry,$fieldimps);
  482. }
  483. return join "\n", @rfc822;
  484. }
  485. =pod
  486. =head3 get_dpkg_changes
  487. Takes a Dpkg::Changelog::Entry object as first argument.
  488. Returns a string that is suitable for using it in a C<Changes> field
  489. in the output format of C<dpkg-parsechangelog>.
  490. =cut
  491. sub get_dpkg_changes {
  492. my $changes = "\n ".($_[0]->Header||'')."\n .\n".($_[0]->Changes||'');
  493. chomp $changes;
  494. $changes =~ s/^ $/ ./mgo;
  495. return $changes;
  496. }
  497. =pod
  498. =head3 parse_changelog($file, $format, $since)
  499. Calls "dpkg-parsechangelog -l$file -F$format -v$since" and returns a
  500. Dpkg::Cdata::Object with the values output by the program.
  501. =cut
  502. sub parse_changelog {
  503. my ($changelogfile, $changelogformat, $since) = @_;
  504. my @exec = ('dpkg-parsechangelog');
  505. push(@exec, "-l$changelogfile");
  506. push(@exec, "-F$changelogformat") if defined($changelogformat);
  507. push(@exec, "-v$since") if defined($since);
  508. open(PARSECH, "-|", @exec) || syserr(_g("fork for parse changelog"));
  509. my $fields = parsecdata(\*PARSECH, _g("parsed version of changelog"));
  510. close(PARSECH) || subprocerr(_g("parse changelog"));
  511. return $fields;
  512. }
  513. =head1 NAME
  514. Dpkg::Changelog::Entry - represents one entry in a Debian changelog
  515. =head1 SYNOPSIS
  516. =head1 DESCRIPTION
  517. =head2 Methods
  518. =head3 init
  519. Creates a new object, no options.
  520. =head3 new
  521. Alias for init.
  522. =head3 is_empty
  523. Checks if the object is actually initialized with data. This
  524. currently simply checks if one of the fields Source, Version,
  525. Maintainer, Date, or Changes is initalized.
  526. =head2 Accessors
  527. The following fields are available via accessor functions (all
  528. fields are string values unless otherwise noted):
  529. =over 4
  530. =item *
  531. Source
  532. =item *
  533. Version
  534. =item *
  535. Distribution
  536. =item *
  537. Urgency
  538. =item *
  539. ExtraFields (all fields except for urgency as hash)
  540. =item *
  541. Header (the whole header in verbatim form)
  542. =item *
  543. Changes (the actual content of the bug report, in verbatim form)
  544. =item *
  545. Trailer (the whole trailer in verbatim form)
  546. =item *
  547. Closes (Array of bug numbers)
  548. =item *
  549. Maintainer (name B<and> email address)
  550. =item *
  551. Date
  552. =item *
  553. Timestamp (Date expressed in seconds since the epoche)
  554. =item *
  555. ERROR (last parse error related to this entry in the format described
  556. at Dpkg::Changelog::get_parse_errors.
  557. =back
  558. =cut
  559. package Dpkg::Changelog::Entry;
  560. use base qw( Class::Accessor );
  561. Dpkg::Changelog::Entry->mk_accessors(qw( Closes Changes Maintainer
  562. MaintainerEmail Date
  563. Urgency Distribution
  564. Source Version ERROR
  565. ExtraFields Header
  566. Trailer Timestamp ));
  567. sub new {
  568. return init(@_);
  569. }
  570. sub init {
  571. my $classname = shift;
  572. my $self = {};
  573. bless( $self, $classname );
  574. return $self;
  575. }
  576. sub is_empty {
  577. my ($self) = @_;
  578. return !($self->{Changes}
  579. || $self->{Source}
  580. || $self->{Version}
  581. || $self->{Maintainer}
  582. || $self->{Date});
  583. }
  584. 1;
  585. __END__
  586. =head1 AUTHOR
  587. Frank Lichtenheld, E<lt>frank@lichtenheld.deE<gt>
  588. =head1 COPYRIGHT AND LICENSE
  589. Copyright E<copy> 2005, 2007 by Frank Lichtenheld
  590. This program is free software; you can redistribute it and/or modify
  591. it under the terms of the GNU General Public License as published by
  592. the Free Software Foundation; either version 2 of the License, or
  593. (at your option) any later version.
  594. This program is distributed in the hope that it will be useful,
  595. but WITHOUT ANY WARRANTY; without even the implied warranty of
  596. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  597. GNU General Public License for more details.
  598. You should have received a copy of the GNU General Public License
  599. along with this program; if not, write to the Free Software
  600. Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  601. =cut