Debian.pm 8.3 KB

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