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