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

Diff of /webwml/check_trans.pl

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

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

Legend:
Removed from v.1.4  
changed lines
  Added in v.1.74

  ViewVC Help
Powered by ViewVC 1.1.5