~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/doc_generate/writers/texinfo.py

  • Committer: Vincent Ladeuil
  • Date: 2012-01-18 14:09:19 UTC
  • mto: This revision was merged to the branch mainline in revision 6468.
  • Revision ID: v.ladeuil+lp@free.fr-20120118140919-rlvdrhpc0nq1lbwi
Change set/remove to require a lock for the branch config files.

This means that tests (or any plugin for that matter) do not requires an
explicit lock on the branch anymore to change a single option. This also
means the optimisation becomes "opt-in" and as such won't be as
spectacular as it may be and/or harder to get right (nothing fails
anymore).

This reduces the diff by ~300 lines.

Code/tests that were updating more than one config option is still taking
a lock to at least avoid some IOs and demonstrate the benefits through
the decreased number of hpss calls.

The duplication between BranchStack and BranchOnlyStack will be removed
once the same sharing is in place for local config files, at which point
the Stack class itself may be able to host the changes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2010 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
"""A sphinx/docutil writer producing texinfo output."""
 
18
 
 
19
from __future__ import absolute_import
 
20
 
 
21
from docutils import (
 
22
    nodes,
 
23
    writers,
 
24
    )
 
25
 
 
26
DEBUG = 0
 
27
 
 
28
class TexinfoWriter(writers.Writer):
 
29
 
 
30
    supported = ('texinfo',)
 
31
    settings_spec = ('No options here.', '', ())
 
32
    settings_defaults = {}
 
33
 
 
34
    output = None
 
35
 
 
36
    def __init__(self, builder):
 
37
        writers.Writer.__init__(self)
 
38
        self.builder = builder
 
39
 
 
40
    def translate(self):
 
41
        visitor = TexinfoTranslator(self.document, self.builder)
 
42
        self.document.walkabout(visitor)
 
43
        self.output = visitor.body
 
44
 
 
45
 
 
46
class TexinfoTranslator(nodes.NodeVisitor):
 
47
 
 
48
    section_names = ['chapter', 'section', 'subsection', 'subsubsection']
 
49
    """texinfo section names differ from the sphinx ones.
 
50
 
 
51
    Since this can be confusing, the correspondences are shown below
 
52
    (shpinx -> texinfo):
 
53
    part       -> chapter
 
54
    chapter    -> section
 
55
    section    -> subsection
 
56
    subsection -> subsubsection
 
57
 
 
58
    Additionally, sphinx defines subsubsections and paragraphs which are
 
59
    handled as @heading (unnumbered).
 
60
    """
 
61
 
 
62
    def __init__(self, document, builder):
 
63
        nodes.NodeVisitor.__init__(self, document)
 
64
        self.builder = builder
 
65
        # toctree uses some nodes for different purposes (namely:
 
66
        # compact_paragraph, bullet_list) that needs to know when they are
 
67
        # processing a toctree.
 
68
        self.in_toctree = False
 
69
        # sections can be embedded and produce different directives depending
 
70
        # on the depth.
 
71
        self.section_level = -1
 
72
        # By default paragraghs are separated by newlines, but there are some
 
73
        # exceptions that set it to '' for some subtrees instead
 
74
        self.paragraph_sep = '\n'
 
75
 
 
76
    # The whole document
 
77
 
 
78
    def visit_document(self, node):
 
79
        if DEBUG:
 
80
            import sys
 
81
            sys.stdout.write(node.pformat().encode('utf8'))
 
82
        set_item_list_collector(node, 'text')
 
83
 
 
84
    def depart_document(self, node):
 
85
        # FIXME: info requires a Top node for each info file, but unless we
 
86
        # chose a global layout to divide the overall documentation into a set
 
87
        # of info files, there is no criteria to decide for a title.
 
88
        top_cmd = '''\
 
89
This file has been converted using a beta rst->texinfo converter. 
 
90
Most of the info links are currently bogus, don't report bugs about them,
 
91
this is currently worked on.
 
92
@node Top
 
93
@top Placeholder
 
94
'''
 
95
        self.body = top_cmd + ''.join(node['text'])
 
96
 
 
97
    # Layout
 
98
 
 
99
    def visit_section(self, node):
 
100
        self.section_level += 1
 
101
        set_item_list_collector(node, 'text')
 
102
 
 
103
    def depart_section(self, node):
 
104
        title = node['title']
 
105
        ids = node.get('ids', [])
 
106
        try:
 
107
            section_name = self.section_names[self.section_level]
 
108
        except IndexError:
 
109
            # Just use @heading, it's not numbered anyway
 
110
            section_name = 'heading'
 
111
        if ids:
 
112
            # There shouldn't be different ids for a section, so until we
 
113
            # encounter bugs, just take the first one.
 
114
            node_cmd = '@node %s\n' % (ids[0],)
 
115
        else:
 
116
            node_cmd = ''
 
117
        section_cmd = '@%s %s\n' % (section_name, title)
 
118
        text = ''.join(node['text'])
 
119
        node.parent.collect_text(node_cmd + section_cmd + text)
 
120
        self.section_level -= 1
 
121
 
 
122
    def visit_topic(self, node):
 
123
        pass
 
124
 
 
125
    def depart_topic(self, node):
 
126
        pass
 
127
 
 
128
    def visit_compound(self, node):
 
129
        # compound is new in sphinx >= 1.0 and just add a optional layer so we
 
130
        # relay the text to the parent when it occurs. This may requires a
 
131
        # cleaner approach once we settle on which sphinx versions we want to
 
132
        # support.
 
133
        set_item_list_collector(node, 'text')
 
134
 
 
135
    def depart_compound(self, node):
 
136
        text = ''.join(node['text'])
 
137
        node.parent.collect_text(text)
 
138
 
 
139
    def visit_paragraph(self, node):
 
140
        set_item_list_collector(node, 'text')
 
141
 
 
142
    def depart_paragraph(self, node):
 
143
        # End the paragraph with a new line (or '' depending on the parent) and
 
144
        # leave a blank line after it.
 
145
        text = ''.join(node['text']) + self.paragraph_sep * 2
 
146
        node.parent.collect_text(text)
 
147
 
 
148
    def visit_compact_paragraph(self, node):
 
149
        set_item_list_collector(node, 'text')
 
150
        if node.has_key('toctree'):
 
151
            self.in_toctree = True
 
152
        elif self.in_toctree:
 
153
            set_item_collector(node, 'reference')
 
154
 
 
155
    def depart_compact_paragraph(self, node):
 
156
        # FIXME: Using a different visitor specific to toctree may be a better
 
157
        # design and makes code clearer. -- vila 20100708
 
158
        if node.has_key('toctree'):
 
159
            if node['text']:
 
160
                node.parent.collect_text('@menu\n')
 
161
                node.parent.collect_text(''.join(node['text']))
 
162
                node.parent.collect_text('@end menu\n')
 
163
            self.in_toctree = False
 
164
        elif self.in_toctree:
 
165
            # * FIRST-ENTRY-NAME:(FILENAME)NODENAME.     DESCRIPTION
 
166
            # XXX: the file name should probably be adjusted to the targeted
 
167
            # info file name
 
168
            node_name, file_name, entry_name = node['reference']
 
169
            if not node_name:
 
170
                node_name = entry_name
 
171
            description = '' # We can't specify a description in rest AFAICS
 
172
            # XXX: What if :maxdepth: is not 1 ?
 
173
            text = '* %s: (%s)%s. %s\n' % (entry_name, file_name,
 
174
                                           node_name, description)
 
175
            node.parent.collect_text(text)
 
176
        else:
 
177
            # End the paragraph with a new line (or '' depending on the parent)
 
178
            # and leave a blank line after it.
 
179
            text = ''.join(node['text']) + self.paragraph_sep * 2
 
180
            node.parent.collect_text(text)
 
181
 
 
182
    def visit_literal_block(self, node):
 
183
        set_item_collector(node, 'text')
 
184
 
 
185
    def depart_literal_block(self, node):
 
186
        text = '@samp{%s}' % ''.join(node['text']) + self.paragraph_sep * 2
 
187
        node.parent.collect_text(text)
 
188
 
 
189
    def visit_block_quote(self, node):
 
190
        set_item_list_collector(node, 'text')
 
191
 
 
192
    def depart_block_quote(self, node):
 
193
        node.parent.collect_text('@example\n')
 
194
        node.parent.collect_text(''.join(node['text']))
 
195
        node.parent.collect_text('@end example\n\n')
 
196
 
 
197
    def depart_warning(self, node):
 
198
        pass
 
199
 
 
200
    def visit_warning(self, node):
 
201
        raise nodes.SkipNode # Not implemented yet
 
202
 
 
203
    def visit_note(self, node):
 
204
        raise nodes.SkipNode # Not implemented yet
 
205
 
 
206
    def depart_note(self, node):
 
207
        pass
 
208
 
 
209
    def visit_footnote(self, node):
 
210
        raise nodes.SkipNode # Not implemented yet
 
211
 
 
212
    def depart_footnote(self, node):
 
213
        pass
 
214
 
 
215
    def visit_comment(self, node):
 
216
        raise nodes.SkipNode # Not implemented yet
 
217
 
 
218
    # Attributes
 
219
 
 
220
    def visit_title(self, node):
 
221
        set_item_collector(node, 'text')
 
222
 
 
223
    def depart_title(self, node):
 
224
        node.parent['title'] = node['text']
 
225
 
 
226
    def visit_label(self, node):
 
227
        raise nodes.SkipNode # Not implemented yet
 
228
 
 
229
    def visit_substitution_definition(self, node):
 
230
        raise nodes.SkipNode # Not implemented yet
 
231
 
 
232
    # Plain text
 
233
 
 
234
    def visit_Text(self, node):
 
235
        pass
 
236
 
 
237
    def depart_Text(self, node):
 
238
        text = node.astext()
 
239
        if '@' in text:
 
240
            text = text.replace('@', '@@')
 
241
        if '{' in text:
 
242
            text = text.replace('{', '@{')
 
243
        if '}' in text:
 
244
            text = text.replace('}', '@}')
 
245
        node.parent.collect_text(text)
 
246
 
 
247
 
 
248
    # Styled text
 
249
 
 
250
    def visit_emphasis(self, node):
 
251
        set_item_collector(node, 'text')
 
252
 
 
253
    def depart_emphasis(self, node):
 
254
        text = '@emph{%s}' % node['text']
 
255
        node.parent.collect_text(text)
 
256
 
 
257
    def visit_strong(self, node):
 
258
        set_item_collector(node, 'text')
 
259
 
 
260
    def depart_strong(self, node):
 
261
        text = '@strong{%s}' % node['text']
 
262
        node.parent.collect_text(text)
 
263
 
 
264
    def visit_literal(self, node):
 
265
        set_item_collector(node, 'text')
 
266
 
 
267
    def depart_literal(self, node):
 
268
        text = '@code{%s}' % node['text']
 
269
        node.parent.collect_text(text)
 
270
 
 
271
    # Lists
 
272
 
 
273
    def _decorate_list(self, item_list, collect, item_fmt='%s',
 
274
                       head=None, foot=None):
 
275
        if head is not None:
 
276
            collect(head)
 
277
        for item in item_list:
 
278
            collect(item_fmt % item)
 
279
        if foot is not None:
 
280
            collect(foot)
 
281
 
 
282
    def visit_bullet_list(self, node):
 
283
        set_item_list_collector(node, 'list_item')
 
284
 
 
285
    def depart_bullet_list(self, node):
 
286
        l = node['list_item']
 
287
        if self.in_toctree:
 
288
            self._decorate_list(node['list_item'], node.parent.collect_text)
 
289
        else:
 
290
            self._decorate_list(node['list_item'], node.parent.collect_text,
 
291
                                '@item\n%s',
 
292
                                # FIXME: Should respect the 'bullet' attribute
 
293
                                '@itemize @bullet\n', '@end itemize\n')
 
294
 
 
295
    def visit_enumerated_list(self, node):
 
296
        set_item_list_collector(node, 'list_item')
 
297
 
 
298
    def depart_enumerated_list(self, node):
 
299
        self._decorate_list(node['list_item'], node.parent.collect_text,
 
300
                            '@item\n%s',
 
301
                            '@enumerate\n', '@end enumerate\n')
 
302
 
 
303
    def visit_definition_list(self, node):
 
304
        raise nodes.SkipNode # Not implemented yet
 
305
 
 
306
    def depart_definition_list(self, node):
 
307
        raise nodes.SkipNode # Not implemented yet
 
308
 
 
309
    def visit_definition_list_item(self, node):
 
310
        raise nodes.SkipNode # Not implemented yet
 
311
 
 
312
    def depart_definition_list_item(self, node):
 
313
        pass
 
314
 
 
315
    def visit_term(self, node):
 
316
        raise nodes.SkipNode # Not implemented yet
 
317
 
 
318
    def depart_term(self, node):
 
319
        pass
 
320
 
 
321
    def visit_definition(self, node):
 
322
        raise nodes.SkipNode # Not implemented yet
 
323
 
 
324
    def depart_definition(self, node):
 
325
        pass
 
326
 
 
327
    def visit_field_list(self, node):
 
328
        raise nodes.SkipNode # Not implemented yet
 
329
 
 
330
    def depart_field_list(self, node):
 
331
        pass
 
332
 
 
333
    def visit_field(self, node):
 
334
        raise nodes.SkipNode # Not implemented yet
 
335
 
 
336
    def depart_field(self, node):
 
337
        pass
 
338
 
 
339
    def visit_field_name(self, node):
 
340
        raise nodes.SkipNode # Not implemented yet
 
341
 
 
342
    def depart_field_name(self, node):
 
343
        pass
 
344
 
 
345
    def visit_field_body(self, node):
 
346
        raise nodes.SkipNode # Not implemented yet
 
347
 
 
348
    def depart_field_body(self, node):
 
349
        pass
 
350
 
 
351
    def visit_list_item(self, node):
 
352
        set_item_list_collector(node, 'text')
 
353
 
 
354
    def depart_list_item(self, node):
 
355
        text = ''.join(node['text'])
 
356
        node.parent.collect_list_item(text)
 
357
 
 
358
    def visit_option_list(self, node):
 
359
        raise nodes.SkipNode # Not implemented yet
 
360
 
 
361
    def depart_option_list(self, node):
 
362
        pass
 
363
 
 
364
    def visit_option_list_item(self, node):
 
365
        pass
 
366
 
 
367
    def depart_option_list_item(self, node):
 
368
        pass
 
369
 
 
370
    def visit_option_group(self, node):
 
371
        pass
 
372
 
 
373
    def depart_option_group(self, node):
 
374
        pass
 
375
 
 
376
    def visit_option(self, node):
 
377
        pass
 
378
 
 
379
    def depart_option(self, node):
 
380
        pass
 
381
 
 
382
    def visit_option_string(self, node):
 
383
        pass
 
384
    def depart_option_string(self, node):
 
385
        pass
 
386
 
 
387
    def visit_option_argument(self, node):
 
388
        pass
 
389
 
 
390
    def depart_option_argument(self, node):
 
391
        pass
 
392
 
 
393
    def visit_description(self, node):
 
394
        pass
 
395
    def depart_description(self, node):
 
396
        pass
 
397
 
 
398
    # Tables
 
399
    def visit_table(self, node):
 
400
        set_item_collector(node, 'table')
 
401
 
 
402
    def depart_table(self, node):
 
403
        node.parent.collect_text(node['table'])
 
404
 
 
405
    def visit_tgroup(self, node):
 
406
        set_item_list_collector(node, 'colspec')
 
407
        set_item_collector(node, 'head_entries')
 
408
        set_item_collector(node, 'body_rows')
 
409
 
 
410
    def depart_tgroup(self, node):
 
411
        header = []
 
412
        # The '@multitable {xxx}{xxx}' line
 
413
        self._decorate_list(node['colspec'], header.append,
 
414
                            '{%s}', '@multitable ', '\n')
 
415
        # The '@headitem xxx @tab yyy...' line
 
416
        head_entries = node['head_entries']
 
417
        if head_entries is not None:
 
418
            # Not all tables define titles for the columns... rest parser bug ?
 
419
            # FIXME: need a test
 
420
            self._decorate_list(head_entries[1:], header.append,
 
421
                                ' @tab %s',
 
422
                                '@headitem %s' % head_entries[0], '\n')
 
423
        header = ''.join(header)
 
424
        # The '@item xxx\n @tab yyy\n ...' lines
 
425
        body_rows = node['body_rows']
 
426
        rows = []
 
427
        for r in body_rows:
 
428
            self._decorate_list(r[1:], rows.append,
 
429
                                '@tab %s\n', '@item %s\n' % r[0])
 
430
        footer = '@end multitable\n'
 
431
        node.parent.collect_table(header + ''.join(rows) + footer)
 
432
 
 
433
    def visit_colspec(self, node):
 
434
        pass
 
435
 
 
436
    def depart_colspec(self, node):
 
437
        node.parent.collect_colspec('x' * node['colwidth'])
 
438
 
 
439
    def visit_thead(self, node):
 
440
        set_item_collector(node, 'row')
 
441
 
 
442
    def depart_thead(self, node):
 
443
        node.parent.collect_head_entries(node['row'])
 
444
 
 
445
    def visit_tbody(self, node):
 
446
        set_item_list_collector(node, 'row')
 
447
 
 
448
    def depart_tbody(self, node):
 
449
        node.parent.collect_body_rows(node['row'])
 
450
 
 
451
    def visit_row(self, node):
 
452
        set_item_list_collector(node, 'entry')
 
453
 
 
454
    def depart_row(self, node):
 
455
        node.parent.collect_row(node['entry'])
 
456
 
 
457
    def visit_entry(self, node):
 
458
        set_item_list_collector(node, 'text')
 
459
        node['par_sep_orig'] = self.paragraph_sep
 
460
        self.paragraph_sep = ''
 
461
 
 
462
    def depart_entry(self, node):
 
463
        node.parent.collect_entry(''.join(node['text']))
 
464
        self.paragraph_sep = node['par_sep_orig']
 
465
 
 
466
    # References
 
467
 
 
468
    def visit_reference(self, node):
 
469
        for c in node.children:
 
470
            if getattr(c, 'parent', None) is None:
 
471
                # Bug sphinx
 
472
                node.setup_child(c)
 
473
        set_item_collector(node, 'text')
 
474
 
 
475
    def depart_reference(self, node):
 
476
        anchorname = node.get('anchorname', None)
 
477
        refuri = node.get('refuri', None)
 
478
        refid = node.get('refid', None)
 
479
        text = ''.join(node['text'])
 
480
        collect = getattr(node.parent, 'collect_reference', None)
 
481
        if collect is not None:
 
482
            if not self.in_toctree:
 
483
                raise AssertionError('collect_reference is specific to toctree')
 
484
            if anchorname is None:
 
485
                anchorname = ''
 
486
            if refuri is None:
 
487
                refuri = ''
 
488
            collect((anchorname, refuri, text))
 
489
        elif refuri is not None:
 
490
            node.parent.collect_text('@uref{%s,%s}' % (refuri, text))
 
491
        elif refid is not None:
 
492
            # Info format requires that a reference is followed by some
 
493
            # punctuation char ('.', ','. ')', etc). Rest is more liberal. To
 
494
            # accommodate, we use pxref inside parenthesis.
 
495
            node.parent.collect_text('%s (@pxref{%s})' % (text, refid))
 
496
 
 
497
    def visit_footnote_reference(self, node):
 
498
        raise nodes.SkipNode # Not implemented yet
 
499
 
 
500
    def visit_citation_reference(self, node):
 
501
        raise nodes.SkipNode # Not implemented yet
 
502
 
 
503
    def visit_title_reference(self, node):
 
504
        raise nodes.SkipNode # Not implemented yet
 
505
 
 
506
    def depart_title_reference(self, node):
 
507
        pass
 
508
 
 
509
    def visit_target(self, node):
 
510
        raise nodes.SkipNode # Not implemented yet
 
511
 
 
512
    def depart_target(self, node):
 
513
        pass
 
514
 
 
515
    def visit_image(self, node):
 
516
        raise nodes.SkipNode # Not implemented yet
 
517
 
 
518
# Helpers to collect data in parent node
 
519
 
 
520
def set_item_collector(node, name):
 
521
    node[name] = None
 
522
    def set_item(item):
 
523
        node[name] = item
 
524
    setattr(node, 'collect_' + name, set_item)
 
525
 
 
526
 
 
527
def set_item_list_collector(node, name, sep=''):
 
528
    node[name] = []
 
529
    node[name + '_sep'] = sep
 
530
    def append_item(item):
 
531
        node[name].append(item)
 
532
    setattr(node, 'collect_' + name, append_item)
 
533
 
 
534