20
20
"""Extract docstrings from Bazaar commands.
22
This module only handles bzrlib objects that use strings not directly wrapped
22
This module only handles breezy objects that use strings not directly wrapped
23
23
by a gettext() call. To generate a complete translation template file, this
24
24
output needs to be combined with that of xgettext or a similar command for
25
25
extracting those strings, as is done in the bzr Makefile. Sorting the output
26
26
is also left to that stage of the process.
29
from __future__ import absolute_import
35
35
commands as _mod_commands,
39
plugin as _mod_plugin,
42
from bzrlib.trace import (
46
from bzrlib.i18n import gettext
45
from .i18n import gettext
50
49
s = (s.replace('\\', '\\\\')
59
59
# This converts the various Python string types into a format that
60
60
# is appropriate for .po files, namely much closer to C style.
67
67
lines[-1] = lines[-1] + '\n'
68
lines = map(_escape, lines)
69
68
lineterm = '\\n"\n"'
70
s = '""\n"' + lineterm.join(lines) + '"'
69
s = '""\n"' + lineterm.join(map(_escape, lines)) + '"'
74
def _parse_source(source_text):
73
def _parse_source(source_text, filename='<unknown>'):
75
74
"""Get object to lineno mappings from given source_text"""
79
for node in ast.walk(ast.parse(source_text)):
78
for node in ast.walk(ast.parse(source_text, filename)):
80
79
# TODO: worry about duplicates?
81
80
if isinstance(node, ast.ClassDef):
82
81
# TODO: worry about nesting?
86
85
# string terminates on. It's more useful to have the line the
87
86
# string begins on. Unfortunately, counting back newlines is
88
87
# only an approximation as the AST is ignorant of escaping.
89
str_to_lineno[node.s] = node.lineno - node.s.count('\n')
88
str_to_lineno[node.s] = node.lineno - (0 if sys.version_info >= (3, 8) else node.s.count('\n'))
90
89
return cls_to_lineno, str_to_lineno
106
105
# TODO: fix this to do the right thing rather than rely on cwd
107
106
relpath = os.path.relpath(sourcepath)
108
107
return cls(relpath,
109
_source_info=_parse_source("".join(inspect.findsource(module)[0])))
108
_source_info=_parse_source("".join(inspect.findsource(module)[0]), module.__file__))
111
110
def from_class(self, cls):
112
111
"""Get new context with same details but lineno of class in source"""
116
115
mutter("Definition of %r not found in %r", cls, self.path)
118
117
return self.__class__(self.path, lineno,
119
(self._cls_to_lineno, self._str_to_lineno))
118
(self._cls_to_lineno, self._str_to_lineno))
121
120
def from_string(self, string):
122
121
"""Get new context with same details but lineno of string in source"""
126
125
mutter("String %r not found in %r", string[:20], self.path)
128
127
return self.__class__(self.path, lineno,
129
(self._cls_to_lineno, self._str_to_lineno))
128
(self._cls_to_lineno, self._str_to_lineno))
132
131
class _PotExporter(object):
151
150
comment = "# %s\n" % comment
152
151
mutter("Exporting msg %r at line %d in %r", s[:20], lineno, path)
154
153
"#: {path}:{lineno}\n"
159
158
path=path, lineno=lineno, comment=comment, msg=_normalize(s)))
159
self.outf.write(line)
161
161
def poentry_in_context(self, context, string, comment=None):
162
162
context = context.from_string(string)
186
186
def _write_option(exporter, context, opt, note):
187
187
if getattr(opt, 'hidden', False):
189
189
optname = opt.name
190
190
if getattr(opt, 'title', None):
191
191
exporter.poentry_in_context(context, opt.title,
192
"title of {name!r} {what}".format(name=optname, what=note))
192
"title of {name!r} {what}".format(name=optname, what=note))
193
193
for name, _, _, helptxt in opt.iter_switches():
194
194
if name != optname:
195
195
if opt.is_hidden(name):
197
197
name = "=".join([optname, name])
199
199
exporter.poentry_in_context(context, helptxt,
200
"help of {name!r} {what}".format(name=name, what=note))
200
"help of {name!r} {what}".format(name=name, what=note))
203
203
def _standard_options(exporter):
204
204
OPTIONS = option.Option.OPTIONS
205
205
context = exporter.get_context(option)
206
for name in sorted(OPTIONS.keys()):
206
for name in sorted(OPTIONS):
207
207
opt = OPTIONS[name]
208
208
_write_option(exporter, context.from_string(name), opt, "option")
231
231
exporter.poentry_per_paragraph(dcontext.path, dcontext.lineno, doc,
233
233
_command_options(exporter, context, cmd)
252
251
note(gettext("Exporting messages from builtin command: %s"), cmd_name)
253
252
_write_command_help(exporter, command)
255
plugin_path = plugin.get_core_plugin_path()
256
core_plugins = glob(plugin_path + '/*/__init__.py')
257
core_plugins = [os.path.basename(os.path.dirname(p))
258
for p in core_plugins]
254
plugins = _mod_plugin.plugins()
255
if plugin_name is not None and plugin_name not in plugins:
256
raise errors.BzrError(gettext('Plugin %s is not loaded' % plugin_name))
258
name for name in plugins
259
if plugins[name].path().startswith(breezy.__path__[0]))
260
261
for cmd_name in _mod_commands.plugin_command_names():
261
262
command = _mod_commands.get_cmd_object(cmd_name, False)
262
263
if command.hidden:
264
265
if plugin_name is not None and command.plugin_name() != plugin_name:
265
# if we are exporting plugin commands, skip plugins we have not specified.
266
# if we are exporting plugin commands, skip plugins we have not
267
269
if plugin_name is None and command.plugin_name() not in core_plugins:
268
270
# skip non-core plugins
269
271
# TODO: Support extracting from third party plugins.
271
273
note(gettext("Exporting messages from plugin command: {0} in {1}").format(
272
cmd_name, command.plugin_name() ))
274
cmd_name, command.plugin_name()))
273
275
_write_command_help(exporter, command)
276
278
def _error_messages(exporter):
277
"""Extract fmt string from bzrlib.errors."""
279
"""Extract fmt string from breezy.errors."""
278
280
context = exporter.get_context(errors)
279
281
base_klass = errors.BzrError
280
282
for name in dir(errors):
299
301
doc = topic_registry.get(key)
300
302
if isinstance(doc, str):
301
303
exporter.poentry_per_paragraph(
302
'dummy/help_topics/'+key+'/detail.txt',
304
elif callable(doc): # help topics from files
304
'dummy/help_topics/' + key + '/detail.txt',
306
elif callable(doc): # help topics from files
305
307
exporter.poentry_per_paragraph(
306
'en/help_topics/'+key+'.txt',
308
'en/help_topics/' + key + '.txt',
308
310
summary = topic_registry.get_summary(key)
309
311
if summary is not None:
310
exporter.poentry('dummy/help_topics/'+key+'/summary.txt',
312
exporter.poentry('dummy/help_topics/' + key + '/summary.txt',
314
316
def export_pot(outf, plugin=None, include_duplicates=False):