Debian.pm 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. # Copyright © 2009 Raphaël Hertzog <hertzog@debian.org>
  2. # Copyright © 2012-2013 Guillem Jover <guillem@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. package Dpkg::Changelog::Entry::Debian;
  17. use strict;
  18. use warnings;
  19. our $VERSION = '1.01';
  20. use Exporter qw(import);
  21. use Dpkg::Changelog::Entry;
  22. use parent qw(Dpkg::Changelog::Entry);
  23. our @EXPORT_OK = qw(match_header match_trailer find_closes
  24. $regex_header $regex_trailer);
  25. use Date::Parse;
  26. use Dpkg::Gettext;
  27. use Dpkg::Control::Fields;
  28. use Dpkg::Control::Changelog;
  29. use Dpkg::Version;
  30. =encoding utf8
  31. =head1 NAME
  32. Dpkg::Changelog::Entry::Debian - represents a Debian changelog entry
  33. =head1 DESCRIPTION
  34. This object represents a Debian changelog entry. It implements the
  35. generic interface Dpkg::Changelog::Entry. Only functions specific to this
  36. implementation are described below.
  37. =cut
  38. my $name_chars = qr/[-+0-9a-z.]/i;
  39. # XXX: Backwards compatibility, stop exporting on VERSION 2.00.
  40. ## no critic (Variables::ProhibitPackageVars)
  41. # The matched content is the source package name ($1), the version ($2),
  42. # the target distributions ($3) and the options on the rest of the line ($4).
  43. our $regex_header = qr/^(\w$name_chars*) \(([^\(\) \t]+)\)((?:\s+$name_chars+)+)\;(.*?)\s*$/i;
  44. # The matched content is the maintainer name ($1), its email ($2),
  45. # some blanks ($3) and the timestamp ($4).
  46. 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;
  47. ## use critic
  48. =head1 FUNCTIONS
  49. =over 4
  50. =item my @items = $entry->get_change_items()
  51. Return a list of change items. Each item contains at least one line.
  52. A change line starting with an asterisk denotes the start of a new item.
  53. Any change line like "[ Raphaël Hertzog ]" is treated like an item of its
  54. own even if it starts a set of items attributed to this person (the
  55. following line necessarily starts a new item).
  56. =cut
  57. sub get_change_items {
  58. my ($self) = @_;
  59. my (@items, @blanks, $item);
  60. foreach my $line (@{$self->get_part('changes')}) {
  61. if ($line =~ /^\s*\*/) {
  62. push @items, $item if defined $item;
  63. $item = "$line\n";
  64. } elsif ($line =~ /^\s*\[\s[^\]]+\s\]\s*$/) {
  65. push @items, $item if defined $item;
  66. push @items, "$line\n";
  67. $item = undef;
  68. @blanks = ();
  69. } elsif ($line =~ /^\s*$/) {
  70. push @blanks, "$line\n";
  71. } else {
  72. if (defined $item) {
  73. $item .= "@blanks$line\n";
  74. } else {
  75. $item = "$line\n";
  76. }
  77. @blanks = ();
  78. }
  79. }
  80. push @items, $item if defined $item;
  81. return @items;
  82. }
  83. =item my @errors = $entry->check_header()
  84. =item my @errors = $entry->check_trailer()
  85. Return a list of errors. Each item in the list is an error message
  86. describing the problem. If the empty list is returned, no errors
  87. have been found.
  88. =cut
  89. sub check_header {
  90. my ($self) = @_;
  91. my @errors;
  92. if (defined($self->{header}) and $self->{header} =~ $regex_header) {
  93. my ($version, $options) = ($2, $4);
  94. $options =~ s/^\s+//;
  95. my %optdone;
  96. foreach my $opt (split(/\s*,\s*/, $options)) {
  97. unless ($opt =~ m/^([-0-9a-z]+)\=\s*(.*\S)$/i) {
  98. push @errors, sprintf(_g("bad key-value after \`;': \`%s'"), $opt);
  99. next;
  100. }
  101. my ($k, $v) = (field_capitalize($1), $2);
  102. if ($optdone{$k}) {
  103. push @errors, sprintf(_g('repeated key-value %s'), $k);
  104. }
  105. $optdone{$k} = 1;
  106. if ($k eq 'Urgency') {
  107. push @errors, sprintf(_g('badly formatted urgency value: %s'), $v)
  108. unless ($v =~ m/^([-0-9a-z]+)((\s+.*)?)$/i);
  109. } elsif ($k eq 'Binary-Only') {
  110. push @errors, sprintf(_g('bad binary-only value: %s'), $v)
  111. unless ($v eq 'yes');
  112. } elsif ($k =~ m/^X[BCS]+-/i) {
  113. } else {
  114. push @errors, sprintf(_g('unknown key-value %s'), $k);
  115. }
  116. }
  117. my ($ok, $msg) = version_check($version);
  118. unless ($ok) {
  119. push @errors, sprintf(_g("version '%s' is invalid: %s"), $version, $msg);
  120. }
  121. } else {
  122. push @errors, _g("the header doesn't match the expected regex");
  123. }
  124. return @errors;
  125. }
  126. sub check_trailer {
  127. my ($self) = @_;
  128. my @errors;
  129. if (defined($self->{trailer}) and $self->{trailer} =~ $regex_trailer) {
  130. if ($3 ne ' ') {
  131. push @errors, _g('badly formatted trailer line');
  132. }
  133. unless (defined str2time($4)) {
  134. push @errors, sprintf(_g("couldn't parse date %s"), $4);
  135. }
  136. } else {
  137. push @errors, _g("the trailer doesn't match the expected regex");
  138. }
  139. return @errors;
  140. }
  141. =item $entry->normalize()
  142. Normalize the content. Strip whitespaces at end of lines, use a single
  143. empty line to separate each part.
  144. =cut
  145. sub normalize {
  146. my ($self) = @_;
  147. $self->SUPER::normalize();
  148. #XXX: recreate header/trailer
  149. }
  150. sub get_source {
  151. my ($self) = @_;
  152. if (defined($self->{header}) and $self->{header} =~ $regex_header) {
  153. return $1;
  154. }
  155. return;
  156. }
  157. sub get_version {
  158. my ($self) = @_;
  159. if (defined($self->{header}) and $self->{header} =~ $regex_header) {
  160. return Dpkg::Version->new($2);
  161. }
  162. return;
  163. }
  164. sub get_distributions {
  165. my ($self) = @_;
  166. if (defined($self->{header}) and $self->{header} =~ $regex_header) {
  167. my $value = $3;
  168. $value =~ s/^\s+//;
  169. my @dists = split(/\s+/, $value);
  170. return @dists if wantarray;
  171. return $dists[0];
  172. }
  173. return;
  174. }
  175. sub get_optional_fields {
  176. my ($self) = @_;
  177. my $f = Dpkg::Control::Changelog->new();
  178. if (defined($self->{header}) and $self->{header} =~ $regex_header) {
  179. my $options = $4;
  180. $options =~ s/^\s+//;
  181. foreach my $opt (split(/\s*,\s*/, $options)) {
  182. if ($opt =~ m/^([-0-9a-z]+)\=\s*(.*\S)$/i) {
  183. $f->{$1} = $2;
  184. }
  185. }
  186. }
  187. my @closes = find_closes(join("\n", @{$self->{changes}}));
  188. if (@closes) {
  189. $f->{Closes} = join(' ', @closes);
  190. }
  191. return $f;
  192. }
  193. sub get_urgency {
  194. my ($self) = @_;
  195. my $f = $self->get_optional_fields();
  196. if (exists $f->{Urgency}) {
  197. $f->{Urgency} =~ s/\s.*$//;
  198. return lc($f->{Urgency});
  199. }
  200. return;
  201. }
  202. sub get_maintainer {
  203. my ($self) = @_;
  204. if (defined($self->{trailer}) and $self->{trailer} =~ $regex_trailer) {
  205. return "$1 <$2>";
  206. }
  207. return;
  208. }
  209. sub get_timestamp {
  210. my ($self) = @_;
  211. if (defined($self->{trailer}) and $self->{trailer} =~ $regex_trailer) {
  212. return $4;
  213. }
  214. return;
  215. }
  216. =back
  217. =head1 UTILITY FUNCTIONS
  218. =over 4
  219. =item my $bool = match_header($line)
  220. Checks if the line matches a valid changelog header line.
  221. =cut
  222. sub match_header {
  223. my ($line) = @_;
  224. return $line =~ /$regex_header/;
  225. }
  226. =item my $bool = match_trailer($line)
  227. Checks if the line matches a valid changelog trailing line.
  228. =cut
  229. sub match_trailer {
  230. my ($line) = @_;
  231. return $line =~ /$regex_trailer/;
  232. }
  233. =item my @closed_bugs = find_closes($changes)
  234. Takes one string as argument and finds "Closes: #123456, #654321" statements
  235. as supported by the Debian Archive software in it. Returns all closed bug
  236. numbers in an array.
  237. =cut
  238. sub find_closes {
  239. my $changes = shift;
  240. my %closes;
  241. while ($changes &&
  242. ($changes =~ /closes:\s*(?:bug)?\#?\s?\d+(?:,\s*(?:bug)?\#?\s?\d+)*/pig)) {
  243. $closes{$_} = 1 foreach (${^MATCH} =~ /\#?\s?(\d+)/g);
  244. }
  245. my @closes = sort { $a <=> $b } keys %closes;
  246. return @closes;
  247. }
  248. =back
  249. =head1 CHANGES
  250. =head2 Version 1.01
  251. New functions: match_header(), match_trailer()
  252. Deprecated variables: $regex_header, $regex_trailer
  253. =head2 Version 1.00
  254. Mark the module as public.
  255. =head1 AUTHOR
  256. Raphaël Hertzog <hertzog@debian.org>.
  257. =cut
  258. 1;