/[webwml]/webwml/check_trans.pl
ViewVC logotype

Contents of /webwml/check_trans.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.86 - (hide annotations) (download)
Sun Oct 5 19:50:38 2008 UTC (4 years, 7 months ago) by bas
Branch: MAIN
Changes since 1.85: +1 -1 lines
File MIME type: text/plain
Fixed the mail subject of sent mails.
1 bas 1.65 #!/usr/bin/perl
2     #
3     # This is a little utility designed to keep track of translations
4     # in the Debian web site Subversion repository.
5     #
6     ## For information about translation revisions please see
7     ## http://www.debian.org/devel/website/uptodate
8     #
9     # Copyright (C) 2008 Bas Zoetekouw <bas@debian.org>
10     # Based on on code from:
11     # Copyright (C) 1998 Paolo Molaro <lupus@debian.org>
12     # Copyright (C) 1999-2003 Peter Karlsson <peterk@debian.org>
13     # Copyright (C) 2000,2001 Martin Quinson <mquinson@ens-lyon.fr>
14     #
15     # This program is free software; you can redistribute it and/or modify
16     # it under the terms of version 2 of the GNU General Public License as
17     # published by the Free Software Foundation.
18     #
19     ## This program is distributed in the hope that it will be useful, but
20     ## WITHOUT ANY WARRANTY; without even the implied warranty of
21     ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22     ## General Public License for more details.
23     #
24     #
25     # Invocation:
26 bas 1.82 # check_trans.pl [-cdlvqQ] [-C dir] [-p pattern] [-s subtree]
27 bas 1.84 # [-m { -n <num> | -M <email> } [-g] ]
28     # [-t outputtype]
29 bas 1.65 # [language]
30     #
31     # It needs to be run from the top level webwml directory.
32     # If you don't specify a language on the command line, the language name
33     # will be loaded from a file called language.conf, if such a file exists.
34     #
35     # For example:
36     # $ check_trans.pl -v italian
37     # You may also check only some subtrees as in:
38     # $ check_trans.pl -s devel italian
39     #
40     # Options:
41 bas 1.74 # -Q be really quiet (only show errors/warnings on stderr)
42 bas 1.65 # -q just don't whine about missing files
43     # -v show the status of all files (verbose)
44     # -V output what we're doing (very verbose)
45     # -d output diffs
46     # -l output log messages
47     # -T output translated diffs
48     # -p <pattern> include only files matching <pattern>,
49     # default is *.src|*.wml
50     # -s <subtree> check only that subtree
51     # -a output age of translation (if older than 2 months)
52 bas 1.82 # -c disable use of color in the output
53 bas 1.65 #
54     # Options useful when sending mails:
55 bas 1.84 # -m sends mails to translation maintainers as specified in
56     # in database in $lang/international/$lang/translator.db.pl
57 bas 1.65 # PLEASE CAREFULLY READ THE BELOW TEXT ABOUT MAKING MAILS!
58 bas 1.84 # -n <1|2|3> send mails of priority upper or equal to 1 (monthly),
59     # 2 (weekly) or 3 (daily), as specified in the translator
60     # database
61     # -M <email> instead of using the translator database, send all email
62     # the specified address. The translator database is not
63     # used.
64     # -g instead of sending mails, dump them to the console
65     # (no mails will be sent)
66 bas 1.65 #
67 bas 1.78 # GENERATING EMAILS
68 bas 1.65 # If you want to, this script send mails to the maintainer of the mails.
69     # BEWARE, SOME PEOPLE DO NOT LIKE TO RECEIVE AUTOMATIC MAILS!
70     #
71     # PREREQUISITES:
72     # You will need one database:
73     # - one in which to get info about translators and the frequency at
74     # which they want to get mails. It must be named
75 bas 1.84 # webwml/$lang/international/$lang/translator.db.pl
76 bas 1.65 # Please refer to the French one for more info.
77     #
78     # USAGE:
79 bas 1.84 # If you give the "-g" option, all mails are written to the console. No
80     # mails are sent out at all. This is useful for debugging.
81     # If you specify an email addres with the "-M" options, all mails are sent
82     # to the specified addressee. No mails are sent to any other addresses. It
83     # is useful if you want to run it for yourself.
84     # Without either of these options, real mails will be sent to real
85     # addresses.
86     # MAKE SURE THE ADDRESSEES REALLY WANT TO GET THESE MAILS!
87 italian 1.1
88     use Getopt::Std;
89 bas 1.65 use File::Basename;
90     use File::Spec::Functions;
91     use Term::ANSIColor;
92     use Encode;
93     #use Data::Dumper;
94     use FindBin;
95     FindBin::again();
96 barbier 1.31
97 bas 1.84 # These modules reside under webwml/Perl
98 bas 1.65 use lib "$FindBin::Bin/Perl";
99     use Local::VCS ':all';
100 bas 1.72 use Local::Util qw/ uniq read_file /;
101 french 1.41 use Local::WmlDiffTrans;
102 barbier 1.36 use Webwml::TransCheck;
103     use Webwml::TransIgnore;
104    
105 bas 1.65 use strict;
106     use warnings;
107    
108    
109 bas 1.84 # global variable to record verbosity
110     my $VERBOSE = 0;
111 french 1.13
112 bas 1.84 # default files to process
113 bas 1.65 my $DEFAULT_PATTERN = '(?:\.wml|\.src)$';
114    
115     # status codes
116     use constant {
117     ST_MISSING => 1,
118     ST_NEEDSUPDATE => 3,
119     ST_UPTODATE => 4,
120     ST_NOTATRANSL => 5,
121     ST_BROKEN => 6,
122     ST_OBSOLETE => 7,
123     ST_UNDEFINED => 8,
124     };
125    
126     # how to colour each different status
127     my %COLOURS = (
128     main::ST_MISSING => 'magenta',
129     main::ST_NEEDSUPDATE => 'blue',
130     main::ST_UPTODATE => 'green',
131     main::ST_NOTATRANSL => 'yellow',
132     main::ST_BROKEN => 'red',
133     main::ST_OBSOLETE => 'red',
134     main::ST_UNDEFINED => 'red',
135     'warn' => 'bold red',
136     );
137    
138 bas 1.84 # default values for sending mails
139     my $MY_EMAIL = q{Debian WWW translation watch <debian-www@lists.debian.org>};
140     my $DEFAULT_SUBJECT = q{Debian web page translations needing updates};
141     (my $DEFAULT_BODY = <<"EOF") =~ s/^\t//gm;
142     Hi!
143    
144     This is an automatic message providing an overview of Debian webpages
145     of which the translation is outdated.
146    
147     Kind regards,
148     Your automatic daemon.
149     EOF
150    
151 bas 1.80 # these is called in "main" so need to be declared here
152 bas 1.65 sub switch_var(\$\$);
153     sub verbose;
154    
155     #=================================================
156     #== "main"
157     #==
158     {
159 bas 1.81 # install a signal handler to catch Ctrl-C
160     $SIG{'INT'} = \&handle_INT;
161    
162 bas 1.84 # parse the command line
163 bas 1.65 my ($language,$file_pattern,%OPT) = parse_cmdargs();
164 bas 1.84
165     # read the tranlator db if we need it (-n was specified)
166     my %translators = $OPT{n} ? read_translators( $language ) : ();
167    
168     # this hash will be used to store the emails we want to send out
169 bas 1.65 my %emails_to_send;
170    
171 bas 1.84 # the subdirs where the english and translated files are located
172 bas 1.65 my $english_path = 'english';
173     my $language_path = $language;
174    
175 bas 1.84 # -s allows the user to restrict processing to a subtree
176 bas 1.65 my $subdir = $OPT{'s'} || undef;
177    
178     # Global .transignore
179     my $transignore = Webwml::TransIgnore->new( vcs_get_topdir );
180    
181     # first get a list with revision information from all files in english...
182     my %english_revs = vcs_path_info( $english_path,
183     'recursive' => 1,
184     'match_pat' => $file_pattern,
185     );
186 bas 1.84 # ... and in the translation
187 bas 1.65 my %translation_revs = vcs_path_info( $language_path,
188     'recursive' => 1,
189     'match_pat' => $file_pattern,
190     );
191    
192     # construct a list with all files that either occur in english or
193     # in the translation
194     my @files = uniq ( keys %english_revs, keys %translation_revs );
195    
196    
197     # now check each of the files
198     foreach my $file (sort @files)
199     {
200     # ignore this file?
201     next if $transignore->is_global( $file );
202     next if $subdir and not $file =~ m{^$subdir};
203    
204     # note: $language is the name of the current language we're
205     # processing, whereas $transl is the name of the language which the
206     # current file is translated into (which might be english!)
207     my $orig = 'english';
208     my $transl = $language;
209    
210     my $file_orig = catfile( $orig, $file );
211     my $file_transl = catfile( $transl, $file );
212    
213     my $revinfo_orig = $english_revs{$file};
214     my $revinfo_transl = $translation_revs{$file};
215    
216     # TODO: put this in a separate function
217     # first we check if the translated file has an origin other than
218     # english
219     if ( -e $file_transl )
220     {
221     my $transcheck = Webwml::TransCheck->new( $file_transl );
222     my $original_lang = $transcheck->original();
223    
224     if ( $original_lang and $original_lang ne 'english' )
225     {
226     die( "Unknown original language `$original_lang' "
227     ."for `$file_transl'\n" ) unless -d $original_lang;
228    
229     verbose "`$file_transl' is translated from $original_lang";
230    
231     # now, we use the correct (non-english) original file
232     $file_orig = catfile( $original_lang, $file );
233    
234     # and find the correct revision info for this file
235     $revinfo_orig = { vcs_file_info( $file_orig ) };
236     }
237     }
238    
239     # TODO: put this in a separate function
240     # secondly, we check if perhaps the original file is a translation
241     # (such as in the case of english/international/Swedish/index.wml)
242     if ( -e $file_transl and -e $file_orig )
243     {
244     my $transcheck = Webwml::TransCheck->new( $file_orig );
245     my $original_lang = $transcheck->original();
246     my $rev = $transcheck->revision();
247    
248     if ( $rev )
249     {
250 bas 1.70 ## This check is too strict: some translators like to translate
251     ##from other translations rather than from the original english
252     ##(see e.g., danish/international/Norwegian.wml)
253     #if ( not $original_lang )
254     #{
255     # # TODO: ideally, this would also be mailed out to the
256     # # translation team
257     # warn "`$file_orig' has a revision header but no origin language\n";
258     # next;
259     #}
260 bas 1.65
261 bas 1.70 if ( $original_lang and $original_lang eq $language )
262 bas 1.65 {
263     verbose "`$file_orig' is a translation from $language";
264    
265     # switch $orig and $transl
266     switch_var( $orig, $transl );
267     switch_var( $file_orig, $file_transl );
268     switch_var( $revinfo_orig, $revinfo_transl );
269     }
270     }
271    
272     }
273    
274     # determine the status of the file
275     my ($status,$str,$rev_transl,$maintainer,$maxdelta) = check_file(
276     $file,
277     $orig, $transl,
278     $revinfo_orig, $revinfo_transl,
279     );
280    
281    
282     ######################################################################
283     ## Everything below is just output logic
284     ######################################################################
285    
286     # print info
287     if ( ( $OPT{v} )
288     or ( $status == ST_MISSING and not $OPT{q} )
289     or ( $status != ST_MISSING and $status != ST_UPTODATE
290     and $status != ST_NOTATRANSL )
291     )
292     {
293     print colored( "$str\n", $COLOURS{$status} );
294     }
295    
296     # check age of the translation
297     if ( $OPT{a} and $status == ST_NEEDSUPDATE )
298     {
299     my $age = int get_revision_age( $revinfo_transl );
300    
301     # only warn if the translation is older than 2 weeks
302     if ( $age > 14 )
303     {
304     print colored( "$file is outdated by $age days\n",
305     $COLOURS{warn} );
306     }
307     }
308    
309 bas 1.76 # print log if requested and an update is needed
310     if ( $OPT{'l'} and $status == ST_NEEDSUPDATE )
311     {
312     my $log = get_log(
313     $file_orig,
314     $rev_transl,
315     $revinfo_orig->{'cmt_rev'},
316     );
317     print $log;
318     }
319    
320 bas 1.65 # print diff if requested and an update is needed
321     if ( $OPT{'d'} and $status == ST_NEEDSUPDATE )
322     {
323     my $diff = get_diff(
324     $file_orig,
325     $rev_transl,
326     $revinfo_orig->{'cmt_rev'},
327     );
328     print $diff;
329     }
330    
331     # print text diff, if requested and an update is needed
332     if ( $OPT{'T'} and $status == ST_NEEDSUPDATE )
333     {
334     my $diff = get_diff_txt(
335     $file_orig,
336     $rev_transl,
337     $revinfo_orig->{'cmt_rev'},
338     $file_transl
339     );
340     print $diff;
341     }
342    
343    
344     # prepare a mail to be sent
345     if ( $OPT{'m'} and $status != ST_UPTODATE )
346     {
347 bas 1.84 # -M was specified, so all mails to there
348     if ( $OPT{'M'} )
349     {
350     $maintainer = 'default';
351    
352     # don't send mail about untranslated files if -q was specified
353     $maintainer = 'none'
354     if $status == ST_MISSING and $OPT{'q'}
355     }
356     else # addresses from the database is used
357     {
358     # handle special case maintainer fields
359     $maintainer = 'unmaintained'
360     unless $maintainer and exists $translators{$maintainer};
361     $maintainer = 'untranslated'
362     if $status == ST_MISSING;
363     }
364    
365     verbose "Found maintainer $maintainer for this file";
366 bas 1.65
367     # mail to send to the maintainer
368     push @{ $emails_to_send{$maintainer} }, {
369     'file' => $file,
370     'status' => $status,
371     'info' => $str,
372     'last_trans_rev' => $rev_transl,
373 bas 1.84 };
374 bas 1.65
375 bas 1.84 # additionally, if -n was specified, also see if we need to
376     # send a mail to maxdelta
377     if ( $OPT{'n'} and $status != ST_MISSING and -e $file_orig )
378 bas 1.65 {
379     $maxdelta ||= $translators{maxdelta}{maxdelta} || 5;
380    
381 bas 1.84 my $delta;
382     $delta = vcs_count_changes( $file_orig, $rev_transl, 'HEAD' );
383    
384     if ( $delta >= $maxdelta )
385 bas 1.66 {
386     push @{ $emails_to_send{'maxdelta'} }, {
387     'file' => $file,
388     'status' => $status,
389     'info' => $str,
390     'delta' => $delta,
391     'last_trans_rev' => $rev_transl,
392     }
393 bas 1.65 }
394     }
395    
396     }
397    
398     }
399    
400 bas 1.84 send_email( \%emails_to_send, \%translators, $language,
401     $OPT{'n'}, $OPT{'M'}, $OPT{'g'} );
402 bas 1.65
403     exit 0;
404     }
405     die("Never reached");
406    
407    
408     #=================================================
409     #== swich two variables around
410     #==
411     sub switch_var(\$\$)
412     {
413     my $a = shift;
414     my $b = shift;
415    
416     my $c = $$a;
417     $$a = $$b;
418     $$b = $c;
419     }
420    
421    
422     #=================================================
423     #== output verbose messages
424     #==
425     sub verbose
426     {
427     return unless $VERBOSE;
428     print @_, "\n";
429     }
430    
431 bas 1.81 #=================================================
432     #== handles INT signal
433     #==
434     sub handle_INT
435     {
436     # reset terminal color
437     print color('reset');
438     die( "Interrupted by user" );
439     }
440 bas 1.65
441     #=================================================
442     #== send out the emails
443     #==
444     sub send_email
445     {
446     my $emails = shift or die("No emails specified");
447     my $translators = shift or die("No translators specified");
448     my $lang = shift or die("No language specified");
449 bas 1.84 my $priority = shift;
450     my $default_rec = shift;
451     my $debug = shift;
452 bas 1.65
453     foreach my $name (sort keys %$emails)
454     {
455 bas 1.84 # special case
456     next if $name eq 'none';
457    
458 bas 1.65 verbose("Preparing email for $name");
459    
460 bas 1.84 my $recipient;
461     my $subject;
462     my $mailbody;
463    
464     # First handle the case in whcih all mail goes to the -M address
465     if ( $default_rec )
466     {
467     # address was already validated while parsing the command line
468     $recipient = $default_rec;
469     $subject = $DEFAULT_SUBJECT;
470     $mailbody = $DEFAULT_BODY;
471     }
472     else # handle the case in whcih addresses are fetch from the db
473     {
474     # skip unconfigured users
475     if ( not exists $translators->{$name}
476     or not $translators->{$name}{'email'} )
477     {
478     verbose( "Woops! Can't find info for user `$name'\n" );
479     next;
480     }
481    
482     # check the user's email addres
483     if ( not Email::Address->parse( $translators->{$name}{'email'} ) )
484     {
485     printf STDERR "Can't parse email address `%s' for %s!\n",
486     $translators->{$name}{'email'}, $name;
487     next;
488     }
489 bas 1.65
490 bas 1.84 # skip if the user doesn't want a summary at all
491     if ( $translators->{$name}{'summary'} < $priority )
492     {
493     verbose( "Not sending message to $name (prio "
494     . $translators->{$name}{'summary'} . " < $priority)" );
495     next;
496     }
497 bas 1.65
498 bas 1.84 $recipient = $translators->{$name}{'email'};
499 bas 1.86 $subject = $translators->{'default'}{'mailsubject'};
500 bas 1.65
501 bas 1.84 # read body and interpret perl that's embedded there
502     $mailbody = read_file_enc( $translators->{'default'}{'mailbody'} )
503     or die("Can't read $translators->{'default'}{'mailbody'}");
504     {
505     # a bit hackish, but I want to keep the curent format of
506     # the mail body files intact, for now
507     # so we need to use the same old variable names as the original
508     # script used
509     my %translators = %{$translators};
510     $mailbody =~ s{#(.*?)#}{eval $1}mge;
511     }
512 bas 1.65
513 bas 1.84 }
514 bas 1.65
515     my $msg = MIME::Lite->new(
516     'From' => $MY_EMAIL,
517 bas 1.84 'To' => $recipient,
518     'Subject' => $subject,
519 bas 1.65 'Type' => 'multipart/mixed',
520     );
521    
522     # and attach the body to the mail
523 bas 1.66 my $part = MIME::Lite->new(
524     'Type' => 'text/plain',
525 bas 1.85 'Data' => encode('utf-8',$mailbody),
526 bas 1.65 );
527 bas 1.66 $part->attr( 'content-type.charset' => 'utf-8' );
528     $msg->attach( $part );
529 bas 1.65
530     # attach part about NeedToUpdate files
531     my $text = '';
532     foreach my $file ( @{ $emails->{$name} } )
533     {
534     next unless $file->{'status'} == ST_NEEDSUPDATE;
535     $text .= $file->{'info'};
536    
537     if ( exists $file->{'delta'} )
538     {
539     $text .= sprintf( " [out of date by %d revisions]",
540     $file->{'delta'} );
541     }
542    
543     $text .= "\n";
544     }
545     $msg->attach(
546     'Type' => 'TEXT',
547     'Filename' => 'NeedToUpdate summary',
548     'Data' => $text,
549     )
550     if $text;
551    
552     # attach part about Missing files
553 bas 1.84 if ( not $default_rec )
554 bas 1.65 {
555 bas 1.84 $text = '';
556     foreach my $file ( @{ $emails->{$name} } )
557     {
558     next unless $file->{'status'} == ST_MISSING;
559     $text .= sprintf( "%s\n", $file->{'info'} );
560     }
561     $msg->attach(
562     'Type' => 'TEXT',
563     'Filename' => 'Missing summary',
564     'Data' => $text,
565     'Encoding' => 'quoted-printable',
566     )
567     if $text;
568 bas 1.65 }
569    
570     # add diffs, if requested
571 bas 1.84 if ( $default_rec or $priority <= $translators->{$name}{'diff'} )
572 bas 1.65 {
573     foreach my $file ( @{ $emails->{$name} } )
574     {
575     # diffs really only make sense if there is an existing
576     # translation
577     next unless $file->{'status'} == ST_NEEDSUPDATE;
578    
579     my $filename = catfile( 'english', $file->{'file'} );
580     my $rev = $file->{'last_trans_rev'};
581     my $diff = get_diff( $filename, $rev, 'HEAD' );
582     $msg->attach(
583     'Type' => 'TEXT',
584     'Filename' => "$filename.diff",
585     'Data' => $diff,
586     'Encoding' => 'quoted-printable',
587     );
588     }
589     }
590     else
591     {
592     verbose( "Not attaching diffs (prio "
593     . $translators->{$name}{'diff'} . " < $priority)" );
594     }
595    
596     # add tdiffs, if requested
597 bas 1.84 if ( not $default_rec and $priority <= $translators->{$name}{'tdiff'} )
598 bas 1.65 {
599     foreach my $file ( @{ $emails->{$name} } )
600     {
601     # diffs really only make sense if there is an existing
602     # translation
603     next unless $file->{'status'} == ST_NEEDSUPDATE;
604    
605     my $filename = catfile( 'english', $file->{'file'} );
606     my $filename2 = catfile( $lang, $file->{'file'} );
607     my $rev = $file->{'last_trans_rev'};
608     my $tdiff = get_diff_txt( $filename, $rev, 'HEAD',
609     $filename2 );
610     $msg->attach(
611     'Type' => 'TEXT',
612     'Filename' => "$filename.tdiff",
613     'Data' => $tdiff,
614     'Encoding' => 'quoted-printable',
615     );
616     }
617     }
618     else
619     {
620     verbose( "Not attaching tdiffs (prio "
621 bas 1.84 . $translators->{$name}{'tdiff'} . " < $priority)" )
622     unless $default_rec;
623 bas 1.65 }
624    
625     # add logs, if requested
626 bas 1.84 if ( $default_rec or $priority <= $translators->{$name}{'logs'} )
627 bas 1.65 {
628     foreach my $file ( @{ $emails->{$name} } )
629     {
630     # logs really only make sense if there is an existing
631     # translation
632     next unless $file->{'status'} == ST_NEEDSUPDATE;
633    
634     my $filename = catfile( 'english', $file->{'file'} );
635     my $rev = $file->{'last_trans_rev'};
636     my $log = get_log( $filename, $rev, 'HEAD' );
637     my $part = MIME::Lite->new(
638 bas 1.66 'Type' => 'TEXT',
639 bas 1.65 'Filename' => "$filename.log",
640     'Data' => $log,
641     'Encoding' => 'quoted-printable',
642     );
643     $part->attr( 'content-type.charset' => 'utf-8' );
644     $msg->attach( $part );
645     }
646     }
647     else
648     {
649     verbose( "Not attaching logs (prio "
650     . $translators->{$name}{'logs'} . " < $priority)" );
651     }
652    
653     # add file, if requested
654 bas 1.84 if ( not $default_rec and $priority <= $translators->{$name}{'file'} )
655 bas 1.65 {
656     foreach my $file ( @{ $emails->{$name} } )
657     {
658     my $filename = catfile( $lang, $file->{'file'} );
659     my $part = MIME::Lite->new(
660     'Type' => 'text/wml',
661     'Filename' => $filename,
662     'Path' => $filename,
663     'Encoding' => 'quoted-printable',
664     );
665     $part->attr( 'content-type.charset' => get_file_charset($filename) );
666     $msg->attach( $part );
667    
668     }
669     }
670     else
671     {
672     verbose( "Not attaching files (prio "
673 bas 1.84 . $translators->{$name}{'file'} . " < $priority)" )
674     unless $default_rec;
675 bas 1.65 }
676    
677    
678    
679     # check if we really want to send the mail
680 bas 1.84 if ( $debug )
681 bas 1.65 {
682 bas 1.84 print color('bold yellow');
683     print '*'x72, "\n";
684     printf "Would send email to %s (but -g was specified):\n",
685     $recipient;
686     print '-'x72, "\n";
687     print color('reset');
688    
689     print $msg->as_string;
690    
691     print color('bold yellow');
692     print '*'x72, "\n";
693     print color('reset');
694 bas 1.65 }
695     else
696     {
697 bas 1.84 verbose "Sending email to $recipient";
698     $msg->send or warn("Couldn't send message to $name");
699 bas 1.65 }
700     }
701     }
702    
703    
704     #=================================================
705     #== return the age of the revision (in days)
706     #==
707     sub get_revision_age
708     {
709     my $rev_info = shift;
710    
711     die("No revision info specified") unless ref $rev_info eq 'HASH';
712    
713     my $rev_timestamp = $rev_info->{'cmt_date'};
714     my $age = time - $rev_timestamp;
715    
716     warn( "Timestamp is in the future!" ) if $age < 0;
717    
718     # return age in days
719     return $age / ( 60*60*24 );
720     }
721    
722    
723    
724     #=================================================
725     #== get a log
726     #==
727     sub get_log
728     {
729     my $file = shift or die("No file specified for diff");
730     my $rev1 = shift;
731     my $rev2 = shift;
732    
733     die("NO such file `$file'") unless -e $file;
734    
735     my @log = vcs_get_log( $file, $rev1, $rev2 );
736    
737     # remove the first item of the log, because we only want
738     # to see when changed in the (left-open) range (rev1,rev2]
739     shift @log;
740    
741     # format it nicely
742     my $str = '-' x 78 . "\n";
743     foreach my $l (@log)
744     {
745     chomp $l->{'message'};
746    
747 bas 1.66 $str .= sprintf( "%s | %s | %s\n",
748 bas 1.65 $l->{'rev'}, $l->{'author'}, scalar localtime $l->{'date'} );
749     $str .= "\n";
750     $str .= $l->{'message'} . "\n";
751     $str .= "\n";
752    
753     $str .= '-' x 78 . "\n";
754    
755     }
756    
757    
758     return $str;
759     }
760    
761     #=================================================
762     #== get a diff
763     #==
764     sub get_diff
765     {
766     my $file = shift or die("No file specified for diff");
767     my $rev1 = shift;
768     my $rev2 = shift;
769    
770     die("NO such file `$file'") unless -e $file;
771    
772     my %diffs = vcs_get_diff( $file, $rev1, $rev2 );
773    
774     # just glue all diffs together and return it as a big string
775     my $difftxt = join( '', values %diffs );
776    
777     return $difftxt;
778     }
779    
780 bas 1.69 #=================================================
781 bas 1.65 #== get a diff while trying to match html tags
782     #==
783     sub get_diff_txt
784     {
785     my $english_file = shift or die("No file specified");
786     my $rev1 = shift or die("No revision specified");
787     my $rev2 = shift or die("No revision specified");
788     my $transl_file = shift or die("No transl file specified");
789    
790     die("No such file $english_file") unless -e $english_file;
791     die("No such file $transl_file") unless -e $transl_file;
792    
793     # Get old revision file
794     my @english_txt = split( "\n", vcs_get_file( $english_file, $rev1 ) );
795    
796     # Get translation file
797 bas 1.72 my $transl_txt = read_file( $transl_file )
798     or die("Couln't read `$transl_file': $!");
799     my @transl_txt = split( "\n", $transl_txt );
800 bas 1.65
801     # Get diff lines
802     my @diff_txt = split( "\n", get_diff( $english_file, $rev1, $rev2 ) );
803    
804     # do the matching
805     my $txt = Local::WmlDiffTrans::find_trans_parts(
806     \@english_txt,
807     \@transl_txt,
808     \@diff_txt
809     );
810    
811     return $txt;
812     }
813    
814    
815     #=================================================
816     #== show help from the top of this file
817     #==
818     sub show_help
819     {
820     # read the help from the comments above and display it
821     open( my $me, '<', $0 ) or die "Unable to display help: $!\n";
822    
823 bas 1.78 while ( my $line = <$me> )
824 bas 1.65 {
825 bas 1.78 last if $line =~ m{^use};
826     print "\n" if $line =~ m{^#$};
827     next unless $line =~ m{^# };
828 bas 1.65
829 bas 1.78 $line =~ s{^# ?}{};
830 italian 1.1
831 bas 1.78 print $line;
832 peterk 1.21 }
833 bas 1.65
834     close( $me );
835     }
836    
837    
838     #=================================================
839     #== parse command line options and read defaults
840     #==
841     sub parse_cmdargs
842     {
843     my %OPT;
844     $OPT{s} = '';
845    
846     # parse options
847 bas 1.84 if ( not getopts( 'acdghlmM:n:p:Qqs:TvV', \%OPT ) )
848 bas 1.65 {
849     show_help();
850     exit -1;
851     }
852    
853     # show help
854     if ( $OPT{h} )
855     {
856     show_help();
857     exit 0;
858     }
859    
860     # handle verbosity setting
861 bas 1.74 if ( ( $OPT{'v'} or $OPT{'V'} ) and ( $OPT{'q'} or $OPT{'Q'} ) )
862 bas 1.65 {
863     die "you can't have both verbose and quiet, doh!\n";
864     }
865     $VERBOSE = 1 if $OPT{'V'};
866     $OPT{'v'} = 1 if $OPT{'V'};
867    
868 bas 1.74 # handle really quiet setting
869     if ( $OPT{'Q'} )
870     {
871     # redirect stdout to /dev/null
872     close( STDOUT );
873 bas 1.75 open( STDOUT, '>', '/dev/null' )
874 bas 1.74 or die( "Can't redirect STDOUT to /dev/null: $!" );
875     }
876    
877 bas 1.82 # handle -c (disable color) setting
878     if ( $OPT{'c'} )
879     {
880     # nice feature of Term::ANSIColor
881     $ENV{'ANSI_COLORS_DISABLED'} = '1';
882     }
883 bas 1.84 else
884     {
885     # we need flushed STDOUT putput, because otherwise the colours wills
886     # blend into STDERR
887     $| = 1;
888     }
889 bas 1.82
890 bas 1.65 # handle -s (subtree check) setting
891     if ( $OPT{s})
892     {
893     verbose "Checking subtree $OPT{s} only\n";
894     }
895    
896 bas 1.84 # check validity of mail options
897     # if -m is specified, either -n or -M must also be given
898     # furthermore, the argument to -n must be 1, 2, or 3, and
899     # the argument to -M must be a valid email address
900 bas 1.67 if ( $OPT{'m'} )
901     {
902 bas 1.84 # load additional module we need for mail
903 bas 1.67 eval {
904     require MIME::Lite;
905     import MIME::Lite;
906     };
907     die "The module MIME::Lite could not be loaded.\n"
908     ."Please install libmime-lite-perl\n" if $@;
909    
910     eval {
911     require Email::Address;
912     import Email::Address;
913     };
914     die "The module Email::Address could not be loaded.\n"
915     ."Please install libemail-address-perl\n" if $@;
916 bas 1.84
917     # now check the options
918     if ( $OPT{'n'} and $OPT{'M'} )
919     {
920     die "You can't specify both -n and -M\n";
921     }
922     elsif ( $OPT{'n'} )
923     {
924     die "Invalid priority `$OPT{n}'. "
925     ."Please set -n value to 1, 2 or 3.\n"
926     unless $OPT{'n'} =~ m{^[123]$}
927     }
928     elsif ( $OPT{'M'} )
929     {
930     die "Invalid email address `$OPT{M}'\n"
931     unless Email::Address->parse( $OPT{M} );
932     }
933     else
934     {
935     die "You specified -m (send mails), but you didn't specify "
936     ."either -n or -M, so I don't knwo where to send my mails\n";
937     }
938    
939 bas 1.67 }
940    
941 bas 1.65 if ( $OPT{'g'} and not $OPT{'m'} )
942     {
943     die "Option -g (debuG mail) without -m (use mail) "
944     ."really doesn't make much sense\n";
945     }
946    
947     # include only files matching $filename
948     my $file_pattern = $OPT{'p'} || $DEFAULT_PATTERN;
949    
950     my $translation = shift @ARGV || '';
951    
952     # language configuration
953     if ( not $translation )
954     {
955     if ( exists $ENV{DWWW_LANG} )
956     {
957     $translation = $ENV{DWWW_LANG};
958     }
959     elsif ( -e "language.conf" )
960     {
961     open( my $conf, '<', 'language.conf' )
962     or die("Can't read language.conf: $!\n");
963     while (<$conf>)
964     {
965     next if /^#/;
966     chomp;
967     $translation = $_;
968     last;
969     }
970     close $conf;
971     }
972     }
973    
974     # Remove slash from the end
975     $translation =~ s{/$}{};
976    
977     if ( $translation eq '' )
978     {
979     die "Language not defined in DWWW_LANG, language.conf, "
980     ."or on command line\n";
981     }
982    
983     return ($translation,$file_pattern,%OPT);
984     }
985    
986     #=================================================
987     #== read the translators from translator.db
988     #==
989     sub read_translators
990     {
991     my $lang = shift or die("Internal error: no language specified");
992    
993     my %translators;
994    
995     my $db_file = catfile( $lang, 'international', $lang, 'translator.db.pl' );
996    
997     verbose "Reading translation database $db_file";
998    
999     if ( -e $db_file)
1000     {
1001     require $db_file;
1002    
1003     verbose "READ TRANSLATOR DB: $db_file\n";
1004    
1005     %translators = %{ init_translators() };
1006    
1007     if ( exists $translators{default} )
1008     {
1009     my @field_list = keys %{ $translators{default} };
1010     foreach my $user (keys %translators)
1011     {
1012     next if $user eq 'default';
1013     foreach my $f (@field_list)
1014     {
1015     $translators{$user}{$f} = $translators{default}{$f}
1016     unless exists $translators{$user}{$f};
1017     }
1018     }
1019     }
1020 bas 1.84 }
1021     else
1022 bas 1.65 {
1023 bas 1.84 die "File `$db_file' doesn't exist!\n"
1024     ."I need my DBs to send mails.\n"
1025 bas 1.65 ."Please read the comments in the script and try again\n";
1026     }
1027    
1028     return %translators;
1029 peterk 1.21 }
1030 joy 1.32
1031 bas 1.65 #=================================================
1032     #== check if a single file is up to date
1033     #== returns ($status,$message)
1034     #== where status is one of the ST_* constants (see top of file)
1035     #==
1036     sub check_file
1037 peterk 1.57 {
1038 bas 1.65 my $file = shift;
1039     my $orig = shift;
1040     my $lang = shift;
1041     my $english_rev = shift; # might be undef
1042     my $translation_rev = shift; # might be undef
1043    
1044     die("Internal error: insufficient arguments")
1045     unless $file and $orig and $lang;
1046    
1047     # filename of english and translated files
1048     my $file_orig = catfile( $orig, $file );
1049     my $file_translation = catfile( $lang, $file );
1050    
1051     # revision of the latest change in the english file
1052     my $orig_last_change = $english_rev ? $english_rev->{cmt_rev} : 'n/a';
1053    
1054     # revision of the english file that was translated
1055     my $transcheck = Webwml::TransCheck->new( $file_translation );
1056     my $translation_last_change = $transcheck->revision() || 'n/a';
1057     my $translation_translator = $transcheck->maintainer() || undef;
1058     my $translation_maxdelta = $transcheck->maxdelta() || undef;
1059    
1060     verbose "Checking $file_translation, $orig revision $orig_last_change";
1061    
1062     # status information
1063     my $status = undef;
1064     my $str = undef;
1065    
1066     # at this point, there are several possibilities:
1067     # 1) file exists both in english and translation
1068     # 2) file exists only in english
1069     # 3) file exists only in translation
1070     # 4) file exists in neither original or translation: can't happen
1071     # we handle those cases one by one
1072    
1073     # 1) both files exist
1074     if ( -e $file_orig and -e $file_translation )
1075     {
1076     # now check if both files have correct revisions
1077     # again, three cases
1078     # 1a) original file doesn't have a revision (can't happen)
1079     # 1b) translated file doesn't have a revision (error in wml file)
1080     # 1c) revision of both files is known
1081    
1082     # 1a) no revision for english file
1083     if ( $orig_last_change eq 'n/a' )
1084     {
1085     # this can't happen: something must be wrong with this script
1086     die( "internal error: no revision for english file" );
1087     }
1088    
1089     # 1b) no revision on translated file: error
1090     elsif ( $translation_last_change eq 'n/a' )
1091     {
1092     $status = ST_UNDEFINED;
1093     $str = "Unknown status of $file_translation "
1094     ."(revision should be $orig_last_change)";
1095     }
1096    
1097     # 1c) both files have revisions
1098     else
1099     {
1100     # check the revisions to see if they're up to date
1101 bas 1.75 my $cmp = vcs_cmp_rev( $translation_last_change,
1102 bas 1.65 $orig_last_change );
1103    
1104     if ( $cmp == 0 ) # revisions equal
1105     {
1106     # up to date
1107     $str = "UpToDate $file_translation";
1108     $status = ST_UPTODATE;
1109     }
1110     elsif ( $cmp == -1 ) # $translation_last_change < $orig_last_change
1111     {
1112     # out of date
1113     $status = ST_NEEDSUPDATE;
1114     $str = "NeedToUpdate $file_translation from revision "
1115     ."$translation_last_change to revision $orig_last_change";
1116     }
1117     else # $translation_last_change > $orig_last_change
1118     {
1119     # weirdness: translation is newer than original
1120     $status = ST_BROKEN;
1121     $str = "Broken revision number r$translation_last_change "
1122     ."for $file_translation, it should be $orig_last_change";
1123     }
1124     }
1125     }
1126    
1127     # 2) original file exists, but translation is missing
1128     elsif ( -e $file_orig and not -e $file_translation )
1129     {
1130     $status = ST_MISSING;
1131     $str = "Missing $file_translation version $orig_last_change";
1132     }
1133    
1134     # 3) translation exists, but original is missing
1135     elsif ( not -e $file_orig and -e $file_translation )
1136     {
1137     # the translated file doesn't have a translation header,
1138     # so it probably is an original
1139     if ( $translation_last_change eq 'n/a' )
1140     {
1141     $status = ST_NOTATRANSL;
1142     $str = "NotATranslation $file_translation";
1143     }
1144 bas 1.75 # otherwise, it has a translation header,
1145 bas 1.65 # so the english file was removed
1146     else
1147     {
1148     $status = ST_OBSOLETE;
1149     $str = "Obsolete $file_translation";
1150     }
1151     }
1152    
1153     # neither original nor translation exists
1154     else
1155     {
1156     # this should never occur, because it means the function was
1157     # called with an invalid argument
1158     die( "Internal error: file not present in english nor $lang" );
1159     }
1160    
1161     # add name of translator
1162     $str .= " (maintainer $translation_translator)" if $translation_translator;
1163    
1164     return ($status,$str,$translation_last_change,
1165     $translation_translator,$translation_maxdelta);
1166 peterk 1.57 }
1167    
1168 joy 1.56
1169 bas 1.65 # get the encoding of a certain file, by looking for wmlrc
1170     sub get_file_charset
1171     {
1172     my $file = shift or croak("No file specified");
1173 italian 1.1
1174 bas 1.65 # default charset
1175     my $charset = 'utf-8';
1176 italian 1.1
1177 bas 1.65 # read the wmlrc file
1178     my $wmlrc_dir = dirname($file);
1179     while ( not -e catfile( $wmlrc_dir, '.wmlrc' ) )
1180     {
1181     $wmlrc_dir = dirname $wmlrc_dir;
1182     last if length( $wmlrc_dir ) < 3
1183     }
1184 barbier 1.36
1185 bas 1.65 # now read the wmlrc file to find the charset
1186     my $wmlrc = catfile( $wmlrc_dir,'.wmlrc' );
1187     if ( open( my $fd, '<', $wmlrc ) )
1188     {
1189     while ( my $line = <$fd> )
1190     {
1191     next unless $line =~ m{CHARSET=(.*?)\s*$};
1192     $charset = $1;
1193     last;
1194     }
1195     close($fd);
1196     }
1197     else
1198     {
1199     verbose "wmlrc for `$file' not found; assuming $charset charset";
1200     }
1201 barbier 1.43
1202 bas 1.65 return $charset;
1203     }
1204    
1205     sub read_file_enc
1206     {
1207     my $file = shift or croak("No file specified");
1208    
1209     my $charset = get_file_charset( $file );
1210 french 1.41
1211 bas 1.85 return read_file( $file, $charset );
1212 italian 1.1 }
1213 bas 1.65
1214     __END__

  ViewVC Help
Powered by ViewVC 1.1.5