| Index: grit/extern/tclib.py
|
| ===================================================================
|
| --- grit/extern/tclib.py (revision 202)
|
| +++ grit/extern/tclib.py (working copy)
|
| @@ -1,503 +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.
|
| -
|
| -# 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)
|
| -
|
|
|