Package translate :: Package tools :: Module poconflicts

[hide private]
[frames] | no frames]

# Source Code for Module translate.tools.poconflicts

      1  #!/usr/bin/env python
      2  # -*- coding: utf-8 -*-
      3  #
      4  # Copyright 2005-2007 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  """Conflict finder for Gettext PO localization files
     23
     24  See: http://translate.sourceforge.net/wiki/toolkit/poconflicts for examples and
     25  usage instructions
     26  """
     27
     28  import sys
     29  import os
     30
     31  from translate.storage import factory
     32  from translate.storage import po
     33  from translate.misc import optrecurse
     34
     35
     36 -class ConflictOptionParser(optrecurse.RecursiveOptionParser):
     37      """a specialized Option Parser for the conflict tool..."""
     38
     39 -    def parse_args(self, args=None, values=None):
     40          """parses the command line options, handling implicit input/output args"""
     41          (options, args) = optrecurse.optparse.OptionParser.parse_args(self, args, values)
     42          # some intelligence as to what reasonable people might give on the command line
     43          if args and not options.input:
     44              if not options.output:
     45                  options.input = args[:-1]
     46                  args = args[-1:]
     47              else:
     48                  options.input = args
     49                  args = []
     50          if args and not options.output:
     51              options.output = args[-1]
     52              args = args[:-1]
     53          if not options.output:
     54              self.error("output file is required")
     55          if args:
     56              self.error("You have used an invalid combination of --input, --output and freestanding args")
     57          if isinstance(options.input, list) and len(options.input) == 1:
     58              options.input = options.input[0]
     59          return (options, args)
     60
     61 -    def set_usage(self, usage=None):
     62          """sets the usage string - if usage not given, uses getusagestring for each option"""
     63          if usage is None:
     64              self.usage = "%prog " + " ".join([self.getusagestring(option) for option in self.option_list]) + \
     65                      "\n  input directory is searched for PO files, PO files with name of conflicting string are output in output directory"
     66          else:
     67              super(ConflictOptionParser, self).set_usage(usage)
     68
     69 -    def run(self):
     70          """parses the arguments, and runs recursiveprocess with the resulting options"""
     71          (options, args) = self.parse_args()
     72          options.inputformats = self.inputformats
     73          options.outputoptions = self.outputoptions
     74          self.usepsyco(options)
     75          self.recursiveprocess(options)
     76
     77 -    def recursiveprocess(self, options):
     78          """recurse through directories and process files"""
     79          if self.isrecursive(options.input, 'input') and getattr(options, "allowrecursiveinput", True):
     80              if not self.isrecursive(options.output, 'output'):
     81                  try:
     82                      self.warning("Output directory does not exist. Attempting to create")
     83                      os.mkdir(options.output)
     84                  except:
     85                      self.error(optrecurse.optparse.OptionValueError("Output directory does not exist, attempt to create failed"))
     86              if isinstance(options.input, list):
     87                  inputfiles = self.recurseinputfilelist(options)
     88              else:
     89                  inputfiles = self.recurseinputfiles(options)
     90          else:
     91              if options.input:
     92                  inputfiles = [os.path.basename(options.input)]
     93                  options.input = os.path.dirname(options.input)
     94              else:
     95                  inputfiles = [options.input]
     96          self.textmap = {}
     97          self.initprogressbar(inputfiles, options)
     98          for inputpath in inputfiles:
     99              fullinputpath = self.getfullinputpath(options, inputpath)
    100              try:
    101                  success = self.processfile(None, options, fullinputpath)
    102              except Exception, error:
    103                  if isinstance(error, KeyboardInterrupt):
    104                      raise
    105                  self.warning("Error processing: input %s" % (fullinputpath), options, sys.exc_info())
    106                  success = False
    107              self.reportprogress(inputpath, success)
    108          del self.progressbar
    109          self.buildconflictmap()
    110          self.outputconflicts(options)
    111
    112 -    def clean(self, string, options):
    113          """returns the cleaned string that contains the text to be matched"""
    114          if options.ignorecase:
    115              string = string.lower()
    116          for accelerator in options.accelchars:
    117              string = string.replace(accelerator, "")
    118          string = string.strip()
    119          return string
    120
    121 -    def processfile(self, fileprocessor, options, fullinputpath):
    122          """process an individual file"""
    123          inputfile = self.openinputfile(options, fullinputpath)
    124          inputfile = factory.getobject(inputfile)
    125          for unit in inputfile.units:
    126              if unit.isheader() or not unit.istranslated():
    127                  continue
    128              if unit.hasplural():
    129                  continue
    130              if not options.invert:
    131                  source = self.clean(unit.source, options)
    132                  target = self.clean(unit.target, options)
    133              else:
    134                  target = self.clean(unit.source, options)
    135                  source = self.clean(unit.target, options)
    136              self.textmap.setdefault(source, []).append((target, unit, fullinputpath))
    137
    138 -    def flatten(self, text, joinchar):
    139          """flattens text to just be words"""
    140          flattext = ""
    141          for c in text:
    142              if c.isalnum():
    143                  flattext += c
    144              elif flattext[-1:].isalnum():
    145                  flattext += joinchar
    146          return flattext.rstrip(joinchar)
    147
    148 -    def buildconflictmap(self):
    149          """work out which strings are conflicting"""
    150          self.conflictmap = {}
    151          for source, translations in self.textmap.iteritems():
    152              source = self.flatten(source, " ")
    153              if len(source) <= 1:
    154                  continue
    155              if len(translations) > 1:
    156                  uniquetranslations = dict.fromkeys([target for target, unit, filename in translations])
    157                  if len(uniquetranslations) > 1:
    158                      self.conflictmap[source] = translations
    159
    160 -    def outputconflicts(self, options):
    161          """saves the result of the conflict match"""
    162          print "%d/%d different strings have conflicts" % (len(self.conflictmap), len(self.textmap))
    163          reducedmap = {}
    164          for source, translations in self.conflictmap.iteritems():
    165              words = source.split()
    166              words.sort(lambda x, y: cmp(len(x), len(y)))
    167              source = words[-1]
    168              reducedmap.setdefault(source, []).extend(translations)
    169          # reduce plurals
    170          plurals = {}
    171          for word in reducedmap:
    172              if word + "s" in reducedmap:
    173                  plurals[word] = word + "s"
    174          for word, pluralword in plurals.iteritems():
    175              reducedmap[word].extend(reducedmap.pop(pluralword))
    176          for source, translations in reducedmap.iteritems():
    177              flatsource = self.flatten(source, "-")
    178              fulloutputpath = os.path.join(options.output, flatsource + os.extsep + "po")
    179              conflictfile = po.pofile()
    180              for target, unit, filename in translations:
    181                  unit.othercomments.append("# (poconflicts) %s\n" % filename)
    182                  conflictfile.units.append(unit)
    183              open(fulloutputpath, "w").write(str(conflictfile))
    184
    185
    186 -def main():
    187      formats = {"po": ("po", None), None: ("po", None)}
    188      parser = ConflictOptionParser(formats)
    189      parser.add_option("-I", "--ignore-case", dest="ignorecase",
    190          action="store_true", default=False, help="ignore case distinctions")
    191      parser.add_option("-v", "--invert", dest="invert",
    192          action="store_true", default=False, help="invert the conflicts thus extracting conflicting destination words")
    193      parser.add_option("", "--accelerator", dest="accelchars", default="",
    194          metavar="ACCELERATORS", help="ignores the given accelerator characters when matching")
    195      parser.set_usage()
    196      parser.description = __doc__
    197      parser.run()
    198
    199
    200  if __name__ == '__main__':
    201      main()
    202