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