| Index: grit/gather/rc.py
|
| ===================================================================
|
| --- grit/gather/rc.py (revision 0)
|
| +++ grit/gather/rc.py (revision 0)
|
| @@ -0,0 +1,403 @@
|
| +#!/usr/bin/python2.4
|
| +# Copyright (c) 2006-2008 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
|
| +import types
|
| +
|
| +from grit import clique
|
| +from grit import exception
|
| +from grit import util
|
| +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 = re.compile(r'""|\\\\|\\n|\\t')
|
| +
|
| +# Find portions that need escaping to encode string as a resource string.
|
| +_NEED_ESCAPE = 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.'''
|
| +
|
| + def __init__(self, section_text):
|
| + '''Creates a new object.
|
| +
|
| + Args:
|
| + section_text: 'ID_SECTION_ID SECTIONTYPE\n.....\nBEGIN\n.....\nEND'
|
| + '''
|
| + regexp.RegexpGatherer.__init__(self, section_text)
|
| +
|
| + # static method
|
| + 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)
|
| + Escape = staticmethod(Escape)
|
| +
|
| + # static method
|
| + 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)
|
| + UnEscape = staticmethod(UnEscape)
|
| +
|
| + def _RegExpParse(self, rexp, text_to_parse):
|
| + '''Overrides _RegExpParse to add shortcut group handling. Otherwise
|
| + the same.
|
| + '''
|
| + regexp.RegexpGatherer._RegExpParse(self, rexp, text_to_parse)
|
| +
|
| + if not self.IsSkeleton() and len(self.GetTextualIds()) > 0:
|
| + group_name = self.GetTextualIds()[0]
|
| + for c in self.GetCliques():
|
| + c.AddToShortcutGroup(group_name)
|
| +
|
| + # Static method
|
| + def FromFileImpl(rc_file, extkey, encoding, type):
|
| + '''Implementation of FromFile. Need to keep separate so we can have
|
| + a FromFile in this class that has its type set to Section by default.
|
| + '''
|
| + if isinstance(rc_file, types.StringTypes):
|
| + rc_file = util.WrapInputStream(file(rc_file, 'r'), encoding)
|
| +
|
| + out = ''
|
| + begin_count = 0
|
| + for line in rc_file.readlines():
|
| + if len(out) > 0 or (line.strip().startswith(extkey) and
|
| + line.strip().split()[0] == extkey):
|
| + 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' % (extkey, rc_file))
|
| +
|
| + return type(out)
|
| + FromFileImpl = staticmethod(FromFileImpl)
|
| +
|
| + # static method
|
| + def FromFile(rc_file, extkey, encoding='cp1252'):
|
| + '''Retrieves the section of 'rc_file' that has the key 'extkey'. This is
|
| + matched against the start of a line, and that line and the rest of that
|
| + section in the RC file is returned.
|
| +
|
| + If 'rc_file' is a filename, it will be opened for reading using 'encoding'.
|
| + Otherwise the 'encoding' parameter is ignored.
|
| +
|
| + This method instantiates an object of type 'type' with the text from the
|
| + file.
|
| +
|
| + Args:
|
| + rc_file: file('') | 'filename.rc'
|
| + extkey: 'ID_MY_DIALOG'
|
| + encoding: 'utf-8'
|
| + type: class to instantiate with text of section
|
| +
|
| + Return:
|
| + type(text_of_section)
|
| + '''
|
| + return Section.FromFileImpl(rc_file, extkey, encoding, Section)
|
| + FromFile = staticmethod(FromFile)
|
| +
|
| +
|
| +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_ = 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._RegExpParse(self.dialog_re_, self.text_)
|
| +
|
| + # static method
|
| + def FromFile(rc_file, extkey, encoding = 'cp1252'):
|
| + return Section.FromFileImpl(rc_file, extkey, encoding, Dialog)
|
| + FromFile = staticmethod(FromFile)
|
| +
|
| +
|
| +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_ = 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.single_message_ = tclib.Message(description=self.MENU_MESSAGE_DESCRIPTION)
|
| + self._RegExpParse(self.menu_re_, self.text_)
|
| +
|
| + # static method
|
| + def FromFile(rc_file, extkey, encoding = 'cp1252'):
|
| + return Section.FromFileImpl(rc_file, extkey, encoding, Menu)
|
| + FromFile = staticmethod(FromFile)
|
| +
|
| +
|
| +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_ = 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._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.
|
| +
|
| + # static method
|
| + def FromFile(rc_file, extkey, encoding = 'cp1252'):
|
| + return Section.FromFileImpl(rc_file, extkey, encoding, Version)
|
| + FromFile = staticmethod(FromFile)
|
| +
|
| +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_ = re.compile('''
|
| + ^(?P<id1>[A-Z0-9_]+)\s+RCDATA\s+(DISCARDABLE)?\s+\{.*?\}
|
| + ''', re.MULTILINE | re.VERBOSE | re.DOTALL)
|
| +
|
| + def Parse(self):
|
| + '''Knows how to parse RCDATA resource sections.'''
|
| + self._RegExpParse(self.dialog_re_, self.text_)
|
| +
|
| + # static method
|
| + def FromFile(rc_file, extkey, encoding = 'cp1252'):
|
| + '''Implementation of FromFile for resource types w/braces (not BEGIN/END)
|
| + '''
|
| + if isinstance(rc_file, types.StringTypes):
|
| + rc_file = util.WrapInputStream(file(rc_file, 'r'), encoding)
|
| +
|
| + out = ''
|
| + begin_count = 0
|
| + openbrace_count = 0
|
| + for line in rc_file.readlines():
|
| + if len(out) > 0 or line.strip().startswith(extkey):
|
| + out += line
|
| +
|
| + # we stop once balance the braces (could happen on 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' % (extkey, rc_file))
|
| +
|
| + return RCData(out)
|
| + FromFile = staticmethod(FromFile)
|
| +
|
| +
|
| +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_ = 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._RegExpParse(self.accelerators_re_, self.text_)
|
| +
|
| + # static method
|
| + def FromFile(rc_file, extkey, encoding = 'cp1252'):
|
| + return Section.FromFileImpl(rc_file, extkey, encoding, Accelerators)
|
| + FromFile = staticmethod(FromFile)
|
| +
|
|
|
| Property changes on: grit/gather/rc.py
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|