home *** CD-ROM | disk | FTP | other *** search
/ Chip 2005 June / ccd0605.iso / LINUX / gopchop-1.1.7.tar.tar / gopchop-1.1.7.tar / gopchop-1.1.7 / intltool-update.in < prev    next >
Text File  |  2005-04-30  |  27KB  |  1,064 lines

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