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