Index: grit/format/rc.py |
=================================================================== |
--- grit/format/rc.py (revision 0) |
+++ grit/format/rc.py (revision 0) |
@@ -0,0 +1,445 @@ |
+#!/usr/bin/python2.4 |
+# Copyright (c) 2011 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+'''Support for formatting an RC file for compilation. |
+''' |
+ |
+import os |
+import types |
+import re |
+ |
+from grit import util |
+from grit.format import interface |
+ |
+# Matches all different types of linebreaks. |
+_LINEBREAKS = re.compile('\r\n|\n|\r') |
+ |
+''' |
+This dictionary defines the langauge charset pair lookup table, which is used |
+for replacing the GRIT expand variables for language info in Product Version |
+resource. The key is the language ISO country code, and the value |
+is the language and character-set pair, which is a hexadecimal string |
+consisting of the concatenation of the language and character-set identifiers. |
+The first 4 digit of the value is the hex value of LCID, the remaining |
+4 digits is the hex value of character-set id(code page)of the language. |
+ |
+We have defined three GRIT expand_variables to be used in the version resource |
+file to set the language info. Here is an example how they should be used in |
+the VS_VERSION_INFO section of the resource file to allow GRIT to localize |
+the language info correctly according to product locale. |
+ |
+VS_VERSION_INFO VERSIONINFO |
+... |
+BEGIN |
+ BLOCK "StringFileInfo" |
+ BEGIN |
+ BLOCK "[GRITVERLANGCHARSETHEX]" |
+ BEGIN |
+ ... |
+ END |
+ END |
+ BLOCK "VarFileInfo" |
+ BEGIN |
+ VALUE "Translation", [GRITVERLANGID], [GRITVERCHARSETID] |
+ END |
+END |
+ |
+''' |
+ |
+_LANGUAGE_CHARSET_PAIR = { |
+ 'ar' : '040104e8', |
+ 'fi' : '040b04e4', |
+ 'ko' : '041203b5', |
+ 'es' : '040a04e4', |
+ 'bg' : '040204e3', |
+ 'fr' : '040c04e4', |
+ 'lv' : '042604e9', |
+ 'sv' : '041d04e4', |
+ 'ca' : '040304e4', |
+ 'de' : '040704e4', |
+ 'lt' : '042704e9', |
+ # no lcid for tl(Tagalog), use default custom locale |
+ 'tl' : '0c0004b0', |
+ 'zh-CN' : '080403a8', |
+ 'el' : '040804e5', |
+ 'no' : '041404e4', |
+ 'th' : '041e036a', |
+ 'zh-TW' : '040403b6', |
+ 'iw' : '040d04e7', |
+ 'pl' : '041504e2', |
+ 'tr' : '041f04e6', |
+ 'hr' : '041a04e4', |
+ # no codepage for hindi, use unicode(1200) |
+ 'hi' : '043904b0', |
+ 'pt-BR' : '041604e4', |
+ 'uk' : '042204e3', |
+ 'cs' : '040504e2', |
+ 'hu' : '040e04e2', |
+ 'ro' : '041804e2', |
+ # no codepage for urdu, use unicode(1200) |
+ 'ur' : '042004b0', |
+ 'da' : '040604e4', |
+ 'is' : '040f04e4', |
+ 'ru' : '041904e3', |
+ 'vi' : '042a04ea', |
+ 'nl' : '041304e4', |
+ 'id' : '042104e4', |
+ 'sr' : '081a04e2', |
+ 'en-GB' : '0809040e', |
+ 'it' : '041004e4', |
+ 'sk' : '041b04e2', |
+ 'et' : '042504e9', |
+ 'ja' : '041103a4', |
+ 'sl' : '042404e2', |
+ 'en' : '040904b0', |
+ 'fake_bidi' : '040d04e7', |
+} |
+ |
+_LANGUAGE_DIRECTIVE_PAIR = { |
+ 'ar' : 'LANG_ARABIC, SUBLANG_DEFAULT', |
+ 'fi' : 'LANG_FINNISH, SUBLANG_DEFAULT', |
+ 'ko' : 'LANG_KOREAN, SUBLANG_KOREAN', |
+ 'es' : 'LANG_SPANISH, SUBLANG_SPANISH_MODERN', |
+ 'bg' : 'LANG_BULGARIAN, SUBLANG_DEFAULT', |
+ 'fr' : 'LANG_FRENCH, SUBLANG_FRENCH', |
+ 'lv' : 'LANG_LATVIAN, SUBLANG_DEFAULT', |
+ 'sv' : 'LANG_SWEDISH, SUBLANG_SWEDISH', |
+ 'ca' : 'LANG_CATALAN, SUBLANG_DEFAULT', |
+ 'de' : 'LANG_GERMAN, SUBLANG_GERMAN', |
+ 'lt' : 'LANG_LITHUANIAN, SUBLANG_LITHUANIAN', |
+ 'tl' : 'LANG_NEUTRAL, SUBLANG_DEFAULT', |
+ 'zh-CN' : 'LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED', |
+ 'el' : 'LANG_GREEK, SUBLANG_DEFAULT', |
+ 'no' : 'LANG_NORWEGIAN, SUBLANG_DEFAULT', |
+ 'th' : 'LANG_THAI, SUBLANG_DEFAULT', |
+ 'zh-TW' : 'LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL', |
+ 'iw' : 'LANG_HEBREW, SUBLANG_DEFAULT', |
+ 'pl' : 'LANG_POLISH, SUBLANG_DEFAULT', |
+ 'tr' : 'LANG_TURKISH, SUBLANG_DEFAULT', |
+ 'hr' : 'LANG_CROATIAN, SUBLANG_DEFAULT', |
+ 'hi' : 'LANG_HINDI, SUBLANG_DEFAULT', |
+ 'pt-BR' : 'LANG_PORTUGUESE, SUBLANG_DEFAULT', |
+ 'uk' : 'LANG_UKRAINIAN, SUBLANG_DEFAULT', |
+ 'cs' : 'LANG_CZECH, SUBLANG_DEFAULT', |
+ 'hu' : 'LANG_HUNGARIAN, SUBLANG_DEFAULT', |
+ 'ro' : 'LANG_ROMANIAN, SUBLANG_DEFAULT', |
+ 'ur' : 'LANG_URDU, SUBLANG_DEFAULT', |
+ 'da' : 'LANG_DANISH, SUBLANG_DEFAULT', |
+ 'is' : 'LANG_ICELANDIC, SUBLANG_DEFAULT', |
+ 'ru' : 'LANG_RUSSIAN, SUBLANG_DEFAULT', |
+ 'vi' : 'LANG_VIETNAMESE, SUBLANG_DEFAULT', |
+ 'nl' : 'LANG_DUTCH, SUBLANG_DEFAULT', |
+ 'id' : 'LANG_INDONESIAN, SUBLANG_DEFAULT', |
+ 'sr' : 'LANG_SERBIAN, SUBLANG_SERBIAN_CYRILLIC', |
+ 'en-GB' : 'LANG_ENGLISH, SUBLANG_ENGLISH_UK', |
+ 'it' : 'LANG_ITALIAN, SUBLANG_DEFAULT', |
+ 'sk' : 'LANG_SLOVAK, SUBLANG_DEFAULT', |
+ 'et' : 'LANG_ESTONIAN, SUBLANG_DEFAULT', |
+ 'ja' : 'LANG_JAPANESE, SUBLANG_DEFAULT', |
+ 'sl' : 'LANG_SLOVENIAN, SUBLANG_DEFAULT', |
+ 'en' : 'LANG_ENGLISH, SUBLANG_ENGLISH_US', |
+ 'fake_bidi' : 'LANG_HEBREW, SUBLANG_DEFAULT', |
+} |
+ |
+def GetLangCharsetPair(language) : |
+ if _LANGUAGE_CHARSET_PAIR.has_key(language) : |
+ return _LANGUAGE_CHARSET_PAIR[language] |
+ else : |
+ print 'Warning:GetLangCharsetPair() found undefined language %s' %(language) |
+ return '' |
+ |
+def GetLangDirectivePair(language) : |
+ if _LANGUAGE_DIRECTIVE_PAIR.has_key(language) : |
+ return _LANGUAGE_DIRECTIVE_PAIR[language] |
+ else : |
+ print 'Warning:GetLangDirectivePair() found undefined language %s' % (language) |
+ return 'unknown language: see tools/grit/format/rc.py' |
+ |
+def GetLangIdHex(language) : |
+ if _LANGUAGE_CHARSET_PAIR.has_key(language) : |
+ langcharset = _LANGUAGE_CHARSET_PAIR[language] |
+ lang_id = '0x' + langcharset[0:4] |
+ return lang_id |
+ else : |
+ print 'Warning:GetLangIdHex() found undefined language %s' %(language) |
+ return '' |
+ |
+ |
+def GetCharsetIdDecimal(language) : |
+ if _LANGUAGE_CHARSET_PAIR.has_key(language) : |
+ langcharset = _LANGUAGE_CHARSET_PAIR[language] |
+ charset_decimal = int(langcharset[4:], 16) |
+ return str(charset_decimal) |
+ else : |
+ print 'Warning:GetCharsetIdDecimal() found undefined language %s' %(language) |
+ return '' |
+ |
+ |
+def GetUnifiedLangCode(language) : |
+ r = re.compile('([a-z]{1,2})_([a-z]{1,2})') |
+ if r.match(language) : |
+ underscore = language.find('_') |
+ return language[0:underscore] + '-' + language[underscore + 1:].upper() |
+ else : |
+ return language |
+ |
+ |
+def _MakeRelativePath(base_path, path_to_make_relative): |
+ '''Returns a relative path such from the base_path to |
+ the path_to_make_relative. |
+ |
+ In other words, os.join(base_path, |
+ MakeRelativePath(base_path, path_to_make_relative)) |
+ is the same location as path_to_make_relative. |
+ |
+ Args: |
+ base_path: the root path |
+ path_to_make_relative: an absolute path that is on the same drive |
+ as base_path |
+ ''' |
+ |
+ def _GetPathAfterPrefix(prefix_path, path_with_prefix): |
+ '''Gets the subpath within in prefix_path for the path_with_prefix |
+ with no beginning or trailing path separators. |
+ |
+ Args: |
+ prefix_path: the base path |
+ path_with_prefix: a path that starts with prefix_path |
+ ''' |
+ assert path_with_prefix.startswith(prefix_path) |
+ path_without_prefix = path_with_prefix[len(prefix_path):] |
+ normalized_path = os.path.normpath(path_without_prefix.strip(os.path.sep)) |
+ if normalized_path == '.': |
+ normalized_path = '' |
+ return normalized_path |
+ |
+ def _GetCommonBaseDirectory(*args): |
+ '''Returns the common prefix directory for the given paths |
+ |
+ Args: |
+ The list of paths (at least one of which should be a directory) |
+ ''' |
+ prefix = os.path.commonprefix(args) |
+ # prefix is a character-by-character prefix (i.e. it does not end |
+ # on a directory bound, so this code fixes that) |
+ |
+ # if the prefix ends with the separator, then it is prefect. |
+ if len(prefix) > 0 and prefix[-1] == os.path.sep: |
+ return prefix |
+ |
+ # We need to loop through all paths or else we can get |
+ # tripped up by "c:\a" and "c:\abc". The common prefix |
+ # is "c:\a" which is a directory and looks good with |
+ # respect to the first directory but it is clear that |
+ # isn't a common directory when the second path is |
+ # examined. |
+ for path in args: |
+ assert len(path) >= len(prefix) |
+ # If the prefix the same length as the path, |
+ # then the prefix must be a directory (since one |
+ # of the arguements should be a directory). |
+ if path == prefix: |
+ continue |
+ # if the character after the prefix in the path |
+ # is the separator, then the prefix appears to be a |
+ # valid a directory as well for the given path |
+ if path[len(prefix)] == os.path.sep: |
+ continue |
+ # Otherwise, the prefix is not a directory, so it needs |
+ # to be shortened to be one |
+ index_sep = prefix.rfind(os.path.sep) |
+ # The use "index_sep + 1" because it includes the final sep |
+ # and it handles the case when the index_sep is -1 as well |
+ prefix = prefix[:index_sep + 1] |
+ # At this point we backed up to a directory bound which is |
+ # common to all paths, so we can quit going through all of |
+ # the paths. |
+ break |
+ return prefix |
+ |
+ prefix = _GetCommonBaseDirectory(base_path, path_to_make_relative) |
+ # If the paths had no commonality at all, then return the absolute path |
+ # because it is the best that can be done. If the path had to be relative |
+ # then eventually this absolute path will be discovered (when a build breaks) |
+ # and an appropriate fix can be made, but having this allows for the best |
+ # backward compatibility with the absolute path behavior in the past. |
+ if len(prefix) <= 0: |
+ return path_to_make_relative |
+ # Build a path from the base dir to the common prefix |
+ remaining_base_path = _GetPathAfterPrefix(prefix, base_path) |
+ |
+ # The follow handles two case: "" and "foo\\bar" |
+ path_pieces = remaining_base_path.split(os.path.sep) |
+ base_depth_from_prefix = len([d for d in path_pieces if len(d)]) |
+ base_to_prefix = (".." + os.path.sep) * base_depth_from_prefix |
+ |
+ # Put add in the path from the prefix to the path_to_make_relative |
+ remaining_other_path = _GetPathAfterPrefix(prefix, path_to_make_relative) |
+ return base_to_prefix + remaining_other_path |
+ |
+ |
+class TopLevel(interface.ItemFormatter): |
+ '''Writes out the required preamble for RC files.''' |
+ def Format(self, item, lang='en', begin_item=True, output_dir='.'): |
+ assert isinstance(lang, types.StringTypes) |
+ if not begin_item: |
+ return '' |
+ else: |
+ # Find the location of the resource header file, so that we can include |
+ # it. |
+ resource_header = 'resource.h' # fall back to this |
+ language_directive = '' |
+ for output in item.GetRoot().GetOutputFiles(): |
+ if output.attrs['type'] == 'rc_header': |
+ resource_header = os.path.abspath(output.GetOutputFilename()) |
+ resource_header = _MakeRelativePath(output_dir, resource_header) |
+ if output.attrs['lang'] != lang: |
+ continue |
+ if output.attrs['language_section'] == '': |
+ # If no language_section is requested, no directive is added |
+ # (Used when the generated rc will be included from another rc |
+ # file that will have the appropriate language directive) |
+ language_directive = '' |
+ elif output.attrs['language_section'] == 'neutral': |
+ # If a neutral language section is requested (default), add a |
+ # neutral language directive |
+ language_directive = 'LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL' |
+ elif output.attrs['language_section'] == 'lang': |
+ language_directive = 'LANGUAGE %s' % GetLangDirectivePair(lang) |
+ resource_header = resource_header.replace('\\', '\\\\') |
+ return '''// Copyright (c) Google Inc. %d |
+// All rights reserved. |
+// This file is automatically generated by GRIT. Do not edit. |
+ |
+#include "%s" |
+#include <winresrc.h> |
+#ifdef IDC_STATIC |
+#undef IDC_STATIC |
+#endif |
+#define IDC_STATIC (-1) |
+ |
+%s |
+ |
+ |
+''' % (util.GetCurrentYear(), resource_header, language_directive) |
+# end Format() function |
+ |
+ |
+ |
+class StringTable(interface.ItemFormatter): |
+ '''Surrounds a collection of string messages with the required begin and |
+ end blocks to declare a string table.''' |
+ |
+ def Format(self, item, lang='en', begin_item=True, output_dir='.'): |
+ assert isinstance(lang, types.StringTypes) |
+ if begin_item: |
+ return 'STRINGTABLE\nBEGIN\n' |
+ else: |
+ return 'END\n\n' |
+ |
+ |
+class Message(interface.ItemFormatter): |
+ '''Writes out a single message to a string table.''' |
+ |
+ def Format(self, item, lang='en', begin_item=True, output_dir='.'): |
+ from grit.node import message |
+ if not begin_item: |
+ return '' |
+ |
+ assert isinstance(lang, types.StringTypes) |
+ assert isinstance(item, message.MessageNode) |
+ |
+ message = item.ws_at_start + item.Translate(lang) + item.ws_at_end |
+ # Escape quotation marks (RC format uses doubling-up |
+ message = message.replace('"', '""') |
+ # Replace linebreaks with a \n escape |
+ message = _LINEBREAKS.sub(r'\\n', message) |
+ |
+ name_attr = item.GetTextualIds()[0] |
+ |
+ return ' %-15s "%s"\n' % (name_attr, message) |
+ |
+ |
+class RcSection(interface.ItemFormatter): |
+ '''Writes out an .rc file section.''' |
+ |
+ def Format(self, item, lang='en', begin_item=True, output_dir='.'): |
+ if not begin_item: |
+ return '' |
+ |
+ assert isinstance(lang, types.StringTypes) |
+ from grit.node import structure |
+ assert isinstance(item, structure.StructureNode) |
+ |
+ if item.IsExcludedFromRc(): |
+ return '' |
+ else: |
+ text = item.gatherer.Translate( |
+ lang, skeleton_gatherer=item.GetSkeletonGatherer(), |
+ pseudo_if_not_available=item.PseudoIsAllowed(), |
+ fallback_to_english=item.ShouldFallbackToEnglish()) + '\n\n' |
+ |
+ # Replace the language expand_variables in version rc info. |
+ unified_lang_code = GetUnifiedLangCode(lang) |
+ if text.find('[GRITVERLANGCHARSETHEX]') != -1: |
+ text = text.replace('[GRITVERLANGCHARSETHEX]', |
+ GetLangCharsetPair(unified_lang_code)) |
+ if text.find('[GRITVERLANGID]') != -1: |
+ text = text.replace('[GRITVERLANGID]', GetLangIdHex(unified_lang_code)) |
+ if text.find('[GRITVERCHARSETID]') != -1: |
+ text = text.replace('[GRITVERCHARSETID]', |
+ GetCharsetIdDecimal(unified_lang_code)) |
+ |
+ return text |
+ |
+ |
+class RcInclude(interface.ItemFormatter): |
+ '''Writes out an item that is included in an .rc file (e.g. an ICON)''' |
+ |
+ def __init__(self, type, filenameWithoutPath = 0, relative_path = 0, |
+ flatten_html = 0): |
+ '''Indicates to the instance what the type of the resource include is, |
+ e.g. 'ICON' or 'HTML'. Case must be correct, i.e. if the type is all-caps |
+ the parameter should be all-caps. |
+ |
+ Args: |
+ type: 'ICON' |
+ ''' |
+ self.type_ = type |
+ self.filenameWithoutPath = filenameWithoutPath |
+ self.relative_path_ = relative_path |
+ self.flatten_html = flatten_html |
+ |
+ def Format(self, item, lang='en', begin_item=True, output_dir='.'): |
+ if not begin_item: |
+ return '' |
+ |
+ assert isinstance(lang, types.StringTypes) |
+ from grit.node import structure |
+ from grit.node import include |
+ assert isinstance(item, (structure.StructureNode, include.IncludeNode)) |
+ assert (isinstance(item, include.IncludeNode) or |
+ item.attrs['type'] in ['tr_html', 'admin_template', 'txt', 'muppet']) |
+ |
+ # By default, we use relative pathnames to included resources so that |
+ # sharing the resulting .rc files is possible. |
+ # |
+ # The FileForLanguage() Function has the side effect of generating the file |
+ # if needed (e.g. if it is an HTML file include). |
+ filename = os.path.abspath(item.FileForLanguage(lang, output_dir)) |
+ if self.flatten_html: |
+ filename = item.Flatten(output_dir) |
+ elif self.filenameWithoutPath: |
+ filename = os.path.basename(filename) |
+ elif self.relative_path_: |
+ filename = _MakeRelativePath(output_dir, filename) |
+ |
+ filename = filename.replace('\\', '\\\\') # escape for the RC format |
+ |
+ if isinstance(item, structure.StructureNode) and item.IsExcludedFromRc(): |
+ return '' |
+ else: |
+ return '%-18s %-18s "%s"\n' % (item.attrs['name'], self.type_, filename) |
+ |
Property changes on: grit/format/rc.py |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |