1
# Copyright (C) 2011 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
# The normalize function is taken from pygettext which is distributed
18
# with Python under the Python License, which is GPL compatible.
20
"""Extract docstrings from Bazaar commands.
22
This module only handles bzrlib objects that use strings not directly wrapped
23
by a gettext() call. To generate a complete translation template file, this
24
output needs to be combined with that of xgettext or a similar command for
25
extracting those strings, as is done in the bzr Makefile. Sorting the output
26
is also left to that stage of the process.
33
commands as _mod_commands,
39
from bzrlib.trace import (
43
from bzrlib.i18n import gettext
47
s = (s.replace('\\', '\\\\')
56
# This converts the various Python string types into a format that
57
# is appropriate for .po files, namely much closer to C style.
60
s = '"' + _escape(s) + '"'
64
lines[-1] = lines[-1] + '\n'
65
lines = map(_escape, lines)
67
s = '""\n"' + lineterm.join(lines) + '"'
71
class _PotExporter(object):
72
"""Write message details to output stream in .pot file format"""
74
def __init__(self, outf):
78
def poentry(self, path, lineno, s, comment=None):
85
comment = "# %s\n" % comment
86
mutter("Exporting msg %r at line %d in %r", s[:20], lineno, path)
88
"#: {path}:{lineno}\n"
93
path=path, lineno=lineno, comment=comment, msg=_normalize(s)))
95
def poentry_per_paragraph(self, path, lineno, msgid, include=None):
96
# TODO: How to split long help?
97
paragraphs = msgid.split('\n\n')
98
if include is not None:
99
paragraphs = filter(include, paragraphs)
101
self.poentry(path, lineno, p)
102
lineno += p.count('\n') + 2
105
_LAST_CACHE = _LAST_CACHED_SRC = None
107
def _offsets_of_literal(src):
108
global _LAST_CACHE, _LAST_CACHED_SRC
109
if src == _LAST_CACHED_SRC:
110
return _LAST_CACHE.copy()
113
root = ast.parse(src)
115
for node in ast.walk(root):
116
if not isinstance(node, ast.Str):
118
offsets[node.s] = node.lineno - node.s.count('\n')
120
_LAST_CACHED_SRC = src
121
_LAST_CACHE = offsets.copy()
124
def _standard_options(exporter):
125
from bzrlib.option import Option
126
src = inspect.findsource(Option)[0]
128
path = 'bzrlib/option.py'
129
offsets = _offsets_of_literal(src)
131
for name in sorted(Option.OPTIONS.keys()):
132
opt = Option.OPTIONS[name]
133
if getattr(opt, 'hidden', False):
135
if getattr(opt, 'title', None):
136
lineno = offsets.get(opt.title, 9999)
138
note(gettext("%r is not found in bzrlib/option.py") % opt.title)
139
exporter.poentry(path, lineno, opt.title,
140
'title of %r option' % name)
141
if getattr(opt, 'help', None):
142
lineno = offsets.get(opt.help, 9999)
144
note(gettext("%r is not found in bzrlib/option.py") % opt.help)
145
exporter.poentry(path, lineno, opt.help,
146
'help of %r option' % name)
148
def _command_options(exporter, path, cmd):
149
src, default_lineno = inspect.findsource(cmd.__class__)
150
offsets = _offsets_of_literal(''.join(src))
151
for opt in cmd.takes_options:
152
if isinstance(opt, str):
154
if getattr(opt, 'hidden', False):
157
if getattr(opt, 'title', None):
158
lineno = offsets.get(opt.title, default_lineno)
159
exporter.poentry(path, lineno, opt.title,
160
'title of %r option of %r command' % (name, cmd.name()))
161
if getattr(opt, 'help', None):
162
lineno = offsets.get(opt.help, default_lineno)
163
exporter.poentry(path, lineno, opt.help,
164
'help of %r option of %r command' % (name, cmd.name()))
167
def _write_command_help(exporter, cmd):
168
path = inspect.getfile(cmd.__class__)
169
if path.endswith('.pyc'):
171
path = os.path.relpath(path)
172
src, lineno = inspect.findsource(cmd.__class__)
173
offsets = _offsets_of_literal(''.join(src))
174
lineno = offsets[cmd.__doc__]
175
doc = inspect.getdoc(cmd)
177
def exclude_usage(p):
178
# ':Usage:' has special meaning in help topics.
179
# This is usage example of command and should not be translated.
180
if p.splitlines()[0] != ':Usage:':
183
exporter.poentry_per_paragraph(path, lineno, doc, exclude_usage)
184
_command_options(exporter, path, cmd)
187
def _command_helps(exporter, plugin_name=None):
188
"""Extract docstrings from path.
190
This respects the Bazaar cmdtable/table convention and will
191
only extract docstrings from functions mentioned in these tables.
193
from glob import glob
196
for cmd_name in _mod_commands.builtin_command_names():
197
command = _mod_commands.get_cmd_object(cmd_name, False)
200
if plugin_name is not None:
201
# only export builtins if we are not exporting plugin commands
203
note(gettext("Exporting messages from builtin command: %s"), cmd_name)
204
_write_command_help(exporter, command)
206
plugin_path = plugin.get_core_plugin_path()
207
core_plugins = glob(plugin_path + '/*/__init__.py')
208
core_plugins = [os.path.basename(os.path.dirname(p))
209
for p in core_plugins]
211
for cmd_name in _mod_commands.plugin_command_names():
212
command = _mod_commands.get_cmd_object(cmd_name, False)
215
if plugin_name is not None and command.plugin_name() != plugin_name:
216
# if we are exporting plugin commands, skip plugins we have not specified.
218
if plugin_name is None and command.plugin_name() not in core_plugins:
219
# skip non-core plugins
220
# TODO: Support extracting from third party plugins.
222
note(gettext("Exporting messages from plugin command: {0} in {1}").format(
223
cmd_name, command.plugin_name() ))
224
_write_command_help(exporter, command)
227
def _error_messages(exporter):
228
"""Extract fmt string from bzrlib.errors."""
229
path = errors.__file__
230
if path.endswith('.pyc'):
232
offsets = _offsets_of_literal(open(path).read())
234
base_klass = errors.BzrError
235
for name in dir(errors):
236
klass = getattr(errors, name)
237
if not inspect.isclass(klass):
239
if not issubclass(klass, base_klass):
241
if klass is base_klass:
243
if klass.internal_error:
245
fmt = getattr(klass, "_fmt", None)
247
note(gettext("Exporting message from error: %s"), name)
248
exporter.poentry('bzrlib/errors.py',
249
offsets.get(fmt, 9999), fmt)
251
def _help_topics(exporter):
252
topic_registry = help_topics.topic_registry
253
for key in topic_registry.keys():
254
doc = topic_registry.get(key)
255
if isinstance(doc, str):
256
exporter.poentry_per_paragraph(
257
'dummy/help_topics/'+key+'/detail.txt',
259
elif callable(doc): # help topics from files
260
exporter.poentry_per_paragraph(
261
'en/help_topics/'+key+'.txt',
263
summary = topic_registry.get_summary(key)
264
if summary is not None:
265
exporter.poentry('dummy/help_topics/'+key+'/summary.txt',
268
def export_pot(outf, plugin=None):
269
exporter = _PotExporter(outf)
271
_standard_options(exporter)
272
_command_helps(exporter)
273
_error_messages(exporter)
274
_help_topics(exporter)
276
_command_helps(exporter, plugin)