[WTF] Move currentCPUTime and sleep(Seconds) to CPUTime.h and Seconds.h respectively
[WebKit-https.git] / Source / WebCore / extract-localizable-strings.pl
index 126798c..625f202 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/perl -w
+#!/usr/bin/env perl
 
 # Copyright (C) 2006, 2007, 2009, 2010, 2013 Apple Inc. All rights reserved.
 #
 
 # Copyright (C) 2006, 2007, 2009, 2010, 2013 Apple Inc. All rights reserved.
 #
 # The exceptions file has a list of strings in quotes, filenames, and filename/string pairs separated by :.
 
 use strict;
 # The exceptions file has a list of strings in quotes, filenames, and filename/string pairs separated by :.
 
 use strict;
+use warnings;
+use File::Compare;
+use File::Copy;
+use FindBin;
 use Getopt::Long;
 use Getopt::Long;
+use lib $FindBin::Bin;
+use LocalizableStrings;
 no warnings 'deprecated';
 
 no warnings 'deprecated';
 
-sub UnescapeHexSequence($);
-
 my %isDebugMacro = ( ASSERT_WITH_MESSAGE => 1, LOG_ERROR => 1, ERROR => 1, NSURL_ERROR => 1, FATAL => 1, LOG => 1, LOG_WARNING => 1, UI_STRING_LOCALIZE_LATER => 1, UI_STRING_LOCALIZE_LATER_KEY => 1, LPCTSTR_UI_STRING_LOCALIZE_LATER => 1, UNLOCALIZED_STRING => 1, UNLOCALIZED_LPCTSTR => 1, dprintf => 1, NSException => 1, NSLog => 1, printf => 1 );
 
 my $verify;
 my $exceptionsFile;
 my @directoriesToSkip = ();
 my %isDebugMacro = ( ASSERT_WITH_MESSAGE => 1, LOG_ERROR => 1, ERROR => 1, NSURL_ERROR => 1, FATAL => 1, LOG => 1, LOG_WARNING => 1, UI_STRING_LOCALIZE_LATER => 1, UI_STRING_LOCALIZE_LATER_KEY => 1, LPCTSTR_UI_STRING_LOCALIZE_LATER => 1, UNLOCALIZED_STRING => 1, UNLOCALIZED_LPCTSTR => 1, dprintf => 1, NSException => 1, NSLog => 1, printf => 1 );
 
 my $verify;
 my $exceptionsFile;
 my @directoriesToSkip = ();
+my $treatWarningsAsErrors;
 
 my %options = (
     'verify' => \$verify,
     'exceptions=s' => \$exceptionsFile,
     'skip=s' => \@directoriesToSkip,
 
 my %options = (
     'verify' => \$verify,
     'exceptions=s' => \$exceptionsFile,
     'skip=s' => \@directoriesToSkip,
+    'treat-warnings-as-errors' => \$treatWarningsAsErrors,
 );
 
 GetOptions(%options);
 
 );
 
 GetOptions(%options);
 
-@ARGV >= 2 or die "Usage: extract-localizable-strings [--verify] [--exceptions <exceptions file>] <file to update> [--skip directory | directory]...\nDid you mean to run update-webkit-localizable-strings instead?\n";
+setTreatWarningsAsErrors($treatWarningsAsErrors);
+
+@ARGV >= 2 or die "Usage: extract-localizable-strings [--verify] [--treat-warnings-as-errors] [--exceptions <exceptions file>] <file to update> [--skip directory | directory]...\nDid you mean to run update-webkit-localizable-strings instead?\n";
 
 -f $exceptionsFile or die "Couldn't find exceptions file $exceptionsFile\n" unless !defined $exceptionsFile;
 
 
 -f $exceptionsFile or die "Couldn't find exceptions file $exceptionsFile\n" unless !defined $exceptionsFile;
 
@@ -80,10 +88,6 @@ if (@ARGV < 1) {
     }
 }
 
     }
 }
 
-my $sawError = 0;
-
-my $localizedCount = 0;
-my $keyCollisionCount = 0;
 my $notLocalizedCount = 0;
 my $NSLocalizeCount = 0;
 
 my $notLocalizedCount = 0;
 my $NSLocalizeCount = 0;
 
@@ -95,13 +99,13 @@ if (defined $exceptionsFile && open EXCEPTIONS, $exceptionsFile) {
         chomp;
         if (/^"([^\\"]|\\.)*"$/ or /^[-_\/\w\s.]+.(h|m|mm|c|cpp)$/ or /^[-_\/\w\s.]+.(h|m|mm|c|cpp):"([^\\"]|\\.)*"$/) {
             if ($exception{$_}) {
         chomp;
         if (/^"([^\\"]|\\.)*"$/ or /^[-_\/\w\s.]+.(h|m|mm|c|cpp)$/ or /^[-_\/\w\s.]+.(h|m|mm|c|cpp):"([^\\"]|\\.)*"$/) {
             if ($exception{$_}) {
-                print "$exceptionsFile:$.: warning: exception for $_ appears twice\n";
-                print "$exceptionsFile:$exception{$_}: warning: first appearance\n";
+                emitWarning($exceptionsFile, $., "exception for $_ appears twice");
+                emitWarning($exceptionsFile, $exception{$_}, "first appearance");
             } else {
                 $exception{$_} = $.;
             }
         } else {
             } else {
                 $exception{$_} = $.;
             }
         } else {
-            print "$exceptionsFile:$.: warning: syntax error\n";
+            emitWarning($exceptionsFile, $., "syntax error");
         }
     }
     close EXCEPTIONS;
         }
     }
     close EXCEPTIONS;
@@ -129,6 +133,7 @@ for my $file (sort @files) {
     my $UIString;
     my $key;
     my $comment;
     my $UIString;
     my $key;
     my $comment;
+    my $mnemonic;
     
     my $string;
     my $stringLine;
     
     my $string;
     my $stringLine;
@@ -150,11 +155,14 @@ for my $file (sort @files) {
         # Handle all the tokens in the line.
         while (s-^\s*([#\w]+|/\*|//|[^#\w/'"()\[\],]+|.)--) {
             my $token = $1;
         # Handle all the tokens in the line.
         while (s-^\s*([#\w]+|/\*|//|[^#\w/'"()\[\],]+|.)--) {
             my $token = $1;
-            
+
+            if ($token eq "@" and $expected and $expected eq "a quoted string") {
+                next;
+            }
+
             if ($token eq "\"") {
                 if ($expected and $expected ne "a quoted string") {
             if ($token eq "\"") {
                 if ($expected and $expected ne "a quoted string") {
-                    print "$file:$.: found a quoted string but expected $expected\n";
-                    $sawError = 1;
+                    emitError($file, $., "found a quoted string but expected $expected");
                     $expected = "";
                 }
                 if (s-^(([^\\$token]|\\.)*?)$token--) {
                     $expected = "";
                 }
                 if (s-^(([^\\$token]|\\.)*?)$token--) {
@@ -165,8 +173,7 @@ for my $file (sort @files) {
                         $string .= $1;
                     }
                 } else {
                         $string .= $1;
                     }
                 } else {
-                    print "$file:$.: mismatched quotes\n";
-                    $sawError = 1;
+                    emitError($file, $., "mismatched quotes");
                     $_ = "";
                 }
                 next;
                     $_ = "";
                 }
                 next;
@@ -183,6 +190,9 @@ handleString:
                         # FIXME: Validate UTF-8 here?
                         $key = $string;
                         $expected = ",";
                         # FIXME: Validate UTF-8 here?
                         $key = $string;
                         $expected = ",";
+                    } elsif (($macro =~ /WEB_UI_STRING_WITH_MNEMONIC$/) and !defined $mnemonic) {
+                        $mnemonic = $string;
+                        $expected = ",";
                     } elsif (!defined $comment) {
                         # FIXME: Validate UTF-8 here?
                         $comment = $string;
                     } elsif (!defined $comment) {
                         # FIXME: Validate UTF-8 here?
                         $comment = $string;
@@ -204,7 +214,7 @@ handleString:
                     } elsif ($exception{"$file:\"$string\""}) {
                         $usedException{"$file:\"$string\""} = 1;
                     } else {
                     } elsif ($exception{"$file:\"$string\""}) {
                         $usedException{"$file:\"$string\""} = 1;
                     } else {
-                        print "$file:$stringLine: warning: \"$string\" is not marked for localization\n" if $warnAboutUnlocalizedStrings;
+                        emitWarning($file, $stringLine, "\"$string\" is not marked for localization") if $warnAboutUnlocalizedStrings;
                         $notLocalizedCount++;
                     }
                 }
                         $notLocalizedCount++;
                     }
                 }
@@ -215,9 +225,8 @@ handleString:
             $previousToken = $token;
 
             if ($token =~ /^NSLocalized/ && $token !~ /NSLocalizedDescriptionKey/ && $token !~ /NSLocalizedStringFromTableInBundle/ && $token !~ /NSLocalizedFileSizeDescription/ && $token !~ /NSLocalizedDescriptionKey/ && $token !~ /NSLocalizedRecoverySuggestionErrorKey/) {
             $previousToken = $token;
 
             if ($token =~ /^NSLocalized/ && $token !~ /NSLocalizedDescriptionKey/ && $token !~ /NSLocalizedStringFromTableInBundle/ && $token !~ /NSLocalizedFileSizeDescription/ && $token !~ /NSLocalizedDescriptionKey/ && $token !~ /NSLocalizedRecoverySuggestionErrorKey/) {
-                print "$file:$.: found a use of an NSLocalized macro ($token); not supported\n";
+                emitError($file, $., "found a use of an NSLocalized macro ($token); not supported");
                 $nestingLevel = 0 if !defined $nestingLevel;
                 $nestingLevel = 0 if !defined $nestingLevel;
-                $sawError = 1;
                 $NSLocalizeCount++;
             } elsif ($token eq "/*") {
                 if (!s-^.*?\*/--) {
                 $NSLocalizeCount++;
             } elsif ($token eq "/*") {
                 if (!s-^.*?\*/--) {
@@ -228,22 +237,21 @@ handleString:
                 $_ = ""; # Discard the rest of the line
             } elsif ($token eq "'") {
                 if (!s-([^\\]|\\.)'--) { #' <-- that single quote makes the Project Builder editor less confused
                 $_ = ""; # Discard the rest of the line
             } elsif ($token eq "'") {
                 if (!s-([^\\]|\\.)'--) { #' <-- that single quote makes the Project Builder editor less confused
-                    print "$file:$.: mismatched single quote\n";
-                    $sawError = 1;
+                    emitError($file, $., "mismatched single quote");
                     $_ = "";
                 }
             } else {
                 if ($expected and $expected ne $token) {
                     $_ = "";
                 }
             } else {
                 if ($expected and $expected ne $token) {
-                    print "$file:$.: found $token but expected $expected\n";
-                    $sawError = 1;
+                    emitError($file, $., "found $token but expected $expected");
                     $expected = "";
                 }
                     $expected = "";
                 }
-                if ($token =~ /(WEB_)?UI_STRING(_KEY)?(_INTERNAL)?$/) {
+                if (($token =~ /(WEB_)?UI_STRING(_KEY)?(_INTERNAL)?$/) || ($token =~ /WEB_UI_NSSTRING$/) || ($token =~ /WEB_UI_STRING_WITH_MNEMONIC$/) || ($token =~ /WEB_UI_CFSTRING$/)) {
                     $expected = "(";
                     $macro = $token;
                     $UIString = undef;
                     $key = undef;
                     $comment = undef;
                     $expected = "(";
                     $macro = $token;
                     $UIString = undef;
                     $key = undef;
                     $comment = undef;
+                    $mnemonic = undef;
                     $macroLine = $.;
                 } elsif ($token eq "(" or $token eq "[") {
                     ++$nestingLevel if defined $nestingLevel;
                     $macroLine = $.;
                 } elsif ($token eq "(" or $token eq "[") {
                     ++$nestingLevel if defined $nestingLevel;
@@ -257,7 +265,6 @@ handleString:
                         HandleUIString($UIString, $key, $comment, $file, $macroLine);
                         $macro = "";
                         $expected = "";
                         HandleUIString($UIString, $key, $comment, $file, $macroLine);
                         $macro = "";
                         $expected = "";
-                        $localizedCount++;
                     }
                 } elsif ($isDebugMacro{$token}) {
                     $nestingLevel = 0 if !defined $nestingLevel;
                     }
                 } elsif ($isDebugMacro{$token}) {
                     $nestingLevel = 0 if !defined $nestingLevel;
@@ -270,176 +277,46 @@ handleString:
     goto handleString if defined $string;
     
     if ($expected) {
     goto handleString if defined $string;
     
     if ($expected) {
-        print "$file: reached end of file but expected $expected\n";
-        $sawError = 1;
+        emitError($file, 0, "reached end of file but expected $expected");
     }
     
     close SOURCE;
 }
 
     }
     
     close SOURCE;
 }
 
-# Unescapes C language hexadecimal escape sequences.
-sub UnescapeHexSequence($)
-{
-    my ($originalStr) = @_;
-
-    my $escapedStr = $originalStr;
-    my $unescapedStr = "";
-
-    for (;;) {
-        if ($escapedStr =~ s-^\\x([[:xdigit:]]+)--) {
-            if (256 <= hex($1)) {
-                print "Hexadecimal escape sequence out of range: \\x$1\n";
-                return undef;
-            }
-            $unescapedStr .= pack("H*", $1);
-        } elsif ($escapedStr =~ s-^(.)--) {
-            $unescapedStr .= $1;
-        } else {
-            return $unescapedStr;
-        }
-    }
-}
-
-my %stringByKey;
-my %commentByKey;
-my %fileByKey;
-my %lineByKey;
-
-sub HandleUIString
-{
-    my ($string, $key, $comment, $file, $line) = @_;
-
-    my $bad = 0;
-    $string = UnescapeHexSequence($string);
-    if (!defined($string)) {
-        print "$file:$line: string has an illegal hexadecimal escape sequence\n";
-        $bad = 1;
-    }
-    $key = UnescapeHexSequence($key);
-    if (!defined($key)) {
-        print "$file:$line: key has an illegal hexadecimal escape sequence\n";
-        $bad = 1;
-    }
-    $comment = UnescapeHexSequence($comment);
-    if (!defined($comment)) {
-        print "$file:$line: comment has an illegal hexadecimal escape sequence\n";
-        $bad = 1;
-    }
-    if (grep { $_ == 0xFFFD } unpack "U*", $string) {
-        print "$file:$line: string for translation has illegal UTF-8 -- most likely a problem with the Text Encoding of the source file\n";
-        $bad = 1;
-    }
-    if ($string ne $key && grep { $_ == 0xFFFD } unpack "U*", $key) {
-        print "$file:$line: key has illegal UTF-8 -- most likely a problem with the Text Encoding of the source file\n";
-        $bad = 1;
-    }
-    if (grep { $_ == 0xFFFD } unpack "U*", $comment) {
-        print "$file:$line: comment for translation has illegal UTF-8 -- most likely a problem with the Text Encoding of the source file\n";
-        $bad = 1;
-    }
-    if ($bad) {
-        $sawError = 1;
-        return;
-    }
-    
-    if ($stringByKey{$key} && $stringByKey{$key} ne $string) {
-        print "$file:$line: warning: encountered the same key, \"$key\", twice, with different strings\n";
-        print "$fileByKey{$key}:$lineByKey{$key}: warning: previous occurrence\n";
-        $keyCollisionCount++;
-        return;
-    }
-    if ($commentByKey{$key} && $commentByKey{$key} ne $comment) {
-        print "$file:$line: warning: encountered the same key, \"$key\", twice, with different comments\n";
-        print "$fileByKey{$key}:$lineByKey{$key}: warning: previous occurrence\n";
-        $keyCollisionCount++;
-        return;
-    }
-
-    $fileByKey{$key} = $file;
-    $lineByKey{$key} = $line;
-    $stringByKey{$key} = $string;
-    $commentByKey{$key} = $comment;
-}
-
-print "\n" if $sawError || $notLocalizedCount || $NSLocalizeCount;
+print "\n" if sawError() || $notLocalizedCount || $NSLocalizeCount;
 
 my @unusedExceptions = sort grep { !$usedException{$_} } keys %exception;
 if (@unusedExceptions) {
     for my $unused (@unusedExceptions) {
 
 my @unusedExceptions = sort grep { !$usedException{$_} } keys %exception;
 if (@unusedExceptions) {
     for my $unused (@unusedExceptions) {
-        print "$exceptionsFile:$exception{$unused}: warning: exception $unused not used\n";
+        emitWarning($exceptionsFile, $exception{$unused}, "exception $unused not used");
     }
     print "\n";
 }
 
     }
     print "\n";
 }
 
-print "$localizedCount localizable strings\n" if $localizedCount;
-print "$keyCollisionCount key collisions\n" if $keyCollisionCount;
+print localizedCount() . " localizable strings\n" if localizedCount();
+print keyCollisionCount() . " key collisions\n" if keyCollisionCount();
 print "$notLocalizedCount strings not marked for localization\n" if $notLocalizedCount;
 print "$NSLocalizeCount uses of NSLocalize\n" if $NSLocalizeCount;
 print scalar(@unusedExceptions), " unused exceptions\n" if @unusedExceptions;
 
 print "$notLocalizedCount strings not marked for localization\n" if $notLocalizedCount;
 print "$NSLocalizeCount uses of NSLocalize\n" if $NSLocalizeCount;
 print scalar(@unusedExceptions), " unused exceptions\n" if @unusedExceptions;
 
-if ($sawError) {
+if (sawError()) {
     print "\nErrors encountered. Exiting without writing to $fileToUpdate.\n";
     exit 1;
 }
 
     print "\nErrors encountered. Exiting without writing to $fileToUpdate.\n";
     exit 1;
 }
 
-my $localizedStrings = "";
-
-for my $key (sort keys %commentByKey) {
-    $localizedStrings .= "/* $commentByKey{$key} */\n\"$key\" = \"$stringByKey{$key}\";\n\n";
-}
-
 if (-e "$fileToUpdate") {
     if (!$verify) {
 if (-e "$fileToUpdate") {
     if (!$verify) {
-        # Write out the strings file as UTF-8
-        open STRINGS, ">", "$fileToUpdate" or die;
-        print STRINGS $localizedStrings;
-        close STRINGS;
-    } else {
-        open STRINGS, $fileToUpdate or die;
-
-        my $lastComment;
-        my $line;
-
-        while (<STRINGS>) {
-            chomp;
-
-            next if (/^\s*$/);
+        my $temporaryFile = "$fileToUpdate.updated";
+        writeStringsFile($temporaryFile);
 
 
-            if (/^\/\* (.*) \*\/$/) {
-                $lastComment = $1;
-            } elsif (/^"((?:[^\\]|\\[^"])*)"\s*=\s*"((?:[^\\]|\\[^"])*)";$/) #
-            {
-                my $string = delete $stringByKey{$1};
-                if (!defined $string) {
-                    print "$fileToUpdate:$.: unused key \"$1\"\n";
-                    $sawError = 1;
-                } else {
-                    if (!($string eq $2)) {
-                        print "$fileToUpdate:$.: unexpected value \"$2\" for key \"$1\"\n";
-                        print "$fileByKey{$1}:$lineByKey{$1}: expected value \"$string\" defined here\n";
-                        $sawError = 1;
-                    }
-                    if (!($lastComment eq $commentByKey{$1})) {
-                        print "$fileToUpdate:$.: unexpected comment /* $lastComment */ for key \"$1\"\n";
-                        print "$fileByKey{$1}:$lineByKey{$1}: expected comment /* $commentByKey{$1} */ defined here\n";
-                        $sawError = 1;
-                    }
-                }
-            } else {
-                print "$fileToUpdate:$.: line with unexpected format: $_\n";
-                $sawError = 1;
-            }
-        }
-
-        for my $missing (keys %stringByKey) {
-            print "$fileByKey{$missing}:$lineByKey{$missing}: missing key \"$missing\"\n";
-            $sawError = 1;
-        }
-
-        if ($sawError) {
-            print "\n$fileToUpdate:0: file is not up to date.\n";
-            exit 1;
+        # Avoid updating the target file's modification time if the contents have not changed.
+        if (compare($temporaryFile, $fileToUpdate)) {
+            move($temporaryFile, $fileToUpdate);
+        } else {
+            unlink $temporaryFile;
         }
         }
+    } else {
+        verifyStringsFile($fileToUpdate);
     }
 } else {
     print "error: $fileToUpdate does not exist\n";
     }
 } else {
     print "error: $fileToUpdate does not exist\n";