| Index: grit/gather/rc.py
|
| ===================================================================
|
| --- grit/gather/rc.py (revision 202)
|
| +++ grit/gather/rc.py (working copy)
|
| @@ -1,343 +0,0 @@
|
| -#!/usr/bin/env python
|
| -# Copyright (c) 2012 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 gathering resources from RC files.
|
| -'''
|
| -
|
| -
|
| -import re
|
| -
|
| -from grit import exception
|
| -from grit import lazy_re
|
| -from grit import tclib
|
| -
|
| -from grit.gather import regexp
|
| -
|
| -
|
| -# Find portions that need unescaping in resource strings. We need to be
|
| -# careful that a \\n is matched _first_ as a \\ rather than matching as
|
| -# a \ followed by a \n.
|
| -# TODO(joi) Handle ampersands if we decide to change them into <ph>
|
| -# TODO(joi) May need to handle other control characters than \n
|
| -_NEED_UNESCAPE = lazy_re.compile(r'""|\\\\|\\n|\\t')
|
| -
|
| -# Find portions that need escaping to encode string as a resource string.
|
| -_NEED_ESCAPE = lazy_re.compile(r'"|\n|\t|\\|\ \;')
|
| -
|
| -# How to escape certain characters
|
| -_ESCAPE_CHARS = {
|
| - '"' : '""',
|
| - '\n' : '\\n',
|
| - '\t' : '\\t',
|
| - '\\' : '\\\\',
|
| - ' ' : ' '
|
| -}
|
| -
|
| -# How to unescape certain strings
|
| -_UNESCAPE_CHARS = dict([[value, key] for key, value in _ESCAPE_CHARS.items()])
|
| -
|
| -
|
| -
|
| -class Section(regexp.RegexpGatherer):
|
| - '''A section from a resource file.'''
|
| -
|
| - @staticmethod
|
| - def Escape(text):
|
| - '''Returns a version of 'text' with characters escaped that need to be
|
| - for inclusion in a resource section.'''
|
| - def Replace(match):
|
| - return _ESCAPE_CHARS[match.group()]
|
| - return _NEED_ESCAPE.sub(Replace, text)
|
| -
|
| - @staticmethod
|
| - def UnEscape(text):
|
| - '''Returns a version of 'text' with escaped characters unescaped.'''
|
| - def Replace(match):
|
| - return _UNESCAPE_CHARS[match.group()]
|
| - return _NEED_UNESCAPE.sub(Replace, text)
|
| -
|
| - def _RegExpParse(self, rexp, text_to_parse):
|
| - '''Overrides _RegExpParse to add shortcut group handling. Otherwise
|
| - the same.
|
| - '''
|
| - super(Section, self)._RegExpParse(rexp, text_to_parse)
|
| -
|
| - if not self.is_skeleton and len(self.GetTextualIds()) > 0:
|
| - group_name = self.GetTextualIds()[0]
|
| - for c in self.GetCliques():
|
| - c.AddToShortcutGroup(group_name)
|
| -
|
| - def ReadSection(self):
|
| - rc_text = self._LoadInputFile()
|
| -
|
| - out = ''
|
| - begin_count = 0
|
| - assert self.extkey
|
| - first_line_re = re.compile(r'\s*' + self.extkey + r'\b')
|
| - for line in rc_text.splitlines(True):
|
| - if out or first_line_re.match(line):
|
| - out += line
|
| -
|
| - # we stop once we reach the END for the outermost block.
|
| - begin_count_was = begin_count
|
| - if len(out) > 0 and line.strip() == 'BEGIN':
|
| - begin_count += 1
|
| - elif len(out) > 0 and line.strip() == 'END':
|
| - begin_count -= 1
|
| - if begin_count_was == 1 and begin_count == 0:
|
| - break
|
| -
|
| - if len(out) == 0:
|
| - raise exception.SectionNotFound('%s in file %s' % (self.extkey, self.rc_file))
|
| -
|
| - self.text_ = out.strip()
|
| -
|
| -
|
| -class Dialog(Section):
|
| - '''A resource section that contains a dialog resource.'''
|
| -
|
| - # A typical dialog resource section looks like this:
|
| - #
|
| - # IDD_ABOUTBOX DIALOGEX 22, 17, 230, 75
|
| - # STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
|
| - # CAPTION "About"
|
| - # FONT 8, "System", 0, 0, 0x0
|
| - # BEGIN
|
| - # ICON IDI_KLONK,IDC_MYICON,14,9,20,20
|
| - # LTEXT "klonk Version ""yibbee"" 1.0",IDC_STATIC,49,10,119,8,
|
| - # SS_NOPREFIX
|
| - # LTEXT "Copyright (C) 2005",IDC_STATIC,49,20,119,8
|
| - # DEFPUSHBUTTON "OK",IDOK,195,6,30,11,WS_GROUP
|
| - # CONTROL "Jack ""Black"" Daniels",IDC_RADIO1,"Button",
|
| - # BS_AUTORADIOBUTTON,46,51,84,10
|
| - # END
|
| -
|
| - # We are using a sorted set of keys, and we assume that the
|
| - # group name used for descriptions (type) will come after the "text"
|
| - # group in alphabetical order. We also assume that there cannot be
|
| - # more than one description per regular expression match.
|
| - # If that's not the case some descriptions will be clobbered.
|
| - dialog_re_ = lazy_re.compile('''
|
| - # The dialog's ID in the first line
|
| - (?P<id1>[A-Z0-9_]+)\s+DIALOG(EX)?
|
| - |
|
| - # The caption of the dialog
|
| - (?P<type1>CAPTION)\s+"(?P<text1>.*?([^"]|""))"\s
|
| - |
|
| - # Lines for controls that have text and an ID
|
| - \s+(?P<type2>[A-Z]+)\s+"(?P<text2>.*?([^"]|"")?)"\s*,\s*(?P<id2>[A-Z0-9_]+)\s*,
|
| - |
|
| - # Lines for controls that have text only
|
| - \s+(?P<type3>[A-Z]+)\s+"(?P<text3>.*?([^"]|"")?)"\s*,
|
| - |
|
| - # Lines for controls that reference other resources
|
| - \s+[A-Z]+\s+[A-Z0-9_]+\s*,\s*(?P<id3>[A-Z0-9_]*[A-Z][A-Z0-9_]*)
|
| - |
|
| - # This matches "NOT SOME_STYLE" so that it gets consumed and doesn't get
|
| - # matched by the next option (controls that have only an ID and then just
|
| - # numbers)
|
| - \s+NOT\s+[A-Z][A-Z0-9_]+
|
| - |
|
| - # Lines for controls that have only an ID and then just numbers
|
| - \s+[A-Z]+\s+(?P<id4>[A-Z0-9_]*[A-Z][A-Z0-9_]*)\s*,
|
| - ''', re.MULTILINE | re.VERBOSE)
|
| -
|
| - def Parse(self):
|
| - '''Knows how to parse dialog resource sections.'''
|
| - self.ReadSection()
|
| - self._RegExpParse(self.dialog_re_, self.text_)
|
| -
|
| -
|
| -class Menu(Section):
|
| - '''A resource section that contains a menu resource.'''
|
| -
|
| - # A typical menu resource section looks something like this:
|
| - #
|
| - # IDC_KLONK MENU
|
| - # BEGIN
|
| - # POPUP "&File"
|
| - # BEGIN
|
| - # MENUITEM "E&xit", IDM_EXIT
|
| - # MENUITEM "This be ""Klonk"" me like", ID_FILE_THISBE
|
| - # POPUP "gonk"
|
| - # BEGIN
|
| - # MENUITEM "Klonk && is ""good""", ID_GONK_KLONKIS
|
| - # END
|
| - # END
|
| - # POPUP "&Help"
|
| - # BEGIN
|
| - # MENUITEM "&About ...", IDM_ABOUT
|
| - # END
|
| - # END
|
| -
|
| - # Description used for the messages generated for menus, to explain to
|
| - # the translators how to handle them.
|
| - MENU_MESSAGE_DESCRIPTION = (
|
| - 'This message represents a menu. Each of the items appears in sequence '
|
| - '(some possibly within sub-menus) in the menu. The XX01XX placeholders '
|
| - 'serve to separate items. Each item contains an & (ampersand) character '
|
| - 'in front of the keystroke that should be used as a shortcut for that item '
|
| - 'in the menu. Please make sure that no two items in the same menu share '
|
| - 'the same shortcut.'
|
| - )
|
| -
|
| - # A dandy regexp to suck all the IDs and translateables out of a menu
|
| - # resource
|
| - menu_re_ = lazy_re.compile('''
|
| - # Match the MENU ID on the first line
|
| - ^(?P<id1>[A-Z0-9_]+)\s+MENU
|
| - |
|
| - # Match the translateable caption for a popup menu
|
| - POPUP\s+"(?P<text1>.*?([^"]|""))"\s
|
| - |
|
| - # Match the caption & ID of a MENUITEM
|
| - MENUITEM\s+"(?P<text2>.*?([^"]|""))"\s*,\s*(?P<id2>[A-Z0-9_]+)
|
| - ''', re.MULTILINE | re.VERBOSE)
|
| -
|
| - def Parse(self):
|
| - '''Knows how to parse menu resource sections. Because it is important that
|
| - menu shortcuts are unique within the menu, we return each menu as a single
|
| - message with placeholders to break up the different menu items, rather than
|
| - return a single message per menu item. we also add an automatic description
|
| - with instructions for the translators.'''
|
| - self.ReadSection()
|
| - self.single_message_ = tclib.Message(description=self.MENU_MESSAGE_DESCRIPTION)
|
| - self._RegExpParse(self.menu_re_, self.text_)
|
| -
|
| -
|
| -class Version(Section):
|
| - '''A resource section that contains a VERSIONINFO resource.'''
|
| -
|
| - # A typical version info resource can look like this:
|
| - #
|
| - # VS_VERSION_INFO VERSIONINFO
|
| - # FILEVERSION 1,0,0,1
|
| - # PRODUCTVERSION 1,0,0,1
|
| - # FILEFLAGSMASK 0x3fL
|
| - # #ifdef _DEBUG
|
| - # FILEFLAGS 0x1L
|
| - # #else
|
| - # FILEFLAGS 0x0L
|
| - # #endif
|
| - # FILEOS 0x4L
|
| - # FILETYPE 0x2L
|
| - # FILESUBTYPE 0x0L
|
| - # BEGIN
|
| - # BLOCK "StringFileInfo"
|
| - # BEGIN
|
| - # BLOCK "040904e4"
|
| - # BEGIN
|
| - # VALUE "CompanyName", "TODO: <Company name>"
|
| - # VALUE "FileDescription", "TODO: <File description>"
|
| - # VALUE "FileVersion", "1.0.0.1"
|
| - # VALUE "LegalCopyright", "TODO: (c) <Company name>. All rights reserved."
|
| - # VALUE "InternalName", "res_format_test.dll"
|
| - # VALUE "OriginalFilename", "res_format_test.dll"
|
| - # VALUE "ProductName", "TODO: <Product name>"
|
| - # VALUE "ProductVersion", "1.0.0.1"
|
| - # END
|
| - # END
|
| - # BLOCK "VarFileInfo"
|
| - # BEGIN
|
| - # VALUE "Translation", 0x409, 1252
|
| - # END
|
| - # END
|
| - #
|
| - #
|
| - # In addition to the above fields, VALUE fields named "Comments" and
|
| - # "LegalTrademarks" may also be translateable.
|
| -
|
| - version_re_ = lazy_re.compile('''
|
| - # Match the ID on the first line
|
| - ^(?P<id1>[A-Z0-9_]+)\s+VERSIONINFO
|
| - |
|
| - # Match all potentially translateable VALUE sections
|
| - \s+VALUE\s+"
|
| - (
|
| - CompanyName|FileDescription|LegalCopyright|
|
| - ProductName|Comments|LegalTrademarks
|
| - )",\s+"(?P<text1>.*?([^"]|""))"\s
|
| - ''', re.MULTILINE | re.VERBOSE)
|
| -
|
| - def Parse(self):
|
| - '''Knows how to parse VERSIONINFO resource sections.'''
|
| - self.ReadSection()
|
| - self._RegExpParse(self.version_re_, self.text_)
|
| -
|
| - # TODO(joi) May need to override the Translate() method to change the
|
| - # "Translation" VALUE block to indicate the correct language code.
|
| -
|
| -
|
| -class RCData(Section):
|
| - '''A resource section that contains some data .'''
|
| -
|
| - # A typical rcdataresource section looks like this:
|
| - #
|
| - # IDR_BLAH RCDATA { 1, 2, 3, 4 }
|
| -
|
| - dialog_re_ = lazy_re.compile('''
|
| - ^(?P<id1>[A-Z0-9_]+)\s+RCDATA\s+(DISCARDABLE)?\s+\{.*?\}
|
| - ''', re.MULTILINE | re.VERBOSE | re.DOTALL)
|
| -
|
| - def Parse(self):
|
| - '''Implementation for resource types w/braces (not BEGIN/END)
|
| - '''
|
| - rc_text = self._LoadInputFile()
|
| -
|
| - out = ''
|
| - begin_count = 0
|
| - openbrace_count = 0
|
| - assert self.extkey
|
| - first_line_re = re.compile(r'\s*' + self.extkey + r'\b')
|
| - for line in rc_text.splitlines(True):
|
| - if out or first_line_re.match(line):
|
| - out += line
|
| -
|
| - # We stop once the braces balance (could happen in one line).
|
| - begin_count_was = begin_count
|
| - if len(out) > 0:
|
| - openbrace_count += line.count('{')
|
| - begin_count += line.count('{')
|
| - begin_count -= line.count('}')
|
| - if ((begin_count_was == 1 and begin_count == 0) or
|
| - (openbrace_count > 0 and begin_count == 0)):
|
| - break
|
| -
|
| - if len(out) == 0:
|
| - raise exception.SectionNotFound('%s in file %s' % (self.extkey, self.rc_file))
|
| -
|
| - self.text_ = out
|
| -
|
| - self._RegExpParse(self.dialog_re_, out)
|
| -
|
| -
|
| -class Accelerators(Section):
|
| - '''An ACCELERATORS table.
|
| - '''
|
| -
|
| - # A typical ACCELERATORS section looks like this:
|
| - #
|
| - # IDR_ACCELERATOR1 ACCELERATORS
|
| - # BEGIN
|
| - # "^C", ID_ACCELERATOR32770, ASCII, NOINVERT
|
| - # "^V", ID_ACCELERATOR32771, ASCII, NOINVERT
|
| - # VK_INSERT, ID_ACCELERATOR32772, VIRTKEY, CONTROL, NOINVERT
|
| - # END
|
| -
|
| - accelerators_re_ = lazy_re.compile('''
|
| - # Match the ID on the first line
|
| - ^(?P<id1>[A-Z0-9_]+)\s+ACCELERATORS\s+
|
| - |
|
| - # Match accelerators specified as VK_XXX
|
| - \s+VK_[A-Z0-9_]+,\s*(?P<id2>[A-Z0-9_]+)\s*,
|
| - |
|
| - # Match accelerators specified as e.g. "^C"
|
| - \s+"[^"]*",\s+(?P<id3>[A-Z0-9_]+)\s*,
|
| - ''', re.MULTILINE | re.VERBOSE)
|
| -
|
| - def Parse(self):
|
| - '''Knows how to parse ACCELERATORS resource sections.'''
|
| - self.ReadSection()
|
| - self._RegExpParse(self.accelerators_re_, self.text_)
|
|
|