/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
1
# Copyright (C) 2011 Canonical Ltd
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
2
#
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.
7
#
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.
12
#
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
16
17
# The normalize function is taken from pygettext which is distributed
18
# with Python under the Python License, which is GPL compatible.
19
20
"""Extract docstrings from Bazaar commands.
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
21
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
22
This module only handles breezy objects that use strings not directly wrapped
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
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.
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
27
"""
28
6379.6.7 by Jelmer Vernooij
Move importing from future until after doc string, otherwise the doc string will disappear.
29
from __future__ import absolute_import
30
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
31
import inspect
32
import os
7290.47.2 by Jelmer Vernooij
More improvements to make tests success with Python 3.8.
33
import sys
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
34
6651.4.3 by Martin
Fix export-pot core plugin handling
35
import breezy
6624 by Jelmer Vernooij
Merge Python3 porting work ('py3 pokes')
36
from . import (
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
37
    commands as _mod_commands,
38
    errors,
39
    help_topics,
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
40
    option,
6759.4.3 by Jelmer Vernooij
Avoid accessing global state.
41
    plugin as _mod_plugin,
5830.2.15 by INADA Naoki
Add debug trace.
42
    )
7027.3.3 by Jelmer Vernooij
Add some more bees; support writing both bytes and unicode strings in build_tree_contents.
43
from .sixish import PY3
6624 by Jelmer Vernooij
Merge Python3 porting work ('py3 pokes')
44
from .trace import (
5830.2.15 by INADA Naoki
Add debug trace.
45
    mutter,
46
    note,
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
47
    )
6624 by Jelmer Vernooij
Merge Python3 porting work ('py3 pokes')
48
from .i18n import gettext
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
49
50
51
def _escape(s):
7027.3.7 by Jelmer Vernooij
drop broken tests.
52
    s = (s.replace('\\', '\\\\')
7143.15.2 by Jelmer Vernooij
Run autopep8.
53
         .replace('\n', '\\n')
54
         .replace('\r', '\\r')
55
         .replace('\t', '\\t')
56
         .replace('"', '\\"')
57
         )
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
58
    return s
59
7143.15.2 by Jelmer Vernooij
Run autopep8.
60
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
61
def _normalize(s):
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
62
    # This converts the various Python string types into a format that
63
    # is appropriate for .po files, namely much closer to C style.
7027.3.7 by Jelmer Vernooij
drop broken tests.
64
    lines = s.split('\n')
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
65
    if len(lines) == 1:
7027.3.7 by Jelmer Vernooij
drop broken tests.
66
        s = '"' + _escape(s) + '"'
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
67
    else:
68
        if not lines[-1]:
69
            del lines[-1]
70
            lines[-1] = lines[-1] + '\n'
71
        lineterm = '\\n"\n"'
7027.3.7 by Jelmer Vernooij
drop broken tests.
72
        s = '""\n"' + lineterm.join(map(_escape, lines)) + '"'
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
73
    return s
74
75
7027.3.7 by Jelmer Vernooij
drop broken tests.
76
def _parse_source(source_text, filename='<unknown>'):
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
77
    """Get object to lineno mappings from given source_text"""
78
    import ast
79
    cls_to_lineno = {}
80
    str_to_lineno = {}
7027.3.7 by Jelmer Vernooij
drop broken tests.
81
    for node in ast.walk(ast.parse(source_text, filename)):
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
82
        # TODO: worry about duplicates?
83
        if isinstance(node, ast.ClassDef):
84
            # TODO: worry about nesting?
85
            cls_to_lineno[node.name] = node.lineno
86
        elif isinstance(node, ast.Str):
87
            # Python AST gives location of string literal as the line the
88
            # string terminates on. It's more useful to have the line the
89
            # string begins on. Unfortunately, counting back newlines is
90
            # only an approximation as the AST is ignorant of escaping.
7290.47.2 by Jelmer Vernooij
More improvements to make tests success with Python 3.8.
91
            str_to_lineno[node.s] = node.lineno - (0 if sys.version_info >= (3, 8) else node.s.count('\n'))
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
92
    return cls_to_lineno, str_to_lineno
93
94
95
class _ModuleContext(object):
96
    """Record of the location within a source tree"""
97
98
    def __init__(self, path, lineno=1, _source_info=None):
99
        self.path = path
100
        self.lineno = lineno
101
        if _source_info is not None:
102
            self._cls_to_lineno, self._str_to_lineno = _source_info
103
104
    @classmethod
105
    def from_module(cls, module):
106
        """Get new context from module object and parse source for linenos"""
107
        sourcepath = inspect.getsourcefile(module)
108
        # TODO: fix this to do the right thing rather than rely on cwd
109
        relpath = os.path.relpath(sourcepath)
110
        return cls(relpath,
7143.15.2 by Jelmer Vernooij
Run autopep8.
111
                   _source_info=_parse_source("".join(inspect.findsource(module)[0]), module.__file__))
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
112
113
    def from_class(self, cls):
114
        """Get new context with same details but lineno of class in source"""
115
        try:
116
            lineno = self._cls_to_lineno[cls.__name__]
117
        except (AttributeError, KeyError):
118
            mutter("Definition of %r not found in %r", cls, self.path)
119
            return self
120
        return self.__class__(self.path, lineno,
7143.15.2 by Jelmer Vernooij
Run autopep8.
121
                              (self._cls_to_lineno, self._str_to_lineno))
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
122
123
    def from_string(self, string):
124
        """Get new context with same details but lineno of string in source"""
125
        try:
126
            lineno = self._str_to_lineno[string]
127
        except (AttributeError, KeyError):
128
            mutter("String %r not found in %r", string[:20], self.path)
129
            return self
130
        return self.__class__(self.path, lineno,
7143.15.2 by Jelmer Vernooij
Run autopep8.
131
                              (self._cls_to_lineno, self._str_to_lineno))
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
132
133
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
134
class _PotExporter(object):
135
    """Write message details to output stream in .pot file format"""
136
6351.2.1 by Martin Packman
Add export-pot --include-duplicates option for permitting multiple entries with the same msgid
137
    def __init__(self, outf, include_duplicates=False):
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
138
        self.outf = outf
6351.2.1 by Martin Packman
Add export-pot --include-duplicates option for permitting multiple entries with the same msgid
139
        if include_duplicates:
140
            self._msgids = None
141
        else:
142
            self._msgids = set()
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
143
        self._module_contexts = {}
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
144
145
    def poentry(self, path, lineno, s, comment=None):
6351.2.1 by Martin Packman
Add export-pot --include-duplicates option for permitting multiple entries with the same msgid
146
        if self._msgids is not None:
147
            if s in self._msgids:
148
                return
149
            self._msgids.add(s)
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
150
        if comment is None:
151
            comment = ''
152
        else:
153
            comment = "# %s\n" % comment
154
        mutter("Exporting msg %r at line %d in %r", s[:20], lineno, path)
6973.11.1 by Jelmer Vernooij
Fix export_pot.
155
        line = (
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
156
            "#: {path}:{lineno}\n"
157
            "{comment}"
158
            "msgid {msg}\n"
159
            "msgstr \"\"\n"
160
            "\n".format(
161
                path=path, lineno=lineno, comment=comment, msg=_normalize(s)))
7027.3.3 by Jelmer Vernooij
Add some more bees; support writing both bytes and unicode strings in build_tree_contents.
162
        if not PY3:
163
            line = line.decode('utf-8')
164
        self.outf.write(line)
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
165
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
166
    def poentry_in_context(self, context, string, comment=None):
167
        context = context.from_string(string)
168
        self.poentry(context.path, context.lineno, string, comment)
169
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
170
    def poentry_per_paragraph(self, path, lineno, msgid, include=None):
171
        # TODO: How to split long help?
172
        paragraphs = msgid.split('\n\n')
173
        if include is not None:
174
            paragraphs = filter(include, paragraphs)
175
        for p in paragraphs:
176
            self.poentry(path, lineno, p)
177
            lineno += p.count('\n') + 2
178
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
179
    def get_context(self, obj):
180
        module = inspect.getmodule(obj)
181
        try:
182
            context = self._module_contexts[module.__name__]
183
        except KeyError:
184
            context = _ModuleContext.from_module(module)
185
            self._module_contexts[module.__name__] = context
186
        if inspect.isclass(obj):
187
            context = context.from_class(obj)
188
        return context
189
190
191
def _write_option(exporter, context, opt, note):
192
    if getattr(opt, 'hidden', False):
7143.15.2 by Jelmer Vernooij
Run autopep8.
193
        return
6282.2.3 by Martin Packman
Export registry help to pot for unhidden option value switches
194
    optname = opt.name
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
195
    if getattr(opt, 'title', None):
196
        exporter.poentry_in_context(context, opt.title,
7143.15.2 by Jelmer Vernooij
Run autopep8.
197
                                    "title of {name!r} {what}".format(name=optname, what=note))
6282.2.3 by Martin Packman
Export registry help to pot for unhidden option value switches
198
    for name, _, _, helptxt in opt.iter_switches():
199
        if name != optname:
200
            if opt.is_hidden(name):
201
                continue
202
            name = "=".join([optname, name])
203
        if helptxt:
204
            exporter.poentry_in_context(context, helptxt,
7143.15.2 by Jelmer Vernooij
Run autopep8.
205
                                        "help of {name!r} {what}".format(name=name, what=note))
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
206
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
207
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
208
def _standard_options(exporter):
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
209
    OPTIONS = option.Option.OPTIONS
210
    context = exporter.get_context(option)
6656.1.1 by Martin
Apply 2to3 dict fixer and clean up resulting mess using view helpers
211
    for name in sorted(OPTIONS):
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
212
        opt = OPTIONS[name]
213
        _write_option(exporter, context.from_string(name), opt, "option")
214
215
216
def _command_options(exporter, context, cmd):
217
    note = "option of {0!r} command".format(cmd.name())
5830.2.17 by INADA Naoki
Split command specific options and standard options.
218
    for opt in cmd.takes_options:
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
219
        # String values in Command option lists are for global options
220
        if not isinstance(opt, str):
221
            _write_option(exporter, context, opt, note)
5830.2.3 by INADA Naoki
bzrgettext extracts help message of command option too.
222
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
223
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
224
def _write_command_help(exporter, cmd):
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
225
    context = exporter.get_context(cmd.__class__)
226
    rawdoc = cmd.__doc__
227
    dcontext = context.from_string(rawdoc)
228
    doc = inspect.cleandoc(rawdoc)
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
229
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
230
    def exclude_usage(p):
5875.3.20 by INADA Naoki
Add test for exporting command help.
231
        # ':Usage:' has special meaning in help topics.
232
        # This is usage example of command and should not be translated.
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
233
        if p.splitlines()[0] != ':Usage:':
5875.3.20 by INADA Naoki
Add test for exporting command help.
234
            return True
235
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
236
    exporter.poentry_per_paragraph(dcontext.path, dcontext.lineno, doc,
7143.15.2 by Jelmer Vernooij
Run autopep8.
237
                                   exclude_usage)
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
238
    _command_options(exporter, context, cmd)
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
239
240
241
def _command_helps(exporter, plugin_name=None):
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
242
    """Extract docstrings from path.
243
244
    This respects the Bazaar cmdtable/table convention and will
245
    only extract docstrings from functions mentioned in these tables.
246
    """
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
247
248
    # builtin commands
249
    for cmd_name in _mod_commands.builtin_command_names():
5830.2.16 by INADA Naoki
Skip hidden commands to focus important commands.
250
        command = _mod_commands.get_cmd_object(cmd_name, False)
251
        if command.hidden:
252
            continue
6162.4.5 by Jonathan Riddell
change export-pot --plugins option to --plugin which takes a plugin name rather than command name
253
        if plugin_name is not None:
6162.4.3 by Jonathan Riddell
add new option 'plugins' to 'export-pot' to export strings from given plugin commands help
254
            # only export builtins if we are not exporting plugin commands
255
            continue
6138.3.4 by Jonathan Riddell
add gettext() to uses of trace.note()
256
        note(gettext("Exporting messages from builtin command: %s"), cmd_name)
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
257
        _write_command_help(exporter, command)
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
258
6759.4.3 by Jelmer Vernooij
Avoid accessing global state.
259
    plugins = _mod_plugin.plugins()
6759.4.2 by Jelmer Vernooij
Use get_global_state>
260
    if plugin_name is not None and plugin_name not in plugins:
6703.1.4 by Jelmer Vernooij
Print an error when trying to export pot file from a plugin that doesn't exist.
261
        raise errors.BzrError(gettext('Plugin %s is not loaded' % plugin_name))
6759.4.3 by Jelmer Vernooij
Avoid accessing global state.
262
    core_plugins = set(
7143.15.2 by Jelmer Vernooij
Run autopep8.
263
        name for name in plugins
264
        if plugins[name].path().startswith(breezy.__path__[0]))
6162.4.3 by Jonathan Riddell
add new option 'plugins' to 'export-pot' to export strings from given plugin commands help
265
    # plugins
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
266
    for cmd_name in _mod_commands.plugin_command_names():
267
        command = _mod_commands.get_cmd_object(cmd_name, False)
5830.2.16 by INADA Naoki
Skip hidden commands to focus important commands.
268
        if command.hidden:
269
            continue
6162.4.5 by Jonathan Riddell
change export-pot --plugins option to --plugin which takes a plugin name rather than command name
270
        if plugin_name is not None and command.plugin_name() != plugin_name:
6759.4.3 by Jelmer Vernooij
Avoid accessing global state.
271
            # if we are exporting plugin commands, skip plugins we have not
272
            # specified.
6162.4.3 by Jonathan Riddell
add new option 'plugins' to 'export-pot' to export strings from given plugin commands help
273
            continue
6162.4.5 by Jonathan Riddell
change export-pot --plugins option to --plugin which takes a plugin name rather than command name
274
        if plugin_name is None and command.plugin_name() not in core_plugins:
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
275
            # skip non-core plugins
276
            # TODO: Support extracting from third party plugins.
277
            continue
6138.3.4 by Jonathan Riddell
add gettext() to uses of trace.note()
278
        note(gettext("Exporting messages from plugin command: {0} in {1}").format(
7143.15.2 by Jelmer Vernooij
Run autopep8.
279
             cmd_name, command.plugin_name()))
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
280
        _write_command_help(exporter, command)
281
282
283
def _error_messages(exporter):
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
284
    """Extract fmt string from breezy.errors."""
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
285
    context = exporter.get_context(errors)
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
286
    base_klass = errors.BzrError
287
    for name in dir(errors):
288
        klass = getattr(errors, name)
289
        if not inspect.isclass(klass):
290
            continue
291
        if not issubclass(klass, base_klass):
292
            continue
293
        if klass is base_klass:
294
            continue
295
        if klass.internal_error:
296
            continue
297
        fmt = getattr(klass, "_fmt", None)
298
        if fmt:
6138.3.4 by Jonathan Riddell
add gettext() to uses of trace.note()
299
            note(gettext("Exporting message from error: %s"), name)
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
300
            exporter.poentry_in_context(context, fmt)
301
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
302
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
303
def _help_topics(exporter):
5830.2.14 by INADA Naoki
Cleanup import
304
    topic_registry = help_topics.topic_registry
5830.2.9 by INADA Naoki
Export from help_topics that is directly registered into
305
    for key in topic_registry.keys():
306
        doc = topic_registry.get(key)
307
        if isinstance(doc, str):
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
308
            exporter.poentry_per_paragraph(
7143.15.2 by Jelmer Vernooij
Run autopep8.
309
                'dummy/help_topics/' + key + '/detail.txt',
310
                1, doc)
311
        elif callable(doc):  # help topics from files
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
312
            exporter.poentry_per_paragraph(
7143.15.2 by Jelmer Vernooij
Run autopep8.
313
                'en/help_topics/' + key + '.txt',
314
                1, doc(key))
5830.2.9 by INADA Naoki
Export from help_topics that is directly registered into
315
        summary = topic_registry.get_summary(key)
316
        if summary is not None:
7143.15.2 by Jelmer Vernooij
Run autopep8.
317
            exporter.poentry('dummy/help_topics/' + key + '/summary.txt',
318
                             1, summary)
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
319
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
320
6351.2.1 by Martin Packman
Add export-pot --include-duplicates option for permitting multiple entries with the same msgid
321
def export_pot(outf, plugin=None, include_duplicates=False):
322
    exporter = _PotExporter(outf, include_duplicates)
6162.4.5 by Jonathan Riddell
change export-pot --plugins option to --plugin which takes a plugin name rather than command name
323
    if plugin is None:
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
324
        _standard_options(exporter)
325
        _command_helps(exporter)
326
        _error_messages(exporter)
327
        _help_topics(exporter)
6162.4.3 by Jonathan Riddell
add new option 'plugins' to 'export-pot' to export strings from given plugin commands help
328
    else:
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
329
        _command_helps(exporter, plugin)