~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/export_pot.py

  • Committer: INADA Naoki
  • Date: 2011-05-14 09:58:36 UTC
  • mfrom: (5830.2.22 i18n-msgextract)
  • mto: This revision was merged to the branch mainline in revision 5891.
  • Revision ID: songofacandy@gmail.com-20110514095836-38l651fp8bmb4bni
mergeĀ fromĀ i18n-msgextract

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python
2
 
#
3
 
# bzrgettext - extract docstrings for Bazaar commands
4
 
#
5
 
# Copyright 2009 Matt Mackall <mpm@selenic.com> and others
6
 
# Copyright 2011 Canonical Ltd
 
1
# Copyright (C) 2011 Canonical Ltd
7
2
#
8
3
# This program is free software; you can redistribute it and/or modify
9
4
# it under the terms of the GNU General Public License as published by
19
14
# along with this program; if not, write to the Free Software
20
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
16
 
22
 
# This script is copied from mercurial/i18n/hggettext and modified
23
 
# for Bazaar.
24
 
 
25
17
# The normalize function is taken from pygettext which is distributed
26
18
# with Python under the Python License, which is GPL compatible.
27
19
 
28
 
 
29
20
"""Extract docstrings from Bazaar commands.
30
21
"""
31
22
 
32
 
import os, sys, inspect
33
 
 
34
 
 
35
 
def escape(s):
 
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):
36
39
    s = (s.replace('\\', '\\\\')
37
40
        .replace('\n', '\\n')
38
41
        .replace('\r', '\\r')
41
44
        )
42
45
    return s
43
46
 
44
 
 
45
 
def normalize(s):
 
47
def _normalize(s):
46
48
    # This converts the various Python string types into a format that
47
49
    # is appropriate for .po files, namely much closer to C style.
48
50
    lines = s.split('\n')
49
51
    if len(lines) == 1:
50
 
        s = '"' + escape(s) + '"'
 
52
        s = '"' + _escape(s) + '"'
51
53
    else:
52
54
        if not lines[-1]:
53
55
            del lines[-1]
54
56
            lines[-1] = lines[-1] + '\n'
55
 
        lines = map(escape, lines)
 
57
        lines = map(_escape, lines)
56
58
        lineterm = '\\n"\n"'
57
59
        s = '""\n"' + lineterm.join(lines) + '"'
58
60
    return s
59
61
 
60
62
 
61
 
MSGID_FOUND = set()
 
63
_FOUND_MSGID = None # set by entry function.
62
64
 
63
 
def poentry(path, lineno, s, comment=None):
64
 
    if s in MSGID_FOUND:
 
65
def _poentry(outf, path, lineno, s, comment=None):
 
66
    if s in _FOUND_MSGID:
65
67
        return
66
 
    MSGID_FOUND.add(s)
 
68
    _FOUND_MSGID.add(s)
67
69
    if comment is None:
68
70
        comment = ''
69
71
    else:
70
72
        comment = "# %s\n" % comment
71
 
    print ('#: %s:%d\n' % (path, lineno) +
 
73
    mutter("Exporting msg %r at line %d in %r", s[:20], lineno, path)
 
74
    print >>outf, ('#: %s:%d\n' % (path, lineno) +
72
75
           comment+
73
 
           'msgid %s\n' % normalize(s) +
 
76
           'msgid %s\n' % _normalize(s) +
74
77
           'msgstr ""\n')
75
78
 
76
 
def poentry_per_paragraph(path, lineno, msgid):
 
79
def _poentry_per_paragraph(outf, path, lineno, msgid):
 
80
    # TODO: How to split long help?
77
81
    paragraphs = msgid.split('\n\n')
78
82
    for p in paragraphs:
79
 
        poentry(path, lineno, p)
 
83
        _poentry(outf, path, lineno, p)
80
84
        lineno += p.count('\n') + 2
81
85
 
82
 
def offset(src, doc, name, default):
83
 
    """Compute offset or issue a warning on stdout."""
84
 
    # Backslashes in doc appear doubled in src.
85
 
    end = src.find(doc.replace('\\', '\\\\'))
86
 
    if end == -1:
87
 
        # This can happen if the docstring contains unnecessary escape
88
 
        # sequences such as \" in a triple-quoted string. The problem
89
 
        # is that \" is turned into " and so doc wont appear in src.
90
 
        sys.stderr.write("warning: unknown offset in %s, assuming %d lines\n"
91
 
                         % (name, default))
92
 
        return default
93
 
    else:
94
 
        return src.count('\n', 0, end)
95
 
 
96
 
 
97
 
def importpath(path):
98
 
    """Import a path like foo/bar/baz.py and return the baz module."""
99
 
    if path.endswith('.py'):
100
 
        path = path[:-3]
101
 
    if path.endswith('/__init__'):
102
 
        path = path[:-9]
103
 
    path = path.replace('/', '.')
104
 
    mod = __import__(path)
105
 
    for comp in path.split('.')[1:]:
106
 
        mod = getattr(mod, comp)
107
 
    return mod
108
 
 
109
 
def options(path, lineno, cmdklass):
110
 
    cmd = cmdklass()
111
 
    for name, opt in cmd.options().iteritems():
112
 
        poentry(path, lineno, opt.help,
113
 
                "help of '%s' option of '%s' command" % (name, cmd.name()))
114
 
 
115
 
 
116
 
def docstrings(path):
 
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):
117
162
    """Extract docstrings from path.
118
163
 
119
164
    This respects the Bazaar cmdtable/table convention and will
120
165
    only extract docstrings from functions mentioned in these tables.
121
166
    """
122
 
    from bzrlib.commands import Command as cmd_klass
123
 
    mod = importpath(path)
124
 
    for name in dir(mod):
125
 
        if not name.startswith('cmd_'):
126
 
            continue
127
 
        obj = getattr(mod, name)
128
 
        try:
129
 
            doc = obj.__doc__
130
 
            if doc:
131
 
                doc = inspect.cleandoc(doc)
132
 
            else:
133
 
                continue
134
 
        except AttributeError:
135
 
            continue
136
 
        if (inspect.isclass(obj) and issubclass(obj, cmd_klass)
137
 
                and not obj is cmd_klass):
138
 
            lineno = inspect.findsource(obj)[1]
139
 
            poentry_per_paragraph(path, lineno, doc)
140
 
            options(path, lineno, obj)
141
 
 
142
 
def bzrerrors():
 
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):
143
196
    """Extract fmt string from bzrlib.errors."""
144
 
    from bzrlib import errors
 
197
    path = errors.__file__
 
198
    if path.endswith('.pyc'):
 
199
        path = path[:-1]
 
200
    offsets = _offsets_of_literal(open(path).read())
 
201
 
145
202
    base_klass = errors.BzrError
146
203
    for name in dir(errors):
147
204
        klass = getattr(errors, name)
155
212
            continue
156
213
        fmt = getattr(klass, "_fmt", None)
157
214
        if fmt:
158
 
            poentry('bzrlib/erros.py', inspect.findsource(klass)[1], fmt)
159
 
 
160
 
 
161
 
def rawtext(path):
162
 
    src = open(path).read()
163
 
    poentry_per_paragraph(path, 1, src)
164
 
 
165
 
 
166
 
if __name__ == "__main__":
167
 
    # It is very important that we import the Bazaar modules from
168
 
    # the source tree where bzrgettext is executed. Otherwise we might
169
 
    # accidentally import and extract strings from a Bazaar
170
 
    # installation mentioned in PYTHONPATH.
171
 
    sys.path.insert(0, os.getcwd())
172
 
    import bzrlib.lazy_import
173
 
    for path in sys.argv[1:]:
174
 
        if path.endswith('.txt'):
175
 
            rawtext(path)
176
 
        else:
177
 
            docstrings(path)
178
 
    bzrerrors()
 
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)