Mercurial > hg
view i18n/check-translation.py @ 40034:393e44324037
httppeer: report http statistics
Now that keepalive.py records HTTP request count and the
number of bytes sent and received as part of performing those
requests, we can easily print a report on the activity when
closing a peer instance!
Exact byte counts are globbed in tests because they are influenced
by non-deterministic things, such as hostnames and port numbers.
Plus, the exact byte count isn't too important anyway.
I feel obliged to note that printing the byte count could have
security implications. e.g. if sending a password via HTTP basic
auth, the length of that password will influence the byte count
and the reporting of the byte count could be a side-channel leak
of the password length. I /think/ this is beyond our threshold
for concern. But if we think it poses a problem, we can teach the
byte count logging code to e.g. ignore sensitive HTTP request
headers. We could also consider not reporting the byte count of
request headers altogether. But since the wire protocol uses HTTP
headers for sending command arguments, it is kind of important to
report their size.
Differential Revision: https://phab.mercurial-scm.org/D4858
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Mon, 01 Oct 2018 13:17:38 -0700 |
parents | 078099304772 |
children | 19fc5a986669 |
line wrap: on
line source
#!/usr/bin/env python # # check-translation.py - check Mercurial specific translation problems from __future__ import absolute_import import re import polib scanners = [] checkers = [] def scanner(): def decorator(func): scanners.append(func) return func return decorator def levelchecker(level, msgidpat): def decorator(func): if msgidpat: match = re.compile(msgidpat).search else: match = lambda msgid: True checkers.append((func, level)) func.match = match return func return decorator def match(checker, pe): """Examine whether POEntry "pe" is target of specified checker or not """ if not checker.match(pe.msgid): return # examine suppression by translator comment nochecker = 'no-%s-check' % checker.__name__ for tc in pe.tcomment.split(): if nochecker == tc: return return True #################### def fatalchecker(msgidpat=None): return levelchecker('fatal', msgidpat) @fatalchecker(r'\$\$') def promptchoice(pe): """Check translation of the string given to "ui.promptchoice()" >>> pe = polib.POEntry( ... msgid ='prompt$$missing &sep$$missing &$$followed by &none', ... msgstr='prompt missing &sep$$missing amp$$followed by none&') >>> match(promptchoice, pe) True >>> for e in promptchoice(pe): print(e) number of choices differs between msgid and msgstr msgstr has invalid choice missing '&' msgstr has invalid '&' followed by none """ idchoices = [c.rstrip(' ') for c in pe.msgid.split('$$')[1:]] strchoices = [c.rstrip(' ') for c in pe.msgstr.split('$$')[1:]] if len(idchoices) != len(strchoices): yield "number of choices differs between msgid and msgstr" indices = [(c, c.find('&')) for c in strchoices] if [c for c, i in indices if i == -1]: yield "msgstr has invalid choice missing '&'" if [c for c, i in indices if len(c) == i + 1]: yield "msgstr has invalid '&' followed by none" deprecatedpe = None @scanner() def deprecatedsetup(pofile): pes = [p for p in pofile if p.msgid == '(DEPRECATED)' and p.msgstr] if len(pes): global deprecatedpe deprecatedpe = pes[0] @fatalchecker(r'\(DEPRECATED\)') def deprecated(pe): """Check for DEPRECATED >>> ped = polib.POEntry( ... msgid = '(DEPRECATED)', ... msgstr= '(DETACERPED)') >>> deprecatedsetup([ped]) >>> pe = polib.POEntry( ... msgid = 'Something (DEPRECATED)', ... msgstr= 'something (DEPRECATED)') >>> match(deprecated, pe) True >>> for e in deprecated(pe): print(e) >>> pe = polib.POEntry( ... msgid = 'Something (DEPRECATED)', ... msgstr= 'something (DETACERPED)') >>> match(deprecated, pe) True >>> for e in deprecated(pe): print(e) >>> pe = polib.POEntry( ... msgid = 'Something (DEPRECATED)', ... msgstr= 'something') >>> match(deprecated, pe) True >>> for e in deprecated(pe): print(e) msgstr inconsistently translated (DEPRECATED) >>> pe = polib.POEntry( ... msgid = 'Something (DEPRECATED, foo bar)', ... msgstr= 'something (DETACERPED, foo bar)') >>> match(deprecated, pe) """ if not ('(DEPRECATED)' in pe.msgstr or (deprecatedpe and deprecatedpe.msgstr in pe.msgstr)): yield "msgstr inconsistently translated (DEPRECATED)" #################### def warningchecker(msgidpat=None): return levelchecker('warning', msgidpat) @warningchecker() def taildoublecolons(pe): """Check equality of tail '::'-ness between msgid and msgstr >>> pe = polib.POEntry( ... msgid ='ends with ::', ... msgstr='ends with ::') >>> for e in taildoublecolons(pe): print(e) >>> pe = polib.POEntry( ... msgid ='ends with ::', ... msgstr='ends without double-colons') >>> for e in taildoublecolons(pe): print(e) tail '::'-ness differs between msgid and msgstr >>> pe = polib.POEntry( ... msgid ='ends without double-colons', ... msgstr='ends with ::') >>> for e in taildoublecolons(pe): print(e) tail '::'-ness differs between msgid and msgstr """ if pe.msgid.endswith('::') != pe.msgstr.endswith('::'): yield "tail '::'-ness differs between msgid and msgstr" @warningchecker() def indentation(pe): """Check equality of initial indentation between msgid and msgstr This may report unexpected warning, because this doesn't aware the syntax of rst document and the context of msgstr. >>> pe = polib.POEntry( ... msgid =' indented text', ... msgstr=' narrowed indentation') >>> for e in indentation(pe): print(e) initial indentation width differs betweeen msgid and msgstr """ idindent = len(pe.msgid) - len(pe.msgid.lstrip()) strindent = len(pe.msgstr) - len(pe.msgstr.lstrip()) if idindent != strindent: yield "initial indentation width differs betweeen msgid and msgstr" #################### def check(pofile, fatal=True, warning=False): targetlevel = { 'fatal': fatal, 'warning': warning } targetcheckers = [(checker, level) for checker, level in checkers if targetlevel[level]] if not targetcheckers: return [] detected = [] for checker in scanners: checker(pofile) for pe in pofile.translated_entries(): errors = [] for checker, level in targetcheckers: if match(checker, pe): errors.extend((level, checker.__name__, error) for error in checker(pe)) if errors: detected.append((pe, errors)) return detected ######################################## if __name__ == "__main__": import sys import optparse optparser = optparse.OptionParser("""%prog [options] pofile ... This checks Mercurial specific translation problems in specified '*.po' files. Each detected problems are shown in the format below:: filename:linenum:type(checker): problem detail ..... "type" is "fatal" or "warning". "checker" is the name of the function detecting corresponded error. Checking by checker "foo" on the specific msgstr can be suppressed by the "translator comment" like below. Multiple "no-xxxx-check" should be separated by whitespaces:: # no-foo-check msgid = "....." msgstr = "....." """) optparser.add_option("", "--warning", help="show also warning level problems", action="store_true") optparser.add_option("", "--doctest", help="run doctest of this tool, instead of check", action="store_true") (options, args) = optparser.parse_args() if options.doctest: import os if 'TERM' in os.environ: del os.environ['TERM'] import doctest failures, tests = doctest.testmod() sys.exit(failures and 1 or 0) # replace polib._POFileParser to show linenum of problematic msgstr class ExtPOFileParser(polib._POFileParser): def process(self, symbol, linenum): super(ExtPOFileParser, self).process(symbol, linenum) if symbol == 'MS': # msgstr self.current_entry.linenum = linenum polib._POFileParser = ExtPOFileParser detected = [] warning = options.warning for f in args: detected.extend((f, pe, errors) for pe, errors in check(polib.pofile(f), warning=warning)) if detected: for f, pe, errors in detected: for level, checker, error in errors: sys.stderr.write('%s:%d:%s(%s): %s\n' % (f, pe.linenum, level, checker, error)) sys.exit(1)