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