src/crypto.c: possible fix for memory leak related
[citadel.git] / webcit / intltool-update.in
1 #!@INTLTOOL_PERL@ -w
2 # -*- Mode: perl; indent-tabs-mode: nil; c-basic-offset: 4  -*-
3
4 #
5 #  The Intltool Message Updater
6 #
7 #  Copyright (C) 2000-2003 Free Software Foundation.
8 #
9 #  Intltool is free software; you can redistribute it and/or
10 #  modify it under the terms of the GNU General Public License 
11 #  version 2 published by the Free Software Foundation.
12 #
13 #  Intltool is distributed in the hope that it will be useful,
14 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 #  General Public License for more details.
17 #
18 #  You should have received a copy of the GNU General Public License
19 #  along with this program; if not, write to the Free Software
20 #  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 #
22 #  As a special exception to the GNU General Public License, if you
23 #  distribute this file as part of a program that contains a
24 #  configuration script generated by Autoconf, you may include it under
25 #  the same distribution terms that you use for the rest of that program.
26 #
27 #  Authors: Kenneth Christiansen <kenneth@gnu.org>
28 #           Maciej Stachowiak
29 #           Darin Adler <darin@bentspoon.com>
30
31 ## Release information
32 my $PROGRAM = "intltool-update";
33 my $VERSION = "0.34.2";
34 my $PACKAGE = "intltool";
35
36 ## Loaded modules
37 use strict;
38 use Getopt::Long;
39 use Cwd;
40 use File::Copy;
41 use File::Find;
42
43 ## Scalars used by the option stuff
44 my $HELP_ARG       = 0;
45 my $VERSION_ARG    = 0;
46 my $DIST_ARG       = 0;
47 my $POT_ARG        = 0;
48 my $HEADERS_ARG    = 0;
49 my $MAINTAIN_ARG   = 0;
50 my $REPORT_ARG     = 0;
51 my $VERBOSE        = 0;
52 my $GETTEXT_PACKAGE = "";
53 my $OUTPUT_FILE    = "";
54
55 my @languages;
56 my %varhash = ();
57 my %po_files_by_lang = ();
58
59 # Regular expressions to categorize file types.
60 # FIXME: Please check if the following is correct
61
62 my $xml_support =
63 "xml(?:\\.in)*|".       # http://www.w3.org/XML/ (Note: .in is not required)
64 "ui|".                  # Bonobo specific - User Interface desc. files
65 "lang|".                # ?
66 "glade2?(?:\\.in)*|".   # Glade specific - User Interface desc. files (Note: .in is not required)
67 "scm(?:\\.in)*|".       # ? (Note: .in is not required)
68 "oaf(?:\\.in)+|".       # DEPRECATED: Replaces by Bonobo .server files 
69 "etspec|".              # ?
70 "server(?:\\.in)+|".    # Bonobo specific
71 "sheet(?:\\.in)+|".     # ?
72 "schemas(?:\\.in)+|".   # GConf specific
73 "pong(?:\\.in)+|".      # DEPRECATED: PONG is not used [by GNOME] any longer.
74 "kbd(?:\\.in)+";        # GOK specific. 
75
76 my $ini_support =
77 "icon(?:\\.in)+|".      # http://www.freedesktop.org/Standards/icon-theme-spec
78 "desktop(?:\\.in)+|".   # http://www.freedesktop.org/Standards/menu-spec
79 "caves(?:\\.in)+|".     # GNOME Games specific
80 "directory(?:\\.in)+|". # http://www.freedesktop.org/Standards/menu-spec
81 "soundlist(?:\\.in)+|". # GNOME specific
82 "keys(?:\\.in)+|".      # GNOME Mime database specific
83 "theme(?:\\.in)+|".     # http://www.freedesktop.org/Standards/icon-theme-spec
84 "service(?:\\.in)+";    # DBus specific
85
86 my $buildin_gettext_support = 
87 "c|y|cs|cc|cpp|c\\+\\+|h|hh|gob|py";
88
89 ## Always flush buffer when printing
90 $| = 1;
91
92 ## Sometimes the source tree will be rooted somewhere else.
93 my $SRCDIR = ".";
94 my $POTFILES_in;
95
96 $SRCDIR = $ENV{"srcdir"} if $ENV{"srcdir"};
97 $POTFILES_in = "<$SRCDIR/POTFILES.in";
98
99 my $devnull = ($^O eq 'MSWin32' ? 'NUL:' : '/dev/null');
100
101 ## Handle options
102 GetOptions 
103 (
104  "help"                => \$HELP_ARG,
105  "version"             => \$VERSION_ARG,
106  "dist|d"              => \$DIST_ARG,
107  "pot|p"               => \$POT_ARG,
108  "headers|s"           => \$HEADERS_ARG,
109  "maintain|m"          => \$MAINTAIN_ARG,
110  "report|r"            => \$REPORT_ARG,
111  "verbose|x"           => \$VERBOSE,
112  "gettext-package|g=s" => \$GETTEXT_PACKAGE,
113  "output-file|o=s"     => \$OUTPUT_FILE,
114  ) or &Console_WriteError_InvalidOption;
115
116 &Console_Write_IntltoolHelp if $HELP_ARG;
117 &Console_Write_IntltoolVersion if $VERSION_ARG;
118
119 my $arg_count = ($DIST_ARG > 0)
120     + ($POT_ARG > 0)
121     + ($HEADERS_ARG > 0)
122     + ($MAINTAIN_ARG > 0)
123     + ($REPORT_ARG > 0);
124
125 &Console_Write_IntltoolHelp if $arg_count > 1;
126
127 # --version and --help don't require a module name
128 my $MODULE = $GETTEXT_PACKAGE || &FindPackageName;
129
130 if ($POT_ARG)
131 {
132     &GenerateHeaders;
133     &GeneratePOTemplate;
134 }
135 elsif ($HEADERS_ARG)
136 {
137     &GenerateHeaders;
138 }
139 elsif ($MAINTAIN_ARG)
140 {
141     &FindLeftoutFiles;
142 }
143 elsif ($REPORT_ARG)
144 {
145     &GenerateHeaders;
146     &GeneratePOTemplate;
147     &Console_Write_CoverageReport;
148 }
149 elsif ((defined $ARGV[0]) && $ARGV[0] =~ /^[a-z]/)
150 {
151     my $lang = $ARGV[0];
152
153     ## Report error if the language file supplied
154     ## to the command line is non-existent
155     &Console_WriteError_NotExisting("$SRCDIR/$lang.po")
156         if ! -s "$SRCDIR/$lang.po";
157
158     if (!$DIST_ARG)
159     {
160         print "Working, please wait..." if $VERBOSE;
161         &GenerateHeaders;
162         &GeneratePOTemplate;
163     }
164     &POFile_Update ($lang, $OUTPUT_FILE);
165     &Console_Write_TranslationStatus ($lang, $OUTPUT_FILE);
166
167 else 
168 {
169     &Console_Write_IntltoolHelp;
170 }
171
172 exit;
173
174 #########
175
176 sub Console_Write_IntltoolVersion
177 {
178     print <<_EOF_;
179 ${PROGRAM} (${PACKAGE}) $VERSION
180 Written by Kenneth Christiansen, Maciej Stachowiak, and Darin Adler.
181
182 Copyright (C) 2000-2003 Free Software Foundation, Inc.
183 This is free software; see the source for copying conditions.  There is NO
184 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
185 _EOF_
186     exit;
187 }
188
189 sub Console_Write_IntltoolHelp
190 {
191     print <<_EOF_;
192 Usage: ${PROGRAM} [OPTION]... LANGCODE
193 Updates PO template files and merge them with the translations.
194
195 Mode of operation (only one is allowed):
196   -p, --pot                   generate the PO template only
197   -s, --headers               generate the header files in POTFILES.in
198   -m, --maintain              search for left out files from POTFILES.in
199   -r, --report                display a status report for the module
200   -d, --dist                  merge LANGCODE.po with existing PO template
201
202 Extra options:
203   -g, --gettext-package=NAME  override PO template name, useful with --pot
204   -o, --output-file=FILE      write merged translation to FILE
205   -x, --verbose               display lots of feedback
206       --help                  display this help and exit
207       --version               output version information and exit
208
209 Examples of use:
210 ${PROGRAM} --pot    just create a new PO template
211 ${PROGRAM} xy       create new PO template and merge xy.po with it
212
213 Report bugs to http://bugzilla.gnome.org/ (product name "$PACKAGE")
214 or send email to <xml-i18n-tools\@gnome.org>.
215 _EOF_
216     exit;
217 }
218
219 sub echo_n
220 {
221     my $str = shift;
222     my $ret = `echo "$str"`;
223
224     $ret =~ s/\n$//; # do we need the "s" flag?
225
226     return $ret;
227 }
228
229 sub POFile_DetermineType ($) 
230 {
231    my $type = $_;
232    my $gettext_type;
233
234    my $xml_regex     = "(?:" . $xml_support . ")";
235    my $ini_regex     = "(?:" . $ini_support . ")";
236    my $buildin_regex = "(?:" . $buildin_gettext_support . ")";
237
238    if ($type =~ /\[type: gettext\/([^\]].*)]/) 
239    {
240         $gettext_type=$1;
241    }
242    elsif ($type =~ /schemas(\.in)+$/) 
243    {
244         $gettext_type="schemas";
245    }
246    elsif ($type =~ /glade2?(\.in)*$/) 
247    {
248        $gettext_type="glade";
249    }
250    elsif ($type =~ /scm(\.in)*$/) 
251    {
252        $gettext_type="scheme";
253    }
254    elsif ($type =~ /keys(\.in)+$/) 
255    {
256        $gettext_type="keys";
257    }
258
259    # bucket types
260
261    elsif ($type =~ /$xml_regex$/) 
262    {
263        $gettext_type="xml";
264    }
265    elsif ($type =~ /$ini_regex$/) 
266    { 
267        $gettext_type="ini";
268    }
269    elsif ($type =~ /$buildin_regex$/) 
270    {
271        $gettext_type="buildin";
272    }
273    else
274    { 
275        $gettext_type="unknown"; 
276    }
277
278    return "gettext\/$gettext_type";
279 }
280
281 sub TextFile_DetermineEncoding ($) 
282 {
283     my $gettext_code="ASCII"; # All files are ASCII by default
284     my $filetype=`file $_ | cut -d ' ' -f 2`;
285
286     if ($? eq "0")
287     {
288         if ($filetype =~ /^(ISO|UTF)/)
289         {
290             chomp ($gettext_code = $filetype);
291         }
292         elsif ($filetype =~ /^XML/)
293         {
294             $gettext_code="UTF-8"; # We asume that .glade and other .xml files are UTF-8
295         }
296     }
297
298     return $gettext_code;
299 }
300
301 sub isNotValidMissing
302 {
303     my ($file) = @_;
304
305     return if $file =~ /^\{arch\}\/.*$/;
306     return if $file =~ /^$varhash{"PACKAGE"}-$varhash{"VERSION"}\/.*$/;
307 }
308
309 sub FindLeftoutFiles
310 {
311     my (@buf_i18n_plain,
312         @buf_i18n_xml,
313         @buf_i18n_xml_unmarked,
314         @buf_i18n_ini,
315         @buf_potfiles,
316         @buf_potfiles_ignore,
317         @buf_allfiles,
318         @buf_allfiles_sorted,
319         @buf_potfiles_sorted
320     );
321
322     ## Search and find all translatable files
323     find sub { 
324         push @buf_i18n_plain,        "$File::Find::name" if /\.($buildin_gettext_support)$/;
325         push @buf_i18n_xml,          "$File::Find::name" if /\.($xml_support)$/;
326         push @buf_i18n_ini,          "$File::Find::name" if /\.($ini_support)$/;
327         push @buf_i18n_xml_unmarked, "$File::Find::name" if /\.(schemas(\.in)+)$/;
328         }, "..";
329
330
331     open POTFILES, $POTFILES_in or die "$PROGRAM:  there's no POTFILES.in!\n";
332     @buf_potfiles = grep !/^(#|\s*$)/, <POTFILES>;
333     close POTFILES;
334
335     foreach (@buf_potfiles) {
336         s/^\[.*]\s*//;
337     }
338
339     print "Searching for missing translatable files...\n" if $VERBOSE;
340
341     ## Check if we should ignore some found files, when
342     ## comparing with POTFILES.in
343     foreach my $ignore ("POTFILES.skip", "POTFILES.ignore")
344     {
345         (-s $ignore) or next;
346
347         if ("$ignore" eq "POTFILES.ignore")
348         {
349             print "The usage of POTFILES.ignore is deprecated. Please consider moving the\n".
350                   "content of this file to POTFILES.skip.\n";
351         }
352
353         print "Found $ignore: Ignoring files...\n" if $VERBOSE;
354         open FILE, "<$ignore" or die "ERROR: Failed to open $ignore!\n";
355             
356         while (<FILE>)
357         {
358             push @buf_potfiles_ignore, $_ unless /^(#|\s*$)/;
359         }
360         close FILE;
361
362         @buf_potfiles = (@buf_potfiles_ignore, @buf_potfiles);
363     }
364
365     foreach my $file (@buf_i18n_plain)
366     {
367         my $in_comment = 0;
368         my $in_macro = 0;
369
370         open FILE, "<$file";
371         while (<FILE>)
372         {
373             # Handle continued multi-line comment.
374             if ($in_comment)
375             {
376                 next unless s-.*\*/--;
377                 $in_comment = 0;
378             }
379
380             # Handle continued macro.
381             if ($in_macro)
382             {
383                 $in_macro = 0 unless /\\$/;
384                 next;
385             }
386
387             # Handle start of macro (or any preprocessor directive).
388             if (/^\s*\#/)
389             {
390                 $in_macro = 1 if /^([^\\]|\\.)*\\$/;
391                 next;
392             }
393
394             # Handle comments and quoted text.
395             while (m-(/\*|//|\'|\")-) # \' and \" keep emacs perl mode happy
396             {
397                 my $match = $1;
398                 if ($match eq "/*")
399                 {
400                     if (!s-/\*.*?\*/--)
401                     {
402                         s-/\*.*--;
403                         $in_comment = 1;
404                     }
405                 }
406                 elsif ($match eq "//")
407                 {
408                     s-//.*--;
409                 }
410                 else # ' or "
411                 {
412                     if (!s-$match([^\\]|\\.)*?$match-QUOTEDTEXT-)
413                     {
414                         warn "mismatched quotes at line $. in $file\n";
415                         s-$match.*--;
416                     }
417                 }
418             }       
419
420             if (/\.GetString ?\(QUOTEDTEXT/)
421             {
422                 if (defined isNotValidMissing (unpack("x3 A*", $file))) {
423                     ## Remove the first 3 chars and add newline
424                     push @buf_allfiles, unpack("x3 A*", $file) . "\n";
425                 }
426                 last;
427             }
428
429             if (/_\(QUOTEDTEXT/)
430             {
431                 if (defined isNotValidMissing (unpack("x3 A*", $file))) {
432                     ## Remove the first 3 chars and add newline
433                     push @buf_allfiles, unpack("x3 A*", $file) . "\n";
434                 }
435                 last;
436             }
437         }
438         close FILE;
439     }
440
441     foreach my $file (@buf_i18n_xml) 
442     {
443         open FILE, "<$file";
444         
445         while (<FILE>) 
446         {
447             # FIXME: share the pattern matching code with intltool-extract
448             if (/\s_[-A-Za-z0-9._:]+\s*=\s*\"([^"]+)\"/ || /<_[^>]+>/ || /translatable=\"yes\"/)
449             {
450                 if (defined isNotValidMissing (unpack("x3 A*", $file))) {
451                     push @buf_allfiles, unpack("x3 A*", $file) . "\n";
452                 }
453                 last;
454             }
455         }
456         close FILE;
457     }
458
459     foreach my $file (@buf_i18n_ini)
460     {
461         open FILE, "<$file";
462         while (<FILE>) 
463         {
464             if (/_(.*)=/)
465             {
466                 if (defined isNotValidMissing (unpack("x3 A*", $file))) {
467                     push @buf_allfiles, unpack("x3 A*", $file) . "\n";
468                 }
469                 last;
470             }
471         }
472         close FILE;
473     }
474
475     foreach my $file (@buf_i18n_xml_unmarked)
476     {
477         if (defined isNotValidMissing (unpack("x3 A*", $file))) {
478             push @buf_allfiles, unpack("x3 A*", $file) . "\n";
479         }
480     }
481
482
483     @buf_allfiles_sorted = sort (@buf_allfiles);
484     @buf_potfiles_sorted = sort (@buf_potfiles);
485
486     my %in2;
487     foreach (@buf_potfiles_sorted) 
488     {
489         $in2{$_} = 1;
490     }
491
492     my @result;
493
494     foreach (@buf_allfiles_sorted)
495     {
496         if (!exists($in2{$_}))
497         {
498             push @result, $_
499         }
500     }
501
502     my @buf_potfiles_notexist;
503
504     foreach (@buf_potfiles_sorted)
505     {
506         chomp (my $dummy = $_);
507         if ("$dummy" ne "" and ! -f "../$dummy")
508         {
509             push @buf_potfiles_notexist, $_;
510         }
511     }
512
513     ## Save file with information about the files missing
514     ## if any, and give information about this procedure.
515     if (@result + @buf_potfiles_notexist > 0)
516     {
517         if (@result) 
518         {
519             print "\n" if $VERBOSE;
520             unlink "missing";
521             open OUT, ">missing";
522             print OUT @result;
523             close OUT;
524             warn "\e[1mThe following files contain translations and are currently not in use. Please\e[0m\n".
525                  "\e[1mconsider adding these to the POTFILES.in file, located in the po/ directory.\e[0m\n\n";
526             print STDERR @result, "\n";
527             warn "If some of these files are left out on purpose then please add them to\n".
528                  "POTFILES.skip instead of POTFILES.in. A file \e[1m'missing'\e[0m containing this list\n".
529                  "of left out files has been written in the current directory.\n";
530         }
531         if (@buf_potfiles_notexist)
532         {
533             unlink "notexist";
534             open OUT, ">notexist";
535             print OUT @buf_potfiles_notexist;
536             close OUT;
537             warn "\n" if ($VERBOSE or @result);
538             warn "\e[1mThe following files do not exist anymore:\e[0m\n\n";
539             warn @buf_potfiles_notexist, "\n";
540             warn "Please remove them from POTFILES.in or POTFILES.skip. A file \e[1m'notexist'\e[0m\n".
541                  "containing this list of absent files has been written in the current directory.\n";
542         }
543     }
544
545     ## If there is nothing to complain about, notify the user
546     else {
547         print "\nAll files containing translations are present in POTFILES.in.\n" if $VERBOSE;
548     }
549 }
550
551 sub Console_WriteError_InvalidOption
552 {
553     ## Handle invalid arguments
554     print STDERR "Try `${PROGRAM} --help' for more information.\n";
555     exit 1;
556 }
557
558 sub GenerateHeaders
559 {
560     my $EXTRACT = "@INTLTOOL_EXTRACT@";
561     chomp $EXTRACT;
562
563     $EXTRACT = $ENV{"INTLTOOL_EXTRACT"} if $ENV{"INTLTOOL_EXTRACT"};
564
565     ## Generate the .h header files, so we can allow glade and
566     ## xml translation support
567     if (! -x "$EXTRACT")
568     {
569         print STDERR "\n *** The intltool-extract script wasn't found!"
570              ."\n *** Without it, intltool-update can not generate files.\n";
571         exit;
572     }
573     else
574     {
575         open (FILE, $POTFILES_in) or die "$PROGRAM: POTFILES.in not found.\n";
576         
577         while (<FILE>) 
578         {
579            chomp;
580            next if /^\[\s*encoding/;
581
582            ## Find xml files in POTFILES.in and generate the
583            ## files with help from the extract script
584
585            my $gettext_type= &POFile_DetermineType ($1);
586
587            if (/\.($xml_support|$ini_support)$/ || /^\[/)
588            {
589                s/^\[[^\[].*]\s*//;
590
591                my $filename = "../$_";
592
593                if ($VERBOSE)
594                {
595                    system ($EXTRACT, "--update", "--srcdir=$SRCDIR",
596                            "--type=$gettext_type", $filename);
597                } 
598                else 
599                {
600                    system ($EXTRACT, "--update", "--type=$gettext_type", 
601                            "--srcdir=$SRCDIR", "--quiet", $filename);
602                }
603            }
604        }
605        close FILE;
606    }
607 }
608
609 #
610 # Generate .pot file from POTFILES.in
611 #
612 sub GeneratePOTemplate
613 {
614     my $XGETTEXT = $ENV{"XGETTEXT"} || "@INTLTOOL_XGETTEXT@";
615     my $XGETTEXT_ARGS = $ENV{"XGETTEXT_ARGS"} || '';
616     chomp $XGETTEXT;
617
618     if (! -x $XGETTEXT)
619     {
620         print STDERR " *** xgettext is not found on this system!\n".
621                      " *** Without it, intltool-update can not extract strings.\n";
622         exit;
623     }
624
625     print "Building $MODULE.pot...\n" if $VERBOSE;
626
627     open INFILE, $POTFILES_in;
628     unlink "POTFILES.in.temp";
629     open OUTFILE, ">POTFILES.in.temp" or die("Cannot open POTFILES.in.temp for writing");
630
631     my $gettext_support_nonascii = 0;
632
633     # checks for GNU gettext >= 0.12
634     my $dummy = `$XGETTEXT --version --from-code=UTF-8 >$devnull 2>$devnull`;
635     if ($? == 0)
636     {
637         $gettext_support_nonascii = 1;
638     }
639     else
640     {
641         # urge everybody to upgrade gettext
642         print STDERR "WARNING: This version of gettext does not support extracting non-ASCII\n".
643                      "         strings. That means you should install a version of gettext\n".
644                      "         that supports non-ASCII strings (such as GNU gettext >= 0.12),\n".
645                      "         or have to let non-ASCII strings untranslated. (If there is any)\n";
646     }
647
648     my $encoding = "ASCII";
649     my $forced_gettext_code;
650     my @temp_headers;
651     my $encoding_problem_is_reported = 0;
652
653     while (<INFILE>) 
654     {
655         next if (/^#/ or /^\s*$/);
656
657         chomp;
658
659         my $gettext_code;
660
661         if (/^\[\s*encoding:\s*(.*)\s*\]/)
662         {
663             $forced_gettext_code=$1;
664         }
665         elsif (/\.($xml_support|$ini_support)$/ || /^\[/)
666         {
667             s/^\[.*]\s*//;
668             print OUTFILE "../$_.h\n";
669             push @temp_headers, "../$_.h";
670             $gettext_code = &TextFile_DetermineEncoding ("../$_.h") if ($gettext_support_nonascii and not defined $forced_gettext_code);
671         } 
672         else 
673         {
674             if ($SRCDIR eq ".") {
675                 print OUTFILE "../$_\n";
676             } else {
677                 print OUTFILE "$SRCDIR/../$_\n";
678             }
679             $gettext_code = &TextFile_DetermineEncoding ("../$_") if ($gettext_support_nonascii and not defined $forced_gettext_code);
680         }
681
682         next if (! $gettext_support_nonascii);
683
684         if (defined $forced_gettext_code)
685         {
686             $encoding=$forced_gettext_code;
687         }
688         elsif (defined $gettext_code and "$encoding" ne "$gettext_code")
689         {
690             if ($encoding eq "ASCII")
691             {
692                 $encoding=$gettext_code;
693             }
694             elsif ($gettext_code ne "ASCII")
695             {
696                 # Only report once because the message is quite long
697                 if (! $encoding_problem_is_reported)
698                 {
699                     print STDERR "WARNING: You should use the same file encoding for all your project files,\n".
700                                  "         but $PROGRAM thinks that most of the source files are in\n".
701                                  "         $encoding encoding, while \"$_\" is (likely) in\n".
702                                  "         $gettext_code encoding. If you are sure that all translatable strings\n".
703                                  "         are in same encoding (say UTF-8), please \e[1m*prepend*\e[0m the following\n".
704                                  "         line to POTFILES.in:\n\n".
705                                  "                 [encoding: UTF-8]\n\n".
706                                  "         and make sure that configure.in/ac checks for $PACKAGE >= 0.27 .\n".
707                                  "(such warning message will only be reported once.)\n";
708                     $encoding_problem_is_reported = 1;
709                 }
710             }
711         }
712     }
713
714     close OUTFILE;
715     close INFILE;
716
717     unlink "$MODULE.pot";
718     my @xgettext_argument=("$XGETTEXT",
719                            "--add-comments",
720                            "--directory\=\.",
721                            "--output\=$MODULE\.pot",
722                            "--files-from\=\.\/POTFILES\.in\.temp");
723     my $XGETTEXT_KEYWORDS = &FindPOTKeywords;
724     push @xgettext_argument, $XGETTEXT_KEYWORDS;
725     push @xgettext_argument, "--from-code\=$encoding" if ($gettext_support_nonascii);
726     push @xgettext_argument, $XGETTEXT_ARGS if $XGETTEXT_ARGS;
727     my $xgettext_command = join ' ', @xgettext_argument;
728
729     # intercept xgettext error message
730     print "Running $xgettext_command\n" if $VERBOSE;
731     my $xgettext_error_msg = `$xgettext_command 2>\&1`;
732     my $command_failed = $?;
733
734     unlink "POTFILES.in.temp";
735
736     print "Removing generated header (.h) files..." if $VERBOSE;
737     unlink foreach (@temp_headers);
738     print "done.\n" if $VERBOSE;
739
740     if (! $command_failed)
741     {
742         if (! -e "$MODULE.pot")
743         {
744             print "None of the files in POTFILES.in contain strings marked for translation.\n" if $VERBOSE;
745         }
746         else
747         {
748             print "Wrote $MODULE.pot\n" if $VERBOSE;
749         }
750     }
751     else
752     {
753         if ($xgettext_error_msg =~ /--from-code/)
754         {
755             # replace non-ASCII error message with a more useful one.
756             print STDERR "ERROR: xgettext failed to generate PO template file because there is non-ASCII\n".
757                          "       string marked for translation. Please make sure that all strings marked\n".
758                          "       for translation are in uniform encoding (say UTF-8), then \e[1m*prepend*\e[0m the\n".
759                          "       following line to POTFILES.in and rerun $PROGRAM:\n\n".
760                          "           [encoding: UTF-8]\n\n";
761         }
762         else
763         {
764             print STDERR "$xgettext_error_msg";
765             if (-e "$MODULE.pot")
766             {
767                 # is this possible?
768                 print STDERR "ERROR: xgettext failed but still managed to generate PO template file.\n".
769                              "       Please consult error message above if there is any.\n";
770             }
771             else
772             {
773                 print STDERR "ERROR: xgettext failed to generate PO template file. Please consult\n".
774                              "       error message above if there is any.\n";
775             }
776         }
777         exit (1);
778     }
779 }
780
781 sub POFile_Update
782 {
783     -f "$MODULE.pot" or die "$PROGRAM: $MODULE.pot does not exist.\n";
784
785     my $MSGMERGE = $ENV{"MSGMERGE"} || "@INTLTOOL_MSGMERGE@";
786     my ($lang, $outfile) = @_;
787
788     print "Merging $SRCDIR/$lang.po with $MODULE.pot..." if $VERBOSE;
789
790     my $infile = "$SRCDIR/$lang.po";
791     $outfile = "$SRCDIR/$lang.po" if ($outfile eq "");
792
793     # I think msgmerge won't overwrite old file if merge is not successful
794     system ("$MSGMERGE", "-o", $outfile, $infile, "$MODULE.pot");
795 }
796
797 sub Console_WriteError_NotExisting
798 {
799     my ($file) = @_;
800
801     ## Report error if supplied language file is non-existing
802     print STDERR "$PROGRAM: $file does not exist!\n";
803     print STDERR "Try '$PROGRAM --help' for more information.\n";
804     exit;
805 }
806
807 sub GatherPOFiles
808 {
809     my @po_files = glob ("./*.po");
810
811     @languages = map (&POFile_GetLanguage, @po_files);
812
813     foreach my $lang (@languages) 
814     {
815         $po_files_by_lang{$lang} = shift (@po_files);
816     }
817 }
818
819 sub POFile_GetLanguage ($)
820 {
821     s/^(.*\/)?(.+)\.po$/$2/;
822     return $_;
823 }
824
825 sub Console_Write_TranslationStatus
826 {
827     my ($lang, $output_file) = @_;
828     my $MSGFMT = $ENV{"MSGFMT"} || "@INTLTOOL_MSGFMT@";
829
830     $output_file = "$SRCDIR/$lang.po" if ($output_file eq "");
831
832     system ("$MSGFMT", "-o", "$devnull", "--verbose", $output_file);
833 }
834
835 sub Console_Write_CoverageReport
836 {
837     my $MSGFMT = $ENV{"MSGFMT"} || "@INTLTOOL_MSGFMT@";
838
839     &GatherPOFiles;
840
841     foreach my $lang (@languages) 
842     {
843         print "$lang: ";
844         &POFile_Update ($lang, "");
845     }
846
847     print "\n\n * Current translation support in $MODULE \n\n";
848
849     foreach my $lang (@languages)
850     {
851         print "$lang: ";
852         system ("$MSGFMT", "-o", "$devnull", "--verbose", "$SRCDIR/$lang.po");
853     }
854 }
855
856 sub SubstituteVariable
857 {
858     my ($str) = @_;
859     
860     # always need to rewind file whenever it has been accessed
861     seek (CONF, 0, 0);
862
863     # cache each variable. varhash is global to we can add
864     # variables elsewhere.
865     while (<CONF>)
866     {
867         if (/^(\w+)=(.*)$/)
868         {
869             ($varhash{$1} = $2) =~  s/^["'](.*)["']$/$1/;
870         }
871     }
872     
873     if ($str =~ /^(.*)\${?([A-Z_]+)}?(.*)$/)
874     {
875         my $rest = $3;
876         my $untouched = $1;
877         my $sub = $varhash{$2};
878         
879         return SubstituteVariable ("$untouched$sub$rest");
880     }
881     
882     # We're using Perl backticks ` and "echo -n" here in order to 
883     # expand any shell escapes (such as backticks themselves) in every variable
884     return echo_n ($str);
885 }
886
887 sub CONF_Handle_Open
888 {
889     my $base_dirname = getcwd();
890     $base_dirname =~ s@.*/@@;
891
892     my ($conf_in, $src_dir);
893
894     if ($base_dirname =~ /^po(-.+)?$/) 
895     {
896         if (-f "Makevars") 
897         {
898             my $makefile_source;
899
900             local (*IN);
901             open (IN, "<Makevars") || die "can't open Makevars: $!";
902
903             while (<IN>) 
904             {
905                 if (/^top_builddir[ \t]*=/) 
906                 {
907                     $src_dir = $_;
908                     $src_dir =~ s/^top_builddir[ \t]*=[ \t]*([^ \t\n\r]*)/$1/;
909
910                     chomp $src_dir;
911                     if (-f "$src_dir" . "/configure.ac") {
912                         $conf_in = "$src_dir" . "/configure.ac" . "\n";
913                     } else {
914                         $conf_in = "$src_dir" . "/configure.in" . "\n";
915                     }
916                     last;
917                 }
918             }
919             close IN;
920
921             $conf_in || die "Cannot find top_builddir in Makevars.";
922         }
923         elsif (-f "../configure.ac") 
924         {
925             $conf_in = "../configure.ac";
926         } 
927         elsif (-f "../configure.in") 
928         {
929             $conf_in = "../configure.in";
930         } 
931         else 
932         {
933             my $makefile_source;
934
935             local (*IN);
936             open (IN, "<Makefile") || return;
937
938             while (<IN>) 
939             {
940                 if (/^top_srcdir[ \t]*=/) 
941                 {
942                     $src_dir = $_;                  
943                     $src_dir =~ s/^top_srcdir[ \t]*=[ \t]*([^ \t\n\r]*)/$1/;
944
945                     chomp $src_dir;
946                     $conf_in = "$src_dir" . "/configure.in" . "\n";
947
948                     last;
949                 }
950             }
951             close IN;
952
953             $conf_in || die "Cannot find top_srcdir in Makefile.";
954         }
955
956         open (CONF, "<$conf_in");
957     }
958     else
959     {
960         print STDERR "$PROGRAM: Unable to proceed.\n" .
961                      "Make sure to run this script inside the po directory.\n";
962         exit;
963     }
964 }
965
966 sub FindPackageName
967 {
968     my $version;
969     my $domain = &FindMakevarsDomain;
970     my $name = $domain || "untitled";
971
972     &CONF_Handle_Open;
973
974     my $conf_source; {
975         local (*IN);
976         open (IN, "<&CONF") || return $name;
977         seek (IN, 0, 0);
978         local $/; # slurp mode
979         $conf_source = <IN>;
980         close IN;
981     }
982
983     # priority for getting package name:
984     # 1. GETTEXT_PACKAGE
985     # 2. first argument of AC_INIT (with >= 2 arguments)
986     # 3. first argument of AM_INIT_AUTOMAKE (with >= 2 argument)
987
988     # /^AM_INIT_AUTOMAKE\([\s\[]*([^,\)\s\]]+)/m 
989     # the \s makes this not work, why?
990     if ($conf_source =~ /^AM_INIT_AUTOMAKE\(([^,\)]+),([^,\)]+)/m)
991     {
992         ($name, $version) = ($1, $2);
993         $name    =~ s/[\[\]\s]//g;
994         $version =~ s/[\[\]\s]//g;
995         $varhash{"PACKAGE_NAME"} = $name;
996         $varhash{"PACKAGE"} = $name;
997         $varhash{"PACKAGE_VERSION"} = $version;
998         $varhash{"VERSION"} = $version;
999     }
1000     
1001     if ($conf_source =~ /^AC_INIT\(([^,\)]+),([^,\)]+)/m) 
1002     {
1003         ($name, $version) = ($1, $2);
1004         $name    =~ s/[\[\]\s]//g;
1005         $version =~ s/[\[\]\s]//g;
1006         $varhash{"PACKAGE_NAME"} = $name;
1007         $varhash{"PACKAGE"} = $name;
1008         $varhash{"PACKAGE_VERSION"} = $version;
1009         $varhash{"VERSION"} = $version;
1010     }
1011
1012     # \s makes this not work, why?
1013     $name = $1 if $conf_source =~ /^GETTEXT_PACKAGE=\[?([^\n\]]+)/m;
1014     
1015     # m4 macros AC_PACKAGE_NAME, AC_PACKAGE_VERSION etc. have same value
1016     # as corresponding $PACKAGE_NAME, $PACKAGE_VERSION etc. shell variables.
1017     $name =~ s/\bAC_PACKAGE_/\$PACKAGE_/g;
1018
1019     $name = $domain if $domain;
1020
1021     $name = SubstituteVariable ($name);
1022     $name =~ s/^["'](.*)["']$/$1/;
1023
1024     return $name if $name;
1025 }
1026
1027
1028 sub FindPOTKeywords
1029 {
1030
1031     my $keywords = "--keyword\=\_ --keyword\=N\_ --keyword\=U\_ --keyword\=Q\_";
1032     my $varname = "XGETTEXT_OPTIONS";
1033     my $make_source; {
1034         local (*IN);
1035         open (IN, "<Makevars") || (open(IN, "<Makefile.in.in") && ($varname = "XGETTEXT_KEYWORDS")) || return $keywords;
1036         seek (IN, 0, 0);
1037         local $/; # slurp mode
1038         $make_source = <IN>;
1039         close IN;
1040     }
1041
1042     $keywords = $1 if $make_source =~ /^$varname[ ]*=\[?([^\n\]]+)/m;
1043     
1044     return $keywords;
1045 }
1046
1047 sub FindMakevarsDomain
1048 {
1049
1050     my $domain = "";
1051     my $makevars_source; { 
1052         local (*IN);
1053         open (IN, "<Makevars") || return $domain;
1054         seek (IN, 0, 0);
1055         local $/; # slurp mode
1056         $makevars_source = <IN>;
1057         close IN;
1058     }
1059
1060     $domain = $1 if $makevars_source =~ /^DOMAIN[ ]*=\[?([^\n\]\$]+)/m;
1061     $domain =~ s/^\s+//;
1062     $domain =~ s/\s+$//;
1063     
1064     return $domain;
1065 }