~bzr-pqm/bzr/bzr.dev

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.
21
"""
22
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
23
import inspect
24
import os
25
26
from bzrlib import (
27
    commands as _mod_commands,
28
    errors,
29
    help_topics,
30
    plugin,
6110.7.1 by Jonathan Riddell
add topic help to translations
31
    help,
5830.2.15 by INADA Naoki
Add debug trace.
32
    )
33
from bzrlib.trace import (
34
    mutter,
35
    note,
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
36
    )
6138.4.4 by Jonathan Riddell
update bzr.pot
37
from bzrlib.i18n import gettext
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
38
39
40
def _escape(s):
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
41
    s = (s.replace('\\', '\\\\')
42
        .replace('\n', '\\n')
43
        .replace('\r', '\\r')
44
        .replace('\t', '\\t')
45
        .replace('"', '\\"')
46
        )
47
    return s
48
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
49
def _normalize(s):
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
50
    # This converts the various Python string types into a format that
51
    # is appropriate for .po files, namely much closer to C style.
52
    lines = s.split('\n')
53
    if len(lines) == 1:
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
54
        s = '"' + _escape(s) + '"'
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
55
    else:
56
        if not lines[-1]:
57
            del lines[-1]
58
            lines[-1] = lines[-1] + '\n'
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
59
        lines = map(_escape, lines)
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
60
        lineterm = '\\n"\n"'
61
        s = '""\n"' + lineterm.join(lines) + '"'
62
    return s
63
64
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
65
_FOUND_MSGID = None # set by entry function.
5830.2.3 by INADA Naoki
bzrgettext extracts help message of command option too.
66
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
67
def _poentry(outf, path, lineno, s, comment=None):
68
    if s in _FOUND_MSGID:
5830.2.3 by INADA Naoki
bzrgettext extracts help message of command option too.
69
        return
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
70
    _FOUND_MSGID.add(s)
5830.2.3 by INADA Naoki
bzrgettext extracts help message of command option too.
71
    if comment is None:
72
        comment = ''
73
    else:
74
        comment = "# %s\n" % comment
5830.2.15 by INADA Naoki
Add debug trace.
75
    mutter("Exporting msg %r at line %d in %r", s[:20], lineno, path)
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
76
    print >>outf, ('#: %s:%d\n' % (path, lineno) +
5830.2.3 by INADA Naoki
bzrgettext extracts help message of command option too.
77
           comment+
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
78
           'msgid %s\n' % _normalize(s) +
5830.2.3 by INADA Naoki
bzrgettext extracts help message of command option too.
79
           'msgstr ""\n')
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
80
5875.3.20 by INADA Naoki
Add test for exporting command help.
81
def _poentry_per_paragraph(outf, path, lineno, msgid, filter=lambda x: False):
5830.2.18 by INADA Naoki
Add some tests for export_pot module.
82
    # TODO: How to split long help?
5830.2.2 by INADA Naoki
Split command's docstring per paragraph.
83
    paragraphs = msgid.split('\n\n')
84
    for p in paragraphs:
5875.3.20 by INADA Naoki
Add test for exporting command help.
85
        if filter(p):
5875.3.4 by INADA Naoki
Don't translate :Usage: sections.
86
            continue
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
87
        _poentry(outf, path, lineno, p)
5830.2.2 by INADA Naoki
Split command's docstring per paragraph.
88
        lineno += p.count('\n') + 2
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
89
5830.2.19 by INADA Naoki
Implement ast based offset detection.
90
_LAST_CACHE = _LAST_CACHED_SRC = None
91
92
def _offsets_of_literal(src):
93
    global _LAST_CACHE, _LAST_CACHED_SRC
94
    if src == _LAST_CACHED_SRC:
95
        return _LAST_CACHE.copy()
96
97
    import ast
98
    root = ast.parse(src)
99
    offsets = {}
100
    for node in ast.walk(root):
101
        if not isinstance(node, ast.Str):
102
            continue
5830.2.21 by INADA Naoki
Fix lineno of command help in pot
103
        offsets[node.s] = node.lineno - node.s.count('\n')
5830.2.19 by INADA Naoki
Implement ast based offset detection.
104
105
    _LAST_CACHED_SRC = src
106
    _LAST_CACHE = offsets.copy()
107
    return offsets
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
108
5830.2.17 by INADA Naoki
Split command specific options and standard options.
109
def _standard_options(outf):
110
    from bzrlib.option import Option
111
    src = inspect.findsource(Option)[0]
112
    src = ''.join(src)
113
    path = 'bzrlib/option.py'
5830.2.19 by INADA Naoki
Implement ast based offset detection.
114
    offsets = _offsets_of_literal(src)
5830.2.17 by INADA Naoki
Split command specific options and standard options.
115
116
    for name in sorted(Option.OPTIONS.keys()):
117
        opt = Option.OPTIONS[name]
118
        if getattr(opt, 'hidden', False):
119
            continue
120
        if getattr(opt, 'title', None):
5830.2.19 by INADA Naoki
Implement ast based offset detection.
121
            lineno = offsets.get(opt.title, 9999)
122
            if lineno == 9999:
6138.3.4 by Jonathan Riddell
add gettext() to uses of trace.note()
123
                note(gettext("%r is not found in bzrlib/option.py") % opt.title)
5830.2.17 by INADA Naoki
Split command specific options and standard options.
124
            _poentry(outf, path, lineno, opt.title,
125
                     'title of %r option' % name)
126
        if getattr(opt, 'help', None):
5830.2.19 by INADA Naoki
Implement ast based offset detection.
127
            lineno = offsets.get(opt.help, 9999)
128
            if lineno == 9999:
6138.3.4 by Jonathan Riddell
add gettext() to uses of trace.note()
129
                note(gettext("%r is not found in bzrlib/option.py") % opt.help)
5830.2.17 by INADA Naoki
Split command specific options and standard options.
130
            _poentry(outf, path, lineno, opt.help,
131
                     'help of %r option' % name)
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
132
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
133
def _command_options(outf, path, cmd):
5830.2.17 by INADA Naoki
Split command specific options and standard options.
134
    src, default_lineno = inspect.findsource(cmd.__class__)
5830.2.19 by INADA Naoki
Implement ast based offset detection.
135
    offsets = _offsets_of_literal(''.join(src))
5830.2.17 by INADA Naoki
Split command specific options and standard options.
136
    for opt in cmd.takes_options:
137
        if isinstance(opt, str):
138
            continue
139
        if getattr(opt, 'hidden', False):
140
            continue
141
        name = opt.name
142
        if getattr(opt, 'title', None):
5830.2.19 by INADA Naoki
Implement ast based offset detection.
143
            lineno = offsets.get(opt.title, default_lineno)
5830.2.17 by INADA Naoki
Split command specific options and standard options.
144
            _poentry(outf, path, lineno, opt.title,
145
                     'title of %r option of %r command' % (name, cmd.name()))
146
        if getattr(opt, 'help', None):
5830.2.19 by INADA Naoki
Implement ast based offset detection.
147
            lineno = offsets.get(opt.help, default_lineno)
5830.2.17 by INADA Naoki
Split command specific options and standard options.
148
            _poentry(outf, path, lineno, opt.help,
149
                     'help of %r option of %r command' % (name, cmd.name()))
5830.2.3 by INADA Naoki
bzrgettext extracts help message of command option too.
150
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
151
5875.3.20 by INADA Naoki
Add test for exporting command help.
152
def _write_command_help(outf, cmd):
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
153
    path = inspect.getfile(cmd.__class__)
154
    if path.endswith('.pyc'):
155
        path = path[:-1]
156
    path = os.path.relpath(path)
5830.2.21 by INADA Naoki
Fix lineno of command help in pot
157
    src, lineno = inspect.findsource(cmd.__class__)
158
    offsets = _offsets_of_literal(''.join(src))
159
    lineno = offsets[cmd.__doc__]
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
160
    doc = inspect.getdoc(cmd)
161
5875.3.20 by INADA Naoki
Add test for exporting command help.
162
    def filter(p):
163
        # ':Usage:' has special meaning in help topics.
164
        # This is usage example of command and should not be translated.
165
        if p.splitlines()[0] == ':Usage:':
166
            return True
167
168
    _poentry_per_paragraph(outf, path, lineno, doc, filter)
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
169
    _command_options(outf, path, cmd)
170
5875.3.20 by INADA Naoki
Add test for exporting command help.
171
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
172
def _command_helps(outf):
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
173
    """Extract docstrings from path.
174
175
    This respects the Bazaar cmdtable/table convention and will
176
    only extract docstrings from functions mentioned in these tables.
177
    """
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
178
    from glob import glob
179
180
    # builtin commands
181
    for cmd_name in _mod_commands.builtin_command_names():
5830.2.16 by INADA Naoki
Skip hidden commands to focus important commands.
182
        command = _mod_commands.get_cmd_object(cmd_name, False)
183
        if command.hidden:
184
            continue
6138.3.4 by Jonathan Riddell
add gettext() to uses of trace.note()
185
        note(gettext("Exporting messages from builtin command: %s"), cmd_name)
5875.3.20 by INADA Naoki
Add test for exporting command help.
186
        _write_command_help(outf, command)
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
187
5830.2.14 by INADA Naoki
Cleanup import
188
    plugin_path = plugin.get_core_plugin_path()
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
189
    core_plugins = glob(plugin_path + '/*/__init__.py')
190
    core_plugins = [os.path.basename(os.path.dirname(p))
191
                        for p in core_plugins]
192
    # core plugins
193
    for cmd_name in _mod_commands.plugin_command_names():
194
        command = _mod_commands.get_cmd_object(cmd_name, False)
5830.2.16 by INADA Naoki
Skip hidden commands to focus important commands.
195
        if command.hidden:
196
            continue
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
197
        if command.plugin_name() not in core_plugins:
198
            # skip non-core plugins
199
            # TODO: Support extracting from third party plugins.
200
            continue
6138.3.4 by Jonathan Riddell
add gettext() to uses of trace.note()
201
        note(gettext("Exporting messages from plugin command: {0} in {1}").format(
202
             cmd_name, command.plugin_name() ))
5875.3.20 by INADA Naoki
Add test for exporting command help.
203
        _write_command_help(outf, command)
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
204
205
206
def _error_messages(outf):
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
207
    """Extract fmt string from bzrlib.errors."""
5830.2.22 by INADA Naoki
Fix line no of error formats in bzr.pot
208
    path = errors.__file__
209
    if path.endswith('.pyc'):
210
        path = path[:-1]
211
    offsets = _offsets_of_literal(open(path).read())
212
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
213
    base_klass = errors.BzrError
214
    for name in dir(errors):
215
        klass = getattr(errors, name)
216
        if not inspect.isclass(klass):
217
            continue
218
        if not issubclass(klass, base_klass):
219
            continue
220
        if klass is base_klass:
221
            continue
222
        if klass.internal_error:
223
            continue
224
        fmt = getattr(klass, "_fmt", None)
225
        if fmt:
6138.3.4 by Jonathan Riddell
add gettext() to uses of trace.note()
226
            note(gettext("Exporting message from error: %s"), name)
5830.2.15 by INADA Naoki
Add debug trace.
227
            _poentry(outf, 'bzrlib/errors.py',
5830.2.22 by INADA Naoki
Fix line no of error formats in bzr.pot
228
                     offsets.get(fmt, 9999), fmt)
5830.2.1 by INADA Naoki
Add update-pot command to Makefile and tools/bzrgettext script that
229
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
230
def _help_topics(outf):
5830.2.14 by INADA Naoki
Cleanup import
231
    topic_registry = help_topics.topic_registry
5830.2.9 by INADA Naoki
Export from help_topics that is directly registered into
232
    for key in topic_registry.keys():
233
        doc = topic_registry.get(key)
234
        if isinstance(doc, str):
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
235
            _poentry_per_paragraph(
236
                    outf,
5830.2.11 by INADA Naoki
Change dummy file path for help topics.
237
                    'dummy/help_topics/'+key+'/detail.txt',
238
                    1, doc)
6110.7.2 by Jonathan Riddell
check for callable
239
        elif callable(doc): # help topics from files
6110.7.1 by Jonathan Riddell
add topic help to translations
240
            _poentry_per_paragraph(
241
                    outf,
242
                    'en/help_topics/'+key+'.txt',
243
                    1, doc(key))
5830.2.9 by INADA Naoki
Export from help_topics that is directly registered into
244
        summary = topic_registry.get_summary(key)
245
        if summary is not None:
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
246
            _poentry(outf, 'dummy/help_topics/'+key+'/summary.txt',
247
                     1, summary)
248
249
def export_pot(outf):
250
    global _FOUND_MSGID
251
    _FOUND_MSGID = set()
5830.2.17 by INADA Naoki
Split command specific options and standard options.
252
    _standard_options(outf)
5830.2.12 by INADA Naoki
Make "export-pot" hidden command.
253
    _command_helps(outf)
254
    _error_messages(outf)
6110.7.1 by Jonathan Riddell
add topic help to translations
255
    _help_topics(outf)