Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(157)

Side by Side Diff: grit/extern/tclib.py

Issue 1442863002: Remove contents of grit's SVN repository. (Closed) Base URL: http://grit-i18n.googlecode.com/svn/trunk/
Patch Set: Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « grit/extern/FP.py ('k') | grit/format/__init__.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 # The tclib module contains tools for aggregating, verifying, and storing
7 # messages destined for the Translation Console, as well as for reading
8 # translations back and outputting them in some desired format.
9 #
10 # This has been stripped down to include only the functionality needed by grit
11 # for creating Windows .rc and .h files. These are the only parts needed by
12 # the Chrome build process.
13
14 import exceptions
15
16 from grit.extern import FP
17
18 # This module assumes that within a bundle no two messages can have the
19 # same id unless they're identical.
20
21 # The basic classes defined here for external use are Message and Translation,
22 # where the former is used for English messages and the latter for
23 # translations. These classes have a lot of common functionality, as expressed
24 # by the common parent class BaseMessage. Perhaps the most important
25 # distinction is that translated text is stored in UTF-8, whereas original text
26 # is stored in whatever encoding the client uses (presumably Latin-1).
27
28 # --------------------
29 # The public interface
30 # --------------------
31
32 # Generate message id from message text and meaning string (optional),
33 # both in utf-8 encoding
34 #
35 def GenerateMessageId(message, meaning=''):
36 fp = FP.FingerPrint(message)
37 if meaning:
38 # combine the fingerprints of message and meaning
39 fp2 = FP.FingerPrint(meaning)
40 if fp < 0:
41 fp = fp2 + (fp << 1) + 1
42 else:
43 fp = fp2 + (fp << 1)
44 # To avoid negative ids we strip the high-order bit
45 return str(fp & 0x7fffffffffffffffL)
46
47 # -------------------------------------------------------------------------
48 # The MessageTranslationError class is used to signal tclib-specific errors.
49
50 class MessageTranslationError(exceptions.Exception):
51 def __init__(self, args = ''):
52 self.args = args
53
54
55 # -----------------------------------------------------------
56 # The Placeholder class represents a placeholder in a message.
57
58 class Placeholder(object):
59 # String representation
60 def __str__(self):
61 return '%s, "%s", "%s"' % \
62 (self.__presentation, self.__original, self.__example)
63
64 # Getters
65 def GetOriginal(self):
66 return self.__original
67
68 def GetPresentation(self):
69 return self.__presentation
70
71 def GetExample(self):
72 return self.__example
73
74 def __eq__(self, other):
75 return self.EqualTo(other, strict=1, ignore_trailing_spaces=0)
76
77 # Equality test
78 #
79 # ignore_trailing_spaces: TC is using varchar to store the
80 # phrwr fields, as a result of that, the trailing spaces
81 # are removed by MySQL when the strings are stored into TC:-(
82 # ignore_trailing_spaces parameter is used to ignore
83 # trailing spaces during equivalence comparison.
84 #
85 def EqualTo(self, other, strict = 1, ignore_trailing_spaces = 1):
86 if type(other) is not Placeholder:
87 return 0
88 if StringEquals(self.__presentation, other.__presentation,
89 ignore_trailing_spaces):
90 if not strict or (StringEquals(self.__original, other.__original,
91 ignore_trailing_spaces) and
92 StringEquals(self.__example, other.__example,
93 ignore_trailing_spaces)):
94 return 1
95 return 0
96
97
98 # -----------------------------------------------------------------
99 # BaseMessage is the common parent class of Message and Translation.
100 # It is not meant for direct use.
101
102 class BaseMessage(object):
103 # Three types of message construction is supported. If the message text is a
104 # simple string with no dynamic content, you can pass it to the constructor
105 # as the "text" parameter. Otherwise, you can omit "text" and assemble the
106 # message step by step using AppendText() and AppendPlaceholder(). Or, as an
107 # alternative, you can give the constructor the "presentable" version of the
108 # message and a list of placeholders; it will then parse the presentation and
109 # build the message accordingly. For example:
110 # Message(text = "There are NUM_BUGS bugs in your code",
111 # placeholders = [Placeholder("NUM_BUGS", "%d", "33")],
112 # description = "Bla bla bla")
113 def __eq__(self, other):
114 # "source encoding" is nonsense, so ignore it
115 return _ObjectEquals(self, other, ['_BaseMessage__source_encoding'])
116
117 def GetName(self):
118 return self.__name
119
120 def GetSourceEncoding(self):
121 return self.__source_encoding
122
123 # Append a placeholder to the message
124 def AppendPlaceholder(self, placeholder):
125 if not isinstance(placeholder, Placeholder):
126 raise MessageTranslationError, ("Invalid message placeholder %s in "
127 "message %s" % (placeholder, self.GetId()) )
128 # Are there other placeholders with the same presentation?
129 # If so, they need to be the same.
130 for other in self.GetPlaceholders():
131 if placeholder.GetPresentation() == other.GetPresentation():
132 if not placeholder.EqualTo(other):
133 raise MessageTranslationError, \
134 "Conflicting declarations of %s within message" % \
135 placeholder.GetPresentation()
136 # update placeholder list
137 dup = 0
138 for item in self.__content:
139 if isinstance(item, Placeholder) and placeholder.EqualTo(item):
140 dup = 1
141 break
142 if not dup:
143 self.__placeholders.append(placeholder)
144
145 # update content
146 self.__content.append(placeholder)
147
148 # Strips leading and trailing whitespace, and returns a tuple
149 # containing the leading and trailing space that was removed.
150 def Strip(self):
151 leading = trailing = ''
152 if len(self.__content) > 0:
153 s0 = self.__content[0]
154 if not isinstance(s0, Placeholder):
155 s = s0.lstrip()
156 leading = s0[:-len(s)]
157 self.__content[0] = s
158
159 s0 = self.__content[-1]
160 if not isinstance(s0, Placeholder):
161 s = s0.rstrip()
162 trailing = s0[len(s):]
163 self.__content[-1] = s
164 return leading, trailing
165
166 # Return the id of this message
167 def GetId(self):
168 if self.__id is None:
169 return self.GenerateId()
170 return self.__id
171
172 # Set the id of this message
173 def SetId(self, id):
174 if id is None:
175 self.__id = None
176 else:
177 self.__id = str(id) # Treat numerical ids as strings
178
179 # Return content of this message as a list (internal use only)
180 def GetContent(self):
181 return self.__content
182
183 # Return a human-readable version of this message
184 def GetPresentableContent(self):
185 presentable_content = ""
186 for item in self.__content:
187 if isinstance(item, Placeholder):
188 presentable_content += item.GetPresentation()
189 else:
190 presentable_content += item
191
192 return presentable_content
193
194 # Return a fragment of a message in escaped format
195 def EscapeFragment(self, fragment):
196 return fragment.replace('%', '%%')
197
198 # Return the "original" version of this message, doing %-escaping
199 # properly. If source_msg is specified, the placeholder original
200 # information inside source_msg will be used instead.
201 def GetOriginalContent(self, source_msg = None):
202 original_content = ""
203 for item in self.__content:
204 if isinstance(item, Placeholder):
205 if source_msg:
206 ph = source_msg.GetPlaceholder(item.GetPresentation())
207 if not ph:
208 raise MessageTranslationError, \
209 "Placeholder %s doesn't exist in message: %s" % \
210 (item.GetPresentation(), source_msg);
211 original_content += ph.GetOriginal()
212 else:
213 original_content += item.GetOriginal()
214 else:
215 original_content += self.EscapeFragment(item)
216 return original_content
217
218 # Return the example of this message
219 def GetExampleContent(self):
220 example_content = ""
221 for item in self.__content:
222 if isinstance(item, Placeholder):
223 example_content += item.GetExample()
224 else:
225 example_content += item
226 return example_content
227
228 # Return a list of all unique placeholders in this message
229 def GetPlaceholders(self):
230 return self.__placeholders
231
232 # Return a placeholder in this message
233 def GetPlaceholder(self, presentation):
234 for item in self.__content:
235 if (isinstance(item, Placeholder) and
236 item.GetPresentation() == presentation):
237 return item
238 return None
239
240 # Return this message's description
241 def GetDescription(self):
242 return self.__description
243
244 # Add a message source
245 def AddSource(self, source):
246 self.__sources.append(source)
247
248 # Return this message's sources as a list
249 def GetSources(self):
250 return self.__sources
251
252 # Return this message's sources as a string
253 def GetSourcesAsText(self, delimiter = "; "):
254 return delimiter.join(self.__sources)
255
256 # Set the obsolete flag for a message (internal use only)
257 def SetObsolete(self):
258 self.__obsolete = 1
259
260 # Get the obsolete flag for a message (internal use only)
261 def IsObsolete(self):
262 return self.__obsolete
263
264 # Get the sequence number (0 by default)
265 def GetSequenceNumber(self):
266 return self.__sequence_number
267
268 # Set the sequence number
269 def SetSequenceNumber(self, number):
270 self.__sequence_number = number
271
272 # Increment instance counter
273 def AddInstance(self):
274 self.__num_instances += 1
275
276 # Return instance count
277 def GetNumInstances(self):
278 return self.__num_instances
279
280 def GetErrors(self, from_tc=0):
281 """
282 Returns a description of the problem if the message is not
283 syntactically valid, or None if everything is fine.
284
285 Args:
286 from_tc: indicates whether this message came from the TC. We let
287 the TC get away with some things we normally wouldn't allow for
288 historical reasons.
289 """
290 # check that placeholders are unambiguous
291 pos = 0
292 phs = {}
293 for item in self.__content:
294 if isinstance(item, Placeholder):
295 phs[pos] = item
296 pos += len(item.GetPresentation())
297 else:
298 pos += len(item)
299 presentation = self.GetPresentableContent()
300 for ph in self.GetPlaceholders():
301 for pos in FindOverlapping(presentation, ph.GetPresentation()):
302 # message contains the same text as a placeholder presentation
303 other_ph = phs.get(pos)
304 if ((not other_ph
305 and not IsSubstringInPlaceholder(pos, len(ph.GetPresentation()), ph s))
306 or
307 (other_ph and len(other_ph.GetPresentation()) < len(ph.GetPresentati on()))):
308 return "message contains placeholder name '%s':\n%s" % (
309 ph.GetPresentation(), presentation)
310 return None
311
312
313 def __CopyTo(self, other):
314 """
315 Returns a copy of this BaseMessage.
316 """
317 assert isinstance(other, self.__class__) or isinstance(self, other.__class_ _)
318 other.__source_encoding = self.__source_encoding
319 other.__content = self.__content[:]
320 other.__description = self.__description
321 other.__id = self.__id
322 other.__num_instances = self.__num_instances
323 other.__obsolete = self.__obsolete
324 other.__name = self.__name
325 other.__placeholders = self.__placeholders[:]
326 other.__sequence_number = self.__sequence_number
327 other.__sources = self.__sources[:]
328
329 return other
330
331 def HasText(self):
332 """Returns true iff this message has anything other than placeholders."""
333 for item in self.__content:
334 if not isinstance(item, Placeholder):
335 return True
336 return False
337
338 # --------------------------------------------------------
339 # The Message class represents original (English) messages
340
341 class Message(BaseMessage):
342 # See BaseMessage constructor
343 def __init__(self, source_encoding, text=None, id=None,
344 description=None, meaning="", placeholders=None,
345 source=None, sequence_number=0, clone_from=None,
346 time_created=0, name=None, is_hidden = 0):
347
348 if clone_from is not None:
349 BaseMessage.__init__(self, None, clone_from=clone_from)
350 self.__meaning = clone_from.__meaning
351 self.__time_created = clone_from.__time_created
352 self.__is_hidden = clone_from.__is_hidden
353 return
354
355 BaseMessage.__init__(self, source_encoding, text, id, description,
356 placeholders, source, sequence_number,
357 name=name)
358 self.__meaning = meaning
359 self.__time_created = time_created
360 self.SetIsHidden(is_hidden)
361
362 # String representation
363 def __str__(self):
364 s = 'source: %s, id: %s, content: "%s", meaning: "%s", ' \
365 'description: "%s"' % \
366 (self.GetSourcesAsText(), self.GetId(), self.GetPresentableContent(),
367 self.__meaning, self.GetDescription())
368 if self.GetName() is not None:
369 s += ', name: "%s"' % self.GetName()
370 placeholders = self.GetPlaceholders()
371 for i in range(len(placeholders)):
372 s += ", placeholder[%d]: %s" % (i, placeholders[i])
373 return s
374
375 # Strips leading and trailing whitespace, and returns a tuple
376 # containing the leading and trailing space that was removed.
377 def Strip(self):
378 leading = trailing = ''
379 content = self.GetContent()
380 if len(content) > 0:
381 s0 = content[0]
382 if not isinstance(s0, Placeholder):
383 s = s0.lstrip()
384 leading = s0[:-len(s)]
385 content[0] = s
386
387 s0 = content[-1]
388 if not isinstance(s0, Placeholder):
389 s = s0.rstrip()
390 trailing = s0[len(s):]
391 content[-1] = s
392 return leading, trailing
393
394 # Generate an id by hashing message content
395 def GenerateId(self):
396 self.SetId(GenerateMessageId(self.GetPresentableContent(),
397 self.__meaning))
398 return self.GetId()
399
400 def GetMeaning(self):
401 return self.__meaning
402
403 def GetTimeCreated(self):
404 return self.__time_created
405
406 # Equality operator
407 def EqualTo(self, other, strict = 1):
408 # Check id, meaning, content
409 if self.GetId() != other.GetId():
410 return 0
411 if self.__meaning != other.__meaning:
412 return 0
413 if self.GetPresentableContent() != other.GetPresentableContent():
414 return 0
415 # Check descriptions if comparison is strict
416 if (strict and
417 self.GetDescription() is not None and
418 other.GetDescription() is not None and
419 self.GetDescription() != other.GetDescription()):
420 return 0
421 # Check placeholders
422 ph1 = self.GetPlaceholders()
423 ph2 = other.GetPlaceholders()
424 if len(ph1) != len(ph2):
425 return 0
426 for i in range(len(ph1)):
427 if not ph1[i].EqualTo(ph2[i], strict):
428 return 0
429
430 return 1
431
432 def Copy(self):
433 """
434 Returns a copy of this Message.
435 """
436 assert isinstance(self, Message)
437 return Message(None, clone_from=self)
438
439 def SetIsHidden(self, is_hidden):
440 """Sets whether this message should be hidden.
441
442 Args:
443 is_hidden : 0 or 1 - if the message should be hidden, 0 otherwise
444 """
445 if is_hidden not in [0, 1]:
446 raise MessageTranslationError, "is_hidden must be 0 or 1, got %s"
447 self.__is_hidden = is_hidden
448
449 def IsHidden(self):
450 """Returns 1 if this message is hidden, and 0 otherwise."""
451 return self.__is_hidden
452
453 # ----------------------------------------------------
454 # The Translation class represents translated messages
455
456 class Translation(BaseMessage):
457 # See BaseMessage constructor
458 def __init__(self, source_encoding, text=None, id=None,
459 description=None, placeholders=None, source=None,
460 sequence_number=0, clone_from=None, ignore_ph_errors=0,
461 name=None):
462 if clone_from is not None:
463 BaseMessage.__init__(self, None, clone_from=clone_from)
464 return
465
466 BaseMessage.__init__(self, source_encoding, text, id, description,
467 placeholders, source, sequence_number,
468 ignore_ph_errors=ignore_ph_errors, name=name)
469
470 # String representation
471 def __str__(self):
472 s = 'source: %s, id: %s, content: "%s", description: "%s"' % \
473 (self.GetSourcesAsText(), self.GetId(), self.GetPresentableContent(),
474 self.GetDescription());
475 placeholders = self.GetPlaceholders()
476 for i in range(len(placeholders)):
477 s += ", placeholder[%d]: %s" % (i, placeholders[i])
478 return s
479
480 # Equality operator
481 def EqualTo(self, other, strict=1):
482 # Check id and content
483 if self.GetId() != other.GetId():
484 return 0
485 if self.GetPresentableContent() != other.GetPresentableContent():
486 return 0
487 # Check placeholders
488 ph1 = self.GetPlaceholders()
489 ph2 = other.GetPlaceholders()
490 if len(ph1) != len(ph2):
491 return 0
492 for i in range(len(ph1)):
493 if not ph1[i].EqualTo(ph2[i], strict):
494 return 0
495
496 return 1
497
498 def Copy(self):
499 """
500 Returns a copy of this Translation.
501 """
502 return Translation(None, clone_from=self)
503
OLDNEW
« no previous file with comments | « grit/extern/FP.py ('k') | grit/format/__init__.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698