1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 import os
22 from lxml import etree
23 from StringIO import StringIO
24
25 __all__ = ['FileExistsInProjectError', 'FileNotInProjectError', 'ProjectStore']
26
27
30
33
34
36 """Basic project file container."""
37
38
40 self._files = {}
41 self._sourcefiles = []
42 self._targetfiles = []
43 self._transfiles = []
44 self.settings = {}
45 self.convert_map = {}
46
47
48
49
50
51
52
53
54
55
56 self.TYPE_INFO = {
57
58 'f_prefix': {
59 'src': 'sources/',
60 'tgt': 'targets/',
61 'trans': 'trans/'
62 },
63
64 'lists': {
65 'src': self._sourcefiles,
66 'tgt': self._targetfiles,
67 'trans': self._transfiles,
68 },
69
70 'next_type': {
71 'src': 'trans',
72 'trans': 'tgt',
73 'tgt': None,
74 },
75
76 'settings': {
77 'src': 'sources',
78 'tgt': 'targets',
79 'trans': 'transfiles',
80 }
81 }
82
84 try:
85 self.close()
86 except Exception:
87 pass
88
89
90
92 """Read-only access to C{self._sourcefiles}."""
93 return tuple(self._sourcefiles)
94 sourcefiles = property(_get_sourcefiles)
95
97 """Read-only access to C{self._targetfiles}."""
98 return tuple(self._targetfiles)
99 targetfiles = property(_get_targetfiles)
100
102 """Read-only access to C{self._transfiles}."""
103 return tuple(self._transfiles)
104 transfiles = property(_get_transfiles)
105
106
107
109 """@returns C{True} if C{lhs} is a file name or file object in the project store."""
110 return lhs in self._sourcefiles or \
111 lhs in self._targetfiles or \
112 lhs in self._transfiles or \
113 lhs in self._files or \
114 lhs in self._files.values()
115
116
117
118 - def append_file(self, afile, fname, ftype='trans', delete_orig=False):
119 """Append the given file to the project with the given filename, marked
120 to be of type C{ftype} ('src', 'trans', 'tgt').
121
122 @type delete_orig: bool
123 @param delete_orig: Whether or not the original (given) file should
124 be deleted after being appended. This is set to
125 C{True} by L{project.convert_forward()}. Not
126 used in this class."""
127 if not ftype in self.TYPE_INFO['f_prefix']:
128 raise ValueError('Invalid file type: %s' % (ftype))
129
130 if isinstance(afile, basestring) and os.path.isfile(afile) and not fname:
131
132 fname, afile = afile, open(afile)
133
134
135 realfname = fname
136 if realfname is None or not os.path.isfile(realfname):
137 realfname = getattr(afile, 'name', None)
138 if realfname is None or not os.path.isfile(realfname):
139 realfname = getattr(afile, 'filename', None)
140 if not realfname or not os.path.isfile(realfname):
141 realfname = None
142
143
144 if not fname:
145 fname = getattr(afile, 'name', None)
146 if not fname:
147 fname = getattr(afile, 'filename', None)
148
149 fname = self._fix_type_filename(ftype, fname)
150
151 if not fname:
152 raise ValueError('Could not deduce file name and none given')
153 if fname in self._files:
154 raise FileExistsInProjectError(fname)
155
156 if realfname is not None and os.path.isfile(realfname):
157 self._files[fname] = realfname
158 else:
159 self._files[fname] = afile
160 self.TYPE_INFO['lists'][ftype].append(fname)
161
162 return afile, fname
163
166
169
171 return self.append_file(afile, fname, ftype='trans')
172
174 """Remove the file with the given project name from the project.
175 If the file type ('src', 'trans' or 'tgt') is not given, it is
176 guessed."""
177 if fname not in self._files:
178 raise FileNotInProjectError(fname)
179 if not ftype:
180
181 for ft, prefix in self.TYPE_INFO['f_prefix'].items():
182 if fname.startswith(prefix):
183 ftype = ft
184 break
185
186 self.TYPE_INFO['lists'][ftype].remove(fname)
187 if self._files[fname] and hasattr(self._files[fname], 'close'):
188 self._files[fname].close()
189 del self._files[fname]
190
193
196
199
202
204 """Retrieve the file with the given name from the project store.
205
206 The file is looked up in the C{self._files} dictionary. The values
207 in this dictionary may be C{None}, to indicate that the file is not
208 cacheable and needs to be retrieved in a special way. This special
209 way must be defined in this method of sub-classes. The value may
210 also be a string, which indicates that it is a real file accessible
211 via C{open()}.
212
213 @type mode: str
214 @param mode: The mode in which to re-open the file (if it is closed)
215 @see BundleProjectStore.get_file"""
216 if fname not in self._files:
217 raise FileNotInProjectError(fname)
218
219 rfile = self._files[fname]
220 if isinstance(rfile, basestring):
221 rfile = open(rfile, 'rb')
222
223 if getattr(rfile, 'closed', False):
224 rfname = fname
225 if not os.path.isfile(rfname):
226 rfname = getattr(rfile, 'name', None)
227 if not rfile or not os.path.isfile(rfname):
228 rfname = getattr(rfile, 'filename', None)
229 if not rfile or not os.path.isfile(rfname):
230 raise IOError('Could not locate file: %s (%s)' % (rfile, fname))
231 rfile = open(rfname, mode)
232 self._files[fname] = rfile
233
234 return rfile
235
237 """Get the type of file ('src', 'trans', 'tgt') with the given name."""
238 for ftype in self.TYPE_INFO['lists']:
239 if fname in self.TYPE_INFO['lists'][ftype]:
240 return ftype
241 raise FileNotInProjectError(fname)
242
244 """Try and find a project file name for the given real file name."""
245 for fname in self._files:
246 if fname == realfname or self._files[fname] == realfname:
247 return fname
248 raise ValueError('Real file not in project store: %s' % (realfname))
249
250 - def load(self, *args, **kwargs):
251 """Load the project in some way. Undefined for this (base) class."""
252 pass
253
254 - def save(self, filename=None, *args, **kwargs):
255 """Save the project in some way. Undefined for this (base) class."""
256 pass
257
259 """Remove the project file with name C{pfname} and add the contents
260 from C{infile} to the project under the same file name.
261
262 @returns: the results from L{self.append_file}."""
263 ftype = self.get_filename_type(pfname)
264 self.remove_file(pfname)
265 self.append_file(infile, pfname, ftype)
266
268 """Strip the path from the filename and prepend the correct prefix."""
269 path, fname = os.path.split(fname)
270 return self.TYPE_INFO['f_prefix'][ftype] + fname
271
273 """@returns A XML string that represents the current settings."""
274 xml = etree.Element('translationproject')
275
276
277 if self._sourcefiles:
278 sources_el = etree.Element('sources')
279 for fname in self._sourcefiles:
280 src_el = etree.Element('filename')
281 src_el.text = fname
282 sources_el.append(src_el)
283 xml.append(sources_el)
284 if self._transfiles:
285 transfiles_el = etree.Element('transfiles')
286 for fname in self._transfiles:
287 trans_el = etree.Element('filename')
288 trans_el.text = fname
289 transfiles_el.append(trans_el)
290 xml.append(transfiles_el)
291 if self._targetfiles:
292 target_el = etree.Element('targets')
293 for fname in self._targetfiles:
294 tgt_el = etree.Element('filename')
295 tgt_el.text = fname
296 target_el.append(tgt_el)
297 xml.append(target_el)
298
299
300 if self.convert_map:
301 conversions_el = etree.Element('conversions')
302 for in_fname, (out_fname, templ_fname) in self.convert_map.iteritems():
303 if in_fname not in self._files or out_fname not in self._files:
304 continue
305 conv_el = etree.Element('conv')
306
307 input_el = etree.Element('input')
308 input_el.text = in_fname
309 conv_el.append(input_el)
310
311 output_el = etree.Element('output')
312 output_el.text = out_fname
313 conv_el.append(output_el)
314
315 if templ_fname:
316 templ_el = etree.Element('template')
317 templ_el.text = templ_fname
318 conv_el.append(templ_el)
319
320 conversions_el.append(conv_el)
321 xml.append(conversions_el)
322
323
324 if 'options' in self.settings:
325 options_el = etree.Element('options')
326 for option, value in self.settings['options'].items():
327 opt_el = etree.Element('option')
328 opt_el.attrib['name'] = option
329 opt_el.text = value
330 options_el.append(opt_el)
331 xml.append(options_el)
332
333 return etree.tostring(xml, pretty_print=True)
334
336 """Load project settings from the given XML string.
337 C{settingsxml} is parsed into a DOM tree (L{lxml.etree.fromstring})
338 which is then inspected."""
339 settings = {}
340 xml = etree.fromstring(settingsxml)
341
342
343 for section in ('sources', 'targets', 'transfiles'):
344 groupnode = xml.find(section)
345 if groupnode is None:
346 continue
347
348 settings[section] = []
349 for fnode in groupnode.getchildren():
350 settings[section].append(fnode.text)
351
352 conversions_el = xml.find('conversions')
353 if conversions_el is not None:
354 self.convert_map = {}
355 for conv_el in conversions_el.iterchildren():
356 in_fname, out_fname, templ_fname = None, None, None
357 for child_el in conv_el.iterchildren():
358 if child_el.tag == 'input':
359 in_fname = child_el.text
360 elif child_el.tag == 'output':
361 out_fname = child_el.text
362 elif child_el.tag == 'template':
363 templ_fname = child_el.text
364
365
366
367 in_found, out_found, templ_found = False, False, False
368 for section in ('sources', 'transfiles', 'targets'):
369 if section not in settings:
370 continue
371 if in_fname in settings[section]:
372 in_found = True
373 if out_fname in settings[section]:
374 out_found = True
375 if templ_fname and templ_fname in settings[section]:
376 templ_found = True
377 if in_found and out_found and (not templ_fname or templ_found):
378 self.convert_map[in_fname] = (out_fname, templ_fname)
379
380
381 groupnode = xml.find('options')
382 if groupnode is not None:
383 settings['options'] = {}
384 for opt in groupnode.iterchildren():
385 settings['options'][opt.attrib['name']] = opt.text
386
387 self.settings = settings
388