julen / pootle

Web-based translation and translation management tool

This URL has Read+Write access

pootle / pootlefile.py
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 1 #!/usr/bin/env python
b353992c » davidfraser 2005-07-01 added encoding markers for ... 2 # -*- coding: utf-8 -*-
5833449c » dwaynebailey 2006-06-15 [Forwardport] Update copyri... 3 #
4 # Copyright 2004-2006 Zuza Software Foundation
5 #
6 # This file is part of translate.
7 #
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
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
12 #
13 # translate is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with translate; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 21
d07801d0 » friedelwolff 2006-10-12 Use a factory to construct ... 22 """manages a translation file and its associated files"""
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 23
0d42d7f4 » friedelwolff 2006-10-03 Merge Wrapper class from po... 24 from translate.storage import base
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 25 from translate.storage import po
bb29a717 » friedelwolff 2006-12-27 Allow XLIFF suggestions usi... 26 from translate.storage import xliff
d07801d0 » friedelwolff 2006-10-12 Use a factory to construct ... 27 from translate.storage import factory
574eec06 » friedelwolff 2006-03-03 Use more of base class and ... 28 from translate.misc.multistring import multistring
944295bd » davidfraser 2005-06-28 add x_generator member and ... 29 from Pootle import __version__
e71ee4eb » dwaynebailey 2006-11-02 Place pootlestatistics clas... 30 from Pootle import statistics
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 31 from jToolkit import timecache
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 32 from jToolkit import glock
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 33 import time
34 import os
35
0d42d7f4 » friedelwolff 2006-10-03 Merge Wrapper class from po... 36 class Wrapper(object):
37 """An object which wraps an inner object, delegating to the encapsulated methods, etc"""
38 def __getattr__(self, attrname, *args):
39 if attrname in self.__dict__:
40 return self.__dict__[attrname]
41 return getattr(self.__dict__["__innerobj__"], attrname, *args)
42
43 def __setattr__(self, attrname, value):
44 if attrname == "__innerobj__":
45 self.__dict__[attrname] = value
46 elif attrname in self.__dict__:
47 if isinstance(self.__dict__[attrname], property):
48 self.__dict__[attrname].fset(value)
49 else:
50 self.__dict__[attrname] = value
51 elif attrname in self.__class__.__dict__:
52 if isinstance(self.__class__.__dict__[attrname], property):
53 self.__class__.__dict__[attrname].fset(self, value)
54 else:
55 self.__dict__[attrname] = value
56 else:
57 return setattr(self.__dict__["__innerobj__"], attrname, value)
58
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 59 class LockedFile:
60 """locked interaction with a filesystem file"""
2877ffa0 » friedelwolff 2007-03-16 Remove the locking code whi... 61 #Locking is disabled for now since it impacts performance negatively and was
62 #not complete yet anyway. Reverse svn revision 5271 to regain the locking
63 #code here.
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 64 def __init__(self, filename):
65 self.filename = filename
a1aeb64b » friedelwolff 2007-03-16 Postpone opening the lock f... 66 self.lock = None
67
68 def initlock(self):
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 69 self.lock = glock.GlobalLock(self.filename + os.extsep + "lock")
70
a1aeb64b » friedelwolff 2007-03-16 Postpone opening the lock f... 71 def dellock(self):
72 del self.lock
73 self.lock = None
74
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 75 def readmodtime(self):
76 """returns the modification time of the file (locked operation)"""
2877ffa0 » friedelwolff 2007-03-16 Remove the locking code whi... 77 return statistics.getmodtime(self.filename)
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 78
79 def getcontents(self):
80 """returns modtime, contents tuple (locked operation)"""
2877ffa0 » friedelwolff 2007-03-16 Remove the locking code whi... 81 pomtime = statistics.getmodtime(self.filename)
82 fp = open(self.filename, 'r')
83 filecontents = fp.read()
84 fp.close()
85 return pomtime, filecontents
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 86
87 def writecontents(self, contents):
88 """writes contents to file, returning modification time (locked operation)"""
2877ffa0 » friedelwolff 2007-03-16 Remove the locking code whi... 89 f = open(self.filename, 'w')
90 f.write(contents)
91 f.close()
92 pomtime = statistics.getmodtime(self.filename)
93 return pomtime
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 94
95 class pootleassigns:
96 """this represents the assignments for a file"""
97 def __init__(self, basefile):
98 """constructs assignments object for the given file"""
99 # TODO: try and remove circular references between basefile and this class
100 self.basefile = basefile
101 self.assignsfilename = self.basefile.filename + os.extsep + "assigns"
102 self.getassigns()
7e84177f » friedelwolff 2006-10-06 Move some pootlefile functi... 103
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 104 def getassigns(self):
105 """reads the assigns if neccessary or returns them from the cache"""
106 if os.path.exists(self.assignsfilename):
107 self.assigns = self.readassigns()
108 else:
109 self.assigns = {}
110 return self.assigns
111
112 def readassigns(self):
113 """reads the assigns from the associated assigns file, returning the assigns
114 the format is a number of lines consisting of
115 username: action: itemranges
116 where itemranges is a comma-separated list of item numbers or itemranges like 3-5
117 e.g. pootlewizz: review: 2-99,101"""
e71ee4eb » dwaynebailey 2006-11-02 Place pootlestatistics clas... 118 assignsmtime = statistics.getmodtime(self.assignsfilename)
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 119 if assignsmtime == getattr(self, "assignsmtime", None):
120 return
609fc3b0 » andreaspauley 2007-02-05 Close files after opening t... 121 assignsfile = open(self.assignsfilename, "r")
122 assignsstring = assignsfile.read()
123 assignsfile.close()
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 124 poassigns = {}
53becf1a » davidfraser 2005-05-20 added sanity check on item ... 125 itemcount = len(getattr(self, "classify", {}).get("total", []))
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 126 for line in assignsstring.split("\n"):
127 if not line.strip():
128 continue
129 if not line.count(":") == 2:
2713b3cf » davidfraser 2005-05-20 clarify invalid assigns mes... 130 print "invalid assigns line in %s: %r" % (self.assignsfilename, line)
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 131 continue
132 username, action, itemranges = line.split(":", 2)
78e88287 » friedelwolff 2006-08-31 Use unicode goalnames and e... 133 username, action = username.strip().decode('utf-8'), action.strip().decode('utf-8')
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 134 if not username in poassigns:
135 poassigns[username] = {}
136 userassigns = poassigns[username]
137 if not action in userassigns:
138 userassigns[action] = []
139 items = userassigns[action]
140 for itemrange in itemranges.split(","):
141 if "-" in itemrange:
2713b3cf » davidfraser 2005-05-20 clarify invalid assigns mes... 142 if not itemrange.count("-") == 1:
143 print "invalid assigns range in %s: %r (from line %r)" % (self.assignsfilename, itemrange, line)
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 144 continue
145 itemstart, itemstop = [int(item.strip()) for item in itemrange.split("-", 1)]
146 items.extend(range(itemstart, itemstop+1))
147 else:
148 item = int(itemrange.strip())
149 items.append(item)
53becf1a » davidfraser 2005-05-20 added sanity check on item ... 150 if itemcount:
151 items = [item for item in items if 0 <= item < itemcount]
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 152 userassigns[action] = items
153 return poassigns
154
155 def assignto(self, item, username, action):
156 """assigns the item to the given username for the given action"""
157 userassigns = self.assigns.setdefault(username, {})
158 items = userassigns.setdefault(action, [])
159 if item not in items:
160 items.append(item)
161 self.saveassigns()
162
65dbd9a1 » davidfraser 2005-05-20 added method reassignpoitem... 163 def unassign(self, item, username=None, action=None):
164 """removes assignments of the item to the given username (or all users) for the given action (or all actions)"""
165 if username is None:
166 usernames = self.assigns.keys()
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 167 else:
65dbd9a1 » davidfraser 2005-05-20 added method reassignpoitem... 168 usernames = [username]
169 for username in usernames:
170 userassigns = self.assigns.setdefault(username, {})
171 if action is None:
172 itemlist = [userassigns.get(action, []) for action in userassigns]
173 else:
174 itemlist = [userassigns.get(action, [])]
175 for items in itemlist:
176 if item in items:
177 items.remove(item)
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 178 self.saveassigns()
179
180 def saveassigns(self):
181 """saves the current assigns to file"""
182 # assumes self.assigns is up to date
183 assignstrings = []
184 usernames = self.assigns.keys()
185 usernames.sort()
186 for username in usernames:
187 actions = self.assigns[username].keys()
188 actions.sort()
189 for action in actions:
190 items = self.assigns[username][action]
191 items.sort()
192 if items:
193 lastitem = None
194 rangestart = None
78e88287 » friedelwolff 2006-08-31 Use unicode goalnames and e... 195 assignstring = "%s: %s: " % (username.encode('utf-8'), action.encode('utf-8'))
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 196 for item in items:
197 if item - 1 == lastitem:
198 if rangestart is None:
199 rangestart = lastitem
200 else:
201 if rangestart is not None:
202 assignstring += "-%d" % lastitem
203 rangestart = None
204 if lastitem is None:
205 assignstring += "%d" % item
206 else:
207 assignstring += ",%d" % item
208 lastitem = item
209 if rangestart is not None:
210 assignstring += "-%d" % lastitem
211 assignstrings.append(assignstring + "\n")
212 assignsfile = open(self.assignsfilename, "w")
213 assignsfile.writelines(assignstrings)
214 assignsfile.close()
215
7e84177f » friedelwolff 2006-10-06 Move some pootlefile functi... 216 def getunassigned(self, action=None):
217 """gets all strings that are unassigned (for the given action if given)"""
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 218 unassigneditems = range(0, self.basefile.statistics.getitemslen())
78cf21ad » andreaspauley 2007-01-16 General code improvements t... 219 self.assigns = self.getassigns()
7e84177f » friedelwolff 2006-10-06 Move some pootlefile functi... 220 for username in self.assigns:
221 if action is not None:
222 assigneditems = self.assigns[username].get(action, [])
223 else:
224 assigneditems = []
225 for action, actionitems in self.assigns[username].iteritems():
226 assigneditems += actionitems
227 unassigneditems = [item for item in unassigneditems if item not in assigneditems]
228 return unassigneditems
229
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 230 def finditems(self, search):
231 """returns items that match the .assignedto and/or .assignedaction criteria in the searchobject"""
232 # search.assignedto == [None] means assigned to nobody
233 if search.assignedto == [None]:
234 assignitems = self.getunassigned(search.assignedaction)
235 else:
236 # filter based on assign criteria
237 assigns = self.getassigns()
238 if search.assignedto:
239 usernames = [search.assignedto]
240 else:
241 usernames = assigns.iterkeys()
242 assignitems = []
243 for username in usernames:
244 if search.assignedaction:
245 actionitems = assigns[username].get(search.assignedaction, [])
246 assignitems.extend(actionitems)
247 else:
248 for actionitems in assigns[username].itervalues():
249 assignitems.extend(actionitems)
250 return assignitems
251
f008f9b5 » friedelwolff 2006-11-07 Various fixes to make all t... 252 class pootlefile(Wrapper):
d07801d0 » friedelwolff 2006-10-12 Use a factory to construct ... 253 """this represents a pootle-managed file and its associated files"""
0800d3d2 » friedelwolff 2006-11-06 Do away with pootleunit and... 254 innerclass = po.pofile
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 255 x_generator = "Pootle %s" % __version__.ver
256 def __init__(self, project=None, pofilename=None, generatestats=True):
d07801d0 » friedelwolff 2006-10-12 Use a factory to construct ... 257 if pofilename:
02d5fde3 » friedelwolff 2006-11-16 Use a local variable for in... 258 innerclass = factory.getclass(pofilename)
259 innerobj = innerclass()
f008f9b5 » friedelwolff 2006-11-07 Various fixes to make all t... 260 self.__innerobj__ = innerobj
261 self.UnitClass = innerobj.UnitClass
d07801d0 » friedelwolff 2006-10-12 Use a factory to construct ... 262
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 263 self.pofilename = pofilename
264 if project is None:
265 from Pootle import projects
266 self.project = projects.DummyProject(None)
267 self.checker = None
268 self.filename = self.pofilename
269 else:
270 self.project = project
271 self.checker = self.project.checker
272 self.filename = os.path.join(self.project.podir, self.pofilename)
273
274 self.lockedfile = LockedFile(self.filename)
275 # we delay parsing until it is required
276 self.pomtime = None
277 self.assigns = pootleassigns(self)
278
279 self.pendingfilename = self.filename + os.extsep + "pending"
280 self.pendingfile = None
e71ee4eb » dwaynebailey 2006-11-02 Place pootlestatistics clas... 281 self.statistics = statistics.pootlestatistics(self, generatestats)
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 282 self.tmfilename = self.filename + os.extsep + "tm"
283 # we delay parsing until it is required
284 self.pomtime = None
285 self.tracker = timecache.timecache(20*60)
286
287 def __str__(self):
288 return self.__innerobj__.__str__()
289
290 def parsestring(cls, storestring):
291 newstore = cls()
292 newstore.parse(storestring)
293 return newstore
294 parsestring = classmethod(parsestring)
295
f008f9b5 » friedelwolff 2006-11-07 Various fixes to make all t... 296 def parsefile(cls, storefile):
297 """Reads the given file (or opens the given filename) and parses back to an object"""
298 if isinstance(storefile, basestring):
299 storefile = open(storefile, "r")
300 if "r" in getattr(storefile, "mode", "r"):
301 storestring = storefile.read()
302 else:
303 storestring = ""
304 return cls.parsestring(storestring)
305 parsefile = classmethod(parsefile)
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 306
5a398d38 » friedelwolff 2006-11-13 Add pootlefile.getheaderplu... 307 def getheaderplural(self):
308 """returns values for nplural and plural values. It tries to see if the
309 file has it specified (in a po header or similar)."""
310 method = getattr(self.__innerobj__, "getheaderplural", None)
311 if method and callable(method):
312 return self.__innerobj__.getheaderplural()
313 else:
314 return None, None
315
c7571240 » friedelwolff 2006-11-14 provide .updateheaderplural... 316 def updateheaderplural(self, *args, **kwargs):
317 """updates the file header. If there is an updateheader function in the
318 underlying store it will be delegated there."""
319 method = getattr(self.__innerobj__, "updateheaderplural", None)
320 if method and callable(method):
321 self.__innerobj__.updateheaderplural(*args, **kwargs)
322
323 def updateheader(self, **kwargs):
324 """updates the file header. If there is an updateheader function in the
325 underlying store it will be delegated there."""
326 method = getattr(self.__innerobj__, "updateheader", None)
327 if method and callable(method):
328 self.__innerobj__.updateheader(**kwargs)
329
c87a05b0 » friedelwolff 2006-10-06 Move some more pootlefile f... 330 def readpendingfile(self):
d07801d0 » friedelwolff 2006-10-12 Use a factory to construct ... 331 """reads and parses the pending file corresponding to this file"""
c87a05b0 » friedelwolff 2006-10-06 Move some more pootlefile f... 332 if os.path.exists(self.pendingfilename):
e71ee4eb » dwaynebailey 2006-11-02 Place pootlestatistics clas... 333 pendingmtime = statistics.getmodtime(self.pendingfilename)
c87a05b0 » friedelwolff 2006-10-06 Move some more pootlefile f... 334 if pendingmtime == getattr(self, "pendingmtime", None):
335 return
336 inputfile = open(self.pendingfilename, "r")
d07801d0 » friedelwolff 2006-10-12 Use a factory to construct ... 337 self.pendingmtime, self.pendingfile = pendingmtime, factory.getobject(inputfile, ignore=".pending")
c87a05b0 » friedelwolff 2006-10-06 Move some more pootlefile f... 338 if self.pomtime:
339 self.reclassifysuggestions()
340 else:
0800d3d2 » friedelwolff 2006-11-06 Do away with pootleunit and... 341 self.pendingfile = po.pofile()
c87a05b0 » friedelwolff 2006-10-06 Move some more pootlefile f... 342 self.savependingfile()
7e84177f » friedelwolff 2006-10-06 Move some pootlefile functi... 343
c87a05b0 » friedelwolff 2006-10-06 Move some more pootlefile f... 344 def savependingfile(self):
345 """saves changes to disk..."""
346 output = str(self.pendingfile)
0c632acf » friedelwolff 2007-05-07 Close pending file after wr... 347 outputfile = open(self.pendingfilename, "w")
348 outputfile.write(output)
349 outputfile.close()
e71ee4eb » dwaynebailey 2006-11-02 Place pootlestatistics clas... 350 self.pendingmtime = statistics.getmodtime(self.pendingfilename)
7e84177f » friedelwolff 2006-10-06 Move some pootlefile functi... 351
c87a05b0 » friedelwolff 2006-10-06 Move some more pootlefile f... 352 def readtmfile(self):
d07801d0 » friedelwolff 2006-10-12 Use a factory to construct ... 353 """reads and parses the tm file corresponding to this file"""
c87a05b0 » friedelwolff 2006-10-06 Move some more pootlefile f... 354 if os.path.exists(self.tmfilename):
e71ee4eb » dwaynebailey 2006-11-02 Place pootlestatistics clas... 355 tmmtime = statistics.getmodtime(self.tmfilename)
c87a05b0 » friedelwolff 2006-10-06 Move some more pootlefile f... 356 if tmmtime == getattr(self, "tmmtime", None):
357 return
358 inputfile = open(self.tmfilename, "r")
d07801d0 » friedelwolff 2006-10-12 Use a factory to construct ... 359 self.tmmtime, self.tmfile = tmmtime, factory.getobject(inputfile, ignore=".tm")
c87a05b0 » friedelwolff 2006-10-06 Move some more pootlefile f... 360 else:
0800d3d2 » friedelwolff 2006-11-06 Do away with pootleunit and... 361 self.tmfile = po.pofile()
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 362
363 def reclassifysuggestions(self):
364 """shortcut to only update classification of has-suggestion for all items"""
365 suggitems = []
f82628f7 » friedelwolff 2006-04-28 Rename: 366 sugglocations = {}
dbcc8d96 » friedelwolff 2006-02-20 pootlefile.pounits -> pootl... 367 for thesugg in self.pendingfile.units:
f82628f7 » friedelwolff 2006-04-28 Rename: 368 locations = tuple(thesugg.getlocations())
369 sugglocations[locations] = thesugg
c4d6f5be » friedelwolff 2006-10-03 Renaming for consistency: e... 370 suggitems = [item for item in self.transunits if tuple(item.getlocations()) in sugglocations]
bb29a717 » friedelwolff 2006-12-27 Allow XLIFF suggestions usi... 371 havesuggestions = self.statistics.classify["has-suggestion"]
c4d6f5be » friedelwolff 2006-10-03 Renaming for consistency: e... 372 for item, poel in enumerate(self.transunits):
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 373 if (poel in suggitems) != (item in havesuggestions):
374 if poel in suggitems:
375 havesuggestions.append(item)
376 else:
377 havesuggestions.remove(item)
378 havesuggestions.sort()
bb29a717 » friedelwolff 2006-12-27 Allow XLIFF suggestions usi... 379 self.statistics.calcstats()
380 self.statistics.savestats()
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 381
382 def getsuggestions(self, item):
bb29a717 » friedelwolff 2006-12-27 Allow XLIFF suggestions usi... 383 """find all the suggestion items submitted for the given item"""
b9c4dfe0 » andreaspauley 2006-11-23 Updated Pootle to work with... 384 unit = self.transunits[item]
bb29a717 » friedelwolff 2006-12-27 Allow XLIFF suggestions usi... 385 if isinstance(unit, xliff.xliffunit):
386 return unit.getalttrans()
387
b9c4dfe0 » andreaspauley 2006-11-23 Updated Pootle to work with... 388 locations = unit.getlocations()
bb29a717 » friedelwolff 2006-12-27 Allow XLIFF suggestions usi... 389 self.readpendingfile()
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 390 # TODO: review the matching method
f82628f7 » friedelwolff 2006-04-28 Rename: 391 suggestpos = [suggestpo for suggestpo in self.pendingfile.units if suggestpo.getlocations() == locations]
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 392 return suggestpos
393
f5aa1184 » varsist 2006-09-18 removal of redundant unquot... 394 def addsuggestion(self, item, suggtarget, username):
bb29a717 » friedelwolff 2006-12-27 Allow XLIFF suggestions usi... 395 """adds a new suggestion for the given item"""
b9c4dfe0 » andreaspauley 2006-11-23 Updated Pootle to work with... 396 unit = self.transunits[item]
bb29a717 » friedelwolff 2006-12-27 Allow XLIFF suggestions usi... 397 if isinstance(unit, xliff.xliffunit):
a57b46db » andreaspauley 2007-03-29 Fix adding of suggestions i... 398 if isinstance(suggtarget, list) and (len(suggtarget) > 0):
399 suggtarget = suggtarget[0]
bb29a717 » friedelwolff 2006-12-27 Allow XLIFF suggestions usi... 400 unit.addalttrans(suggtarget, origin=username)
401 self.statistics.reclassifyunit(item)
402 self.savepofile()
403 return
404
405 self.readpendingfile()
b9c4dfe0 » andreaspauley 2006-11-23 Updated Pootle to work with... 406 newpo = unit.copy()
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 407 if username is not None:
2a84ba23 » friedelwolff 2006-08-15 Add real KDE comments with ... 408 newpo.msgidcomments.append('"_: suggested by %s\\n"' % username)
f5aa1184 » varsist 2006-09-18 removal of redundant unquot... 409 newpo.target = suggtarget
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 410 newpo.markfuzzy(False)
951a2852 » friedelwolff 2008-03-13 Use .addunit() instead of .... 411 self.pendingfile.addunit(newpo)
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 412 self.savependingfile()
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 413 self.statistics.reclassifyunit(item)
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 414
415 def deletesuggestion(self, item, suggitem):
416 """removes the suggestion from the pending file"""
b9c4dfe0 » andreaspauley 2006-11-23 Updated Pootle to work with... 417 unit = self.transunits[item]
bb29a717 » friedelwolff 2006-12-27 Allow XLIFF suggestions usi... 418 if hasattr(unit, "xmlelement"):
419 suggestions = self.getsuggestions(item)
420 unit.delalttrans(suggestions[suggitem])
f2e9e34a » andreaspauley 2007-03-29 Save the file after an alt-... 421 self.savepofile()
bb29a717 » friedelwolff 2006-12-27 Allow XLIFF suggestions usi... 422 else:
423 self.readpendingfile()
424 locations = unit.getlocations()
425 # TODO: remove the suggestion in a less brutal manner
426 pendingitems = [pendingitem for pendingitem, suggestpo in enumerate(self.pendingfile.units) if suggestpo.getlocations() == locations]
427 pendingitem = pendingitems[suggitem]
428 del self.pendingfile.units[pendingitem]
429 self.savependingfile()
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 430 self.statistics.reclassifyunit(item)
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 431
bb29a717 » friedelwolff 2006-12-27 Allow XLIFF suggestions usi... 432 def getsuggester(self, item, suggitem):
433 """returns who suggested the given item's suggitem if recorded, else None"""
434 unit = self.getsuggestions(item)[suggitem]
435 if hasattr(unit, "xmlelement"):
436 return unit.xmlelement.getAttribute("origin")
437
438 for msgidcomment in unit.msgidcomments:
439 if msgidcomment.find("suggested by ") != -1:
440 suggestedby = po.unquotefrompo([msgidcomment]).replace("_:", "", 1).replace("suggested by ", "", 1).strip()
441 return suggestedby
442 return None
443
6c404e8c » friedelwolff 2006-06-27 Support for .po.tm files wi... 444 def gettmsuggestions(self, item):
af8592b1 » friedelwolff 2006-07-26 Update with comments 445 """find all the tmsuggestion items submitted for the given item"""
6c404e8c » friedelwolff 2006-06-27 Support for .po.tm files wi... 446 self.readtmfile()
b9c4dfe0 » andreaspauley 2006-11-23 Updated Pootle to work with... 447 unit = self.transunits[item]
448 locations = unit.getlocations()
6c404e8c » friedelwolff 2006-06-27 Support for .po.tm files wi... 449 # TODO: review the matching method
af8592b1 » friedelwolff 2006-07-26 Update with comments 450 # Can't simply use the location index, because we want multiple matches
6c404e8c » friedelwolff 2006-06-27 Support for .po.tm files wi... 451 suggestpos = [suggestpo for suggestpo in self.tmfile.units if suggestpo.getlocations() == locations]
452 return suggestpos
453
c87a05b0 » friedelwolff 2006-10-06 Move some more pootlefile f... 454 def track(self, item, message):
455 """sets the tracker message for the given item"""
456 self.tracker[item] = message
457
458 def readpofile(self):
d07801d0 » friedelwolff 2006-10-12 Use a factory to construct ... 459 """reads and parses the main file"""
c87a05b0 » friedelwolff 2006-10-06 Move some more pootlefile f... 460 # make sure encoding is reset so it is read from the file
461 self.encoding = None
462 self.units = []
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 463 pomtime, filecontents = self.lockedfile.getcontents()
464 # note: we rely on this not resetting the filename, which we set earlier, when given a string
465 self.parse(filecontents)
c87a05b0 » friedelwolff 2006-10-06 Move some more pootlefile f... 466 # we ignore all the headers by using this filtered set
467 self.transunits = [poel for poel in self.units if not (poel.isheader() or poel.isblank())]
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 468 self.statistics.classifyunits()
c87a05b0 » friedelwolff 2006-10-06 Move some more pootlefile f... 469 self.pomtime = pomtime
470
471 def savepofile(self):
472 """saves changes to the main file to disk..."""
473 output = str(self)
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 474 self.pomtime = self.lockedfile.writecontents(output)
c87a05b0 » friedelwolff 2006-10-06 Move some more pootlefile f... 475
476 def pofreshen(self):
477 """makes sure we have a freshly parsed pofile"""
478 if not os.path.exists(self.filename):
479 # the file has been removed, update the project index (and fail below)
480 self.project.scanpofiles()
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 481 if self.pomtime != self.lockedfile.readmodtime():
c87a05b0 » friedelwolff 2006-10-06 Move some more pootlefile f... 482 self.readpofile()
483
484 def getoutput(self):
485 """returns pofile output"""
486 self.pofreshen()
487 return super(pootlefile, self).getoutput()
488
7a7bac4b » andreaspauley 2006-11-02 Renamed setmsgstr() to upda... 489 def updateunit(self, item, newvalues, userprefs, languageprefs):
c87a05b0 » friedelwolff 2006-10-06 Move some more pootlefile f... 490 """updates a translation with a new target value"""
491 self.pofreshen()
b9c4dfe0 » andreaspauley 2006-11-23 Updated Pootle to work with... 492 unit = self.transunits[item]
7a7bac4b » andreaspauley 2006-11-02 Renamed setmsgstr() to upda... 493
494 if newvalues.has_key("target"):
b9c4dfe0 » andreaspauley 2006-11-23 Updated Pootle to work with... 495 unit.target = newvalues["target"]
7a7bac4b » andreaspauley 2006-11-02 Renamed setmsgstr() to upda... 496 if newvalues.has_key("fuzzy"):
b9c4dfe0 » andreaspauley 2006-11-23 Updated Pootle to work with... 497 unit.markfuzzy(newvalues["fuzzy"])
3b2d571f » andreaspauley 2006-11-10 Added the ability to edit c... 498 if newvalues.has_key("translator_comments"):
b9c4dfe0 » andreaspauley 2006-11-23 Updated Pootle to work with... 499 unit.removenotes()
3b2d571f » andreaspauley 2006-11-10 Added the ability to edit c... 500 if newvalues["translator_comments"]:
b9c4dfe0 » andreaspauley 2006-11-23 Updated Pootle to work with... 501 unit.addnote(newvalues["translator_comments"])
7a7bac4b » andreaspauley 2006-11-02 Renamed setmsgstr() to upda... 502
c87a05b0 » friedelwolff 2006-10-06 Move some more pootlefile f... 503 po_revision_date = time.strftime("%F %H:%M%z")
504 headerupdates = {"PO_Revision_Date": po_revision_date, "X_Generator": self.x_generator}
505 if userprefs:
506 if getattr(userprefs, "name", None) and getattr(userprefs, "email", None):
507 headerupdates["Last_Translator"] = "%s <%s>" % (userprefs.name, userprefs.email)
508 self.updateheader(add=True, **headerupdates)
509 if languageprefs:
510 nplurals = getattr(languageprefs, "nplurals", None)
511 pluralequation = getattr(languageprefs, "pluralequation", None)
512 if nplurals and pluralequation:
513 self.updateheaderplural(nplurals, pluralequation)
514 self.savepofile()
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 515 self.statistics.reclassifyunit(item)
c87a05b0 » friedelwolff 2006-10-06 Move some more pootlefile f... 516
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 517 def iteritems(self, search, lastitem=None):
518 """iterates through the items in this pofile starting after the given lastitem, using the given search"""
519 # update stats if required
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 520 self.statistics.getstats()
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 521 if lastitem is None:
522 minitem = 0
523 else:
524 minitem = lastitem + 1
c4d6f5be » friedelwolff 2006-10-03 Renaming for consistency: e... 525 maxitem = len(self.transunits)
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 526 validitems = range(minitem, maxitem)
c4d6f5be » friedelwolff 2006-10-03 Renaming for consistency: e... 527 if search.assignedto or search.assignedaction:
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 528 assignitems = self.assigns.finditems(search)
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 529 validitems = [item for item in validitems if item in assignitems]
530 # loop through, filtering on matchnames if required
531 for item in validitems:
532 if not search.matchnames:
533 yield item
534 for name in search.matchnames:
76fa87f0 » friedelwolff 2006-10-10 Complete the merge of the r... 535 if item in self.statistics.classify[name]:
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 536 yield item
537
e0296a1e » friedelwolff 2006-07-27 Implement XLIFF uploading. ... 538 def matchitems(self, newfile, uselocations=False):
539 """matches up corresponding items in this pofile with the given newfile, and returns tuples of matching poitems (None if no match found)"""
f82628f7 » friedelwolff 2006-04-28 Rename: 540 if not hasattr(self, "sourceindex"):
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 541 self.makeindex()
e0296a1e » friedelwolff 2006-07-27 Implement XLIFF uploading. ... 542 if not hasattr(newfile, "sourceindex"):
543 newfile.makeindex()
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 544 matches = []
e0296a1e » friedelwolff 2006-07-27 Implement XLIFF uploading. ... 545 for newpo in newfile.units:
0ba87081 » friedelwolff 2006-04-05 don't add second header whe... 546 if newpo.isheader():
547 continue
abcfacfe » friedelwolff 2006-03-13 rename source(s) (i.t.o. so... 548 foundid = False
f82628f7 » friedelwolff 2006-04-28 Rename: 549 if uselocations:
550 newlocations = newpo.getlocations()
551 mergedlocations = []
552 for location in newlocations:
553 if location in mergedlocations:
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 554 continue
f82628f7 » friedelwolff 2006-04-28 Rename: 555 if location in self.locationindex:
556 oldpo = self.locationindex[location]
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 557 if oldpo is not None:
abcfacfe » friedelwolff 2006-03-13 rename source(s) (i.t.o. so... 558 foundid = True
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 559 matches.append((oldpo, newpo))
f82628f7 » friedelwolff 2006-04-28 Rename: 560 mergedlocations.append(location)
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 561 continue
abcfacfe » friedelwolff 2006-03-13 rename source(s) (i.t.o. so... 562 if not foundid:
25a5e6c0 » friedelwolff 2006-07-27 Search in sourceindex using... 563 # We can't use the multistring, because it might contain more than two
564 # entries in a PO xliff file. Rather use the singular.
f5aa1184 » varsist 2006-09-18 removal of redundant unquot... 565 source = unicode(newpo.source)
566 if source in self.sourceindex:
567 oldpo = self.sourceindex[source]
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 568 matches.append((oldpo, newpo))
569 else:
570 matches.append((None, newpo))
571 # find items that have been removed
572 matcheditems = [oldpo for oldpo, newpo in matches if oldpo]
dbcc8d96 » friedelwolff 2006-02-20 pootlefile.pounits -> pootl... 573 for oldpo in self.units:
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 574 if not oldpo in matcheditems:
575 matches.append((oldpo, None))
576 return matches
577
2d2ea731 » friedelwolff 2008-02-29 Provide an upload box for u... 578 def mergeitem(self, oldpo, newpo, username, suggest=False):
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 579 """merges any changes from newpo into oldpo"""
574eec06 » friedelwolff 2006-03-03 Use more of base class and ... 580 unchanged = oldpo.target == newpo.target
2d2ea731 » friedelwolff 2008-02-29 Provide an upload box for u... 581
582 if not suggest and (not oldpo.target or not newpo.target or oldpo.isheader() or newpo.isheader() or unchanged):
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 583 oldpo.merge(newpo)
2d2ea731 » friedelwolff 2008-02-29 Provide an upload box for u... 584 elif not unchanged:
951a2852 » friedelwolff 2008-03-13 Use .addunit() instead of .... 585 #XXX: this is very inefficient!
c4d6f5be » friedelwolff 2006-10-03 Renaming for consistency: e... 586 for item, matchpo in enumerate(self.transunits):
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 587 if matchpo == oldpo:
e0296a1e » friedelwolff 2006-07-27 Implement XLIFF uploading. ... 588 strings = getattr(newpo.target, "strings", [newpo.target])
589 self.addsuggestion(item, strings, username)
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 590 return
591 raise KeyError("Could not find item for merge")
592
2d2ea731 » friedelwolff 2008-02-29 Provide an upload box for u... 593 def mergefile(self, newfile, username, allownewstrings=True, suggestions=False):
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 594 """make sure each msgid is unique ; merge comments etc from duplicates into original"""
595 self.makeindex()
e0296a1e » friedelwolff 2006-07-27 Implement XLIFF uploading. ... 596 matches = self.matchitems(newfile)
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 597 for oldpo, newpo in matches:
2d2ea731 » friedelwolff 2008-02-29 Provide an upload box for u... 598 if suggestions:
599 if oldpo and newpo:
600 self.mergeitem(oldpo, newpo, username, suggest=True)
601 continue
602
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 603 if oldpo is None:
604 if allownewstrings:
e0296a1e » friedelwolff 2006-07-27 Implement XLIFF uploading. ... 605 if isinstance(newpo, po.pounit):
951a2852 » friedelwolff 2008-03-13 Use .addunit() instead of .... 606 self.addunit(newpo)
e0296a1e » friedelwolff 2006-07-27 Implement XLIFF uploading. ... 607 else:
951a2852 » friedelwolff 2008-03-13 Use .addunit() instead of .... 608 self.addunit(self.UnitClass.buildfromunit(newpo))
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 609 elif newpo is None:
610 # TODO: mark the old one as obsolete
611 pass
612 else:
613 self.mergeitem(oldpo, newpo, username)
abcfacfe » friedelwolff 2006-03-13 rename source(s) (i.t.o. so... 614 # we invariably want to get the ids (source locations) from the newpo
e0296a1e » friedelwolff 2006-07-27 Implement XLIFF uploading. ... 615 if hasattr(newpo, "sourcecomments"):
616 oldpo.sourcecomments = newpo.sourcecomments
617
2d2ea731 » friedelwolff 2008-02-29 Provide an upload box for u... 618 if not isinstance(newfile, po.pofile) or suggestions:
e0296a1e » friedelwolff 2006-07-27 Implement XLIFF uploading. ... 619 #TODO: We don't support updating the header yet.
620 self.savepofile()
621 # the easiest way to recalculate everything
622 self.readpofile()
623 return
f9512f37 » friedelwolff 2006-04-05 update header and header co... 624
625 #Let's update selected header entries. Only the ones listed below, and ones
626 #that are empty in self can be updated. The check in header_order is just
627 #a basic sanity check so that people don't insert garbage.
628 updatekeys = ['Content-Type',
629 'POT-Creation-Date',
630 'Last-Translator',
631 'Project-Id-Version',
632 'PO-Revision-Date',
633 'Language-Team']
634 headerstoaccept = {}
635 ownheader = self.parseheader()
e0296a1e » friedelwolff 2006-07-27 Implement XLIFF uploading. ... 636 for (key, value) in newfile.parseheader().items():
d8ecb7e9 » andreaspauley 2007-01-19 Corrected a reference to he... 637 if key in updatekeys or (not key in ownheader or not ownheader[key]) and key in po.pofile.header_order:
f9512f37 » friedelwolff 2006-04-05 update header and header co... 638 headerstoaccept[key] = value
639 self.updateheader(add=True, **headerstoaccept)
640
641 #Now update the comments above the header:
642 header = self.header()
e0296a1e » friedelwolff 2006-07-27 Implement XLIFF uploading. ... 643 newheader = newfile.header()
f9512f37 » friedelwolff 2006-04-05 update header and header co... 644 if header is None and not newheader is None:
1f9c3413 » friedelwolff 2006-06-20 Make pootlefile work with r... 645 header = self.UnitClass("", encoding=self.encoding)
f9512f37 » friedelwolff 2006-04-05 update header and header co... 646 header.target = ""
c4d6f5be » friedelwolff 2006-10-03 Renaming for consistency: e... 647 if header:
f10f15db » friedelwolff 2007-11-13 Update now that ._initallco... 648 header._initallcomments(blankall=True)
f9512f37 » friedelwolff 2006-04-05 update header and header co... 649 if newheader:
650 for i in range(len(header.allcomments)):
651 header.allcomments[i].extend(newheader.allcomments[i])
652
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 653 self.savepofile()
fa5719d4 » friedelwolff 2006-02-24 joinlinebreaks defaults to ... 654 # the easiest way to recalculate everything
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 655 self.readpofile()
656
657 class Search:
20a2bb24 » davidfraser 2006-02-02 use new poelement encoding ... 658 """an object containing all the searching information"""
a079afa1 » davidfraser 2005-04-20 moved from translate/pootle... 659 def __init__(self, dirfilter=None, matchnames=[], assignedto=None, assignedaction=None, searchtext=None):
660 self.dirfilter = dirfilter
661 self.matchnames = matchnames
662 self.assignedto = assignedto
663 self.assignedaction = assignedaction
664 self.searchtext = searchtext
665
6daa67b7 » davidfraser 2005-06-23 add a copy method to Search 666 def copy(self):
667 """returns a copy of this search"""
668 return Search(self.dirfilter, self.matchnames, self.assignedto, self.assignedaction, self.searchtext)
669