195640e3a20adea634b4df4ccf8c93f411184c438joi@chromium.org#!/usr/bin/env python 295640e3a20adea634b4df4ccf8c93f411184c438joi@chromium.org# Copyright (c) 2012 The Chromium Authors. All rights reserved. 301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org# Use of this source code is governed by a BSD-style license that can be 401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org# found in the LICENSE file. 501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org'''Adaptation of the extern.tclib classes for our needs. 701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org''' 801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 1001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgimport re 1101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgimport types 1201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 1301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgfrom grit import exception 1401fadb72b6e94e6511eaffd1874a8cc095f098a7joi@chromium.orgfrom grit import lazy_re 1501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgimport grit.extern.tclib 1601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 178a303dc2cabedfd395b49f21310ea21c77056fe7joi@chromium.org 188a303dc2cabedfd395b49f21310ea21c77056fe7joi@chromium.org# Matches whitespace sequences which can be folded into a single whitespace 198a303dc2cabedfd395b49f21310ea21c77056fe7joi@chromium.org# character. This matches single characters so that non-spaces are replaced 208a303dc2cabedfd395b49f21310ea21c77056fe7joi@chromium.org# with spaces. 218a303dc2cabedfd395b49f21310ea21c77056fe7joi@chromium.org_FOLD_WHITESPACE = re.compile(r'\s+') 228a303dc2cabedfd395b49f21310ea21c77056fe7joi@chromium.org 238a303dc2cabedfd395b49f21310ea21c77056fe7joi@chromium.org 2401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgdef Identity(i): 2501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org return i 2601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 2701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 2801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgclass BaseMessage(object): 2901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org '''Base class with methods shared by Message and Translation. 3001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org ''' 3101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 3201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def __init__(self, text='', placeholders=[], description='', meaning=''): 3301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.parts = [] 3401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.placeholders = [] 3501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.meaning = meaning 3601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.dirty = True # True if self.id is (or might be) wrong 3701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.id = 0 388a303dc2cabedfd395b49f21310ea21c77056fe7joi@chromium.org self.SetDescription(description) 3901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 4001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org if text != '': 4101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org if not placeholders or placeholders == []: 4201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.AppendText(text) 4301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org else: 4401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org tag_map = {} 4501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org for placeholder in placeholders: 4601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org tag_map[placeholder.GetPresentation()] = [placeholder, 0] 476f54ac22395af534c8ccfe6834eedb636f9500dbjoaodasilva@chromium.org # This creates a regexp like '(TAG1|TAG2|TAG3)'. 486f54ac22395af534c8ccfe6834eedb636f9500dbjoaodasilva@chromium.org # The tags have to be sorted in order of decreasing length, so that 496f54ac22395af534c8ccfe6834eedb636f9500dbjoaodasilva@chromium.org # longer tags are substituted before shorter tags that happen to be 506f54ac22395af534c8ccfe6834eedb636f9500dbjoaodasilva@chromium.org # substrings of the longer tag. 516f54ac22395af534c8ccfe6834eedb636f9500dbjoaodasilva@chromium.org # E.g. "EXAMPLE_FOO_NAME" must be matched before "EXAMPLE_FOO", 526f54ac22395af534c8ccfe6834eedb636f9500dbjoaodasilva@chromium.org # otherwise "EXAMPLE_FOO" splits "EXAMPLE_FOO_NAME" too. 536f54ac22395af534c8ccfe6834eedb636f9500dbjoaodasilva@chromium.org tags = tag_map.keys() 546f54ac22395af534c8ccfe6834eedb636f9500dbjoaodasilva@chromium.org tags.sort(cmp=lambda x,y: len(x) - len(y) or cmp(x, y), reverse=True) 556f54ac22395af534c8ccfe6834eedb636f9500dbjoaodasilva@chromium.org tag_re = '(' + '|'.join(tags) + ')' 5601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org chunked_text = re.split(tag_re, text) 5701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org for chunk in chunked_text: 5801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org if chunk: # ignore empty chunk 5901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org if tag_map.has_key(chunk): 6001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.AppendPlaceholder(tag_map[chunk][0]) 6101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org tag_map[chunk][1] += 1 # increase placeholder use count 6201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org else: 6301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.AppendText(chunk) 6401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org for key in tag_map.keys(): 6501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org assert tag_map[key][1] != 0 6601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 6701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def GetRealContent(self, escaping_function=Identity): 6801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org '''Returns the original content, i.e. what your application and users 6901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org will see. 7001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 7101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org Specify a function to escape each translateable bit, if you like. 7201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org ''' 7301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org bits = [] 7401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org for item in self.parts: 7501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org if isinstance(item, types.StringTypes): 7601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org bits.append(escaping_function(item)) 7701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org else: 7801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org bits.append(item.GetOriginal()) 7901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org return ''.join(bits) 8001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 8101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def GetPresentableContent(self): 8201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org presentable_content = [] 8301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org for part in self.parts: 8401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org if isinstance(part, Placeholder): 8501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org presentable_content.append(part.GetPresentation()) 8601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org else: 8701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org presentable_content.append(part) 8801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org return ''.join(presentable_content) 8901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 9001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def AppendPlaceholder(self, placeholder): 9101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org assert isinstance(placeholder, Placeholder) 9201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org dup = False 9301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org for other in self.GetPlaceholders(): 9401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org if other.presentation == placeholder.presentation: 9501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org assert other.original == placeholder.original 9601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org dup = True 9701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 9801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org if not dup: 9901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.placeholders.append(placeholder) 10001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.parts.append(placeholder) 10101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.dirty = True 10201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 10301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def AppendText(self, text): 10401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org assert isinstance(text, types.StringTypes) 10501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org assert text != '' 10601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 10701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.parts.append(text) 10801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.dirty = True 10901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 11001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def GetContent(self): 11101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org '''Returns the parts of the message. You may modify parts if you wish. 11201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org Note that you must not call GetId() on this object until you have finished 11301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org modifying the contents. 11401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org ''' 11501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.dirty = True # user might modify content 11601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org return self.parts 11701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 11801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def GetDescription(self): 11901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org return self.description 12001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 12101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def SetDescription(self, description): 1228a303dc2cabedfd395b49f21310ea21c77056fe7joi@chromium.org self.description = _FOLD_WHITESPACE.sub(' ', description) 12301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 12401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def GetMeaning(self): 12501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org return self.meaning 12601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 12701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def GetId(self): 12801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org if self.dirty: 12901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.id = self.GenerateId() 13001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.dirty = False 13101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org return self.id 13201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 13301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def GenerateId(self): 13401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org # Must use a UTF-8 encoded version of the presentable content, along with 13501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org # the meaning attribute, to match the TC. 13601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org return grit.extern.tclib.GenerateMessageId( 13701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.GetPresentableContent().encode('utf-8'), self.meaning) 13801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 13901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def GetPlaceholders(self): 14001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org return self.placeholders 14101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 14201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def FillTclibBaseMessage(self, msg): 14301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org msg.SetDescription(self.description.encode('utf-8')) 14401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 14501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org for part in self.parts: 14601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org if isinstance(part, Placeholder): 14701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org ph = grit.extern.tclib.Placeholder( 14801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org part.presentation.encode('utf-8'), 14901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org part.original.encode('utf-8'), 15001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org part.example.encode('utf-8')) 15101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org msg.AppendPlaceholder(ph) 15201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org else: 15301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org msg.AppendText(part.encode('utf-8')) 15401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 15501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 15601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgclass Message(BaseMessage): 15701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org '''A message.''' 15801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 15901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def __init__(self, text='', placeholders=[], description='', meaning='', 16001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org assigned_id=None): 161b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.org super(Message, self).__init__(text, placeholders, description, meaning) 16201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.assigned_id = assigned_id 16301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 16401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def ToTclibMessage(self): 16501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org msg = grit.extern.tclib.Message('utf-8', meaning=self.meaning) 16601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.FillTclibBaseMessage(msg) 16701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org return msg 16801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 16901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def GetId(self): 17001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org '''Use the assigned id if we have one.''' 17101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org if self.assigned_id: 17201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org return self.assigned_id 17301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 174b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.org return super(Message, self).GetId() 17501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 176ca6c225d0059dd17b476c934e744a3c4f60e2df2joi@chromium.org def HasAssignedId(self): 177ca6c225d0059dd17b476c934e744a3c4f60e2df2joi@chromium.org '''Returns True if this message has an assigned id.''' 178ca6c225d0059dd17b476c934e744a3c4f60e2df2joi@chromium.org return bool(self.assigned_id) 179ca6c225d0059dd17b476c934e744a3c4f60e2df2joi@chromium.org 18001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 18101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgclass Translation(BaseMessage): 18201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org '''A translation.''' 18301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 18401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def __init__(self, text='', id='', placeholders=[], description='', meaning=''): 185b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.org super(Translation, self).__init__(text, placeholders, description, meaning) 18601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.id = id 18701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 18801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def GetId(self): 18901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org assert id != '', "ID has not been set." 19001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org return self.id 19101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 19201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def SetId(self, id): 19301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.id = id 19401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 19501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def ToTclibMessage(self): 19601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org msg = grit.extern.tclib.Message( 19701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 'utf-8', id=self.id, meaning=self.meaning) 19801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.FillTclibBaseMessage(msg) 19901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org return msg 20001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 20101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 20201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgclass Placeholder(grit.extern.tclib.Placeholder): 20301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org '''Modifies constructor to accept a Unicode string 20401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org ''' 20501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 20601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org # Must match placeholder presentation names 20701fadb72b6e94e6511eaffd1874a8cc095f098a7joi@chromium.org _NAME_RE = lazy_re.compile('^[A-Za-z0-9_]+$') 20801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 20901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def __init__(self, presentation, original, example): 21001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org '''Creates a new placeholder. 21101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 21201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org Args: 21301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org presentation: 'USERNAME' 21401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org original: '%s' 21501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org example: 'Joi' 21601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org ''' 21701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org assert presentation != '' 21801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org assert original != '' 21901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org assert example != '' 22001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org if not self._NAME_RE.match(presentation): 22101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org raise exception.InvalidPlaceholderName(presentation) 22201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.presentation = presentation 22301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.original = original 22401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org self.example = example 22501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 22601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def GetPresentation(self): 22701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org return self.presentation 22801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 22901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def GetOriginal(self): 23001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org return self.original 23101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 23201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org def GetExample(self): 23301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org return self.example 23401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 23501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org 236