/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/export_pot.py

  • Committer: Vincent Ladeuil
  • Date: 2011-11-24 15:48:29 UTC
  • mfrom: (6289 +trunk)
  • mto: This revision was merged to the branch mainline in revision 6337.
  • Revision ID: v.ladeuil+lp@free.fr-20111124154829-avowjpsxdl8yp2vz
merge trunk resolving conflicts

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2011 Canonical Ltd
 
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
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.
 
27
"""
 
28
 
 
29
import inspect
 
30
import os
 
31
 
 
32
from bzrlib import (
 
33
    commands as _mod_commands,
 
34
    errors,
 
35
    help_topics,
 
36
    plugin,
 
37
    help,
 
38
    )
 
39
from bzrlib.trace import (
 
40
    mutter,
 
41
    note,
 
42
    )
 
43
from bzrlib.i18n import gettext
 
44
 
 
45
 
 
46
def _escape(s):
 
47
    s = (s.replace('\\', '\\\\')
 
48
        .replace('\n', '\\n')
 
49
        .replace('\r', '\\r')
 
50
        .replace('\t', '\\t')
 
51
        .replace('"', '\\"')
 
52
        )
 
53
    return s
 
54
 
 
55
def _normalize(s):
 
56
    # This converts the various Python string types into a format that
 
57
    # is appropriate for .po files, namely much closer to C style.
 
58
    lines = s.split('\n')
 
59
    if len(lines) == 1:
 
60
        s = '"' + _escape(s) + '"'
 
61
    else:
 
62
        if not lines[-1]:
 
63
            del lines[-1]
 
64
            lines[-1] = lines[-1] + '\n'
 
65
        lines = map(_escape, lines)
 
66
        lineterm = '\\n"\n"'
 
67
        s = '""\n"' + lineterm.join(lines) + '"'
 
68
    return s
 
69
 
 
70
 
 
71
class _PotExporter(object):
 
72
    """Write message details to output stream in .pot file format"""
 
73
 
 
74
    def __init__(self, outf):
 
75
        self.outf = outf
 
76
        self._msgids = set()
 
77
 
 
78
    def poentry(self, path, lineno, s, comment=None):
 
79
        if s in self._msgids:
 
80
            return
 
81
        self._msgids.add(s)
 
82
        if comment is None:
 
83
            comment = ''
 
84
        else:
 
85
            comment = "# %s\n" % comment
 
86
        mutter("Exporting msg %r at line %d in %r", s[:20], lineno, path)
 
87
        self.outf.write(
 
88
            "#: {path}:{lineno}\n"
 
89
            "{comment}"
 
90
            "msgid {msg}\n"
 
91
            "msgstr \"\"\n"
 
92
            "\n".format(
 
93
                path=path, lineno=lineno, comment=comment, msg=_normalize(s)))
 
94
 
 
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)
 
100
        for p in paragraphs:
 
101
            self.poentry(path, lineno, p)
 
102
            lineno += p.count('\n') + 2
 
103
 
 
104
 
 
105
_LAST_CACHE = _LAST_CACHED_SRC = None
 
106
 
 
107
def _offsets_of_literal(src):
 
108
    global _LAST_CACHE, _LAST_CACHED_SRC
 
109
    if src == _LAST_CACHED_SRC:
 
110
        return _LAST_CACHE.copy()
 
111
 
 
112
    import ast
 
113
    root = ast.parse(src)
 
114
    offsets = {}
 
115
    for node in ast.walk(root):
 
116
        if not isinstance(node, ast.Str):
 
117
            continue
 
118
        offsets[node.s] = node.lineno - node.s.count('\n')
 
119
 
 
120
    _LAST_CACHED_SRC = src
 
121
    _LAST_CACHE = offsets.copy()
 
122
    return offsets
 
123
 
 
124
def _standard_options(exporter):
 
125
    from bzrlib.option import Option
 
126
    src = inspect.findsource(Option)[0]
 
127
    src = ''.join(src)
 
128
    path = 'bzrlib/option.py'
 
129
    offsets = _offsets_of_literal(src)
 
130
 
 
131
    for name in sorted(Option.OPTIONS.keys()):
 
132
        opt = Option.OPTIONS[name]
 
133
        if getattr(opt, 'hidden', False):
 
134
            continue
 
135
        if getattr(opt, 'title', None):
 
136
            lineno = offsets.get(opt.title, 9999)
 
137
            if lineno == 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)
 
143
            if lineno == 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)
 
147
 
 
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):
 
153
            continue
 
154
        if getattr(opt, 'hidden', False):
 
155
            continue
 
156
        name = opt.name
 
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()))
 
165
 
 
166
 
 
167
def _write_command_help(exporter, cmd):
 
168
    path = inspect.getfile(cmd.__class__)
 
169
    if path.endswith('.pyc'):
 
170
        path = path[:-1]
 
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)
 
176
 
 
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:':
 
181
            return True
 
182
 
 
183
    exporter.poentry_per_paragraph(path, lineno, doc, exclude_usage)
 
184
    _command_options(exporter, path, cmd)
 
185
 
 
186
 
 
187
def _command_helps(exporter, plugin_name=None):
 
188
    """Extract docstrings from path.
 
189
 
 
190
    This respects the Bazaar cmdtable/table convention and will
 
191
    only extract docstrings from functions mentioned in these tables.
 
192
    """
 
193
    from glob import glob
 
194
 
 
195
    # builtin commands
 
196
    for cmd_name in _mod_commands.builtin_command_names():
 
197
        command = _mod_commands.get_cmd_object(cmd_name, False)
 
198
        if command.hidden:
 
199
            continue
 
200
        if plugin_name is not None:
 
201
            # only export builtins if we are not exporting plugin commands
 
202
            continue
 
203
        note(gettext("Exporting messages from builtin command: %s"), cmd_name)
 
204
        _write_command_help(exporter, command)
 
205
 
 
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]
 
210
    # plugins
 
211
    for cmd_name in _mod_commands.plugin_command_names():
 
212
        command = _mod_commands.get_cmd_object(cmd_name, False)
 
213
        if command.hidden:
 
214
            continue
 
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.
 
217
            continue
 
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.
 
221
            continue
 
222
        note(gettext("Exporting messages from plugin command: {0} in {1}").format(
 
223
             cmd_name, command.plugin_name() ))
 
224
        _write_command_help(exporter, command)
 
225
 
 
226
 
 
227
def _error_messages(exporter):
 
228
    """Extract fmt string from bzrlib.errors."""
 
229
    path = errors.__file__
 
230
    if path.endswith('.pyc'):
 
231
        path = path[:-1]
 
232
    offsets = _offsets_of_literal(open(path).read())
 
233
 
 
234
    base_klass = errors.BzrError
 
235
    for name in dir(errors):
 
236
        klass = getattr(errors, name)
 
237
        if not inspect.isclass(klass):
 
238
            continue
 
239
        if not issubclass(klass, base_klass):
 
240
            continue
 
241
        if klass is base_klass:
 
242
            continue
 
243
        if klass.internal_error:
 
244
            continue
 
245
        fmt = getattr(klass, "_fmt", None)
 
246
        if fmt:
 
247
            note(gettext("Exporting message from error: %s"), name)
 
248
            exporter.poentry('bzrlib/errors.py',
 
249
                     offsets.get(fmt, 9999), fmt)
 
250
 
 
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',
 
258
                    1, doc)
 
259
        elif callable(doc): # help topics from files
 
260
            exporter.poentry_per_paragraph(
 
261
                    'en/help_topics/'+key+'.txt',
 
262
                    1, doc(key))
 
263
        summary = topic_registry.get_summary(key)
 
264
        if summary is not None:
 
265
            exporter.poentry('dummy/help_topics/'+key+'/summary.txt',
 
266
                     1, summary)
 
267
 
 
268
def export_pot(outf, plugin=None):
 
269
    exporter = _PotExporter(outf)
 
270
    if plugin is None:
 
271
        _standard_options(exporter)
 
272
        _command_helps(exporter)
 
273
        _error_messages(exporter)
 
274
        _help_topics(exporter)
 
275
    else:
 
276
        _command_helps(exporter, plugin)