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