Package translate :: Package convert :: Module convert
[hide private]
[frames] | no frames]

Source Code for Module translate.convert.convert

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  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 
 21   
 22  """Handles converting of files between formats (used by translate.convert tools)""" 
 23   
 24  import os.path 
 25  try: 
 26      from cStringIO import StringIO 
 27  except ImportError: 
 28      from StringIO import StringIO 
 29   
 30  from translate.misc import optrecurse 
 31  # don't import optparse ourselves, get the version from optrecurse 
 32  optparse = optrecurse.optparse 
 33   
 34   
35 -class ConvertOptionParser(optrecurse.RecursiveOptionParser, object):
36 """a specialized Option Parser for convertor tools...""" 37
38 - def __init__(self, formats, usetemplates=False, usepots=False, allowmissingtemplate=False, description=None):
39 """construct the specialized Option Parser""" 40 optrecurse.RecursiveOptionParser.__init__(self, formats, usetemplates, 41 allowmissingtemplate=allowmissingtemplate, description=description) 42 self.usepots = usepots 43 self.setpotoption() 44 self.set_usage()
45
46 - def add_fuzzy_option(self, default=False):
47 """adds an option to include / exclude fuzzy translations""" 48 fuzzyhelp = "use translations marked fuzzy" 49 nofuzzyhelp = "don't use translations marked fuzzy" 50 if default: 51 fuzzyhelp += " (default)" 52 else: 53 nofuzzyhelp += " (default)" 54 self.add_option("", "--fuzzy", dest="includefuzzy", action="store_true", default=default, help=fuzzyhelp) 55 self.add_option("", "--nofuzzy", dest="includefuzzy", action="store_false", default=default, help=nofuzzyhelp) 56 self.passthrough.append("includefuzzy")
57
58 - def add_duplicates_option(self, default="msgctxt"):
59 """adds an option to say what to do with duplicate strings""" 60 self.add_option("", "--duplicates", dest="duplicatestyle", default=default, 61 type="choice", choices=["msgctxt", "merge"], 62 help="what to do with duplicate strings (identical source text): merge, msgctxt (default: '%s')" % default, metavar="DUPLICATESTYLE") 63 self.passthrough.append("duplicatestyle")
64
65 - def add_multifile_option(self, default="single"):
66 """adds an option to say how to split the po/pot files""" 67 self.add_option("", "--multifile", dest="multifilestyle", default=default, 68 type="choice", choices=["single", "toplevel", "onefile"], 69 help="how to split po/pot files (single, toplevel or onefile)", metavar="MULTIFILESTYLE") 70 self.passthrough.append("multifilestyle")
71
72 - def potifyformat(self, fileformat):
73 """converts a .po to a .pot where required""" 74 if fileformat is None: 75 return fileformat 76 elif fileformat == "po": 77 return "pot" 78 elif fileformat.endswith(os.extsep + "po"): 79 return fileformat + "t" 80 else: 81 return fileformat
82
83 - def getformathelp(self, formats):
84 """make a nice help string for describing formats...""" 85 # include implicit pot options... 86 helpformats = [] 87 for fileformat in formats: 88 helpformats.append(fileformat) 89 potformat = self.potifyformat(fileformat) 90 if potformat != fileformat: 91 helpformats.append(potformat) 92 return super(ConvertOptionParser, self).getformathelp(helpformats)
93
94 - def filterinputformats(self, options):
95 """filters input formats, processing relevant switches in options""" 96 if self.usepots and options.pot: 97 return [self.potifyformat(inputformat) for inputformat in self.inputformats] 98 else: 99 return self.inputformats
100
101 - def filteroutputoptions(self, options):
102 """filters output options, processing relevant switches in options""" 103 if self.usepots and options.pot: 104 outputoptions = {} 105 for (inputformat, templateformat), (outputformat, convertor) in self.outputoptions.iteritems(): 106 inputformat = self.potifyformat(inputformat) 107 templateformat = self.potifyformat(templateformat) 108 outputformat = self.potifyformat(outputformat) 109 outputoptions[(inputformat, templateformat)] = (outputformat, convertor) 110 return outputoptions 111 else: 112 return self.outputoptions
113
114 - def setpotoption(self):
115 """sets the -P/--pot option depending on input/output formats etc""" 116 if self.usepots: 117 potoption = optparse.Option("-P", "--pot", \ 118 action="store_true", dest="pot", default=False, \ 119 help="output PO Templates (.pot) rather than PO files (.po)") 120 self.define_option(potoption)
121
122 - def verifyoptions(self, options):
123 """verifies that the options are valid (required options are present, etc)""" 124 pass
125
126 - def run(self, argv=None):
127 """parses the command line options and runs the conversion""" 128 (options, args) = self.parse_args(argv) 129 options.inputformats = self.filterinputformats(options) 130 options.outputoptions = self.filteroutputoptions(options) 131 self.usepsyco(options) 132 try: 133 self.verifyoptions(options) 134 except Exception, e: 135 self.error(str(e)) 136 self.recursiveprocess(options)
137 138
139 -def copyinput(inputfile, outputfile, templatefile, **kwargs):
140 """copies the input file to the output file""" 141 outputfile.write(inputfile.read()) 142 return True
143 144
145 -def copytemplate(inputfile, outputfile, templatefile, **kwargs):
146 """copies the template file to the output file""" 147 outputfile.write(templatefile.read()) 148 return True
149 150
151 -class Replacer:
152 """an object that knows how to replace strings in files""" 153
154 - def __init__(self, searchstring, replacestring):
155 self.searchstring = searchstring 156 self.replacestring = replacestring
157
158 - def doreplace(self, text):
159 """actually replace the text""" 160 if self.searchstring is not None and self.replacestring is not None: 161 return text.replace(self.searchstring, self.replacestring) 162 else: 163 return text
164
165 - def searchreplaceinput(self, inputfile, outputfile, templatefile, **kwargs):
166 """copies the input file to the output file, searching and replacing""" 167 outputfile.write(self.doreplace(inputfile.read())) 168 return True
169
170 - def searchreplacetemplate(self, inputfile, outputfile, templatefile, **kwargs):
171 """copies the template file to the output file, searching and replacing""" 172 outputfile.write(self.doreplace(templatefile.read())) 173 return True
174 175 # archive files need to know how to: 176 # - openarchive: creates an archive object for the archivefilename 177 # * requires a constructor that takes the filename 178 # - iterarchivefile: iterate through the names in the archivefile 179 # * requires the default iterator to do this 180 # - archivefileexists: check if a given pathname exists inside the archivefile 181 # * uses the in operator - requires __contains__ (or will use __iter__ by default) 182 # - openarchiveinputfile: returns an open input file from the archive, given the path 183 # * requires an archivefile.openinputfile method that takes the pathname 184 # - openarchiveoutputfile: returns an open output file from the archive, given the path 185 # * requires an archivefile.openoutputfile method that takes the pathname 186 187
188 -class ArchiveConvertOptionParser(ConvertOptionParser):
189 """ConvertOptionParser that can handle recursing into single archive files. 190 archiveformats maps extension to class. if the extension doesn't matter, it can be None. 191 if the extension is only valid for input/output/template, it can be given as (extension, filepurpose)""" 192
193 - def __init__(self, formats, usetemplates=False, usepots=False, description=None, archiveformats=None):
194 if archiveformats is None: 195 self.archiveformats = {} 196 else: 197 self.archiveformats = archiveformats 198 self.archiveoptions = {} 199 ConvertOptionParser.__init__(self, formats, usetemplates, usepots, description=description)
200
201 - def setarchiveoptions(self, **kwargs):
202 """allows setting options that will always be passed to openarchive""" 203 self.archiveoptions = kwargs
204
205 - def isrecursive(self, fileoption, filepurpose='input'):
206 """checks if fileoption is a recursive file""" 207 if self.isarchive(fileoption, filepurpose): 208 return True 209 return super(ArchiveConvertOptionParser, self).isrecursive(fileoption, filepurpose)
210
211 - def isarchive(self, fileoption, filepurpose='input'):
212 """returns whether the file option is an archive file""" 213 if not isinstance(fileoption, (str, unicode)): 214 return False 215 mustexist = (filepurpose != 'output') 216 if mustexist and not os.path.isfile(fileoption): 217 return False 218 fileext = self.splitext(fileoption)[1] 219 # if None is in the archive formats, then treat all non-directory inputs as archives 220 return self.getarchiveclass(fileext, filepurpose, os.path.isdir(fileoption)) is not None
221
222 - def getarchiveclass(self, fileext, filepurpose, isdir=False):
223 """returns the archiveclass for the given fileext and filepurpose""" 224 archiveclass = self.archiveformats.get(fileext, None) 225 if archiveclass is not None: 226 return archiveclass 227 archiveclass = self.archiveformats.get((fileext, filepurpose), None) 228 if archiveclass is not None: 229 return archiveclass 230 if not isdir: 231 archiveclass = self.archiveformats.get(None, None) 232 if archiveclass is not None: 233 return archiveclass 234 archiveclass = self.archiveformats.get((None, filepurpose), None) 235 if archiveclass is not None: 236 return archiveclass 237 return None
238
239 - def openarchive(self, archivefilename, filepurpose, **kwargs):
240 """creates an archive object for the given file""" 241 archiveext = self.splitext(archivefilename)[1] 242 archiveclass = self.getarchiveclass(archiveext, filepurpose, os.path.isdir(archivefilename)) 243 archiveoptions = self.archiveoptions.copy() 244 archiveoptions.update(kwargs) 245 return archiveclass(archivefilename, **archiveoptions)
246
247 - def recurseinputfiles(self, options):
248 """recurse through archive file / directories and return files to be converted""" 249 if self.isarchive(options.input, 'input'): 250 options.inputarchive = self.openarchive(options.input, 'input') 251 return self.recursearchivefiles(options) 252 else: 253 return super(ArchiveConvertOptionParser, self).recurseinputfiles(options)
254
255 - def recursearchivefiles(self, options):
256 """recurse through archive files and convert files""" 257 inputfiles = [] 258 for inputpath in options.inputarchive: 259 if self.isexcluded(options, inputpath): 260 continue 261 top, name = os.path.split(inputpath) 262 if not self.isvalidinputname(options, name): 263 continue 264 inputfiles.append(inputpath) 265 return inputfiles
266
267 - def openinputfile(self, options, fullinputpath):
268 """opens the input file""" 269 if self.isarchive(options.input, 'input'): 270 return options.inputarchive.openinputfile(fullinputpath) 271 else: 272 return super(ArchiveConvertOptionParser, self).openinputfile(options, fullinputpath)
273
274 - def getfullinputpath(self, options, inputpath):
275 """gets the absolute path to an input file""" 276 if self.isarchive(options.input, 'input'): 277 return inputpath 278 else: 279 return os.path.join(options.input, inputpath)
280
281 - def opentemplatefile(self, options, fulltemplatepath):
282 """opens the template file (if required)""" 283 if fulltemplatepath is not None: 284 if options.recursivetemplate and self.isarchive(options.template, 'template'): 285 # TODO: deal with different names in input/template archives 286 if fulltemplatepath in options.templatearchive: 287 return options.templatearchive.openinputfile(fulltemplatepath) 288 else: 289 self.warning("missing template file %s" % fulltemplatepath) 290 return super(ArchiveConvertOptionParser, self).opentemplatefile(options, fulltemplatepath)
291
292 - def getfulltemplatepath(self, options, templatepath):
293 """gets the absolute path to a template file""" 294 if templatepath is not None and self.usetemplates and options.template: 295 if self.isarchive(options.template, 'template'): 296 return templatepath 297 elif not options.recursivetemplate: 298 return templatepath 299 else: 300 return os.path.join(options.template, templatepath) 301 else: 302 return None
303
304 - def templateexists(self, options, templatepath):
305 """returns whether the given template exists...""" 306 if templatepath is not None: 307 if self.isarchive(options.template, 'template'): 308 # TODO: deal with different names in input/template archives 309 return templatepath in options.templatearchive 310 return super(ArchiveConvertOptionParser, self).templateexists(options, templatepath)
311
312 - def getfulloutputpath(self, options, outputpath):
313 """gets the absolute path to an output file""" 314 if self.isarchive(options.output, 'output'): 315 return outputpath 316 elif options.recursiveoutput and options.output: 317 return os.path.join(options.output, outputpath) 318 else: 319 return outputpath
320
321 - def checkoutputsubdir(self, options, subdir):
322 """checks to see if subdir under options.output needs to be created, creates if neccessary""" 323 if not self.isarchive(options.output, 'output'): 324 super(ArchiveConvertOptionParser, self).checkoutputsubdir(options, subdir)
325
326 - def openoutputfile(self, options, fulloutputpath):
327 """opens the output file""" 328 if self.isarchive(options.output, 'output'): 329 outputstream = options.outputarchive.openoutputfile(fulloutputpath) 330 if outputstream is None: 331 self.warning("Could not find where to put %s in output archive; writing to tmp" % fulloutputpath) 332 return StringIO() 333 return outputstream 334 else: 335 return super(ArchiveConvertOptionParser, self).openoutputfile(options, fulloutputpath)
336
337 - def inittemplatearchive(self, options):
338 """opens the templatearchive if not already open""" 339 if not self.usetemplates: 340 return 341 if options.template and self.isarchive(options.template, 'template') and not hasattr(options, "templatearchive"): 342 options.templatearchive = self.openarchive(options.template, 'template')
343
344 - def initoutputarchive(self, options):
345 """creates an outputarchive if required""" 346 if options.output and self.isarchive(options.output, 'output'): 347 options.outputarchive = self.openarchive(options.output, 'output', mode="w")
348
349 - def recursiveprocess(self, options):
350 """recurse through directories and convert files""" 351 if hasattr(options, "multifilestyle"): 352 self.setarchiveoptions(multifilestyle=options.multifilestyle) 353 for filetype in ("input", "output", "template"): 354 allowoption = "allowrecursive%s" % filetype 355 if options.multifilestyle == "onefile" and getattr(options, allowoption, True): 356 setattr(options, allowoption, False) 357 self.inittemplatearchive(options) 358 self.initoutputarchive(options) 359 return super(ArchiveConvertOptionParser, self).recursiveprocess(options)
360
361 - def processfile(self, fileprocessor, options, fullinputpath, fulloutputpath, fulltemplatepath):
362 """run an invidividual conversion""" 363 if self.isarchive(options.output, 'output'): 364 inputfile = self.openinputfile(options, fullinputpath) 365 # TODO: handle writing back to same archive as input/template 366 templatefile = self.opentemplatefile(options, fulltemplatepath) 367 outputfile = self.openoutputfile(options, fulloutputpath) 368 passthroughoptions = self.getpassthroughoptions(options) 369 if fileprocessor(inputfile, outputfile, templatefile, **passthroughoptions): 370 if not outputfile.isatty(): 371 outputfile.close() 372 return True 373 else: 374 if fulloutputpath and os.path.isfile(fulloutputpath): 375 outputfile.close() 376 os.unlink(fulloutputpath) 377 return False 378 else: 379 return super(ArchiveConvertOptionParser, self).processfile(fileprocessor, options, fullinputpath, fulloutputpath, fulltemplatepath)
380 381
382 -def main(argv=None):
383 parser = ArchiveConvertOptionParser({}, description=__doc__) 384 parser.run(argv)
385