#! @PYTHON@ # # transcheck - (c) 2002 by Simone Piunno # Copyright (C) 2002-2007 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or modify it # under the terms of the version 2.0 of the GNU General Public License as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ Check a given Mailman translation, making sure that variables and tags referenced in translation are the same variables and tags in the original templates and catalog. Usage: cd $MAILMAN_DIR %(program)s [-q] Where is your country code (e.g. 'it' for Italy) and -q is to ask for a brief summary. """ import sys import re import os import getopt import paths from Mailman.i18n import C_ program = sys.argv[0] def usage(code, msg=''): if code: fd = sys.stderr else: fd = sys.stdout print >> fd, C_(__doc__) if msg: print >> fd, msg sys.exit(code) class TransChecker: "check a translation comparing with the original string" def __init__(self, regexp, escaped=None): self.dict = {} self.errs = [] self.regexp = re.compile(regexp) self.escaped = None if escaped: self.escaped = re.compile(escaped) def checkin(self, string): "scan a string from the original file" for key in self.regexp.findall(string): if self.escaped and self.escaped.match(key): continue if self.dict.has_key(key): self.dict[key] += 1 else: self.dict[key] = 1 def checkout(self, string): "scan a translated string" for key in self.regexp.findall(string): if self.escaped and self.escaped.match(key): continue if self.dict.has_key(key): self.dict[key] -= 1 else: self.errs.append( "%(key)s was not found" % { 'key' : key } ) def computeErrors(self): "check for differences between checked in and checked out" for key in self.dict.keys(): if self.dict[key] < 0: self.errs.append( "Too much %(key)s" % { 'key' : key } ) if self.dict[key] > 0: self.errs.append( "Too few %(key)s" % { 'key' : key } ) return self.errs def status(self): if self.errs: return "FAILED" else: return "OK" def errorsAsString(self): msg = "" for err in self.errs: msg += " - %(err)s" % { 'err': err } return msg def reset(self): self.dict = {} self.errs = [] class POParser: "parse a .po file extracting msgids and msgstrs" def __init__(self, filename=""): self.status = 0 self.files = [] self.msgid = "" self.msgstr = "" self.line = 1 self.f = None self.esc = { "n": "\n", "r": "\r", "t": "\t" } if filename: self.f = open(filename) def open(self, filename): self.f = open(filename) def close(self): self.f.close() def parse(self): """States table for the finite-states-machine parser: 0 idle 1 filename-or-comment 2 msgid 3 msgstr 4 end """ # each time we can safely re-initialize those vars self.files = [] self.msgid = "" self.msgstr = "" # can't continue if status == 4, this is a dead status if self.status == 4: return 0 while 1: # continue scanning, char-by-char c = self.f.read(1) if not c: # EOF -> maybe we have a msgstr to save? self.status = 4 if self.msgstr: return 1 else: return 0 # keep the line count up-to-date if c == "\n": self.line += 1 # a pound was detected the previous char... if self.status == 1: if c == ":": # was a line of filenames row = self.f.readline() self.files += row.split() self.line += 1 elif c == "\n": # was a single pount on the line pass else: # was a comment... discard self.f.readline() self.line += 1 # in every case, we switch to idle status self.status = 0; continue # in idle status we search for a '#' or for a 'm' if self.status == 0: if c == "#": # this could be a comment or a filename self.status = 1; continue elif c == "m": # this should be a msgid start... s = self.f.read(4) assert s == "sgid" # so now we search for a '"' self.status = 2 continue # in idle only those other chars are possibile assert c in [ "\n", " ", "\t" ] # searching for the msgid string if self.status == 2: if c == "\n": # a double LF is not possible here c = self.f.read(1) assert c != "\n" if c == "\"": # ok, this is the start of the string, # now search for the end while 1: c = self.f.read(1) if not c: # EOF, bailout self.status = 4 return 0 if c == "\\": # a quoted char... c = self.f.read(1) if self.esc.has_key(c): self.msgid += self.esc[c] else: self.msgid += c continue if c == "\"": # end of string found break # a normal char, add it self.msgid += c if c == "m": # this should be a msgstr identifier s = self.f.read(5) assert s == "sgstr" # ok, now search for the msgstr string self.status = 3 # searching for the msgstr string if self.status == 3: if c == "\n": # a double LF is the end of the msgstr! c = self.f.read(1) if c == "\n": # ok, time to go idle and return self.status = 0 self.line += 1 return 1 if c == "\"": # start of string found while 1: c = self.f.read(1) if not c: # EOF, bail out self.status = 4 return 1 if c == "\\": # a quoted char... c = self.f.read(1) if self.esc.has_key(c): self.msgstr += self.esc[c] else: self.msgstr += c continue if c == "\"": # end of string break # a normal char, add it self.msgstr += c def check_file(translatedFile, originalFile, html=0, quiet=0): """check a translated template against the original one search also tags if html is not zero""" if html: c = TransChecker("(%%|%\([^)]+\)[0-9]*[sd]|]+>)", "^%%$") else: c = TransChecker("(%%|%\([^)]+\)[0-9]*[sd])", "^%%$") try: f = open(originalFile) except IOError: if not quiet: print " - Can'open original file " + originalFile return 1 while 1: line = f.readline() if not line: break c.checkin(line) f.close() try: f = open(translatedFile) except IOError: if not quiet: print " - Can'open translated file " + translatedFile return 1 while 1: line = f.readline() if not line: break c.checkout(line) f.close() n = 0 msg = "" for desc in c.computeErrors(): n +=1 if not quiet: print " - %(desc)s" % { 'desc': desc } return n def check_po(file, quiet=0): "scan the po file comparing msgids with msgstrs" n = 0 p = POParser(file) c = TransChecker("(%%|%\([^)]+\)[0-9]*[sdu]|%[0-9]*[sdu])", "^%%$") while p.parse(): if p.msgstr: c.reset() c.checkin(p.msgid) c.checkout(p.msgstr) for desc in c.computeErrors(): n += 1 if not quiet: print " - near line %(line)d %(file)s: %(desc)s" % { 'line': p.line, 'file': p.files, 'desc': desc } p.close() return n def main(): try: opts, args = getopt.getopt(sys.argv[1:], 'qh', ['quiet', 'help']) except getopt.error, msg: usage(1, msg) quiet = 0 for opt, arg in opts: if opt in ('-h', '--help'): usage(0) elif opt in ('-q', '--quiet'): quiet = 1 if len(args) <> 1: usage(1) lang = args[0] isHtml = re.compile("\.html$"); isTxt = re.compile("\.txt$"); numerrors = 0 numfiles = 0 try: files = os.listdir("templates/" + lang + "/") except: print "can't open templates/%s/" % lang for file in files: fileEN = "templates/en/" + file fileIT = "templates/" + lang + "/" + file errlist = [] if isHtml.search(file): if not quiet: print "HTML checking " + fileIT + "... " n = check_file(fileIT, fileEN, html=1, quiet=quiet) if n: numerrors += n numfiles += 1 elif isTxt.search(file): if not quiet: print "TXT checking " + fileIT + "... " n = check_file(fileIT, fileEN, html=0, quiet=quiet) if n: numerrors += n numfiles += 1 else: continue file = "messages/" + lang + "/LC_MESSAGES/mailman.po" if not quiet: print "PO checking " + file + "... " n = check_po(file, quiet=quiet) if n: numerrors += n numfiles += 1 if quiet: print "%(errs)u warnings in %(files)u files" % { 'errs': numerrors, 'files': numfiles } if __name__ == '__main__': main()