home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / bin / podebconf-report-po < prev    next >
Encoding:
Text File  |  2006-06-28  |  15.2 KB  |  566 lines

  1. #!/usr/bin/perl -w
  2.  
  3. # podebconf-report-po, Send outdated debconf PO files to the last translator
  4. # Copyright (C) 2004-2006 Fabio Tranchitella <kobold@kobold.it>
  5. #                         Denis Barbier <barbier@debian.org>
  6. #
  7. # This program is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation; either version 2 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15. # GNU Library General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program; if not, write to the Free Software
  19. # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  20. #
  21.  
  22. ## Release information
  23. my $PROGRAM = "podebconf-report-po";
  24. my $VERSION = "0.10";
  25.  
  26. ## Loaded modules, require libmail-sendmail-perl
  27. use strict;
  28. eval q{use Mail::Sendmail;};
  29. die "$PROGRAM: This program requires the libmail-sendmail-perl package.\n".
  30.     "$PROGRAM: Aborting!\n" if $@;
  31. my $no_zlib = 0;
  32. eval q{use Compress::Zlib;};
  33. if ($@) {
  34.     $no_zlib = 1;
  35.     eval q{ sub Compress::Zlib::memGzip { return shift; } };
  36. }
  37. use MIME::Base64;
  38. use MIME::QuotedPrint;
  39. use Getopt::Long;
  40. use POSIX;
  41.  
  42. ## Global variables
  43. my $HELP_ARG = 0;
  44. my $VERSION_ARG = 0;
  45. my $VERBOSE_ARG = 0;
  46. my $SUBMIT_ARG = 0;
  47. my $FORCE_ARG = 0;
  48. my $LANGUAGETEAM_ARG = 0;
  49. my $SMTP_ARG = "";
  50. my $TEMPLATE_ARG = "";
  51. my $DEFAULT_ARG = 0;
  52. my $PACKAGE_ARG = "";
  53. my $SUMMARY_ARG = 0;
  54. my $FROM_ARG = (exists($ENV{'DEBEMAIL'}) ? $ENV{'DEBEMAIL'} : "");
  55. my $BTS_ARG = "";
  56. my $DEADLINE_ARG = "";
  57. my $PODIR_ARG = "";
  58. my $GZIP_ARG = 0;
  59.  
  60. my @TOPDIRS = qw{../.. .. .};
  61.  
  62. my $PODIR = '';
  63.  
  64. my $EDITOR = '/usr/bin/sensible-editor';
  65.  
  66. ## Default templates
  67. my $comments = "# Lines beginning with a number sign are comments, they are removed when
  68. # sending mails.  If a line is composed of a # followed by a 'Name: Value'
  69. # pair, it is interpreted as a mail header field and is passed to your mail
  70. # transport agent.  You can edit/add/remove those header fields.";
  71.  
  72. my $SUBJECT_TRANSLATOR = "Please update debconf PO translation for the package <package_and_version>";
  73. my $BODY_TRANSLATOR = $comments. "
  74. # From: <from>
  75. # Subject: <subject>
  76. # Reply-To: <reply-to>
  77. #
  78. # This mail will be sent to the following people:
  79. <filelist>
  80.  
  81. Hi,
  82.  
  83. You are noted as the last translator of the debconf translation for
  84. <package>. The English template has been changed, and now some messages
  85. are marked \"fuzzy\" in your translation or are missing.
  86. I would be grateful if you could take the time and update it.
  87. <reply>
  88. <deadline>
  89.  
  90. Thanks,
  91. ";
  92.  
  93. my $SUBJECT_SUBMIT = "debconf PO translations for the package <package> are outdated";
  94. my $BODY_SUBMIT = $comments. "
  95. # From: <from>
  96. # Subject: <subject>
  97.  
  98. Package: <package>
  99. Version: N/A
  100. Severity: wishlist
  101. Tags: l10n
  102.  
  103. The following debconf translations are outdated:
  104.   <filelist>
  105.  
  106. Translators, please send your translations to this bugreport.
  107. <deadline>
  108.  
  109. Thanks,
  110. ";
  111.  
  112. my $SUBJECT = '';
  113. my $BODY = '';
  114. #  Warnings may be deleted from screen when entering editor,
  115. #  so display them when it is closed.
  116. my $warn = '';
  117.  
  118. ## Handle options
  119. GetOptions
  120. (
  121.  "help"            => \$HELP_ARG,
  122.  "version"         => \$VERSION_ARG,
  123.  "v|verbose"       => \$VERBOSE_ARG,
  124.  "f|force"         => \$FORCE_ARG,
  125.  "podir=s"         => \$PODIR_ARG,
  126.  "smtp=s"          => \$SMTP_ARG,
  127.  "template=s"      => \$TEMPLATE_ARG,
  128.  "default"         => \$DEFAULT_ARG,
  129.  "gzip"            => \$GZIP_ARG,
  130.  "languageteam"    => \$LANGUAGETEAM_ARG,
  131.  "package=s"       => \$PACKAGE_ARG,
  132.  "deadline=s"      => \$DEADLINE_ARG,
  133.  "summary"         => \$SUMMARY_ARG,
  134.  "from=s"          => \$FROM_ARG,
  135.  "bts=s"           => \$BTS_ARG,
  136.  "submit"          => \$SUBMIT_ARG
  137.  ) or &Help_InvalidOption;
  138.  
  139. &Help_PrintVersion if $VERSION_ARG;
  140. &Help_PrintHelp    if $HELP_ARG;
  141.  
  142. ## Try to find default editor
  143. $EDITOR = $ENV{'EDITOR'} if exists($ENV{'EDITOR'});
  144. $EDITOR = $ENV{'VISUAL'} if exists($ENV{'VISUAL'});
  145.  
  146. ## Try to locate the PO directory
  147. if ($PODIR_ARG eq "") {
  148.     foreach my $d (@TOPDIRS) {
  149.         $PODIR = "$d/debian/po" if (-d "$d/debian/po");
  150.     }
  151. } else {
  152.     $PODIR = $PODIR_ARG;
  153. }
  154. die "Directory po not found, exiting!\n" if $PODIR eq "";
  155. die "Wrong argument: $PODIR is not a directory!\n" unless -d $PODIR;
  156.  
  157. if ($no_zlib && $GZIP_ARG) {
  158.     $warn .= 
  159.       "Warning: This program requires the libcompress-zlib-perl package in order\n".
  160.       "         to support the --gzip flag, but it is not installed.\n".
  161.       "         PO files will not be compressed!\n\n";
  162.     $GZIP_ARG = 0;
  163. }
  164.  
  165. ## Try to find the maintainer e-mail address and the package name
  166.  
  167. #  Package version
  168. my $PKG_VERSION = "N/A";
  169. #  Expanded into "<package> <version>" if version is found, <package> otherwise
  170. my $PACKAGE_AND_VERSION = "";
  171. if ($PACKAGE_ARG =~ s/_(.*)//) {
  172.     $PKG_VERSION = $1;
  173. }
  174.  
  175. if ($PACKAGE_ARG eq "" or $FROM_ARG eq "") {
  176.     my $CONTROL = '';
  177.     foreach my $d (@TOPDIRS) {
  178.         $CONTROL = "$d/debian/control" if (-f "$d/debian/control");
  179.     }
  180.     if ($CONTROL eq '') {
  181.         foreach my $d (@TOPDIRS) {
  182.             $CONTROL = "$d/debian/control.in" if (-f "$d/debian/control.in");
  183.         }
  184.     }
  185.  
  186.     if (-f $CONTROL) {
  187.         ##  Only read the first stanza
  188.         local $/ = "\n\n";
  189.         open (CNTRL, "< $CONTROL")
  190.             or die "Unable to read $CONTROL: $!\n";
  191.         my $text = <CNTRL>;
  192.         close (CNTRL)
  193.             or die "Unable to close $CONTROL: $!\n";
  194.         if ($PACKAGE_ARG eq "" && $text =~ m/^Source: (.*)/m) {
  195.             $PACKAGE_ARG = $1;
  196.         }
  197.  
  198.         if ($FROM_ARG eq "" && $text =~ m/^Maintainer: (.*)/m) {
  199.             $FROM_ARG = $1;
  200.         }
  201.     }
  202. }
  203. if ($PKG_VERSION eq "N/A") {
  204.     my $CHANGELOG = '';
  205.     foreach my $d (@TOPDIRS) {
  206.         $CHANGELOG = "$d/debian/changelog" if (-f "$d/debian/changelog");
  207.     }
  208.     if (-f $CHANGELOG) {
  209.         #  Version information is not vital, do not abort
  210.         #  if it cannot be retrieved.
  211.         if (open (CHG, "< $CHANGELOG")) {
  212.             while (<CHG>) {
  213.                 if (m/^$PACKAGE_ARG\s+\((.*)\)\s/) {
  214.                     $PKG_VERSION = $1;
  215.                 }
  216.                 last if m/^ --/;
  217.             }
  218.         }
  219.         close (CHG);
  220.     }
  221. }
  222. $PACKAGE_AND_VERSION = $PACKAGE_ARG .
  223.     ($PKG_VERSION ne 'N/A' ? " ".$PKG_VERSION : "");
  224. Verbose("Package: $PACKAGE_ARG");
  225. Verbose("Version: $PKG_VERSION");
  226. Verbose("Maintainer: $FROM_ARG");
  227.  
  228. if ($DEADLINE_ARG ne "") {
  229.     $DEADLINE_ARG = "\nThe deadline for receiving the updated translation is $DEADLINE_ARG.";
  230. }
  231.  
  232. my $REPLY = '';
  233. if ($BTS_ARG =~ m/^\d+$/) {
  234.     $BTS_ARG .= "\@bugs.debian.org";
  235.     $REPLY = "Please respect the Reply-To: field and send your updated translation to\n$BTS_ARG.";
  236. } else {
  237.     $REPLY = "Please send the updated file to me, or submit it as a wishlist bug\nagainst <package>.";
  238. }
  239.  
  240. if ($SUBMIT_ARG) {
  241.     $BODY = $BODY_SUBMIT;
  242.     $SUBJECT = $SUBJECT_SUBMIT;
  243. } else {
  244.     $BODY = $BODY_TRANSLATOR;
  245.     $SUBJECT = $SUBJECT_TRANSLATOR;
  246. }
  247.  
  248. ## Apply the values to the subject and to the body of the message
  249.  
  250. $SUBJECT =~ s/<package>/$PACKAGE_ARG/g;
  251. $SUBJECT =~ s/<version>/$PKG_VERSION/g;
  252. $SUBJECT =~ s/<package_and_version>/$PACKAGE_AND_VERSION/g;
  253. $BODY =~ s/<reply>/$REPLY/g;
  254. $BODY =~ s/<reply-to>/$BTS_ARG/g;
  255. $BODY =~ s/\n# Reply-To: \n/\n/;
  256. $BODY =~ s/<subject>/$SUBJECT/g;
  257. $BODY =~ s/<package>/$PACKAGE_ARG/g;
  258. $BODY =~ s/<version>/$PKG_VERSION/g;
  259. $BODY =~ s/<package_and_version>/$PACKAGE_AND_VERSION/g;
  260. $BODY =~ s/<from>/$FROM_ARG/g;
  261. $BODY =~ s/\n<deadline>/$DEADLINE_ARG/g;
  262.  
  263. ## Check every file with .po extension in $PODIR ...
  264. Verbose("Checking for PO files in $PODIR");
  265. opendir(DIR, $PODIR);
  266. my $poFiles = {};
  267. foreach my $poFile (grep(/\.po$/, readdir(DIR))) {
  268.     local $/ = "\n\n";
  269.     $poFiles->{$poFile} = {};
  270.     my $outdated = 0;
  271.     my $found_header = 0;
  272.     open (PO, "< $PODIR/$poFile")
  273.         or die "Unable to read $PODIR/$poFile: $!\n";
  274.     while (<PO>) {
  275.         if ($found_header == 0 && m/msgid ""\nmsgstr/s) {
  276.             $found_header = 1;
  277.             #  Concatenate lines
  278.             s/"\n"//g;
  279.             if (m/\\nLast-Translator: (.*?)\\n/ && $1 ne 'FULL NAME <EMAIL@ADDRESS>') {
  280.                 $poFiles->{$poFile}->{translator} = $1;
  281.             } else {
  282.                 $warn .= "Warning: $poFile:  Unable to determine last translator.  Skipping file!\n";
  283.                 last;
  284.             }
  285.             if (m/\\nContent-Type: [^;]*; charset=(.*?)\\n/) {
  286.                 $poFiles->{$poFile}->{charset} = $1;
  287.             } else {
  288.                 $warn .= "Warning: $poFile:  Unable to determine charset.  Skipping file!\n";
  289.                 last;
  290.             }
  291.             if ($LANGUAGETEAM_ARG && m/\\nLanguage-Team: (.*?)\\n/) {
  292.                 $poFiles->{$poFile}->{team} = $1
  293.                     if $1 ne 'LANGUAGE <LL@li.org>';
  294.             }
  295.             next;
  296.         }
  297.         #  Ignore outdated msgids
  298.         next unless m/^msgid /m;
  299.         #  Check for fuzzy or missing translations
  300.         s/\n+$//s;
  301.         if (m/^#, .*fuzzy/m or m/\nmsgstr ""$/s) {
  302.             $outdated = 1;
  303.             last;
  304.         }
  305.     }
  306.     close (PO)
  307.         or die "Unable to close $PODIR/$poFile: $!\n";
  308.     delete $poFiles->{$poFile} unless $outdated;
  309. }
  310. closedir(DIR);
  311. if (keys %$poFiles) {
  312.     print "Outdated files: ".join(' ', keys %$poFiles)."\n";
  313. } else {
  314.     print "No outdated files\n";
  315.     exit(0);
  316. }
  317.  
  318. my $filelist = '';
  319. if ($SUBMIT_ARG) {
  320.     $filelist = join(' ', sort keys %$poFiles)."\n";
  321. } else {
  322.     foreach my $poFile (sort keys %$poFiles) {
  323.         $filelist .= '### ' . $poFile . ': ' . $poFiles->{$poFile}->{translator};
  324.         $filelist .= ', ' . $poFiles->{$poFile}->{team} if defined($poFiles->{$poFile}->{team});
  325.         $filelist .= "\n";
  326.     }
  327.     #  Remove non-ASCII characters
  328.     $filelist =~ s/[\x80-\xff]/?/g;
  329. }
  330. $BODY =~ s/<filelist>\n/$filelist/g;
  331.  
  332. my %headers = ();
  333. if ($TEMPLATE_ARG eq "") {
  334.     $BODY = &OpenEditor($EDITOR, $BODY) if not $DEFAULT_ARG;
  335. } else {
  336.     $BODY = &ReadFile($TEMPLATE_ARG);
  337. }
  338. %headers = &ParseHeaders($BODY);
  339. $BODY = &RemoveHeaders($BODY);
  340.  
  341. print STDERR $warn if $warn ne '';
  342.  
  343. my @mails = ();
  344. if ($SUBMIT_ARG) {
  345.     $BODY =~ s/<filelist>/$filelist/g;
  346.     my %mail = (
  347.         From => $FROM_ARG,
  348.         To => "maintonly\@bugs.debian.org",
  349.         Subject => $SUBJECT,
  350.         'X-Mail-Originator' => "$PROGRAM $VERSION"
  351.     );
  352.     $mail{body} = encode_qp($BODY);
  353.     @mails = (\%mail);
  354. } else {
  355.     $BODY = encode_qp($BODY);
  356.     my $ext = ($GZIP_ARG ? '.gz' : '');
  357.     foreach my $file (keys %$poFiles) {
  358.         my $content = &ReadFile($PODIR . "/" . $file);
  359.         $content = Compress::Zlib::memGzip($content) if $GZIP_ARG;
  360.         my $file_encoded = encode_base64($content);
  361.         my $contentType = ($GZIP_ARG ? "application/octet-stream" : "text/x-gettext; name=\"$file\"; charset=\"$poFiles->{$file}->{charset}\"");
  362.         my %mail = (
  363.             From => $FROM_ARG,
  364.             To => $poFiles->{$file}->{translator},
  365.             Subject => $SUBJECT,
  366.             'X-Mail-Originator' => "$PROGRAM $VERSION"
  367.         );
  368.         $mail{To} .= ", ". $poFiles->{$file}->{team}
  369.             if defined $poFiles->{$file}->{team};
  370.  
  371.         my $boundary = "=" . time() . "=";
  372.         $mail{'content-type'} = "multipart/mixed; boundary=\"$boundary\"";
  373.         $mail{body} = <<_EOF_;
  374. --$boundary
  375. Content-Type: text/plain; charset="us-ascii"
  376. Content-Transfer-Encoding: quoted-printable
  377.  
  378. $BODY
  379.  
  380. --$boundary
  381. Content-Type: $contentType
  382. Content-Transfer-Encoding: base64
  383. Content-Disposition: attachment; filename="$file$ext"
  384.  
  385. $file_encoded
  386. --$boundary--
  387. _EOF_
  388.  
  389.         push(@mails, \%mail);
  390.     }
  391. }
  392.  
  393. #  Add mail headers and remove non-ASCII characters
  394. foreach my $refmail (@mails) {
  395.     foreach my $h (keys(%headers)) {
  396.         $refmail->{$h} = &DropNonASCII($headers{$h});
  397.     }
  398.     foreach my $h (qw(From To Subject)) {
  399.         $refmail->{$h} = &DropNonASCII($refmail->{$h});
  400.     }
  401.     $refmail->{smtp} = $SMTP_ARG if ($SMTP_ARG ne '');
  402. }
  403.  
  404. if (!$FORCE_ARG) {
  405.     if ($SUBMIT_ARG) {
  406.         print "Ready to send the bug report against the package $PACKAGE_ARG, are you sure? [y/N] ";
  407.     } else {
  408.         print "Ready to send the emails, are you sure? [y/N] ";
  409.     }
  410.     my $line = <>;
  411.     chop $line;
  412.     exit(0) if ($line ne "Y" and $line ne "y");
  413. }
  414.  
  415. #  Make Perl compiler quiet
  416. print $Mail::Sendmail::error . $Mail::Sendmail::error if 0;
  417. foreach my $mail (@mails) {
  418.     sendmail(%{$mail}) || print "Couldn't send the email: $Mail::Sendmail::error\n";
  419. }
  420. if ($SUMMARY_ARG) {
  421.     my %summary = (
  422.         From => $FROM_ARG,
  423.         To => $FROM_ARG,
  424.         Subject => $SUBJECT,
  425.         'X-Mail-Originator' => "$PROGRAM $VERSION"
  426.     );
  427.     $summary{body} = "List of outdated files:\n";
  428.     foreach my $poFile (sort keys %$poFiles) {
  429.         $summary{body} .= '  ' . $poFile . ': ' . $poFiles->{$poFile}->{translator};
  430.         $summary{body} .= ', ' . $poFiles->{$poFile}->{team} if defined($poFiles->{$poFile}->{team});
  431.         $summary{body} .= "\n";
  432.     }
  433.     $summary{body} .= "Translators received the mail below.\n\n";
  434.     $summary{body} .= encode_qp($BODY);
  435.     sendmail(%summary) || print "Couldn't send the email: $Mail::Sendmail::error\n";
  436. }
  437. exit(0);
  438.  
  439. ###############################################################################
  440.  
  441. sub OpenEditor
  442. {
  443.     my $editor = shift;
  444.     my $body = shift;
  445.     my $opts = "";
  446.     my $tmpnam = tmpnam();
  447.  
  448.     open (OUT, "> $tmpnam")
  449.         or die ("Couldn't write $tmpnam: $!\nExiting!\n");
  450.     print OUT $body;
  451.     close(OUT)
  452.         or die ("Couldn't close $tmpnam: $!\nExiting!\n");
  453.  
  454.     $opts = "-f" if ($editor eq "vim");
  455.     system("$editor $opts $tmpnam");
  456.  
  457.     $body = &ReadFile($tmpnam) if (-f $tmpnam);
  458.     unlink($tmpnam);
  459.  
  460.     return $body;
  461. }
  462.  
  463. sub ParseHeaders
  464. {
  465.     my $body = shift;
  466.     my %headers = ();
  467.  
  468.     while ($body =~ s/^#[ \t]*([^\n]*)\n//s) {
  469.         my $comment = $1;
  470.         if ($comment =~ m/^([a-zA-Z0-9_-]+):\s*([^\n]+)$/) {
  471.             $headers{$1} = $2;
  472.         }
  473.     }
  474.     return %headers;
  475. }
  476.  
  477. sub RemoveHeaders
  478. {
  479.     my $body = shift;
  480.     #  First remove comments
  481.     1 while $body =~ s/^#[^\n]*\n//s;
  482.     #  Optional empty lines
  483.     $body =~ s/^\s+//s;
  484.     return $body;
  485. }
  486.  
  487. sub DropNonASCII {
  488.     my $text = shift;
  489.     $text =~ s/[\x80-\xff]/?/g;
  490.     return $text;
  491. }
  492.  
  493. sub ReadFile
  494. {
  495.     my $file = shift;
  496.     local $/ = undef;
  497.     open(FILE, "< $file")
  498.         or die ("Couldn't read $file: $!\nExiting!\n");
  499.     my $body = <FILE>;
  500.     close(FILE)
  501.         or die ("Couldn't close $file: $!\nExiting!\n");
  502.     return $body;
  503. }
  504.  
  505. ## Handle invalid arguments
  506. sub Help_InvalidOption
  507. {
  508.     print STDERR "Try `${PROGRAM} --help' for more information.\n";
  509.     exit 1;
  510. }
  511.  
  512. ## Print the usage message and exit
  513. sub Help_PrintHelp
  514. {
  515.     print <<_EOF_;
  516.  
  517. Usage: ${PROGRAM} [OPTIONS]
  518. Send outdated debconf PO files to the last translators.
  519.  
  520. Options:
  521.   --help                display this help and exit
  522.   --version             display version information and exit
  523.   -v, --verbose         display additional information
  524.   -f, --force           send the email without confirmation
  525.   --podir=DIRECTORY     specify where are located the PO files
  526.   --smtp=SERVER         specify SMTP server for mailing (default localhost)
  527.   --template=TEMPLATE   specify file to use it as template for the emails
  528.   --default             don't open the editor and use the template as is
  529.   --gzip                compress PO files with gzip
  530.   --package=PACKAGE     specify the name of the package
  531.   --from=MAINTAINER     specify the name and the email address of the sender
  532.   --deadline=DEADLINE   specify the deadline for receiving the updated
  533.                         translations
  534.   --languageteam        send the email also to the Language Team
  535.   --summary             send a status report to the maintainer with the list
  536.                         of emails sent to translators
  537.   --submit              send a bug report against the package with a report
  538.                         of the outdated debconf translations
  539.   --bts=BUGNUMBER       specify the Debian bug number to set as reply-to
  540.  
  541. _EOF_
  542.     exit 0;
  543. }
  544.  
  545. ## Print the version text and exit
  546. sub Help_PrintVersion
  547. {
  548.     print <<_EOF_;
  549. ${PROGRAM} $VERSION
  550. Copyright (C) 2004-2006 Fabio Tranchitella and Denis Barbier.
  551. This is free software; see the source for copying conditions.  There is NO
  552. warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  553. _EOF_
  554.     exit 0;
  555. }
  556.  
  557. sub Verbose
  558. {
  559.     my $msg = shift;
  560.     return unless $VERBOSE_ARG;
  561.     $msg =~ s/^/**${PROGRAM}: /mg;
  562.     print STDERR $msg."\n";
  563. }
  564.