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