translate.tools.poconflicts

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-2008,2010 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          def str_len(x):
    165              return len(x)
    166
    167          for source, translations in self.conflictmap.iteritems():
    168              words = source.split()
    169              words.sort(key=str_len)
    170              source = words[-1]
    171              reducedmap.setdefault(source, []).extend(translations)
    172          # reduce plurals
    173          plurals = {}
    174          for word in reducedmap:
    175              if word + "s" in reducedmap:
    176                  plurals[word] = word + "s"
    177          for word, pluralword in plurals.iteritems():
    178              reducedmap[word].extend(reducedmap.pop(pluralword))
    179          for source, translations in reducedmap.iteritems():
    180              flatsource = self.flatten(source, "-")
    181              fulloutputpath = os.path.join(options.output, flatsource + os.extsep + "po")
    182              conflictfile = po.pofile()
    183              for target, unit, filename in translations:
    184                  unit.othercomments.append("# (poconflicts) %s\n" % filename)
    185                  conflictfile.units.append(unit)
    186              open(fulloutputpath, "w").write(str(conflictfile))
    187
    188
    189 -def main():
    190      formats = {"po": ("po", None), None: ("po", None)}
    191      parser = ConflictOptionParser(formats)
    192      parser.add_option("-I", "--ignore-case", dest="ignorecase",
    193          action="store_true", default=False, help="ignore case distinctions")
    194      parser.add_option("-v", "--invert", dest="invert",
    195          action="store_true", default=False, help="invert the conflicts thus extracting conflicting destination words")
    196      parser.add_option("", "--accelerator", dest="accelchars", default="",
    197          metavar="ACCELERATORS", help="ignores the given accelerator characters when matching")
    198      parser.set_usage()
    199      parser.description = __doc__
    200      parser.run()
    201
    202
    203  if __name__ == '__main__':
    204      main()
    205