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