Debian.pm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  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.03';
  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{
  49. ^
  50. (\w$name_chars*) # Package name
  51. \ \(([^\(\) \t]+)\) # Package version
  52. ((?:\s+$name_chars+)+) # Target distribution
  53. \; # Separator
  54. (.*?) # Key=Value options
  55. \s*$ # Trailing space
  56. }xi;
  57. # The matched content is the maintainer name ($1), its email ($2),
  58. # some blanks ($3) and the timestamp ($4), which is decomposed into
  59. # day of week ($6), date-time ($7) and this into month name ($8).
  60. our $regex_trailer = qr<
  61. ^
  62. \ \-\- # Trailer marker
  63. \ (.*) # Maintainer name
  64. \ \<(.*)\> # Maintainer email
  65. (\ \ ?) # Blanks
  66. (
  67. ((\w+)\,\s*)? # Day of week (abbreviated)
  68. (
  69. \d{1,2}\s+ # Day of month
  70. (\w+)\s+ # Month name (abbreviated)
  71. \d{4}\s+ # Year
  72. \d{1,2}:\d\d:\d\d\s+[-+]\d{4} # ISO 8601 date
  73. )
  74. )
  75. \s*$ # Trailing space
  76. >xo;
  77. my %week_day = map { $_ => 1 } qw(Mon Tue Wed Thu Fri Sat Sun);
  78. my %month_abbrev = map { $_ => 1 } qw(
  79. Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
  80. );
  81. my %month_name = map { $_ => } qw(
  82. January February March April May June July
  83. August September October November December
  84. );
  85. ## use critic
  86. =head1 METHODS
  87. =over 4
  88. =item @items = $entry->get_change_items()
  89. Return a list of change items. Each item contains at least one line.
  90. A change line starting with an asterisk denotes the start of a new item.
  91. Any change line like "C<[ Raphaël Hertzog ]>" is treated like an item of its
  92. own even if it starts a set of items attributed to this person (the
  93. following line necessarily starts a new item).
  94. =cut
  95. sub get_change_items {
  96. my $self = shift;
  97. my (@items, @blanks, $item);
  98. foreach my $line (@{$self->get_part('changes')}) {
  99. if ($line =~ /^\s*\*/) {
  100. push @items, $item if defined $item;
  101. $item = "$line\n";
  102. } elsif ($line =~ /^\s*\[\s[^\]]+\s\]\s*$/) {
  103. push @items, $item if defined $item;
  104. push @items, "$line\n";
  105. $item = undef;
  106. @blanks = ();
  107. } elsif ($line =~ /^\s*$/) {
  108. push @blanks, "$line\n";
  109. } else {
  110. if (defined $item) {
  111. $item .= "@blanks$line\n";
  112. } else {
  113. $item = "$line\n";
  114. }
  115. @blanks = ();
  116. }
  117. }
  118. push @items, $item if defined $item;
  119. return @items;
  120. }
  121. =item @errors = $entry->parse_header()
  122. =item @errors = $entry->parse_trailer()
  123. Return a list of errors. Each item in the list is an error message
  124. describing the problem. If the empty list is returned, no errors
  125. have been found.
  126. =cut
  127. sub parse_header {
  128. my $self = shift;
  129. my @errors;
  130. if (defined($self->{header}) and $self->{header} =~ $regex_header) {
  131. $self->{header_source} = $1;
  132. my $version = Dpkg::Version->new($2);
  133. my ($ok, $msg) = version_check($version);
  134. if ($ok) {
  135. $self->{header_version} = $version;
  136. } else {
  137. push @errors, sprintf(g_("version '%s' is invalid: %s"), $version, $msg);
  138. }
  139. @{$self->{header_dists}} = split ' ', $3;
  140. my $options = $4;
  141. $options =~ s/^\s+//;
  142. my $f = Dpkg::Control::Changelog->new();
  143. foreach my $opt (split(/\s*,\s*/, $options)) {
  144. unless ($opt =~ m/^([-0-9a-z]+)\=\s*(.*\S)$/i) {
  145. push @errors, sprintf(g_("bad key-value after ';': '%s'"), $opt);
  146. next;
  147. }
  148. my ($k, $v) = (field_capitalize($1), $2);
  149. if (exists $f->{$k}) {
  150. push @errors, sprintf(g_('repeated key-value %s'), $k);
  151. } else {
  152. $f->{$k} = $v;
  153. }
  154. if ($k eq 'Urgency') {
  155. push @errors, sprintf(g_('badly formatted urgency value: %s'), $v)
  156. unless ($v =~ m/^([-0-9a-z]+)((\s+.*)?)$/i);
  157. } elsif ($k eq 'Binary-Only') {
  158. push @errors, sprintf(g_('bad binary-only value: %s'), $v)
  159. unless ($v eq 'yes');
  160. } elsif ($k =~ m/^X[BCS]+-/i) {
  161. } else {
  162. push @errors, sprintf(g_('unknown key-value %s'), $k);
  163. }
  164. }
  165. $self->{header_fields} = $f;
  166. } else {
  167. push @errors, g_("the header doesn't match the expected regex");
  168. }
  169. return @errors;
  170. }
  171. sub parse_trailer {
  172. my $self = shift;
  173. my @errors;
  174. if (defined($self->{trailer}) and $self->{trailer} =~ $regex_trailer) {
  175. $self->{trailer_maintainer} = "$1 <$2>";
  176. if ($3 ne ' ') {
  177. push @errors, g_('badly formatted trailer line');
  178. }
  179. # Validate the week day. Date::Parse used to ignore it, but Time::Piece
  180. # is much more strict and it does not gracefully handle bogus values.
  181. if (defined $5 and not exists $week_day{$6}) {
  182. push @errors, sprintf(g_('ignoring invalid week day \'%s\''), $6);
  183. }
  184. # Ignore the week day ('%a, '), as we have validated it above.
  185. local $ENV{LC_ALL} = 'C';
  186. eval {
  187. my $tp = Time::Piece->strptime($7, '%d %b %Y %T %z');
  188. $self->{trailer_timepiece} = $tp;
  189. } or do {
  190. # Validate the month. Date::Parse used to accept both abbreviated
  191. # and full months, but Time::Piece strptime() implementation only
  192. # matches the abbreviated one with %b, which is what we want anyway.
  193. if (not exists $month_abbrev{$8}) {
  194. # We have to nest the conditionals because May is the same in
  195. # full and abbreviated forms!
  196. if (exists $month_name{$8}) {
  197. push @errors, sprintf(g_('uses full instead of abbreviated month name \'%s\''),
  198. $8, $month_name{$8});
  199. } else {
  200. push @errors, sprintf(g_('invalid abbreviated month name \'%s\''), $8);
  201. }
  202. }
  203. push @errors, sprintf(g_("cannot parse non-comformant date '%s'"), $7);
  204. };
  205. $self->{trailer_timestamp_date} = $4;
  206. } else {
  207. push @errors, g_("the trailer doesn't match the expected regex");
  208. }
  209. return @errors;
  210. }
  211. =item $entry->check_header()
  212. Obsolete method. Use parse_header() instead.
  213. =cut
  214. sub check_header {
  215. my $self = shift;
  216. warnings::warnif('deprecated',
  217. 'obsolete check_header(), use parse_header() instead');
  218. return $self->parse_header();
  219. }
  220. =item $entry->check_trailer()
  221. Obsolete method. Use parse_trailer() instead.
  222. =cut
  223. sub check_trailer {
  224. my $self = shift;
  225. warnings::warnif('deprecated',
  226. 'obsolete check_trailer(), use parse_trailer() instead');
  227. return $self->parse_header();
  228. }
  229. =item $entry->normalize()
  230. Normalize the content. Strip whitespaces at end of lines, use a single
  231. empty line to separate each part.
  232. =cut
  233. sub normalize {
  234. my $self = shift;
  235. $self->SUPER::normalize();
  236. #XXX: recreate header/trailer
  237. }
  238. =item $src = $entry->get_source()
  239. Return the name of the source package associated to the changelog entry.
  240. =cut
  241. sub get_source {
  242. my $self = shift;
  243. return $self->{header_source};
  244. }
  245. =item $ver = $entry->get_version()
  246. Return the version associated to the changelog entry.
  247. =cut
  248. sub get_version {
  249. my $self = shift;
  250. return $self->{header_version};
  251. }
  252. =item @dists = $entry->get_distributions()
  253. Return a list of target distributions for this version.
  254. =cut
  255. sub get_distributions {
  256. my $self = shift;
  257. if (defined $self->{header_dists}) {
  258. return @{$self->{header_dists}} if wantarray;
  259. return $self->{header_dists}[0];
  260. }
  261. return;
  262. }
  263. =item $fields = $entry->get_optional_fields()
  264. Return a set of optional fields exposed by the changelog entry.
  265. It always returns a Dpkg::Control object (possibly empty though).
  266. =cut
  267. sub get_optional_fields {
  268. my $self = shift;
  269. my $f;
  270. if (defined $self->{header_fields}) {
  271. $f = $self->{header_fields};
  272. } else {
  273. $f = Dpkg::Control::Changelog->new();
  274. }
  275. my @closes = find_closes(join("\n", @{$self->{changes}}));
  276. if (@closes) {
  277. $f->{Closes} = join(' ', @closes);
  278. }
  279. return $f;
  280. }
  281. =item $urgency = $entry->get_urgency()
  282. Return the urgency of the associated upload.
  283. =cut
  284. sub get_urgency {
  285. my $self = shift;
  286. my $f = $self->get_optional_fields();
  287. if (exists $f->{Urgency}) {
  288. $f->{Urgency} =~ s/\s.*$//;
  289. return lc($f->{Urgency});
  290. }
  291. return;
  292. }
  293. =item $maint = $entry->get_maintainer()
  294. Return the string identifying the person who signed this changelog entry.
  295. =cut
  296. sub get_maintainer {
  297. my $self = shift;
  298. return $self->{trailer_maintainer};
  299. }
  300. =item $time = $entry->get_timestamp()
  301. Return the timestamp of the changelog entry.
  302. =cut
  303. sub get_timestamp {
  304. my $self = shift;
  305. return $self->{trailer_timestamp_date};
  306. }
  307. =item $time = $entry->get_timepiece()
  308. Return the timestamp of the changelog entry as a Time::Piece object.
  309. This function might return undef if there was no timestamp.
  310. =cut
  311. sub get_timepiece {
  312. my $self = shift;
  313. return $self->{trailer_timepiece};
  314. }
  315. =back
  316. =head1 UTILITY FUNCTIONS
  317. =over 4
  318. =item $bool = match_header($line)
  319. Checks if the line matches a valid changelog header line.
  320. =cut
  321. sub match_header {
  322. my $line = shift;
  323. return $line =~ /$regex_header/;
  324. }
  325. =item $bool = match_trailer($line)
  326. Checks if the line matches a valid changelog trailing line.
  327. =cut
  328. sub match_trailer {
  329. my $line = shift;
  330. return $line =~ /$regex_trailer/;
  331. }
  332. =item @closed_bugs = find_closes($changes)
  333. Takes one string as argument and finds "Closes: #123456, #654321" statements
  334. as supported by the Debian Archive software in it. Returns all closed bug
  335. numbers in an array.
  336. =cut
  337. sub find_closes {
  338. my $changes = shift;
  339. my %closes;
  340. while ($changes && ($changes =~ m{
  341. closes:\s*
  342. (?:bug)?\#?\s?\d+
  343. (?:,\s*(?:bug)?\#?\s?\d+)*
  344. }pigx)) {
  345. $closes{$_} = 1 foreach (${^MATCH} =~ /\#?\s?(\d+)/g);
  346. }
  347. my @closes = sort { $a <=> $b } keys %closes;
  348. return @closes;
  349. }
  350. =back
  351. =head1 CHANGES
  352. =head2 Version 1.03 (dpkg 1.18.8)
  353. New methods: $entry->get_timepiece().
  354. =head2 Version 1.02 (dpkg 1.18.5)
  355. New methods: $entry->parse_header(), $entry->parse_trailer().
  356. Deprecated methods: $entry->check_header(), $entry->check_trailer().
  357. =head2 Version 1.01 (dpkg 1.17.2)
  358. New functions: match_header(), match_trailer()
  359. Deprecated variables: $regex_header, $regex_trailer
  360. =head2 Version 1.00 (dpkg 1.15.6)
  361. Mark the module as public.
  362. =cut
  363. 1;