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