#!/usr/bin/env python # # Copyright (C) 2009 the TortoiseSVN team # This file is distributed under the same license as TortoiseSVN # # $Author$ # $Date$ # $Rev$ # # Author: Luebbe Onken 2009 # """Usage: translation-status.py [OPTION...] Compute the trunk translation status report for the TortoiseSVN GUI and DOCs. Create two include files (trunk/language list) for the status report on the web page. Create two reports (doc/gui) and send them via e-mail. Options: -h, --help Show this help message. -m, --to-email-id Send the translation status report to this email address. """ from __future__ import print_function import sys import getopt import os import re import csv import time import subprocess urlTrunk = 'trunk/Languages' wrkDir = '.' fileGui = 'TortoiseUI' fileDoc = 'TortoiseDoc' langList = os.path.join(wrkDir, 'Languages.txt') langFields = ['LangCC', 'LangWiX', 'LangID', 'FlagByte', 'LangName', 'Translators'] Sep75 = '===========================================================================' def usage_and_exit(errmsg=None): """Print a usage message, plus an ERRMSG (if provided), then exit. If ERRMSG is provided, the usage message is printed to stderr and the script exits with a non-zero error code. Otherwise, the usage message goes to stdout, and the script exits with a zero errorcode.""" if errmsg is None: stream = sys.stdout else: stream = sys.stderr print(__doc__, file=stream) if errmsg: print('\nError: %s' % (errmsg), file=stream) sys.exit(2) sys.exit(0) def makeTimeString(format, timestamp): return time.strftime(format, time.gmtime(timestamp)) class transReport: def __init__(self, to_email_id='luebbe@tigris.org'): self.to_email_id = to_email_id self.from_email_id = '' def safe_command(self, cmd_and_args, cmd_in=''): cmd_in = cmd_in.encode(sys.stdin.encoding) [stdout, stderr] = subprocess.Popen(cmd_and_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate(input=cmd_in) stdout = stdout.decode(sys.stdout.encoding) stderr = stderr.decode(sys.stdout.encoding) return stdout, stderr def match(self, pattern, string): match = re.compile(pattern).search(string) if match and match.groups(): return match.group(1) else: return None def prepareWC(self, dstDir, srcUrl): """Checks out the given URL into the destination path. Creates and cleans up the path if necessary""" # Check if target directory exists locally if os.path.isdir(dstDir): cmd = ['svnversion', dstDir] ver = self.safe_command(cmd)[0] ver = ver.strip() # Check if target directory is a working copy if ver == 'exported': # No -> remove it and make a fresh checkout checkout = True os.rmdir(dstDir) else: # Yes -> switch it to the given URL # works like 'svn up' if URL stays the same checkout = False cmd = ['svn', 'switch', srcUrl, dstDir] self.safe_command(cmd) else: checkout = True # Create target directory and make a fresh checkout if checkout: os.makedirs(dstDir) cmd = ['svn', 'checkout', srcUrl, dstDir] self.safe_command(cmd) return None def getTotal(self, wrkDir, fileMask): """Gets the timestamp and the total number of strings from the given .pot file""" potFile = os.path.join(wrkDir, fileMask + '.pot') total = self.checkAttrib('--untranslated', potFile) timestamp = makeTimeString('%Y-%m-%d', os.path.getmtime(potFile)) return (total, timestamp) def getPoDate(self, poFile): """returns the PO-Revision-Date of the the .po file in a string""" grepout = self.safe_command( ['grep', '-E', 'PO-Revision-Date:', poFile])[0] reout = self.match('.*?(\d{4}-\d{2}-\d{2}).*', grepout.strip()) return reout def checkError(self, attrib, poFile): """Checks for errors in the .po file. Can detect missing accelerators""" msgout = self.safe_command(['msgfmt', attrib, poFile])[1] if msgout: grepout = self.safe_command( ['grep', '-E', 'msgfmt: found'], msgout)[0] reout = self.match('.*?(\d+).*', grepout.strip()) return int(reout) else: return 0 def checkAttrib(self, attrib, file): """Counts the lines that match a particular attribute""" msgout = self.safe_command(['msgattrib', attrib, file])[0] grepout = self.safe_command(['grep', '-E', '^msgid *"'], msgout)[0] sedout = self.safe_command(['sed', '1d'], grepout)[0] msgcount = self.safe_command(['wc', '-l'], sedout)[0] return int(msgcount) def checkStatus(self, wrkDir, langCC, fileMask, total, checkAccel): """Merges the current translations with the latest .po template. Calculates the statistics for the given translation.""" potFile = os.path.join(wrkDir, fileMask + '.pot') poFile = os.path.join(wrkDir, langCC, fileMask + '.po') if not os.path.isfile(poFile): # No need to write status for non-existent .po files # Just print on standard output for the "paper" report return '---' else: wrkFile = os.path.join(wrkDir, 'temp.po') cmd = ['msgmerge', '--no-wrap', '--quiet', '--no-fuzzy-matching', poFile, potFile, '-o', wrkFile] self.safe_command(cmd) poDate = self.getPoDate(wrkFile) error = self.checkError('--check-format', wrkFile) if error: return 'BROKEN' else: trans = self.checkAttrib('--translated', wrkFile) untrans = self.checkAttrib('--untranslated', wrkFile) # transifex doesn't support fuzzy, so don't report it anymore # fuzzy = self.checkAttrib('--only-fuzzy', wrkFile) fuzzy = 0 if checkAccel: accel = self.checkError('--check-accelerators', wrkFile) else: accel = 0 trans = trans - fuzzy if untrans + fuzzy + accel == 0: return 'OK' else: if trans == total: percent = 99 else: percent = 100 * (trans) / total percent = round(percent, 1) if checkAccel: # return '%2s%% (%s/%s/%s)' % (percent, untrans, fuzzy, accel) return '%2s%% (%s/%s)' % (percent, untrans, accel) else: # return '%2s%% (%s/%s)' % (percent, untrans, fuzzy) return '%2s%% (%s)' % (percent, untrans) def printStatLine(self, Lang, Gui, Doc): print('%-33s: %-19s: %-19s' % (Lang, Gui, Doc)) def checkTranslation(self, wrkDir): [totGui, tsGui] = self.getTotal(wrkDir, fileGui) [totDoc, tsDoc] = self.getTotal(wrkDir, fileDoc) firstline = '==== Translation of %s %s' % (urlTrunk, Sep75) print('') print(firstline[0:75]) self.printStatLine('', 'User interface', 'Manuals') self.printStatLine('Total strings', totGui, totDoc) # self.printStatLine('Language', 'Status (un/fu/ma)', 'Status (un/fu)') self.printStatLine('Language', 'Status (un/ma)', 'Status (un)') print(Sep75) csvReader = csv.DictReader( open(langList), langFields, delimiter=';', quotechar='"') csvReader.skipinitialspace = True for row in csvReader: # Ignore lines beginning with: # '\xef' = UTF-8 BOM # '#' = comment line if row['LangCC'][0] != '\xef' and row['LangCC'][0] != '#': langCC = row['LangCC'].strip() statusGui = self.checkStatus( wrkDir, langCC, fileGui, totGui, True) statusDoc = self.checkStatus( wrkDir, langCC, fileDoc, totDoc, False) if statusGui != '---' or statusDoc != '----': langName = row['LangName'].strip() langName = langName + ' (' + langCC + ')' self.printStatLine(langName, statusGui, statusDoc) return None def createReport(self): [info_out, info_err] = self.safe_command(['svn', 'info']) if info_err: print('\nError: %s' % (info_err), file=sys.stderr) sys.exit(0) # Try different matches for older and newer svn clients reposRoot = self.match('Repository Root: (\S+)', info_out) if reposRoot is None: reposRoot = self.match('Repository: (\S+)', info_out) reposTrunk = reposRoot + '/' + urlTrunk + '/' # Prepare the working copies for doc|gui * trunk|branch self.prepareWC(wrkDir, reposTrunk) self.checkTranslation(wrkDir) print(Sep75) # print('Status: fu=fuzzy - un=untranslated - ma=missing accelerator keys') print('Status: un=untranslated - ma=missing accelerator keys') print(Sep75) # Clean up the tmp folder for root, dirs, files in os.walk('tmp', topdown=False): for name in files: os.remove(os.path.join(root, name)) for name in dirs: os.rmdir(os.path.join(root, name)) return None def main(): # Parse the command-line options and arguments. try: opts, args = getopt.gnu_getopt(sys.argv[1:], 'hm:', ['help', 'to-email-id=', ]) except getopt.GetoptError as msg: usage_and_exit(msg) to_email_id = None for opt, arg in opts: if opt in ('-h', '--help'): usage_and_exit() elif opt in ('-m', '--to-email-id'): to_email_id = arg report = transReport() [info_out, info_err] = report.safe_command(['svnversion', wrkDir]) if info_err: print('\nError: %s' % (info_err), file=sys.stderr) sys.exit(0) wcrev = re.sub('[MS]', '', info_out).strip() subject = 'TortoiseSVN translation status report for r%s' % (wcrev) print(subject) report.createReport() timestamp = makeTimeString('%a, %d %b %Y %H:%M UTC', time.time()) if __name__ == '__main__': main()