hggettext
171 lines
| 5.2 KiB
| text/plain
|
TextLexer
/ i18n / hggettext
|
r8542 | #!/usr/bin/env python | ||
# | ||||
# hggettext - carefully extract docstrings for Mercurial | ||||
# | ||||
# Copyright 2009 Matt Mackall <mpm@selenic.com> and others | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
|
r10263 | # GNU General Public License version 2 or any later version. | ||
|
r8542 | |||
# The normalize function is taken from pygettext which is distributed | ||||
# with Python under the Python License, which is GPL compatible. | ||||
"""Extract docstrings from Mercurial commands. | ||||
Compared to pygettext, this script knows about the cmdtable and table | ||||
dictionaries used by Mercurial, and will only extract docstrings from | ||||
functions mentioned therein. | ||||
Use xgettext like normal to extract strings marked as translatable and | ||||
join the message cataloges to get the final catalog. | ||||
""" | ||||
|
r29171 | from __future__ import absolute_import, print_function | ||
|
r29170 | |||
import inspect | ||||
import os | ||||
|
r33816 | import re | ||
|
r29170 | import sys | ||
|
r8542 | |||
def escape(s): | ||||
# The order is important, the backslash must be escaped first | ||||
# since the other replacements introduce new backslashes | ||||
# themselves. | ||||
s = s.replace('\\', '\\\\') | ||||
s = s.replace('\n', '\\n') | ||||
s = s.replace('\r', '\\r') | ||||
s = s.replace('\t', '\\t') | ||||
s = s.replace('"', '\\"') | ||||
return s | ||||
def normalize(s): | ||||
# This converts the various Python string types into a format that | ||||
# is appropriate for .po files, namely much closer to C style. | ||||
lines = s.split('\n') | ||||
if len(lines) == 1: | ||||
s = '"' + escape(s) + '"' | ||||
else: | ||||
if not lines[-1]: | ||||
del lines[-1] | ||||
lines[-1] = lines[-1] + '\n' | ||||
lines = map(escape, lines) | ||||
lineterm = '\\n"\n"' | ||||
s = '""\n"' + lineterm.join(lines) + '"' | ||||
return s | ||||
def poentry(path, lineno, s): | ||||
|
r44089 | return ( | ||
'#: %s:%d\n' % (path, lineno) | ||||
+ 'msgid %s\n' % normalize(s) | ||||
+ 'msgstr ""\n' | ||||
) | ||||
|
r8542 | |||
|
r33816 | doctestre = re.compile(r'^ +>>> ', re.MULTILINE) | ||
|
r8542 | |||
|
r44089 | |||
|
r38851 | def offset(src, doc, name, lineno, default): | ||
|
r8542 | """Compute offset or issue a warning on stdout.""" | ||
|
r33816 | # remove doctest part, in order to avoid backslash mismatching | ||
m = doctestre.search(doc) | ||||
if m: | ||||
|
r44089 | doc = doc[: m.start()] | ||
|
r33816 | |||
|
r8542 | # Backslashes in doc appear doubled in src. | ||
end = src.find(doc.replace('\\', '\\\\')) | ||||
if end == -1: | ||||
# This can happen if the docstring contains unnecessary escape | ||||
# sequences such as \" in a triple-quoted string. The problem | ||||
# is that \" is turned into " and so doc wont appear in src. | ||||
|
r44089 | sys.stderr.write( | ||
"%s:%d:warning:" | ||||
" unknown docstr offset, assuming %d lines\n" | ||||
% (name, lineno, default) | ||||
) | ||||
|
r8542 | return default | ||
else: | ||||
return src.count('\n', 0, end) | ||||
def importpath(path): | ||||
"""Import a path like foo/bar/baz.py and return the baz module.""" | ||||
if path.endswith('.py'): | ||||
path = path[:-3] | ||||
if path.endswith('/__init__'): | ||||
path = path[:-9] | ||||
path = path.replace('/', '.') | ||||
mod = __import__(path) | ||||
for comp in path.split('.')[1:]: | ||||
mod = getattr(mod, comp) | ||||
return mod | ||||
def docstrings(path): | ||||
"""Extract docstrings from path. | ||||
This respects the Mercurial cmdtable/table convention and will | ||||
only extract docstrings from functions mentioned in these tables. | ||||
""" | ||||
mod = importpath(path) | ||||
|
r33818 | if not path.startswith('mercurial/') and mod.__doc__: | ||
|
r36966 | with open(path) as fobj: | ||
src = fobj.read() | ||||
|
r38851 | lineno = 1 + offset(src, mod.__doc__, path, 1, 7) | ||
|
r29171 | print(poentry(path, lineno, mod.__doc__)) | ||
|
r8542 | |||
|
r12823 | functions = list(getattr(mod, 'i18nfunctions', [])) | ||
functions = [(f, True) for f in functions] | ||||
|
r8542 | cmdtable = getattr(mod, 'cmdtable', {}) | ||
if not cmdtable: | ||||
# Maybe we are processing mercurial.commands? | ||||
cmdtable = getattr(mod, 'table', {}) | ||||
|
r46390 | functions.extend((c[0], False) for c in cmdtable.values()) | ||
|
r8542 | |||
|
r12823 | for func, rstrip in functions: | ||
|
r8542 | if func.__doc__: | ||
|
r44089 | docobj = func # this might be a proxy to provide formatted doc | ||
|
r33817 | func = getattr(func, '_origfunc', func) | ||
|
r33623 | funcmod = inspect.getmodule(func) | ||
extra = '' | ||||
if funcmod.__package__ == funcmod.__name__: | ||||
extra = '/__init__' | ||||
actualpath = '%s%s.py' % (funcmod.__name__.replace('.', '/'), extra) | ||||
|
r8542 | src = inspect.getsource(func) | ||
|
r29720 | lineno = inspect.getsourcelines(func)[1] | ||
|
r33817 | doc = docobj.__doc__ | ||
origdoc = getattr(docobj, '_origdoc', '') | ||||
|
r12823 | if rstrip: | ||
doc = doc.rstrip() | ||||
|
r33814 | origdoc = origdoc.rstrip() | ||
if origdoc: | ||||
|
r38851 | lineno += offset(src, origdoc, actualpath, lineno, 1) | ||
|
r33814 | else: | ||
|
r38851 | lineno += offset(src, doc, actualpath, lineno, 1) | ||
|
r33623 | print(poentry(actualpath, lineno, doc)) | ||
|
r8542 | |||
|
r9539 | def rawtext(path): | ||
|
r36966 | with open(path) as f: | ||
src = f.read() | ||||
|
r29171 | print(poentry(path, 1, src)) | ||
|
r9539 | |||
|
r8542 | if __name__ == "__main__": | ||
|
r8626 | # It is very important that we import the Mercurial modules from | ||
# the source tree where hggettext is executed. Otherwise we might | ||||
# accidentally import and extract strings from a Mercurial | ||||
# installation mentioned in PYTHONPATH. | ||||
sys.path.insert(0, os.getcwd()) | ||||
|
r44089 | from mercurial import demandimport | ||
demandimport.enable() | ||||
|
r8542 | for path in sys.argv[1:]: | ||
|
r9539 | if path.endswith('.txt'): | ||
rawtext(path) | ||||
else: | ||||
docstrings(path) | ||||