~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/export_pot.py

  • Committer: Patch Queue Manager
  • Date: 2011-09-15 15:37:20 UTC
  • mfrom: (6140.1.3 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20110915153720-n17t6m5oh5bblqad
(vila) Open 2.5b2 for bugfixes (Vincent Ladeuil)

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