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          self.verifyoptions(options)
    130          self.recursiveprocess(options)
    131
    132 -def copyinput(inputfile, outputfile, templatefile, **kwargs):
    133      """copies the input file to the output file"""
    134      outputfile.write(inputfile.read())
    135      return True
    136
    137 -def copytemplate(inputfile, outputfile, templatefile, **kwargs):
    138      """copies the template file to the output file"""
    139      outputfile.write(templatefile.read())
    140      return True
    141
    142 -class Replacer:
    143      """an object that knows how to replace strings in files"""
    144 -    def __init__(self, searchstring, replacestring):
    145          self.searchstring = searchstring
    146          self.replacestring = replacestring
    147
    148 -    def doreplace(self, text):
    149          """actually replace the text"""
    150          if self.searchstring is not None and self.replacestring is not None:
    151              return text.replace(self.searchstring, self.replacestring)
    152          else:
    153              return text
    154
    155 -    def searchreplaceinput(self, inputfile, outputfile, templatefile, **kwargs):
    156          """copies the input file to the output file, searching and replacing"""
    157          outputfile.write(self.doreplace(inputfile.read()))
    158          return True
    159
    160 -    def searchreplacetemplate(self, inputfile, outputfile, templatefile, **kwargs):
    161          """copies the template file to the output file, searching and replacing"""
    162          outputfile.write(self.doreplace(templatefile.read()))
    163          return True
    164
    165  # archive files need to know how to:
    166  # - openarchive: creates an archive object for the archivefilename
    167  #   * requires a constructor that takes the filename
    168  # - iterarchivefile: iterate through the names in the archivefile
    169  #   * requires the default iterator to do this
    170  # - archivefileexists: check if a given pathname exists inside the archivefile
    171  #   * uses the in operator - requires __contains__ (or will use __iter__ by default)
    172  # - openarchiveinputfile: returns an open input file from the archive, given the path
    173  #   * requires an archivefile.openinputfile method that takes the pathname
    174  # - openarchiveoutputfile: returns an open output file from the archive, given the path
    175  #   * requires an archivefile.openoutputfile method that takes the pathname
    176
    177 -class ArchiveConvertOptionParser(ConvertOptionParser):
    178      """ConvertOptionParser that can handle recursing into single archive files.
    179      archiveformats maps extension to class. if the extension doesn't matter, it can be None.
    180      if the extension is only valid for input/output/template, it can be given as (extension, filepurpose)"""
    181 -    def __init__(self, formats, usetemplates=False, usepots=False, description=None, archiveformats=None):
    182          if archiveformats is None:
    183              self.archiveformats = {}
    184          else:
    185              self.archiveformats = archiveformats
    186          self.archiveoptions = {}
    187          ConvertOptionParser.__init__(self, formats, usetemplates, usepots, description=description)
    188
    189 -    def setarchiveoptions(self, **kwargs):
    190          """allows setting options that will always be passed to openarchive"""
    191          self.archiveoptions = kwargs
    192
    193 -    def isrecursive(self, fileoption, filepurpose='input'):
    194          """checks if fileoption is a recursive file"""
    195          if self.isarchive(fileoption, filepurpose):
    196              return True
    197          return super(ArchiveConvertOptionParser, self).isrecursive(fileoption, filepurpose)
    198
    199 -    def isarchive(self, fileoption, filepurpose='input'):
    200          """returns whether the file option is an archive file"""
    201          if not isinstance(fileoption, (str, unicode)):
    202              return False
    203          mustexist = (filepurpose != 'output')
    204          if mustexist and not os.path.isfile(fileoption):
    205              return False
    206          fileext = self.splitext(fileoption)[1]
    207          # if None is in the archive formats, then treat all non-directory inputs as archives
    208          return self.getarchiveclass(fileext, filepurpose, os.path.isdir(fileoption)) is not None
    209
    210 -    def getarchiveclass(self, fileext, filepurpose, isdir=False):
    211          """returns the archiveclass for the given fileext and filepurpose"""
    212          archiveclass = self.archiveformats.get(fileext, None)
    213          if archiveclass is not None:
    214              return archiveclass
    215          archiveclass = self.archiveformats.get((fileext, filepurpose), None)
    216          if archiveclass is not None:
    217              return archiveclass
    218          if not isdir:
    219              archiveclass = self.archiveformats.get(None, None)
    220              if archiveclass is not None:
    221                  return archiveclass
    222              archiveclass = self.archiveformats.get((None, filepurpose), None)
    223              if archiveclass is not None:
    224                  return archiveclass
    225          return None
    226
    227 -    def openarchive(self, archivefilename, filepurpose, **kwargs):
    228          """creates an archive object for the given file"""
    229          archiveext = self.splitext(archivefilename)[1]
    230          archiveclass = self.getarchiveclass(archiveext, filepurpose, os.path.isdir(archivefilename))
    231          archiveoptions = self.archiveoptions.copy()
    232          archiveoptions.update(kwargs)
    233          return archiveclass(archivefilename, **archiveoptions)
    234
    235 -    def recurseinputfiles(self, options):
    236          """recurse through archive file / directories and return files to be converted"""
    237          if self.isarchive(options.input, 'input'):
    238              options.inputarchive = self.openarchive(options.input, 'input')
    239              return self.recursearchivefiles(options)
    240          else:
    241              return super(ArchiveConvertOptionParser, self).recurseinputfiles(options)
    242
    243 -    def recursearchivefiles(self, options):
    244          """recurse through archive files and convert files"""
    245          inputfiles = []
    246          for inputpath in options.inputarchive:
    247              if self.isexcluded(options, inputpath):
    248                  continue
    249              top, name = os.path.split(inputpath)
    250              if not self.isvalidinputname(options, name):
    251                  continue
    252              inputfiles.append(inputpath)
    253          return inputfiles
    254
    255 -    def openinputfile(self, options, fullinputpath):
    256          """opens the input file"""
    257          if self.isarchive(options.input, 'input'):
    258              return options.inputarchive.openinputfile(fullinputpath)
    259          else:
    260              return super(ArchiveConvertOptionParser, self).openinputfile(options, fullinputpath)
    261
    262 -    def getfullinputpath(self, options, inputpath):
    263          """gets the absolute path to an input file"""
    264          if self.isarchive(options.input, 'input'):
    265              return inputpath
    266          else:
    267              return os.path.join(options.input, inputpath)
    268
    269 -    def opentemplatefile(self, options, fulltemplatepath):
    270          """opens the template file (if required)"""
    271          if fulltemplatepath is not None:
    272              if options.recursivetemplate and self.isarchive(options.template, 'template'):
    273                  # TODO: deal with different names in input/template archives
    274                  if fulltemplatepath in options.templatearchive:
    275                      return options.templatearchive.openinputfile(fulltemplatepath)
    276                  else:
    277                      self.warning("missing template file %s" % fulltemplatepath)
    278          return super(ArchiveConvertOptionParser, self).opentemplatefile(options, fulltemplatepath)
    279
    280 -    def getfulltemplatepath(self, options, templatepath):
    281          """gets the absolute path to a template file"""
    282          if templatepath is not None and self.usetemplates and options.template:
    283              if self.isarchive(options.template, 'template'):
    284                  return templatepath
    285              elif not options.recursivetemplate:
    286                  return templatepath
    287              else:
    288                  return os.path.join(options.template, templatepath)
    289          else:
    290              return None
    291
    292 -    def templateexists(self, options, templatepath):
    293          """returns whether the given template exists..."""
    294          if templatepath is not None:
    295              if self.isarchive(options.template, 'template'):
    296                  # TODO: deal with different names in input/template archives
    297                  return templatepath in options.templatearchive
    298          return super(ArchiveConvertOptionParser, self).templateexists(options, templatepath)
    299
    300 -    def getfulloutputpath(self, options, outputpath):
    301          """gets the absolute path to an output file"""
    302          if self.isarchive(options.output, 'output'):
    303              return outputpath
    304          elif options.recursiveoutput and options.output:
    305              return os.path.join(options.output, outputpath)
    306          else:
    307              return outputpath
    308
    309 -    def checkoutputsubdir(self, options, subdir):
    310          """checks to see if subdir under options.output needs to be created, creates if neccessary"""
    311          if not self.isarchive(options.output, 'output'):
    312              super(ArchiveConvertOptionParser, self).checkoutputsubdir(options, subdir)
    313
    314 -    def openoutputfile(self, options, fulloutputpath):
    315          """opens the output file"""
    316          if self.isarchive(options.output, 'output'):
    317              outputstream = options.outputarchive.openoutputfile(fulloutputpath)
    318              if outputstream is None:
    319                  self.warning("Could not find where to put %s in output archive; writing to tmp" % fulloutputpath)
    320                  return StringIO()
    321              return outputstream
    322          else:
    323              return super(ArchiveConvertOptionParser, self).openoutputfile(options, fulloutputpath)
    324
    325 -    def inittemplatearchive(self, options):
    326          """opens the templatearchive if not already open"""
    327          if not self.usetemplates:
    328              return
    329          if options.template and self.isarchive(options.template, 'template') and not hasattr(options, "templatearchive"):
    330              options.templatearchive = self.openarchive(options.template, 'template')
    331
    332 -    def initoutputarchive(self, options):
    333          """creates an outputarchive if required"""
    334          if options.output and self.isarchive(options.output, 'output'):
    335              options.outputarchive = self.openarchive(options.output, 'output', mode="w")
    336
    337 -    def recursiveprocess(self, options):
    338          """recurse through directories and convert files"""
    339          if hasattr(options, "multifilestyle"):
    340              self.setarchiveoptions(multifilestyle=options.multifilestyle)
    341              for filetype in ("input", "output", "template"):
    342                  allowoption = "allowrecursive%s" % filetype
    343                  if options.multifilestyle == "onefile" and getattr(options, allowoption, True):
    344                      setattr(options, allowoption, False)
    345          self.inittemplatearchive(options)
    346          self.initoutputarchive(options)
    347          return super(ArchiveConvertOptionParser, self).recursiveprocess(options)
    348
    349 -    def processfile(self, fileprocessor, options, fullinputpath, fulloutputpath, fulltemplatepath):
    350          """run an invidividual conversion"""
    351          if self.isarchive(options.output, 'output'):
    352              inputfile = self.openinputfile(options, fullinputpath)
    353              # TODO: handle writing back to same archive as input/template
    354              templatefile = self.opentemplatefile(options, fulltemplatepath)
    355              outputfile = self.openoutputfile(options, fulloutputpath)
    356              passthroughoptions = self.getpassthroughoptions(options)
    357              if fileprocessor(inputfile, outputfile, templatefile, **passthroughoptions):
    358                  if not outputfile.isatty():
    359                      outputfile.close()
    360                  return True
    361              else:
    362                  if fulloutputpath and os.path.isfile(fulloutputpath):
    363                      outputfile.close()
    364                      os.unlink(fulloutputpath)
    365                  return False
    366          else:
    367              return super(ArchiveConvertOptionParser, self).processfile(fileprocessor, options, fullinputpath, fulloutputpath, fulltemplatepath)
    368
    369 -def main(argv=None):
    370      parser = ArchiveConvertOptionParser({}, description=__doc__)
    371      parser.run(argv)
    372