Debian.pm 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. # Copyright © 2009 Raphaël Hertzog <hertzog@debian.org>
  2. #
  3. # This program is free software; you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation; either version 2 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. package Dpkg::Changelog::Entry::Debian;
  16. use strict;
  17. use warnings;
  18. our $VERSION = "1.00";
  19. use Exporter;
  20. use Dpkg::Changelog::Entry;
  21. use base qw(Exporter Dpkg::Changelog::Entry);
  22. our @EXPORT_OK = qw($regex_header $regex_trailer find_closes);
  23. use Date::Parse;
  24. use Dpkg::Gettext;
  25. use Dpkg::Control::Fields;
  26. use Dpkg::Control::Changelog;
  27. use Dpkg::Version;
  28. =encoding utf8
  29. =head1 NAME
  30. Dpkg::Changelog::Entry::Debian - represents a Debian changelog entry
  31. =head1 DESCRIPTION
  32. This object represents a Debian changelog entry. It implements the
  33. generic interface Dpkg::Changelog::Entry. Only functions specific to this
  34. implementation are described below.
  35. =head1 VARIABLES
  36. $regex_header, $regex_trailer are two regular expressions that can be used
  37. to match a line and know whether it's a valid header/trailer line.
  38. The matched content for $regex_header is the source package name ($1), the
  39. version ($2), the target distributions ($3) and the options on the rest
  40. of the line ($4). For $regex_trailer, it's the maintainer name ($1), its
  41. email ($2), some blanks ($3) and the timestamp ($4).
  42. =cut
  43. my $name_chars = qr/[-+0-9a-z.]/i;
  44. our $regex_header = qr/^(\w$name_chars*) \(([^\(\) \t]+)\)((?:\s+$name_chars+)+)\;(.*?)\s*$/i;
  45. our $regex_trailer = qr/^ \-\- (.*) <(.*)>( ?)((\w+\,\s*)?\d{1,2}\s+\w+\s+\d{4}\s+\d{1,2}:\d\d:\d\d\s+[-+]\d{4}(\s+\([^\\\(\)]\))?)\s*$/o;
  46. =head1 FUNCTIONS
  47. =over 4
  48. =item my @items = $entry->get_change_items()
  49. Return a list of change items. Each item contains at least one line.
  50. A change line starting with an asterisk denotes the start of a new item.
  51. Any change line like "[ Raphaël Hertzog ]" is treated like an item of its
  52. own even if it starts a set of items attributed to this person (the
  53. following line necessarily starts a new item).
  54. =cut
  55. sub get_change_items {
  56. my ($self) = @_;
  57. my (@items, @blanks, $item);
  58. foreach my $line (@{$self->get_part("changes")}) {
  59. if ($line =~ /^\s*\*/) {
  60. push @items, $item if defined $item;
  61. $item = "$line\n";
  62. } elsif ($line =~ /^\s*\[\s[^\]]+\s\]\s*$/) {
  63. push @items, $item if defined $item;
  64. push @items, "$line\n";
  65. $item = undef;
  66. @blanks = ();
  67. } elsif ($line =~ /^\s*$/) {
  68. push @blanks, "$line\n";
  69. } else {
  70. if (defined $item) {
  71. $item .= "@blanks$line\n";
  72. } else {
  73. $item = "$line\n";
  74. }
  75. @blanks = ();
  76. }
  77. }
  78. push @items, $item if defined $item;
  79. return @items;
  80. }
  81. =item my @errors = $entry->check_header()
  82. =item my @errors = $entry->check_trailer()
  83. Return a list of errors. Each item in the list is an error message
  84. describing the problem. If the empty list is returned, no errors
  85. have been found.
  86. =cut
  87. sub check_header {
  88. my ($self) = @_;
  89. my @errors;
  90. if (defined($self->{header}) and $self->{header} =~ $regex_header) {
  91. my ($version, $options) = ($2, $4);
  92. $options =~ s/^\s+//;
  93. my %optdone;
  94. foreach my $opt (split(/\s*,\s*/, $options)) {
  95. unless ($opt =~ m/^([-0-9a-z]+)\=\s*(.*\S)$/i) {
  96. push @errors, sprintf(_g("bad key-value after \`;': \`%s'"), $opt);
  97. next;
  98. }
  99. my ($k, $v) = (field_capitalize($1), $2);
  100. if ($optdone{$k}) {
  101. push @errors, sprintf(_g("repeated key-value %s"), $k);
  102. }
  103. $optdone{$k} = 1;
  104. if ($k eq 'Urgency') {
  105. push @errors, sprintf(_g("badly formatted urgency value: %s"), $v)
  106. unless ($v =~ m/^([-0-9a-z]+)((\s+.*)?)$/i);
  107. } elsif ($k eq 'Binary-Only') {
  108. push @errors, sprintf(_g("bad binary-only value: %s"), $v)
  109. unless ($v eq "yes");
  110. } elsif ($k =~ m/^X[BCS]+-/i) {
  111. } else {
  112. push @errors, sprintf(_g("unknown key-value %s"), $k);
  113. }
  114. }
  115. my ($ok, $msg) = version_check($version);
  116. unless ($ok) {
  117. push @errors, sprintf(_g("version '%s' is invalid: %s"), $version, $msg);
  118. }
  119. } else {
  120. push @errors, _g("the header doesn't match the expected regex");
  121. }
  122. return @errors;
  123. }
  124. sub check_trailer {
  125. my ($self) = @_;
  126. my @errors;
  127. if (defined($self->{trailer}) and $self->{trailer} =~ $regex_trailer) {
  128. if ($3 ne ' ') {
  129. push @errors, _g("badly formatted trailer line");
  130. }
  131. unless (defined str2time($4)) {
  132. push @errors, sprintf(_g("couldn't parse date %s"), $4);
  133. }
  134. } else {
  135. push @errors, _g("the trailer doesn't match the expected regex");
  136. }
  137. return @errors;
  138. }
  139. =item $entry->normalize()
  140. Normalize the content. Strip whitespaces at end of lines, use a single
  141. empty line to separate each part.
  142. =cut
  143. sub normalize {
  144. my ($self) = @_;
  145. $self->SUPER::normalize();
  146. #XXX: recreate header/trailer
  147. }
  148. sub get_source {
  149. my ($self) = @_;
  150. if (defined($self->{header}) and $self->{header} =~ $regex_header) {
  151. return $1;
  152. }
  153. return;
  154. }
  155. sub get_version {
  156. my ($self) = @_;
  157. if (defined($self->{header}) and $self->{header} =~ $regex_header) {
  158. return Dpkg::Version->new($2);
  159. }
  160. return;
  161. }
  162. sub get_distributions {
  163. my ($self) = @_;
  164. if (defined($self->{header}) and $self->{header} =~ $regex_header) {
  165. my $value = $3;
  166. $value =~ s/^\s+//;
  167. my @dists = split(/\s+/, $value);
  168. return @dists if wantarray;
  169. return $dists[0];
  170. }
  171. return;
  172. }
  173. sub get_optional_fields {
  174. my ($self) = @_;
  175. my $f = Dpkg::Control::Changelog->new();
  176. if (defined($self->{header}) and $self->{header} =~ $regex_header) {
  177. my $options = $4;
  178. $options =~ s/^\s+//;
  179. foreach my $opt (split(/\s*,\s*/, $options)) {
  180. if ($opt =~ m/^([-0-9a-z]+)\=\s*(.*\S)$/i) {
  181. $f->{$1} = $2;
  182. }
  183. }
  184. }
  185. my @closes = find_closes(join("\n", @{$self->{changes}}));
  186. if (@closes) {
  187. $f->{Closes} = join(" ", @closes);
  188. }
  189. return $f;
  190. }
  191. sub get_urgency {
  192. my ($self) = @_;
  193. my $f = $self->get_optional_fields();
  194. if (exists $f->{Urgency}) {
  195. $f->{Urgency} =~ s/\s.*$//;
  196. return lc($f->{Urgency});
  197. }
  198. return;
  199. }
  200. sub get_maintainer {
  201. my ($self) = @_;
  202. if (defined($self->{trailer}) and $self->{trailer} =~ $regex_trailer) {
  203. return "$1 <$2>";
  204. }
  205. return;
  206. }
  207. sub get_timestamp {
  208. my ($self) = @_;
  209. if (defined($self->{trailer}) and $self->{trailer} =~ $regex_trailer) {
  210. return $4;
  211. }
  212. return;
  213. }
  214. =back
  215. =head1 UTILITY FUNCTIONS
  216. =head3 my @closed_bugs = find_closes($changes)
  217. Takes one string as argument and finds "Closes: #123456, #654321" statements
  218. as supported by the Debian Archive software in it. Returns all closed bug
  219. numbers in an array.
  220. =cut
  221. sub find_closes {
  222. my $changes = shift;
  223. my %closes;
  224. while ($changes &&
  225. ($changes =~ /closes:\s*(?:bug)?\#?\s?\d+(?:,\s*(?:bug)?\#?\s?\d+)*/ig)) {
  226. $closes{$_} = 1 foreach($& =~ /\#?\s?(\d+)/g);
  227. }
  228. my @closes = sort { $a <=> $b } keys %closes;
  229. return @closes;
  230. }
  231. =head1 AUTHOR
  232. Raphaël Hertzog <hertzog@debian.org>.
  233. =cut
  234. 1;