/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: Jelmer Vernooij
  • Date: 2011-12-11 04:03:47 UTC
  • mfrom: (6352 +trunk)
  • mto: This revision was merged to the branch mainline in revision 6362.
  • Revision ID: jelmer@samba.org-20111211040347-uie6f5a7q4j2591w
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
# with Python under the Python License, which is GPL compatible.
19
19
 
20
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.
21
27
"""
22
28
 
23
29
import inspect
27
33
    commands as _mod_commands,
28
34
    errors,
29
35
    help_topics,
 
36
    option,
30
37
    plugin,
31
38
    help,
32
39
    )
62
69
    return s
63
70
 
64
71
 
65
 
_FOUND_MSGID = None # set by entry function.
66
 
 
67
 
def _poentry(outf, path, lineno, s, comment=None):
68
 
    if s in _FOUND_MSGID:
69
 
        return
70
 
    _FOUND_MSGID.add(s)
71
 
    if comment is None:
72
 
        comment = ''
73
 
    else:
74
 
        comment = "# %s\n" % comment
75
 
    mutter("Exporting msg %r at line %d in %r", s[:20], lineno, path)
76
 
    print >>outf, ('#: %s:%d\n' % (path, lineno) +
77
 
           comment+
78
 
           'msgid %s\n' % _normalize(s) +
79
 
           'msgstr ""\n')
80
 
 
81
 
def _poentry_per_paragraph(outf, path, lineno, msgid, filter=lambda x: False):
82
 
    # TODO: How to split long help?
83
 
    paragraphs = msgid.split('\n\n')
84
 
    for p in paragraphs:
85
 
        if filter(p):
86
 
            continue
87
 
        _poentry(outf, path, lineno, p)
88
 
        lineno += p.count('\n') + 2
89
 
 
90
 
_LAST_CACHE = _LAST_CACHED_SRC = None
91
 
 
92
 
def _offsets_of_literal(src):
93
 
    global _LAST_CACHE, _LAST_CACHED_SRC
94
 
    if src == _LAST_CACHED_SRC:
95
 
        return _LAST_CACHE.copy()
96
 
 
 
72
def _parse_source(source_text):
 
73
    """Get object to lineno mappings from given source_text"""
97
74
    import ast
98
 
    root = ast.parse(src)
99
 
    offsets = {}
100
 
    for node in ast.walk(root):
101
 
        if not isinstance(node, ast.Str):
102
 
            continue
103
 
        offsets[node.s] = node.lineno - node.s.count('\n')
104
 
 
105
 
    _LAST_CACHED_SRC = src
106
 
    _LAST_CACHE = offsets.copy()
107
 
    return offsets
108
 
 
109
 
def _standard_options(outf):
110
 
    from bzrlib.option import Option
111
 
    src = inspect.findsource(Option)[0]
112
 
    src = ''.join(src)
113
 
    path = 'bzrlib/option.py'
114
 
    offsets = _offsets_of_literal(src)
115
 
 
116
 
    for name in sorted(Option.OPTIONS.keys()):
117
 
        opt = Option.OPTIONS[name]
118
 
        if getattr(opt, 'hidden', False):
119
 
            continue
120
 
        if getattr(opt, 'title', None):
121
 
            lineno = offsets.get(opt.title, 9999)
122
 
            if lineno == 9999:
123
 
                note(gettext("%r is not found in bzrlib/option.py") % opt.title)
124
 
            _poentry(outf, path, lineno, opt.title,
125
 
                     'title of %r option' % name)
126
 
        if getattr(opt, 'help', None):
127
 
            lineno = offsets.get(opt.help, 9999)
128
 
            if lineno == 9999:
129
 
                note(gettext("%r is not found in bzrlib/option.py") % opt.help)
130
 
            _poentry(outf, path, lineno, opt.help,
131
 
                     'help of %r option' % name)
132
 
 
133
 
def _command_options(outf, path, cmd):
134
 
    src, default_lineno = inspect.findsource(cmd.__class__)
135
 
    offsets = _offsets_of_literal(''.join(src))
 
75
    cls_to_lineno = {}
 
76
    str_to_lineno = {}
 
77
    for node in ast.walk(ast.parse(source_text)):
 
78
        # TODO: worry about duplicates?
 
79
        if isinstance(node, ast.ClassDef):
 
80
            # TODO: worry about nesting?
 
81
            cls_to_lineno[node.name] = node.lineno
 
82
        elif isinstance(node, ast.Str):
 
83
            # Python AST gives location of string literal as the line the
 
84
            # string terminates on. It's more useful to have the line the
 
85
            # string begins on. Unfortunately, counting back newlines is
 
86
            # only an approximation as the AST is ignorant of escaping.
 
87
            str_to_lineno[node.s] = node.lineno - node.s.count('\n')
 
88
    return cls_to_lineno, str_to_lineno
 
89
 
 
90
 
 
91
class _ModuleContext(object):
 
92
    """Record of the location within a source tree"""
 
93
 
 
94
    def __init__(self, path, lineno=1, _source_info=None):
 
95
        self.path = path
 
96
        self.lineno = lineno
 
97
        if _source_info is not None:
 
98
            self._cls_to_lineno, self._str_to_lineno = _source_info
 
99
 
 
100
    @classmethod
 
101
    def from_module(cls, module):
 
102
        """Get new context from module object and parse source for linenos"""
 
103
        sourcepath = inspect.getsourcefile(module)
 
104
        # TODO: fix this to do the right thing rather than rely on cwd
 
105
        relpath = os.path.relpath(sourcepath)
 
106
        return cls(relpath,
 
107
            _source_info=_parse_source("".join(inspect.findsource(module)[0])))
 
108
 
 
109
    def from_class(self, cls):
 
110
        """Get new context with same details but lineno of class in source"""
 
111
        try:
 
112
            lineno = self._cls_to_lineno[cls.__name__]
 
113
        except (AttributeError, KeyError):
 
114
            mutter("Definition of %r not found in %r", cls, self.path)
 
115
            return self
 
116
        return self.__class__(self.path, lineno,
 
117
            (self._cls_to_lineno, self._str_to_lineno))
 
118
 
 
119
    def from_string(self, string):
 
120
        """Get new context with same details but lineno of string in source"""
 
121
        try:
 
122
            lineno = self._str_to_lineno[string]
 
123
        except (AttributeError, KeyError):
 
124
            mutter("String %r not found in %r", string[:20], self.path)
 
125
            return self
 
126
        return self.__class__(self.path, lineno,
 
127
            (self._cls_to_lineno, self._str_to_lineno))
 
128
 
 
129
 
 
130
class _PotExporter(object):
 
131
    """Write message details to output stream in .pot file format"""
 
132
 
 
133
    def __init__(self, outf):
 
134
        self.outf = outf
 
135
        self._msgids = set()
 
136
        self._module_contexts = {}
 
137
 
 
138
    def poentry(self, path, lineno, s, comment=None):
 
139
        if s in self._msgids:
 
140
            return
 
141
        self._msgids.add(s)
 
142
        if comment is None:
 
143
            comment = ''
 
144
        else:
 
145
            comment = "# %s\n" % comment
 
146
        mutter("Exporting msg %r at line %d in %r", s[:20], lineno, path)
 
147
        self.outf.write(
 
148
            "#: {path}:{lineno}\n"
 
149
            "{comment}"
 
150
            "msgid {msg}\n"
 
151
            "msgstr \"\"\n"
 
152
            "\n".format(
 
153
                path=path, lineno=lineno, comment=comment, msg=_normalize(s)))
 
154
 
 
155
    def poentry_in_context(self, context, string, comment=None):
 
156
        context = context.from_string(string)
 
157
        self.poentry(context.path, context.lineno, string, comment)
 
158
 
 
159
    def poentry_per_paragraph(self, path, lineno, msgid, include=None):
 
160
        # TODO: How to split long help?
 
161
        paragraphs = msgid.split('\n\n')
 
162
        if include is not None:
 
163
            paragraphs = filter(include, paragraphs)
 
164
        for p in paragraphs:
 
165
            self.poentry(path, lineno, p)
 
166
            lineno += p.count('\n') + 2
 
167
 
 
168
    def get_context(self, obj):
 
169
        module = inspect.getmodule(obj)
 
170
        try:
 
171
            context = self._module_contexts[module.__name__]
 
172
        except KeyError:
 
173
            context = _ModuleContext.from_module(module)
 
174
            self._module_contexts[module.__name__] = context
 
175
        if inspect.isclass(obj):
 
176
            context = context.from_class(obj)
 
177
        return context
 
178
 
 
179
 
 
180
def _write_option(exporter, context, opt, note):
 
181
    if getattr(opt, 'hidden', False):
 
182
        return   
 
183
    optname = opt.name
 
184
    if getattr(opt, 'title', None):
 
185
        exporter.poentry_in_context(context, opt.title,
 
186
            "title of {name!r} {what}".format(name=optname, what=note))
 
187
    for name, _, _, helptxt in opt.iter_switches():
 
188
        if name != optname:
 
189
            if opt.is_hidden(name):
 
190
                continue
 
191
            name = "=".join([optname, name])
 
192
        if helptxt:
 
193
            exporter.poentry_in_context(context, helptxt,
 
194
                "help of {name!r} {what}".format(name=name, what=note))
 
195
 
 
196
 
 
197
def _standard_options(exporter):
 
198
    OPTIONS = option.Option.OPTIONS
 
199
    context = exporter.get_context(option)
 
200
    for name in sorted(OPTIONS.keys()):
 
201
        opt = OPTIONS[name]
 
202
        _write_option(exporter, context.from_string(name), opt, "option")
 
203
 
 
204
 
 
205
def _command_options(exporter, context, cmd):
 
206
    note = "option of {0!r} command".format(cmd.name())
136
207
    for opt in cmd.takes_options:
137
 
        if isinstance(opt, str):
138
 
            continue
139
 
        if getattr(opt, 'hidden', False):
140
 
            continue
141
 
        name = opt.name
142
 
        if getattr(opt, 'title', None):
143
 
            lineno = offsets.get(opt.title, default_lineno)
144
 
            _poentry(outf, path, lineno, opt.title,
145
 
                     'title of %r option of %r command' % (name, cmd.name()))
146
 
        if getattr(opt, 'help', None):
147
 
            lineno = offsets.get(opt.help, default_lineno)
148
 
            _poentry(outf, path, lineno, opt.help,
149
 
                     'help of %r option of %r command' % (name, cmd.name()))
150
 
 
151
 
 
152
 
def _write_command_help(outf, cmd):
153
 
    path = inspect.getfile(cmd.__class__)
154
 
    if path.endswith('.pyc'):
155
 
        path = path[:-1]
156
 
    path = os.path.relpath(path)
157
 
    src, lineno = inspect.findsource(cmd.__class__)
158
 
    offsets = _offsets_of_literal(''.join(src))
159
 
    lineno = offsets[cmd.__doc__]
160
 
    doc = inspect.getdoc(cmd)
161
 
 
162
 
    def filter(p):
 
208
        # String values in Command option lists are for global options
 
209
        if not isinstance(opt, str):
 
210
            _write_option(exporter, context, opt, note)
 
211
 
 
212
 
 
213
def _write_command_help(exporter, cmd):
 
214
    context = exporter.get_context(cmd.__class__)
 
215
    rawdoc = cmd.__doc__
 
216
    dcontext = context.from_string(rawdoc)
 
217
    doc = inspect.cleandoc(rawdoc)
 
218
 
 
219
    def exclude_usage(p):
163
220
        # ':Usage:' has special meaning in help topics.
164
221
        # This is usage example of command and should not be translated.
165
 
        if p.splitlines()[0] == ':Usage:':
 
222
        if p.splitlines()[0] != ':Usage:':
166
223
            return True
167
224
 
168
 
    _poentry_per_paragraph(outf, path, lineno, doc, filter)
169
 
    _command_options(outf, path, cmd)
170
 
 
171
 
 
172
 
def _command_helps(outf, plugin_name=None):
 
225
    exporter.poentry_per_paragraph(dcontext.path, dcontext.lineno, doc,
 
226
        exclude_usage)
 
227
    _command_options(exporter, context, cmd)
 
228
 
 
229
 
 
230
def _command_helps(exporter, plugin_name=None):
173
231
    """Extract docstrings from path.
174
232
 
175
233
    This respects the Bazaar cmdtable/table convention and will
186
244
            # only export builtins if we are not exporting plugin commands
187
245
            continue
188
246
        note(gettext("Exporting messages from builtin command: %s"), cmd_name)
189
 
        _write_command_help(outf, command)
 
247
        _write_command_help(exporter, command)
190
248
 
191
249
    plugin_path = plugin.get_core_plugin_path()
192
250
    core_plugins = glob(plugin_path + '/*/__init__.py')
206
264
            continue
207
265
        note(gettext("Exporting messages from plugin command: {0} in {1}").format(
208
266
             cmd_name, command.plugin_name() ))
209
 
        _write_command_help(outf, command)
210
 
 
211
 
 
212
 
def _error_messages(outf):
 
267
        _write_command_help(exporter, command)
 
268
 
 
269
 
 
270
def _error_messages(exporter):
213
271
    """Extract fmt string from bzrlib.errors."""
214
 
    path = errors.__file__
215
 
    if path.endswith('.pyc'):
216
 
        path = path[:-1]
217
 
    offsets = _offsets_of_literal(open(path).read())
218
 
 
 
272
    context = exporter.get_context(errors)
219
273
    base_klass = errors.BzrError
220
274
    for name in dir(errors):
221
275
        klass = getattr(errors, name)
230
284
        fmt = getattr(klass, "_fmt", None)
231
285
        if fmt:
232
286
            note(gettext("Exporting message from error: %s"), name)
233
 
            _poentry(outf, 'bzrlib/errors.py',
234
 
                     offsets.get(fmt, 9999), fmt)
235
 
 
236
 
def _help_topics(outf):
 
287
            exporter.poentry_in_context(context, fmt)
 
288
 
 
289
 
 
290
def _help_topics(exporter):
237
291
    topic_registry = help_topics.topic_registry
238
292
    for key in topic_registry.keys():
239
293
        doc = topic_registry.get(key)
240
294
        if isinstance(doc, str):
241
 
            _poentry_per_paragraph(
242
 
                    outf,
 
295
            exporter.poentry_per_paragraph(
243
296
                    'dummy/help_topics/'+key+'/detail.txt',
244
297
                    1, doc)
245
298
        elif callable(doc): # help topics from files
246
 
            _poentry_per_paragraph(
247
 
                    outf,
 
299
            exporter.poentry_per_paragraph(
248
300
                    'en/help_topics/'+key+'.txt',
249
301
                    1, doc(key))
250
302
        summary = topic_registry.get_summary(key)
251
303
        if summary is not None:
252
 
            _poentry(outf, 'dummy/help_topics/'+key+'/summary.txt',
 
304
            exporter.poentry('dummy/help_topics/'+key+'/summary.txt',
253
305
                     1, summary)
254
306
 
 
307
 
255
308
def export_pot(outf, plugin=None):
256
 
    global _FOUND_MSGID
257
 
    _FOUND_MSGID = set()
 
309
    exporter = _PotExporter(outf)
258
310
    if plugin is None:
259
 
        _standard_options(outf)
260
 
        _command_helps(outf)
261
 
        _error_messages(outf)
262
 
        _help_topics(outf)
 
311
        _standard_options(exporter)
 
312
        _command_helps(exporter)
 
313
        _error_messages(exporter)
 
314
        _help_topics(exporter)
263
315
    else:
264
 
        _command_helps(outf, plugin)
 
316
        _command_helps(exporter, plugin)