~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/export_pot.py

Merge bzr.dev to resolve 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
 
 
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):
 
80
    # TODO: How to split long help?
 
81
    paragraphs = msgid.split('\n\n')
 
82
    for p in paragraphs:
 
83
        _poentry(outf, path, lineno, p)
 
84
        lineno += p.count('\n') + 2
 
85
 
 
86
_LAST_CACHE = _LAST_CACHED_SRC = None
 
87
 
 
88
def _offsets_of_literal(src):
 
89
    global _LAST_CACHE, _LAST_CACHED_SRC
 
90
    if src == _LAST_CACHED_SRC:
 
91
        return _LAST_CACHE.copy()
 
92
 
 
93
    import ast
 
94
    root = ast.parse(src)
 
95
    offsets = {}
 
96
    for node in ast.walk(root):
 
97
        if not isinstance(node, ast.Str):
 
98
            continue
 
99
        offsets[node.s] = node.lineno - node.s.count('\n')
 
100
 
 
101
    _LAST_CACHED_SRC = src
 
102
    _LAST_CACHE = offsets.copy()
 
103
    return offsets
 
104
 
 
105
def _standard_options(outf):
 
106
    from bzrlib.option import Option
 
107
    src = inspect.findsource(Option)[0]
 
108
    src = ''.join(src)
 
109
    path = 'bzrlib/option.py'
 
110
    offsets = _offsets_of_literal(src)
 
111
 
 
112
    for name in sorted(Option.OPTIONS.keys()):
 
113
        opt = Option.OPTIONS[name]
 
114
        if getattr(opt, 'hidden', False):
 
115
            continue
 
116
        if getattr(opt, 'title', None):
 
117
            lineno = offsets.get(opt.title, 9999)
 
118
            if lineno == 9999:
 
119
                note("%r is not found in bzrlib/option.py" % opt.title)
 
120
            _poentry(outf, path, lineno, opt.title,
 
121
                     'title of %r option' % name)
 
122
        if getattr(opt, 'help', None):
 
123
            lineno = offsets.get(opt.help, 9999)
 
124
            if lineno == 9999:
 
125
                note("%r is not found in bzrlib/option.py" % opt.help)
 
126
            _poentry(outf, path, lineno, opt.help,
 
127
                     'help of %r option' % name)
 
128
 
 
129
def _command_options(outf, path, cmd):
 
130
    src, default_lineno = inspect.findsource(cmd.__class__)
 
131
    offsets = _offsets_of_literal(''.join(src))
 
132
    for opt in cmd.takes_options:
 
133
        if isinstance(opt, str):
 
134
            continue
 
135
        if getattr(opt, 'hidden', False):
 
136
            continue
 
137
        name = opt.name
 
138
        if getattr(opt, 'title', None):
 
139
            lineno = offsets.get(opt.title, default_lineno)
 
140
            _poentry(outf, path, lineno, opt.title,
 
141
                     'title of %r option of %r command' % (name, cmd.name()))
 
142
        if getattr(opt, 'help', None):
 
143
            lineno = offsets.get(opt.help, default_lineno)
 
144
            _poentry(outf, path, lineno, opt.help,
 
145
                     'help of %r option of %r command' % (name, cmd.name()))
 
146
 
 
147
 
 
148
def _write_command_help(outf, cmd_name, cmd):
 
149
    path = inspect.getfile(cmd.__class__)
 
150
    if path.endswith('.pyc'):
 
151
        path = path[:-1]
 
152
    path = os.path.relpath(path)
 
153
    src, lineno = inspect.findsource(cmd.__class__)
 
154
    offsets = _offsets_of_literal(''.join(src))
 
155
    lineno = offsets[cmd.__doc__]
 
156
    doc = inspect.getdoc(cmd)
 
157
 
 
158
    _poentry_per_paragraph(outf, path, lineno, doc)
 
159
    _command_options(outf, path, cmd)
 
160
 
 
161
def _command_helps(outf):
 
162
    """Extract docstrings from path.
 
163
 
 
164
    This respects the Bazaar cmdtable/table convention and will
 
165
    only extract docstrings from functions mentioned in these tables.
 
166
    """
 
167
    from glob import glob
 
168
 
 
169
    # builtin commands
 
170
    for cmd_name in _mod_commands.builtin_command_names():
 
171
        command = _mod_commands.get_cmd_object(cmd_name, False)
 
172
        if command.hidden:
 
173
            continue
 
174
        note("Exporting messages from builtin command: %s", cmd_name)
 
175
        _write_command_help(outf, cmd_name, command)
 
176
 
 
177
    plugin_path = plugin.get_core_plugin_path()
 
178
    core_plugins = glob(plugin_path + '/*/__init__.py')
 
179
    core_plugins = [os.path.basename(os.path.dirname(p))
 
180
                        for p in core_plugins]
 
181
    # core plugins
 
182
    for cmd_name in _mod_commands.plugin_command_names():
 
183
        command = _mod_commands.get_cmd_object(cmd_name, False)
 
184
        if command.hidden:
 
185
            continue
 
186
        if command.plugin_name() not in core_plugins:
 
187
            # skip non-core plugins
 
188
            # TODO: Support extracting from third party plugins.
 
189
            continue
 
190
        note("Exporting messages from plugin command: %s in %s",
 
191
             cmd_name, command.plugin_name())
 
192
        _write_command_help(outf, cmd_name, command)
 
193
 
 
194
 
 
195
def _error_messages(outf):
 
196
    """Extract fmt string from bzrlib.errors."""
 
197
    path = errors.__file__
 
198
    if path.endswith('.pyc'):
 
199
        path = path[:-1]
 
200
    offsets = _offsets_of_literal(open(path).read())
 
201
 
 
202
    base_klass = errors.BzrError
 
203
    for name in dir(errors):
 
204
        klass = getattr(errors, name)
 
205
        if not inspect.isclass(klass):
 
206
            continue
 
207
        if not issubclass(klass, base_klass):
 
208
            continue
 
209
        if klass is base_klass:
 
210
            continue
 
211
        if klass.internal_error:
 
212
            continue
 
213
        fmt = getattr(klass, "_fmt", None)
 
214
        if fmt:
 
215
            note("Exporting message from error: %s", name)
 
216
            _poentry(outf, 'bzrlib/errors.py',
 
217
                     offsets.get(fmt, 9999), fmt)
 
218
 
 
219
def _help_topics(outf):
 
220
    topic_registry = help_topics.topic_registry
 
221
    for key in topic_registry.keys():
 
222
        doc = topic_registry.get(key)
 
223
        if isinstance(doc, str):
 
224
            _poentry_per_paragraph(
 
225
                    outf,
 
226
                    'dummy/help_topics/'+key+'/detail.txt',
 
227
                    1, doc)
 
228
 
 
229
        summary = topic_registry.get_summary(key)
 
230
        if summary is not None:
 
231
            _poentry(outf, 'dummy/help_topics/'+key+'/summary.txt',
 
232
                     1, summary)
 
233
 
 
234
def export_pot(outf):
 
235
    global _FOUND_MSGID
 
236
    _FOUND_MSGID = set()
 
237
    _standard_options(outf)
 
238
    _command_helps(outf)
 
239
    _error_messages(outf)
 
240
    # disable exporting help topics until we decide  how to translate it.
 
241
    #_help_topics(outf)