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 |