~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/export_pot.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-07-11 02:46:35 UTC
  • mfrom: (6017.1.2 test-isolation-speed)
  • Revision ID: pqm@pqm.ubuntu.com-20110711024635-f39c8kz23s347m1t
(spiv) Speed up TestCaseWithMemoryTransport._check_safety_net by reading the
 dirstate file directly rather than using WorkingTree.open(). (Andrew
 Bennetts)

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