Index: grit/extern/tclib.py |
=================================================================== |
--- grit/extern/tclib.py (revision 0) |
+++ grit/extern/tclib.py (revision 0) |
@@ -0,0 +1,503 @@ |
+#!/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. |
+ |
+# The tclib module contains tools for aggregating, verifying, and storing |
+# messages destined for the Translation Console, as well as for reading |
+# translations back and outputting them in some desired format. |
+# |
+# This has been stripped down to include only the functionality needed by grit |
+# for creating Windows .rc and .h files. These are the only parts needed by |
+# the Chrome build process. |
+ |
+import exceptions |
+ |
+from grit.extern import FP |
+ |
+# This module assumes that within a bundle no two messages can have the |
+# same id unless they're identical. |
+ |
+# The basic classes defined here for external use are Message and Translation, |
+# where the former is used for English messages and the latter for |
+# translations. These classes have a lot of common functionality, as expressed |
+# by the common parent class BaseMessage. Perhaps the most important |
+# distinction is that translated text is stored in UTF-8, whereas original text |
+# is stored in whatever encoding the client uses (presumably Latin-1). |
+ |
+# -------------------- |
+# The public interface |
+# -------------------- |
+ |
+# Generate message id from message text and meaning string (optional), |
+# both in utf-8 encoding |
+# |
+def GenerateMessageId(message, meaning=''): |
+ fp = FP.FingerPrint(message) |
+ if meaning: |
+ # combine the fingerprints of message and meaning |
+ fp2 = FP.FingerPrint(meaning) |
+ if fp < 0: |
+ fp = fp2 + (fp << 1) + 1 |
+ else: |
+ fp = fp2 + (fp << 1) |
+ # To avoid negative ids we strip the high-order bit |
+ return str(fp & 0x7fffffffffffffffL) |
+ |
+# ------------------------------------------------------------------------- |
+# The MessageTranslationError class is used to signal tclib-specific errors. |
+ |
+class MessageTranslationError(exceptions.Exception): |
+ def __init__(self, args = ''): |
+ self.args = args |
+ |
+ |
+# ----------------------------------------------------------- |
+# The Placeholder class represents a placeholder in a message. |
+ |
+class Placeholder(object): |
+ # String representation |
+ def __str__(self): |
+ return '%s, "%s", "%s"' % \ |
+ (self.__presentation, self.__original, self.__example) |
+ |
+ # Getters |
+ def GetOriginal(self): |
+ return self.__original |
+ |
+ def GetPresentation(self): |
+ return self.__presentation |
+ |
+ def GetExample(self): |
+ return self.__example |
+ |
+ def __eq__(self, other): |
+ return self.EqualTo(other, strict=1, ignore_trailing_spaces=0) |
+ |
+ # Equality test |
+ # |
+ # ignore_trailing_spaces: TC is using varchar to store the |
+ # phrwr fields, as a result of that, the trailing spaces |
+ # are removed by MySQL when the strings are stored into TC:-( |
+ # ignore_trailing_spaces parameter is used to ignore |
+ # trailing spaces during equivalence comparison. |
+ # |
+ def EqualTo(self, other, strict = 1, ignore_trailing_spaces = 1): |
+ if type(other) is not Placeholder: |
+ return 0 |
+ if StringEquals(self.__presentation, other.__presentation, |
+ ignore_trailing_spaces): |
+ if not strict or (StringEquals(self.__original, other.__original, |
+ ignore_trailing_spaces) and |
+ StringEquals(self.__example, other.__example, |
+ ignore_trailing_spaces)): |
+ return 1 |
+ return 0 |
+ |
+ |
+# ----------------------------------------------------------------- |
+# BaseMessage is the common parent class of Message and Translation. |
+# It is not meant for direct use. |
+ |
+class BaseMessage(object): |
+ # Three types of message construction is supported. If the message text is a |
+ # simple string with no dynamic content, you can pass it to the constructor |
+ # as the "text" parameter. Otherwise, you can omit "text" and assemble the |
+ # message step by step using AppendText() and AppendPlaceholder(). Or, as an |
+ # alternative, you can give the constructor the "presentable" version of the |
+ # message and a list of placeholders; it will then parse the presentation and |
+ # build the message accordingly. For example: |
+ # Message(text = "There are NUM_BUGS bugs in your code", |
+ # placeholders = [Placeholder("NUM_BUGS", "%d", "33")], |
+ # description = "Bla bla bla") |
+ def __eq__(self, other): |
+ # "source encoding" is nonsense, so ignore it |
+ return _ObjectEquals(self, other, ['_BaseMessage__source_encoding']) |
+ |
+ def GetName(self): |
+ return self.__name |
+ |
+ def GetSourceEncoding(self): |
+ return self.__source_encoding |
+ |
+ # Append a placeholder to the message |
+ def AppendPlaceholder(self, placeholder): |
+ if not isinstance(placeholder, Placeholder): |
+ raise MessageTranslationError, ("Invalid message placeholder %s in " |
+ "message %s" % (placeholder, self.GetId())) |
+ # Are there other placeholders with the same presentation? |
+ # If so, they need to be the same. |
+ for other in self.GetPlaceholders(): |
+ if placeholder.GetPresentation() == other.GetPresentation(): |
+ if not placeholder.EqualTo(other): |
+ raise MessageTranslationError, \ |
+ "Conflicting declarations of %s within message" % \ |
+ placeholder.GetPresentation() |
+ # update placeholder list |
+ dup = 0 |
+ for item in self.__content: |
+ if isinstance(item, Placeholder) and placeholder.EqualTo(item): |
+ dup = 1 |
+ break |
+ if not dup: |
+ self.__placeholders.append(placeholder) |
+ |
+ # update content |
+ self.__content.append(placeholder) |
+ |
+ # Strips leading and trailing whitespace, and returns a tuple |
+ # containing the leading and trailing space that was removed. |
+ def Strip(self): |
+ leading = trailing = '' |
+ if len(self.__content) > 0: |
+ s0 = self.__content[0] |
+ if not isinstance(s0, Placeholder): |
+ s = s0.lstrip() |
+ leading = s0[:-len(s)] |
+ self.__content[0] = s |
+ |
+ s0 = self.__content[-1] |
+ if not isinstance(s0, Placeholder): |
+ s = s0.rstrip() |
+ trailing = s0[len(s):] |
+ self.__content[-1] = s |
+ return leading, trailing |
+ |
+ # Return the id of this message |
+ def GetId(self): |
+ if self.__id is None: |
+ return self.GenerateId() |
+ return self.__id |
+ |
+ # Set the id of this message |
+ def SetId(self, id): |
+ if id is None: |
+ self.__id = None |
+ else: |
+ self.__id = str(id) # Treat numerical ids as strings |
+ |
+ # Return content of this message as a list (internal use only) |
+ def GetContent(self): |
+ return self.__content |
+ |
+ # Return a human-readable version of this message |
+ def GetPresentableContent(self): |
+ presentable_content = "" |
+ for item in self.__content: |
+ if isinstance(item, Placeholder): |
+ presentable_content += item.GetPresentation() |
+ else: |
+ presentable_content += item |
+ |
+ return presentable_content |
+ |
+ # Return a fragment of a message in escaped format |
+ def EscapeFragment(self, fragment): |
+ return fragment.replace('%', '%%') |
+ |
+ # Return the "original" version of this message, doing %-escaping |
+ # properly. If source_msg is specified, the placeholder original |
+ # information inside source_msg will be used instead. |
+ def GetOriginalContent(self, source_msg = None): |
+ original_content = "" |
+ for item in self.__content: |
+ if isinstance(item, Placeholder): |
+ if source_msg: |
+ ph = source_msg.GetPlaceholder(item.GetPresentation()) |
+ if not ph: |
+ raise MessageTranslationError, \ |
+ "Placeholder %s doesn't exist in message: %s" % \ |
+ (item.GetPresentation(), source_msg); |
+ original_content += ph.GetOriginal() |
+ else: |
+ original_content += item.GetOriginal() |
+ else: |
+ original_content += self.EscapeFragment(item) |
+ return original_content |
+ |
+ # Return the example of this message |
+ def GetExampleContent(self): |
+ example_content = "" |
+ for item in self.__content: |
+ if isinstance(item, Placeholder): |
+ example_content += item.GetExample() |
+ else: |
+ example_content += item |
+ return example_content |
+ |
+ # Return a list of all unique placeholders in this message |
+ def GetPlaceholders(self): |
+ return self.__placeholders |
+ |
+ # Return a placeholder in this message |
+ def GetPlaceholder(self, presentation): |
+ for item in self.__content: |
+ if (isinstance(item, Placeholder) and |
+ item.GetPresentation() == presentation): |
+ return item |
+ return None |
+ |
+ # Return this message's description |
+ def GetDescription(self): |
+ return self.__description |
+ |
+ # Add a message source |
+ def AddSource(self, source): |
+ self.__sources.append(source) |
+ |
+ # Return this message's sources as a list |
+ def GetSources(self): |
+ return self.__sources |
+ |
+ # Return this message's sources as a string |
+ def GetSourcesAsText(self, delimiter = "; "): |
+ return delimiter.join(self.__sources) |
+ |
+ # Set the obsolete flag for a message (internal use only) |
+ def SetObsolete(self): |
+ self.__obsolete = 1 |
+ |
+ # Get the obsolete flag for a message (internal use only) |
+ def IsObsolete(self): |
+ return self.__obsolete |
+ |
+ # Get the sequence number (0 by default) |
+ def GetSequenceNumber(self): |
+ return self.__sequence_number |
+ |
+ # Set the sequence number |
+ def SetSequenceNumber(self, number): |
+ self.__sequence_number = number |
+ |
+ # Increment instance counter |
+ def AddInstance(self): |
+ self.__num_instances += 1 |
+ |
+ # Return instance count |
+ def GetNumInstances(self): |
+ return self.__num_instances |
+ |
+ def GetErrors(self, from_tc=0): |
+ """ |
+ Returns a description of the problem if the message is not |
+ syntactically valid, or None if everything is fine. |
+ |
+ Args: |
+ from_tc: indicates whether this message came from the TC. We let |
+ the TC get away with some things we normally wouldn't allow for |
+ historical reasons. |
+ """ |
+ # check that placeholders are unambiguous |
+ pos = 0 |
+ phs = {} |
+ for item in self.__content: |
+ if isinstance(item, Placeholder): |
+ phs[pos] = item |
+ pos += len(item.GetPresentation()) |
+ else: |
+ pos += len(item) |
+ presentation = self.GetPresentableContent() |
+ for ph in self.GetPlaceholders(): |
+ for pos in FindOverlapping(presentation, ph.GetPresentation()): |
+ # message contains the same text as a placeholder presentation |
+ other_ph = phs.get(pos) |
+ if ((not other_ph |
+ and not IsSubstringInPlaceholder(pos, len(ph.GetPresentation()), phs)) |
+ or |
+ (other_ph and len(other_ph.GetPresentation()) < len(ph.GetPresentation()))): |
+ return "message contains placeholder name '%s':\n%s" % ( |
+ ph.GetPresentation(), presentation) |
+ return None |
+ |
+ |
+ def __CopyTo(self, other): |
+ """ |
+ Returns a copy of this BaseMessage. |
+ """ |
+ assert isinstance(other, self.__class__) or isinstance(self, other.__class__) |
+ other.__source_encoding = self.__source_encoding |
+ other.__content = self.__content[:] |
+ other.__description = self.__description |
+ other.__id = self.__id |
+ other.__num_instances = self.__num_instances |
+ other.__obsolete = self.__obsolete |
+ other.__name = self.__name |
+ other.__placeholders = self.__placeholders[:] |
+ other.__sequence_number = self.__sequence_number |
+ other.__sources = self.__sources[:] |
+ |
+ return other |
+ |
+ def HasText(self): |
+ """Returns true iff this message has anything other than placeholders.""" |
+ for item in self.__content: |
+ if not isinstance(item, Placeholder): |
+ return True |
+ return False |
+ |
+# -------------------------------------------------------- |
+# The Message class represents original (English) messages |
+ |
+class Message(BaseMessage): |
+ # See BaseMessage constructor |
+ def __init__(self, source_encoding, text=None, id=None, |
+ description=None, meaning="", placeholders=None, |
+ source=None, sequence_number=0, clone_from=None, |
+ time_created=0, name=None, is_hidden = 0): |
+ |
+ if clone_from is not None: |
+ BaseMessage.__init__(self, None, clone_from=clone_from) |
+ self.__meaning = clone_from.__meaning |
+ self.__time_created = clone_from.__time_created |
+ self.__is_hidden = clone_from.__is_hidden |
+ return |
+ |
+ BaseMessage.__init__(self, source_encoding, text, id, description, |
+ placeholders, source, sequence_number, |
+ name=name) |
+ self.__meaning = meaning |
+ self.__time_created = time_created |
+ self.SetIsHidden(is_hidden) |
+ |
+ # String representation |
+ def __str__(self): |
+ s = 'source: %s, id: %s, content: "%s", meaning: "%s", ' \ |
+ 'description: "%s"' % \ |
+ (self.GetSourcesAsText(), self.GetId(), self.GetPresentableContent(), |
+ self.__meaning, self.GetDescription()) |
+ if self.GetName() is not None: |
+ s += ', name: "%s"' % self.GetName() |
+ placeholders = self.GetPlaceholders() |
+ for i in range(len(placeholders)): |
+ s += ", placeholder[%d]: %s" % (i, placeholders[i]) |
+ return s |
+ |
+ # Strips leading and trailing whitespace, and returns a tuple |
+ # containing the leading and trailing space that was removed. |
+ def Strip(self): |
+ leading = trailing = '' |
+ content = self.GetContent() |
+ if len(content) > 0: |
+ s0 = content[0] |
+ if not isinstance(s0, Placeholder): |
+ s = s0.lstrip() |
+ leading = s0[:-len(s)] |
+ content[0] = s |
+ |
+ s0 = content[-1] |
+ if not isinstance(s0, Placeholder): |
+ s = s0.rstrip() |
+ trailing = s0[len(s):] |
+ content[-1] = s |
+ return leading, trailing |
+ |
+ # Generate an id by hashing message content |
+ def GenerateId(self): |
+ self.SetId(GenerateMessageId(self.GetPresentableContent(), |
+ self.__meaning)) |
+ return self.GetId() |
+ |
+ def GetMeaning(self): |
+ return self.__meaning |
+ |
+ def GetTimeCreated(self): |
+ return self.__time_created |
+ |
+ # Equality operator |
+ def EqualTo(self, other, strict = 1): |
+ # Check id, meaning, content |
+ if self.GetId() != other.GetId(): |
+ return 0 |
+ if self.__meaning != other.__meaning: |
+ return 0 |
+ if self.GetPresentableContent() != other.GetPresentableContent(): |
+ return 0 |
+ # Check descriptions if comparison is strict |
+ if (strict and |
+ self.GetDescription() is not None and |
+ other.GetDescription() is not None and |
+ self.GetDescription() != other.GetDescription()): |
+ return 0 |
+ # Check placeholders |
+ ph1 = self.GetPlaceholders() |
+ ph2 = other.GetPlaceholders() |
+ if len(ph1) != len(ph2): |
+ return 0 |
+ for i in range(len(ph1)): |
+ if not ph1[i].EqualTo(ph2[i], strict): |
+ return 0 |
+ |
+ return 1 |
+ |
+ def Copy(self): |
+ """ |
+ Returns a copy of this Message. |
+ """ |
+ assert isinstance(self, Message) |
+ return Message(None, clone_from=self) |
+ |
+ def SetIsHidden(self, is_hidden): |
+ """Sets whether this message should be hidden. |
+ |
+ Args: |
+ is_hidden : 0 or 1 - if the message should be hidden, 0 otherwise |
+ """ |
+ if is_hidden not in [0, 1]: |
+ raise MessageTranslationError, "is_hidden must be 0 or 1, got %s" |
+ self.__is_hidden = is_hidden |
+ |
+ def IsHidden(self): |
+ """Returns 1 if this message is hidden, and 0 otherwise.""" |
+ return self.__is_hidden |
+ |
+# ---------------------------------------------------- |
+# The Translation class represents translated messages |
+ |
+class Translation(BaseMessage): |
+ # See BaseMessage constructor |
+ def __init__(self, source_encoding, text=None, id=None, |
+ description=None, placeholders=None, source=None, |
+ sequence_number=0, clone_from=None, ignore_ph_errors=0, |
+ name=None): |
+ if clone_from is not None: |
+ BaseMessage.__init__(self, None, clone_from=clone_from) |
+ return |
+ |
+ BaseMessage.__init__(self, source_encoding, text, id, description, |
+ placeholders, source, sequence_number, |
+ ignore_ph_errors=ignore_ph_errors, name=name) |
+ |
+ # String representation |
+ def __str__(self): |
+ s = 'source: %s, id: %s, content: "%s", description: "%s"' % \ |
+ (self.GetSourcesAsText(), self.GetId(), self.GetPresentableContent(), |
+ self.GetDescription()); |
+ placeholders = self.GetPlaceholders() |
+ for i in range(len(placeholders)): |
+ s += ", placeholder[%d]: %s" % (i, placeholders[i]) |
+ return s |
+ |
+ # Equality operator |
+ def EqualTo(self, other, strict=1): |
+ # Check id and content |
+ if self.GetId() != other.GetId(): |
+ return 0 |
+ if self.GetPresentableContent() != other.GetPresentableContent(): |
+ return 0 |
+ # Check placeholders |
+ ph1 = self.GetPlaceholders() |
+ ph2 = other.GetPlaceholders() |
+ if len(ph1) != len(ph2): |
+ return 0 |
+ for i in range(len(ph1)): |
+ if not ph1[i].EqualTo(ph2[i], strict): |
+ return 0 |
+ |
+ return 1 |
+ |
+ def Copy(self): |
+ """ |
+ Returns a copy of this Translation. |
+ """ |
+ return Translation(None, clone_from=self) |
+ |
Property changes on: grit/extern/tclib.py |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |