/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/doc_generate/writers/texinfo.py

  • Committer: Vincent Ladeuil
  • Date: 2010-05-06 20:05:01 UTC
  • mto: (5355.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5356.
  • Revision ID: v.ladeuil+lp@free.fr-20100506200501-ho03kicl06m5lbgg
Some cosmetic tweaks and special char escaping.

* bzrlib/tests/doc_generate/builders/test_texinfo.py:
(TestTextGeneration.test_special_chars): Test all the used special
chars.

* bzrlib/doc_generate/writers/texinfo.py:
(TexinfoTranslator.depart_Text): texinfo requires some special
chars to be protected.
(TexinfoTranslator.depart_table): Leave an empty line after a table.

* bzrlib/doc_generate/builders/texinfo.py:
(TexinfoBuilder.get_target_uri): We really want the finally
produced document.

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 docutils import (
 
20
    nodes,
 
21
    writers,
 
22
    )
 
23
 
 
24
 
 
25
class TexinfoWriter(writers.Writer):
 
26
 
 
27
    supported = ('texinfo',)
 
28
    settings_spec = ('No options here.', '', ())
 
29
    settings_defaults = {}
 
30
 
 
31
    output = None
 
32
 
 
33
    def __init__(self, builder):
 
34
        writers.Writer.__init__(self)
 
35
        self.builder = builder
 
36
 
 
37
    def translate(self):
 
38
        visitor = TexinfoTranslator(self.document, self.builder)
 
39
        self.document.walkabout(visitor)
 
40
        self.output = visitor.body
 
41
 
 
42
 
 
43
class TexinfoTranslator(nodes.NodeVisitor):
 
44
 
 
45
    # Sphinx and texinfo doesn't use the same names for the section levels,
 
46
    # since this can be confusing, here are the correspondances (sphinx ->
 
47
    # texinfo).
 
48
    # part -> chapter
 
49
    # chapter -> section
 
50
    # section -> subsection
 
51
    # subsection -> subsubsection
 
52
    # Additionally, sphinx defines subsubsections and paragraphs
 
53
    section_names = ['chapter', 'section', 'subsection', 'subsubsection']
 
54
    """texinfo section names differ from the sphinx ones.
 
55
 
 
56
    Since this can be confusing, the correspondences are shown below
 
57
    (shpinx -> texinfo):
 
58
    part       -> chapter
 
59
    chapter    -> section
 
60
    section    -> subsection
 
61
    subsection -> subsubsection
 
62
 
 
63
    Additionally, sphinx defines subsubsections and paragraphs.
 
64
    """
 
65
 
 
66
    def __init__(self, document, builder):
 
67
        nodes.NodeVisitor.__init__(self, document)
 
68
        self.chunks = []
 
69
        # toctree uses some nodes for different purposes (namely:
 
70
        # caompact_paragraph, bullet_list, reference, list_item) that needs to
 
71
        # know when they are proessing a toctree. The following attributes take
 
72
        # care of the needs.
 
73
        self.in_toctree = False
 
74
        self.toctree_current_ref = None
 
75
        # sections can be embedded and produce different directives depending
 
76
        # on the depth.
 
77
        self.section_level = -1
 
78
        # The title text is in a Text node that shouldn't be output literally
 
79
        self.in_title = False
 
80
        # Tables has some specific nodes but need more help
 
81
        self.in_table = False
 
82
        self.tab_nb_cols = None
 
83
        self.tab_item_cmd = None
 
84
        self.tab_tab_cmd = None
 
85
        self.tab_entry_num = None
 
86
 
 
87
    def add_text(self, text):
 
88
        self.chunks.append(text)
 
89
 
 
90
    # The whole document
 
91
 
 
92
    def visit_document(self, node):
 
93
        # The debug killer trick
 
94
        # print node.pformat()
 
95
        pass
 
96
 
 
97
    def depart_document(self, node):
 
98
        self.body = ''.join(chunk for chunk in self.chunks)
 
99
 
 
100
    # Layout
 
101
 
 
102
    def visit_section(self, node):
 
103
        self.section_level += 1
 
104
 
 
105
    def depart_section(self, node):
 
106
        self.section_level -= 1
 
107
 
 
108
    def visit_topic(self, node):
 
109
        pass
 
110
 
 
111
    def depart_topic(self, node):
 
112
        pass
 
113
 
 
114
    def visit_paragraph(self, node):
 
115
        pass
 
116
 
 
117
    def depart_paragraph(self, node):
 
118
        if not self.in_table:
 
119
            # End the paragraph with a new line and leave a blank line after
 
120
            # it.
 
121
            self.add_text('\n\n')
 
122
 
 
123
    def visit_compact_paragraph(self, node):
 
124
        if node.has_key('toctree'):
 
125
            self.in_toctree = True
 
126
            self.add_text('@menu\n')
 
127
        elif self.in_toctree:
 
128
            self.toctree_current_ref = None
 
129
 
 
130
    def depart_compact_paragraph(self, node):
 
131
        if node.has_key('toctree'):
 
132
            self.add_text('@end menu\n')
 
133
            self.in_toctree = False
 
134
        elif self.in_toctree:
 
135
            # * FIRST-ENTRY-NAME:(FILENAME)NODENAME.     DESCRIPTION
 
136
            entry_name = node.astext()
 
137
            # XXX: the file name should probably be adjusted to the targeted
 
138
            # info file name
 
139
            file_name = self.toctree_current_ref
 
140
            node_name = entry_name
 
141
            description = ''
 
142
            # XXX: What if :maxdepth: is not 1 ?
 
143
            self.add_text('* %s:(%s)%s. %s\n' % (entry_name, file_name,
 
144
                                                 node_name, description))
 
145
            self.toctree_current_ref = None
 
146
        else:
 
147
            # End the paragraph with a new line and leave a blank line after it.
 
148
            self.add_text('\n\n')
 
149
 
 
150
    def visit_literal_block(self, node):
 
151
        self.add_text('@samp{')
 
152
 
 
153
    def depart_literal_block(self, node):
 
154
        self.add_text('}\n')
 
155
 
 
156
    def visit_block_quote(self, node):
 
157
        pass
 
158
 
 
159
    def depart_block_quote(self, node):
 
160
        pass
 
161
 
 
162
    def visit_note(self, node):
 
163
        pass
 
164
 
 
165
    def depart_warning(self, node):
 
166
        pass
 
167
 
 
168
    def visit_warning(self, node):
 
169
        pass
 
170
 
 
171
    def depart_note(self, node):
 
172
        pass
 
173
 
 
174
    def visit_footnote(self, node):
 
175
        pass
 
176
 
 
177
    def depart_footnote(self, node):
 
178
        pass
 
179
 
 
180
    def visit_comment(self, node):
 
181
        raise nodes.SkipNode
 
182
 
 
183
    # Document attributes
 
184
 
 
185
    def visit_title(self, node):
 
186
        self.in_title = True
 
187
        try:
 
188
            section_name = self.section_names[self.section_level]
 
189
        except IndexError:
 
190
            # Just use @heading, it's not numbered anyway
 
191
            section_name = 'heading'
 
192
        self.add_text('@%s %s\n' % (section_name, node.astext()))
 
193
 
 
194
    def depart_title(self, node):
 
195
        self.in_title = False
 
196
 
 
197
    def visit_label(self, node):
 
198
        raise nodes.SkipNode
 
199
 
 
200
    def visit_substitution_definition(self, node):
 
201
        raise nodes.SkipNode
 
202
 
 
203
    # Plain text
 
204
 
 
205
    def visit_Text(self, node):
 
206
        pass
 
207
 
 
208
    def depart_Text(self, node):
 
209
        if not self.in_toctree and not self.in_title and not self.in_table:
 
210
            text = node.astext()
 
211
            if '@' in text:
 
212
                text = text.replace('@', '@@')
 
213
            if '{' in text:
 
214
                text = text.replace('{', '@{')
 
215
            if '}' in text:
 
216
                text = text.replace('}', '@}')
 
217
            self.add_text(text)
 
218
 
 
219
    # Styled text
 
220
 
 
221
    def visit_emphasis(self, node):
 
222
        self.add_text('@emph{')
 
223
 
 
224
    def depart_emphasis(self, node):
 
225
        self.add_text('}')
 
226
 
 
227
    def visit_strong(self, node):
 
228
        self.add_text('@strong{')
 
229
 
 
230
    def depart_strong(self, node):
 
231
        self.add_text('}')
 
232
 
 
233
    def visit_literal(self, node):
 
234
        self.add_text('@code{')
 
235
 
 
236
    def depart_literal(self, node):
 
237
        self.add_text('}')
 
238
 
 
239
    # Lists
 
240
 
 
241
    def visit_bullet_list(self, node):
 
242
        if self.in_toctree:
 
243
            pass
 
244
        else:
 
245
            self.add_text('@itemize @bullet\n')
 
246
 
 
247
    def depart_bullet_list(self, node):
 
248
        if self.in_toctree:
 
249
            pass
 
250
        else:
 
251
            self.add_text('@end itemize\n')
 
252
 
 
253
    def visit_enumerated_list(self, node):
 
254
        self.add_text('@enumerate\n')
 
255
 
 
256
    def depart_enumerated_list(self, node):
 
257
        self.add_text('@end enumerate\n')
 
258
 
 
259
    def visit_definition_list(self, node):
 
260
        pass
 
261
 
 
262
    def depart_definition_list(self, node):
 
263
        pass
 
264
 
 
265
    def visit_definition_list_item(self, node):
 
266
        pass
 
267
 
 
268
    def depart_definition_list_item(self, node):
 
269
        pass
 
270
 
 
271
    def visit_term(self, node):
 
272
        pass
 
273
 
 
274
    def depart_term(self, node):
 
275
        pass
 
276
 
 
277
    def visit_definition(self, node):
 
278
        pass
 
279
 
 
280
    def depart_definition(self, node):
 
281
        pass
 
282
 
 
283
    def visit_field_list(self, node):
 
284
        pass
 
285
    def depart_field_list(self, node):
 
286
        pass
 
287
 
 
288
    def visit_field(self, node):
 
289
        pass
 
290
    def depart_field(self, node):
 
291
        pass
 
292
 
 
293
    def visit_field_name(self, node):
 
294
        pass
 
295
 
 
296
    def depart_field_name(self, node):
 
297
        pass
 
298
 
 
299
    def visit_field_body(self, node):
 
300
        pass
 
301
 
 
302
    def depart_field_body(self, node):
 
303
        pass
 
304
 
 
305
    def visit_list_item(self, node):
 
306
        if not self.in_toctree:
 
307
            self.add_text('@item\n')
 
308
 
 
309
    def depart_list_item(self, node):
 
310
        # The item contains a paragraph which already ends with a blank line.
 
311
        pass
 
312
 
 
313
    def visit_option_list(self, node):
 
314
        pass
 
315
 
 
316
    def depart_option_list(self, node):
 
317
        pass
 
318
 
 
319
    def visit_option_list_item(self, node):
 
320
        pass
 
321
 
 
322
    def depart_option_list_item(self, node):
 
323
        pass
 
324
 
 
325
    def visit_option_group(self, node):
 
326
        pass
 
327
 
 
328
    def depart_option_group(self, node):
 
329
        pass
 
330
 
 
331
    def visit_option(self, node):
 
332
        pass
 
333
 
 
334
    def depart_option(self, node):
 
335
        pass
 
336
 
 
337
    def visit_option_string(self, node):
 
338
        pass
 
339
    def depart_option_string(self, node):
 
340
        pass
 
341
 
 
342
    def visit_option_argument(self, node):
 
343
        pass
 
344
 
 
345
    def depart_option_argument(self, node):
 
346
        pass
 
347
 
 
348
    def visit_description(self, node):
 
349
        pass
 
350
    def depart_description(self, node):
 
351
        pass
 
352
 
 
353
    # Tables
 
354
    def visit_table(self, node):
 
355
        self.in_table = True
 
356
        self.add_text('@multitable ')
 
357
 
 
358
    def depart_table(self, node):
 
359
        self.add_text('@end multitable\n')
 
360
        # Leave a blank line after a table
 
361
        self.add_text('\n')
 
362
        self.in_table = False
 
363
 
 
364
    def visit_tgroup(self, node):
 
365
        self.tab_nb_cols = node['cols']
 
366
 
 
367
    def depart_tgroup(self, node):
 
368
        self.tab_nb_cols = None
 
369
 
 
370
    def visit_colspec(self, node):
 
371
        self.add_text('{%s}' % ('x' * node['colwidth']))
 
372
 
 
373
    def depart_colspec(self, node):
 
374
        self.tab_nb_cols -= 1
 
375
        if self.tab_nb_cols == 0:
 
376
            self.add_text('\n') # end the @multitable line
 
377
 
 
378
    def visit_thead(self, node):
 
379
        self.tab_item_cmd = '@headitem %s '
 
380
        self.tab_tab_cmd = '@tab %s'
 
381
 
 
382
    def depart_thead(self, node):
 
383
        self.add_text('\n')
 
384
        self.tab_item_cmd = None
 
385
        self.tab_tab_cmd = None
 
386
 
 
387
    def visit_tbody(self, node):
 
388
        self.tab_item_cmd = '@item %s\n'
 
389
        self.tab_tab_cmd = '@tab %s\n'
 
390
 
 
391
    def depart_tbody(self, node):
 
392
        self.tab_item_cmd = None
 
393
        self.tab_tab_cmd = None
 
394
 
 
395
    def visit_row(self, node):
 
396
        self.tab_entry_num = 0
 
397
 
 
398
    def depart_row(self, node):
 
399
        self.tab_entry_num = None
 
400
 
 
401
    def visit_entry(self, node):
 
402
        if self.tab_entry_num == 0:
 
403
            cmd = self.tab_item_cmd
 
404
        else:
 
405
            cmd = self.tab_tab_cmd
 
406
        self.add_text(cmd % node.astext())
 
407
        self.tab_entry_num += 1
 
408
 
 
409
    def depart_entry(self, node):
 
410
        pass
 
411
 
 
412
    # References
 
413
 
 
414
    def visit_reference(self, node):
 
415
        uri = node.get('refuri', '')
 
416
        if self.in_toctree:
 
417
            self.toctree_current_ref = uri
 
418
 
 
419
    def depart_reference(self, node):
 
420
        pass
 
421
 
 
422
    def visit_footnote_reference(self, node):
 
423
        raise nodes.SkipNode
 
424
 
 
425
    def visit_citation_reference(self, node):
 
426
        raise nodes.SkipNode
 
427
 
 
428
    def visit_title_reference(self, node):
 
429
        pass
 
430
 
 
431
    def depart_title_reference(self, node):
 
432
        pass
 
433
 
 
434
    def visit_target(self, node):
 
435
        pass
 
436
 
 
437
    def depart_target(self, node):
 
438
        pass
 
439
 
 
440
    def visit_image(self, node):
 
441
        self.add_text(_('[image]'))
 
442
        raise nodes.SkipNode
 
443