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