| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env python | |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 3 # Use of this source code is governed by a BSD-style license that can be | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 '''Adaptation of the extern.tclib classes for our needs. | |
| 7 ''' | |
| 8 | |
| 9 | |
| 10 import re | |
| 11 import types | |
| 12 | |
| 13 from grit import exception | |
| 14 from grit import lazy_re | |
| 15 import grit.extern.tclib | |
| 16 | |
| 17 | |
| 18 # Matches whitespace sequences which can be folded into a single whitespace | |
| 19 # character. This matches single characters so that non-spaces are replaced | |
| 20 # with spaces. | |
| 21 _FOLD_WHITESPACE = re.compile(r'\s+') | |
| 22 | |
| 23 | |
| 24 def Identity(i): | |
| 25 return i | |
| 26 | |
| 27 | |
| 28 class BaseMessage(object): | |
| 29 '''Base class with methods shared by Message and Translation. | |
| 30 ''' | |
| 31 | |
| 32 def __init__(self, text='', placeholders=[], description='', meaning=''): | |
| 33 self.parts = [] | |
| 34 self.placeholders = [] | |
| 35 self.meaning = meaning | |
| 36 self.dirty = True # True if self.id is (or might be) wrong | |
| 37 self.id = 0 | |
| 38 self.SetDescription(description) | |
| 39 | |
| 40 if text != '': | |
| 41 if not placeholders or placeholders == []: | |
| 42 self.AppendText(text) | |
| 43 else: | |
| 44 tag_map = {} | |
| 45 for placeholder in placeholders: | |
| 46 tag_map[placeholder.GetPresentation()] = [placeholder, 0] | |
| 47 # This creates a regexp like '(TAG1|TAG2|TAG3)'. | |
| 48 # The tags have to be sorted in order of decreasing length, so that | |
| 49 # longer tags are substituted before shorter tags that happen to be | |
| 50 # substrings of the longer tag. | |
| 51 # E.g. "EXAMPLE_FOO_NAME" must be matched before "EXAMPLE_FOO", | |
| 52 # otherwise "EXAMPLE_FOO" splits "EXAMPLE_FOO_NAME" too. | |
| 53 tags = tag_map.keys() | |
| 54 tags.sort(cmp=lambda x,y: len(x) - len(y) or cmp(x, y), reverse=True) | |
| 55 tag_re = '(' + '|'.join(tags) + ')' | |
| 56 chunked_text = re.split(tag_re, text) | |
| 57 for chunk in chunked_text: | |
| 58 if chunk: # ignore empty chunk | |
| 59 if tag_map.has_key(chunk): | |
| 60 self.AppendPlaceholder(tag_map[chunk][0]) | |
| 61 tag_map[chunk][1] += 1 # increase placeholder use count | |
| 62 else: | |
| 63 self.AppendText(chunk) | |
| 64 for key in tag_map.keys(): | |
| 65 assert tag_map[key][1] != 0 | |
| 66 | |
| 67 def GetRealContent(self, escaping_function=Identity): | |
| 68 '''Returns the original content, i.e. what your application and users | |
| 69 will see. | |
| 70 | |
| 71 Specify a function to escape each translateable bit, if you like. | |
| 72 ''' | |
| 73 bits = [] | |
| 74 for item in self.parts: | |
| 75 if isinstance(item, types.StringTypes): | |
| 76 bits.append(escaping_function(item)) | |
| 77 else: | |
| 78 bits.append(item.GetOriginal()) | |
| 79 return ''.join(bits) | |
| 80 | |
| 81 def GetPresentableContent(self): | |
| 82 presentable_content = [] | |
| 83 for part in self.parts: | |
| 84 if isinstance(part, Placeholder): | |
| 85 presentable_content.append(part.GetPresentation()) | |
| 86 else: | |
| 87 presentable_content.append(part) | |
| 88 return ''.join(presentable_content) | |
| 89 | |
| 90 def AppendPlaceholder(self, placeholder): | |
| 91 assert isinstance(placeholder, Placeholder) | |
| 92 dup = False | |
| 93 for other in self.GetPlaceholders(): | |
| 94 if other.presentation == placeholder.presentation: | |
| 95 assert other.original == placeholder.original | |
| 96 dup = True | |
| 97 | |
| 98 if not dup: | |
| 99 self.placeholders.append(placeholder) | |
| 100 self.parts.append(placeholder) | |
| 101 self.dirty = True | |
| 102 | |
| 103 def AppendText(self, text): | |
| 104 assert isinstance(text, types.StringTypes) | |
| 105 assert text != '' | |
| 106 | |
| 107 self.parts.append(text) | |
| 108 self.dirty = True | |
| 109 | |
| 110 def GetContent(self): | |
| 111 '''Returns the parts of the message. You may modify parts if you wish. | |
| 112 Note that you must not call GetId() on this object until you have finished | |
| 113 modifying the contents. | |
| 114 ''' | |
| 115 self.dirty = True # user might modify content | |
| 116 return self.parts | |
| 117 | |
| 118 def GetDescription(self): | |
| 119 return self.description | |
| 120 | |
| 121 def SetDescription(self, description): | |
| 122 self.description = _FOLD_WHITESPACE.sub(' ', description) | |
| 123 | |
| 124 def GetMeaning(self): | |
| 125 return self.meaning | |
| 126 | |
| 127 def GetId(self): | |
| 128 if self.dirty: | |
| 129 self.id = self.GenerateId() | |
| 130 self.dirty = False | |
| 131 return self.id | |
| 132 | |
| 133 def GenerateId(self): | |
| 134 # Must use a UTF-8 encoded version of the presentable content, along with | |
| 135 # the meaning attribute, to match the TC. | |
| 136 return grit.extern.tclib.GenerateMessageId( | |
| 137 self.GetPresentableContent().encode('utf-8'), self.meaning) | |
| 138 | |
| 139 def GetPlaceholders(self): | |
| 140 return self.placeholders | |
| 141 | |
| 142 def FillTclibBaseMessage(self, msg): | |
| 143 msg.SetDescription(self.description.encode('utf-8')) | |
| 144 | |
| 145 for part in self.parts: | |
| 146 if isinstance(part, Placeholder): | |
| 147 ph = grit.extern.tclib.Placeholder( | |
| 148 part.presentation.encode('utf-8'), | |
| 149 part.original.encode('utf-8'), | |
| 150 part.example.encode('utf-8')) | |
| 151 msg.AppendPlaceholder(ph) | |
| 152 else: | |
| 153 msg.AppendText(part.encode('utf-8')) | |
| 154 | |
| 155 | |
| 156 class Message(BaseMessage): | |
| 157 '''A message.''' | |
| 158 | |
| 159 def __init__(self, text='', placeholders=[], description='', meaning='', | |
| 160 assigned_id=None): | |
| 161 super(Message, self).__init__(text, placeholders, description, meaning) | |
| 162 self.assigned_id = assigned_id | |
| 163 | |
| 164 def ToTclibMessage(self): | |
| 165 msg = grit.extern.tclib.Message('utf-8', meaning=self.meaning) | |
| 166 self.FillTclibBaseMessage(msg) | |
| 167 return msg | |
| 168 | |
| 169 def GetId(self): | |
| 170 '''Use the assigned id if we have one.''' | |
| 171 if self.assigned_id: | |
| 172 return self.assigned_id | |
| 173 | |
| 174 return super(Message, self).GetId() | |
| 175 | |
| 176 def HasAssignedId(self): | |
| 177 '''Returns True if this message has an assigned id.''' | |
| 178 return bool(self.assigned_id) | |
| 179 | |
| 180 | |
| 181 class Translation(BaseMessage): | |
| 182 '''A translation.''' | |
| 183 | |
| 184 def __init__(self, text='', id='', placeholders=[], description='', meaning=''
): | |
| 185 super(Translation, self).__init__(text, placeholders, description, meaning) | |
| 186 self.id = id | |
| 187 | |
| 188 def GetId(self): | |
| 189 assert id != '', "ID has not been set." | |
| 190 return self.id | |
| 191 | |
| 192 def SetId(self, id): | |
| 193 self.id = id | |
| 194 | |
| 195 def ToTclibMessage(self): | |
| 196 msg = grit.extern.tclib.Message( | |
| 197 'utf-8', id=self.id, meaning=self.meaning) | |
| 198 self.FillTclibBaseMessage(msg) | |
| 199 return msg | |
| 200 | |
| 201 | |
| 202 class Placeholder(grit.extern.tclib.Placeholder): | |
| 203 '''Modifies constructor to accept a Unicode string | |
| 204 ''' | |
| 205 | |
| 206 # Must match placeholder presentation names | |
| 207 _NAME_RE = lazy_re.compile('^[A-Za-z0-9_]+$') | |
| 208 | |
| 209 def __init__(self, presentation, original, example): | |
| 210 '''Creates a new placeholder. | |
| 211 | |
| 212 Args: | |
| 213 presentation: 'USERNAME' | |
| 214 original: '%s' | |
| 215 example: 'Joi' | |
| 216 ''' | |
| 217 assert presentation != '' | |
| 218 assert original != '' | |
| 219 assert example != '' | |
| 220 if not self._NAME_RE.match(presentation): | |
| 221 raise exception.InvalidPlaceholderName(presentation) | |
| 222 self.presentation = presentation | |
| 223 self.original = original | |
| 224 self.example = example | |
| 225 | |
| 226 def GetPresentation(self): | |
| 227 return self.presentation | |
| 228 | |
| 229 def GetOriginal(self): | |
| 230 return self.original | |
| 231 | |
| 232 def GetExample(self): | |
| 233 return self.example | |
| 234 | |
| 235 | |
| OLD | NEW |