Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(61)

Side by Side Diff: translate/storage/base.py

Issue 62: CPO support for previous messages SVN Base: https://translate.svn.sourceforge.net/svnroot/translate/src/trunk/
Patch Set: Updated accessors, tests and added base.py & test_base.py Created 1 year, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 # 3 #
4 # Copyright 2006-2008 Zuza Software Foundation 4 # Copyright 2006-2008 Zuza Software Foundation
5 # 5 #
6 # This file is part of translate. 6 # This file is part of translate.
7 # 7 #
8 # translate is free software; you can redistribute it and/or modify 8 # translate is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by 9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or 10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version. 11 # (at your option) any later version.
12 # 12 #
13 # translate is distributed in the hope that it will be useful, 13 # translate is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details. 16 # GNU General Public License for more details.
17 # 17 #
18 # You should have received a copy of the GNU General Public License 18 # You should have received a copy of the GNU General Public License
19 # along with translate; if not, write to the Free Software 19 # along with translate; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 21
22 """Base classes for storage interfaces. 22 """Base classes for storage interfaces.
23 23
24 @organization: Zuza Software Foundation 24 @organization: Zuza Software Foundation
25 @copyright: 2006-2007 Zuza Software Foundation 25 @copyright: 2006-2007 Zuza Software Foundation
26 @license: U{GPL <http://www.fsf.org/licensing/licenses/gpl.html>} 26 @license: U{GPL <http://www.fsf.org/licensing/licenses/gpl.html>}
27 """ 27 """
28 28
29 try: 29 try:
30 import cPickle as pickle 30 import cPickle as pickle
31 except: 31 except:
32 import pickle 32 import pickle
33 from exceptions import NotImplementedError 33 from exceptions import NotImplementedError
34 34
35 def force_override(method, baseclass): 35 def force_override(method, baseclass):
36 """Forces derived classes to override method.""" 36 """Forces derived classes to override method."""
37 37
38 if type(method.im_self) == type(baseclass): 38 if type(method.im_self) == type(baseclass):
39 # then this is a classmethod and im_self is the actual class 39 # then this is a classmethod and im_self is the actual class
40 actualclass = method.im_self 40 actualclass = method.im_self
41 else: 41 else:
42 actualclass = method.im_class 42 actualclass = method.im_class
43 if actualclass != baseclass: 43 if actualclass != baseclass:
44 raise NotImplementedError("%s does not reimplement %s as required by %s" % (actualclass.__name__, method.__name__, baseclass.__name__)) 44 raise NotImplementedError("%s does not reimplement %s as required by %s" % (actualclass.__name__, method.__name__, baseclass.__name__))
45 45
46 class ParseError(Exception): 46 class ParseError(Exception):
47 pass 47 pass
48 48
49 class TranslationUnit(object): 49 class TranslationUnit(object):
50 """Base class for translation units. 50 """Base class for translation units.
51 51
52 Our concept of a I{translation unit} is influenced heavily by XLIFF: 52 Our concept of a I{translation unit} is influenced heavily by XLIFF:
53 U{http://www.oasis-open.org/committees/xliff/documents/xliff-specification.h tm} 53 U{http://www.oasis-open.org/committees/xliff/documents/xliff-specification.h tm}
54 54
55 As such most of the method- and variable names borrows from XLIFF terminolog y. 55 As such most of the method- and variable names borrows from XLIFF terminolog y.
56 56
57 A translation unit consists of the following: 57 A translation unit consists of the following:
58 - A I{source} string. This is the original translatable text. 58 - A I{source} string. This is the original translatable text.
59 - A I{target} string. This is the translation of the I{source}. 59 - A I{target} string. This is the translation of the I{source}.
60 - Zero or more I{notes} on the unit. Notes would typically be some 60 - Zero or more I{notes} on the unit. Notes would typically be some
61 comments from a translator on the unit, or some comments originating fro m 61 comments from a translator on the unit, or some comments originating fro m
62 the source code. 62 the source code.
63 - Zero or more I{locations}. Locations indicate where in the original 63 - Zero or more I{locations}. Locations indicate where in the original
64 source code this unit came from. 64 source code this unit came from.
65 - Zero or more I{errors}. Some tools (eg. L{pofilter <filters.pofilter>}) can run checks on 65 - Zero or more I{errors}. Some tools (eg. L{pofilter <filters.pofilter>}) can run checks on
66 translations and produce error messages. 66 translations and produce error messages.
67 - Zero or one I{prev_source} string, the old version of the translatable t ext.
68 - Zero or one I{prev_context} string, the old version of the message conte xt.
67 69
68 @group Source: *source* 70 @group Source: *source*
69 @group Target: *target* 71 @group Target: *target*
72 @group PrevSource: *PrevSource*
73 @group PrevContext: *PrevContext*
70 @group Notes: *note* 74 @group Notes: *note*
71 @group Locations: *location* 75 @group Locations: *location*
72 @group Errors: *error* 76 @group Errors: *error*
73 """ 77 """
74 78
75 def __init__(self, source): 79 def __init__(self, source):
76 """Constructs a TranslationUnit containing the given source string.""" 80 """Constructs a TranslationUnit containing the given source string."""
77 81
78 self.source = source 82 self.source = source
79 self.target = None 83 self.target = None
84 self.prev_source = None
85 self.prev_context = None
80 self.notes = "" 86 self.notes = ""
81 super(TranslationUnit, self).__init__() 87 super(TranslationUnit, self).__init__()
82 88
83 def __eq__(self, other): 89 def __eq__(self, other):
84 """Compares two TranslationUnits. 90 """Compares two TranslationUnits.
85 91
86 @type other: L{TranslationUnit} 92 @type other: L{TranslationUnit}
87 @param other: Another L{TranslationUnit} 93 @param other: Another L{TranslationUnit}
88 @rtype: Boolean 94 @rtype: Boolean
89 @return: Returns True if the supplied TranslationUnit equals this unit. 95 @return: Returns True if the supplied TranslationUnit equals this unit.
90 96
91 """ 97 """
92 98
93 return self.source == other.source and self.target == other.target 99 return self.source == other.source and self.target == other.target
94 100
95 def settarget(self, target): 101 def settarget(self, target):
96 """Sets the target string to the given value.""" 102 """Sets the target string to the given value."""
97 103
98 self.target = target 104 self.target = target
99 105
100 def gettargetlen(self): 106 def gettargetlen(self):
101 """Returns the length of the target string. 107 """Returns the length of the target string.
102 108
103 @note: Plural forms might be combined. 109 @note: Plural forms might be combined.
104 @rtype: Integer 110 @rtype: Integer
105 111
106 """ 112 """
107 113
108 length = len(self.target or "") 114 length = len(self.target or "")
109 strings = getattr(self.target, "strings", []) 115 strings = getattr(self.target, "strings", [])
110 if strings: 116 if strings:
111 length += sum([len(pluralform) for pluralform in strings[1:]]) 117 length += sum([len(pluralform) for pluralform in strings[1:]])
112 return length 118 return length
113 119
114 def getid(self): 120 def getid(self):
115 """A unique identifier for this unit. 121 """A unique identifier for this unit.
116 122
117 @rtype: string 123 @rtype: string
118 @return: an identifier for this unit that is unique in the store 124 @return: an identifier for this unit that is unique in the store
119 125
120 Derived classes should override this in a way that guarantees a unique 126 Derived classes should override this in a way that guarantees a unique
121 identifier for each unit in the store. 127 identifier for each unit in the store.
122 """ 128 """
123 return self.source 129 return self.source
124 130
125 def getlocations(self): 131 def getlocations(self):
126 """A list of source code locations. 132 """A list of source code locations.
127 133
128 @note: Shouldn't be implemented if the format doesn't support it. 134 @note: Shouldn't be implemented if the format doesn't support it.
129 @rtype: List 135 @rtype: List
(...skipping 112 matching lines...) Show 10 above Show 10 below
242 return True 248 return True
243 249
244 def isfuzzy(self): 250 def isfuzzy(self):
245 """Indicates whether this unit is fuzzy.""" 251 """Indicates whether this unit is fuzzy."""
246 252
247 return False 253 return False
248 254
249 def markfuzzy(self, value=True): 255 def markfuzzy(self, value=True):
250 """Marks the unit as fuzzy or not.""" 256 """Marks the unit as fuzzy or not."""
251 pass 257 pass
252 258
253 def isheader(self): 259 def isheader(self):
254 """Indicates whether this unit is a header.""" 260 """Indicates whether this unit is a header."""
255 261
256 return False 262 return False
257 263
258 def isreview(self): 264 def isreview(self):
259 """Indicates whether this unit needs review.""" 265 """Indicates whether this unit needs review."""
260 return False 266 return False
261 267
262 268
263 def isblank(self): 269 def isblank(self):
264 """Used to see if this unit has no source or target string. 270 """Used to see if this unit has no source or target string.
265 271
266 @note: This is probably used more to find translatable units, 272 @note: This is probably used more to find translatable units,
267 and we might want to move in that direction rather and get rid of this. 273 and we might want to move in that direction rather and get rid of this.
268 274
269 """ 275 """
270 276
271 return not (self.source or self.target) 277 return not (self.source or self.target)
272 278
273 def hasplural(self): 279 def hasplural(self):
274 """Tells whether or not this specific unit has plural strings.""" 280 """Tells whether or not this specific unit has plural strings."""
275 281
276 #TODO: Reconsider 282 #TODO: Reconsider
277 return False 283 return False
278 284
279 def merge(self, otherunit, overwrite=False, comments=True): 285 def merge(self, otherunit, overwrite=False, comments=True):
280 """Do basic format agnostic merging.""" 286 """Do basic format agnostic merging."""
281 287
282 if self.target == "" or overwrite: 288 if self.target == "" or overwrite:
283 self.target = otherunit.target 289 self.target = otherunit.target
284 290
285 def unit_iter(self): 291 def unit_iter(self):
286 """Iterator that only returns this unit.""" 292 """Iterator that only returns this unit."""
287 yield self 293 yield self
288 294
289 def getunits(self): 295 def getunits(self):
290 """This unit in a list.""" 296 """This unit in a list."""
291 return [self] 297 return [self]
298
299 def setprev_source(self, source):
300 """Sets the previous source message for this unit"""
301 self.prev_source = source
302
303 def getprev_source(self):
304 """Gets the previous source message for this unit"""
305 return self.prev_source
dwaynebailey 2008/07/17 12:59:27 Shouldn't the decorators be here for prev_source?
georgeyk 2008/07/18 05:15:49 On 2008/07/17 12:59:27, dwaynebailey wrote: > Shou
306
307 def setprev_context(self, context):
308 """Sets the previous context of this unit"""
309 self.prev_context = context
310
311 def getprev_context(self):
312 """Gets the previous context of this unit"""
313 return self.prev_context
314
315 def set_as_previous(self):
316 """Sets this unit as a previous unit message"""
317 if self.isfuzzy():
318 return
319 self.setprev_context(self.getcontext())
320 self.setprev_source(self.source)
321 self.markfuzzy()
292 322
293 def buildfromunit(cls, unit): 323 def buildfromunit(cls, unit):
294 """Build a native unit from a foreign unit, preserving as much 324 """Build a native unit from a foreign unit, preserving as much
295 information as possible.""" 325 information as possible."""
296 326
297 if type(unit) == cls and hasattr(unit, "copy") and callable(unit.copy): 327 if type(unit) == cls and hasattr(unit, "copy") and callable(unit.copy):
298 return unit.copy() 328 return unit.copy()
299 newunit = cls(unit.source) 329 newunit = cls(unit.source)
300 newunit.target = unit.target 330 newunit.target = unit.target
301 newunit.markfuzzy(unit.isfuzzy()) 331 newunit.markfuzzy(unit.isfuzzy())
302 locations = unit.getlocations() 332 locations = unit.getlocations()
303 if locations: 333 if locations:
304 newunit.addlocations(locations) 334 newunit.addlocations(locations)
305 notes = unit.getnotes() 335 notes = unit.getnotes()
306 if notes: 336 if notes:
307 newunit.addnote(notes) 337 newunit.addnote(notes)
308 return newunit 338 return newunit
309 buildfromunit = classmethod(buildfromunit) 339 buildfromunit = classmethod(buildfromunit)
310 340
311 class TranslationStore(object): 341 class TranslationStore(object):
312 """Base class for stores for multiple translation units of type UnitClass."" " 342 """Base class for stores for multiple translation units of type UnitClass."" "
313 343
314 UnitClass = TranslationUnit 344 UnitClass = TranslationUnit
315 Mimetypes = None 345 Mimetypes = None
316 Extensions = None 346 Extensions = None
317 347
318 def __init__(self, unitclass=None): 348 def __init__(self, unitclass=None):
319 """Constructs a blank TranslationStore.""" 349 """Constructs a blank TranslationStore."""
320 350
321 self.units = [] 351 self.units = []
322 self.filepath = None 352 self.filepath = None
323 self.translator = "" 353 self.translator = ""
324 self.date = "" 354 self.date = ""
325 self.sourcelanguage = None 355 self.sourcelanguage = None
326 self.targetlanguage = None 356 self.targetlanguage = None
327 if unitclass: 357 if unitclass:
328 self.UnitClass = unitclass 358 self.UnitClass = unitclass
329 super(TranslationStore, self).__init__() 359 super(TranslationStore, self).__init__()
330 360
331 def setsourcelanguage(self, sourcelanguage): 361 def setsourcelanguage(self, sourcelanguage):
332 """Sets the source language for this store""" 362 """Sets the source language for this store"""
333 self.sourcelanguage = sourcelanguage 363 self.sourcelanguage = sourcelanguage
334 364
335 def settargetlanguage(self, targetlanguage): 365 def settargetlanguage(self, targetlanguage):
336 """Sets the target language for this store""" 366 """Sets the target language for this store"""
337 self.targetlanguage = targetlanguage 367 self.targetlanguage = targetlanguage
338 368
339 def unit_iter(self): 369 def unit_iter(self):
340 """Iterator over all the units in this store.""" 370 """Iterator over all the units in this store."""
341 for unit in self.units: 371 for unit in self.units:
(...skipping 111 matching lines...) Show 10 above Show 10 below
453 return newstore 483 return newstore
454 parsestring = classmethod(parsestring) 484 parsestring = classmethod(parsestring)
455 485
456 def parse(self, data): 486 def parse(self, data):
457 """parser to process the given source string""" 487 """parser to process the given source string"""
458 self.units = pickle.loads(data).units 488 self.units = pickle.loads(data).units
459 489
460 def savefile(self, storefile): 490 def savefile(self, storefile):
461 """Writes the string representation to the given file (or filename).""" 491 """Writes the string representation to the given file (or filename)."""
462 if isinstance(storefile, basestring): 492 if isinstance(storefile, basestring):
463 storefile = open(storefile, "w") 493 storefile = open(storefile, "w")
464 self.fileobj = storefile 494 self.fileobj = storefile
465 self._assignname() 495 self._assignname()
466 storestring = str(self) 496 storestring = str(self)
467 storefile.write(storestring) 497 storefile.write(storestring)
468 storefile.close() 498 storefile.close()
469 499
470 def save(self): 500 def save(self):
471 """Save to the file that data was originally read from, if available.""" 501 """Save to the file that data was originally read from, if available."""
472 fileobj = getattr(self, "fileobj", None) 502 fileobj = getattr(self, "fileobj", None)
473 if not fileobj: 503 if not fileobj:
474 filename = getattr(self, "filename", None) 504 filename = getattr(self, "filename", None)
475 if filename: 505 if filename:
476 fileobj = file(filename, "w") 506 fileobj = file(filename, "w")
477 else: 507 else:
478 fileobj.close() 508 fileobj.close()
479 filename = getattr(fileobj, "name", getattr(fileobj, "filename", Non e)) 509 filename = getattr(fileobj, "name", getattr(fileobj, "filename", Non e))
480 if not filename: 510 if not filename:
481 raise ValueError("No file or filename to save to") 511 raise ValueError("No file or filename to save to")
482 fileobj = fileobj.__class__(filename, "w") 512 fileobj = fileobj.__class__(filename, "w")
483 self.savefile(fileobj) 513 self.savefile(fileobj)
484 514
485 def parsefile(cls, storefile): 515 def parsefile(cls, storefile):
486 """Reads the given file (or opens the given filename) and parses back to an object.""" 516 """Reads the given file (or opens the given filename) and parses back to an object."""
487 517
488 if isinstance(storefile, basestring): 518 if isinstance(storefile, basestring):
489 storefile = open(storefile, "r") 519 storefile = open(storefile, "r")
490 mode = getattr(storefile, "mode", "r") 520 mode = getattr(storefile, "mode", "r")
491 #For some reason GzipFile returns 1, so we have to test for that here 521 #For some reason GzipFile returns 1, so we have to test for that here
492 if mode == 1 or "r" in mode: 522 if mode == 1 or "r" in mode:
493 storestring = storefile.read() 523 storestring = storefile.read()
494 storefile.close() 524 storefile.close()
495 else: 525 else:
496 storestring = "" 526 storestring = ""
497 newstore = cls.parsestring(storestring) 527 newstore = cls.parsestring(storestring)
498 newstore.fileobj = storefile 528 newstore.fileobj = storefile
499 newstore._assignname() 529 newstore._assignname()
500 return newstore 530 return newstore
501 parsefile = classmethod(parsefile) 531 parsefile = classmethod(parsefile)
502 532
OLDNEW

Powered by Google App Engine
This is Rietveld r159