1
# Copyright (C) 2010 Canonical Ltd
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.
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.
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
17
"""A sphinx/docutil writer producing texinfo output."""
19
from docutils import (
26
class TexinfoWriter(writers.Writer):
28
supported = ('texinfo',)
29
settings_spec = ('No options here.', '', ())
30
settings_defaults = {}
34
def __init__(self, builder):
35
writers.Writer.__init__(self)
36
self.builder = builder
39
visitor = TexinfoTranslator(self.document, self.builder)
40
self.document.walkabout(visitor)
41
self.output = visitor.body
44
class TexinfoTranslator(nodes.NodeVisitor):
46
section_names = ['chapter', 'section', 'subsection', 'subsubsection']
47
"""texinfo section names differ from the sphinx ones.
49
Since this can be confusing, the correspondences are shown below
54
subsection -> subsubsection
56
Additionally, sphinx defines subsubsections and paragraphs which are
57
handled as @heading (unnumbered).
60
def __init__(self, document, builder):
61
nodes.NodeVisitor.__init__(self, document)
62
self.builder = builder
63
# toctree uses some nodes for different purposes (namely:
64
# compact_paragraph, bullet_list) that needs to know when they are
65
# processing a toctree.
66
self.in_toctree = False
67
# sections can be embedded and produce different directives depending
69
self.section_level = -1
70
# By default paragraghs are separated by newlines, but there are some
71
# exceptions that set it to '' for some subtrees instead
72
self.paragraph_sep = '\n'
76
def visit_document(self, node):
79
sys.stdout.write(node.pformat().encode('utf8'))
80
set_item_list_collector(node, 'text')
82
def depart_document(self, node):
83
# FIXME: info requires a Top node for each info file, but unless we
84
# chose a global layout to divide the overall documentation into a set
85
# of info files, there is no criteria to decide for a title.
87
This file has been converted using a beta rst->texinfo converter.
88
Most of the info links are currently bogus, don't report bugs about them,
89
this is currently worked on.
93
self.body = top_cmd + ''.join(node['text'])
97
def visit_section(self, node):
98
self.section_level += 1
99
set_item_list_collector(node, 'text')
101
def depart_section(self, node):
102
title = node['title']
103
ids = node.get('ids', [])
105
section_name = self.section_names[self.section_level]
107
# Just use @heading, it's not numbered anyway
108
section_name = 'heading'
110
# There shouldn't be different ids for a section, so until we
111
# encounter bugs, just take the first one.
112
node_cmd = '@node %s\n' % (ids[0],)
115
section_cmd = '@%s %s\n' % (section_name, title)
116
text = ''.join(node['text'])
117
node.parent.collect_text(node_cmd + section_cmd + text)
118
self.section_level -= 1
120
def visit_topic(self, node):
123
def depart_topic(self, node):
126
def visit_paragraph(self, node):
127
set_item_list_collector(node, 'text')
129
def depart_paragraph(self, node):
130
# End the paragraph with a new line (or '' depending on the parent) and
131
# leave a blank line after it.
132
text = ''.join(node['text']) + self.paragraph_sep * 2
133
node.parent.collect_text(text)
135
def visit_compact_paragraph(self, node):
136
set_item_list_collector(node, 'text')
137
if node.has_key('toctree'):
138
self.in_toctree = True
139
elif self.in_toctree:
140
set_item_collector(node, 'reference')
142
def depart_compact_paragraph(self, node):
143
# FIXME: Using a different visitor specific to toctree may be a better
144
# design and makes code clearer. -- vila 20100708
145
if node.has_key('toctree'):
146
node.parent.collect_text('@menu\n')
147
node.parent.collect_text(''.join(node['text']))
148
node.parent.collect_text('@end menu\n')
149
self.in_toctree = False
150
elif self.in_toctree:
151
# * FIRST-ENTRY-NAME:(FILENAME)NODENAME. DESCRIPTION
152
# XXX: the file name should probably be adjusted to the targeted
154
node_name, file_name, entry_name = node['reference']
156
node_name = entry_name
157
description = '' # We can't specify a description in rest AFAICS
158
# XXX: What if :maxdepth: is not 1 ?
159
text = '* %s: (%s)%s. %s\n' % (entry_name, file_name,
160
node_name, description)
161
node.parent.collect_text(text)
163
# End the paragraph with a new line (or '' depending on the parent)
164
# and leave a blank line after it.
165
text = ''.join(node['text']) + self.paragraph_sep * 2
166
node.parent.collect_text(text)
168
def visit_literal_block(self, node):
169
set_item_collector(node, 'text')
171
def depart_literal_block(self, node):
172
text = '@samp{%s}' % ''.join(node['text']) + self.paragraph_sep * 2
173
node.parent.collect_text(text)
175
def visit_block_quote(self, node):
176
set_item_list_collector(node, 'text')
178
def depart_block_quote(self, node):
179
node.parent.collect_text('@example\n')
180
node.parent.collect_text(''.join(node['text']))
181
node.parent.collect_text('@end example\n\n')
183
def depart_warning(self, node):
186
def visit_warning(self, node):
187
raise nodes.SkipNode # Not implemented yet
189
def visit_note(self, node):
190
raise nodes.SkipNode # Not implemented yet
192
def depart_note(self, node):
195
def visit_footnote(self, node):
196
raise nodes.SkipNode # Not implemented yet
198
def depart_footnote(self, node):
201
def visit_comment(self, node):
202
raise nodes.SkipNode # Not implemented yet
206
def visit_title(self, node):
207
set_item_collector(node, 'text')
209
def depart_title(self, node):
210
node.parent['title'] = node['text']
212
def visit_label(self, node):
213
raise nodes.SkipNode # Not implemented yet
215
def visit_substitution_definition(self, node):
216
raise nodes.SkipNode # Not implemented yet
220
def visit_Text(self, node):
223
def depart_Text(self, node):
226
text = text.replace('@', '@@')
228
text = text.replace('{', '@{')
230
text = text.replace('}', '@}')
231
node.parent.collect_text(text)
236
def visit_emphasis(self, node):
237
set_item_collector(node, 'text')
239
def depart_emphasis(self, node):
240
text = '@emph{%s}' % node['text']
241
node.parent.collect_text(text)
243
def visit_strong(self, node):
244
set_item_collector(node, 'text')
246
def depart_strong(self, node):
247
text = '@strong{%s}' % node['text']
248
node.parent.collect_text(text)
250
def visit_literal(self, node):
251
set_item_collector(node, 'text')
253
def depart_literal(self, node):
254
text = '@code{%s}' % node['text']
255
node.parent.collect_text(text)
259
def _decorate_list(self, item_list, collect, item_fmt='%s',
260
head=None, foot=None):
263
for item in item_list:
264
collect(item_fmt % item)
268
def visit_bullet_list(self, node):
269
set_item_list_collector(node, 'list_item')
271
def depart_bullet_list(self, node):
272
l = node['list_item']
274
self._decorate_list(node['list_item'], node.parent.collect_text)
276
self._decorate_list(node['list_item'], node.parent.collect_text,
278
# FIXME: Should respect the 'bullet' attribute
279
'@itemize @bullet\n', '@end itemize\n')
281
def visit_enumerated_list(self, node):
282
set_item_list_collector(node, 'list_item')
284
def depart_enumerated_list(self, node):
285
self._decorate_list(node['list_item'], node.parent.collect_text,
287
'@enumerate\n', '@end enumerate\n')
289
def visit_definition_list(self, node):
290
raise nodes.SkipNode # Not implemented yet
292
def depart_definition_list(self, node):
293
raise nodes.SkipNode # Not implemented yet
295
def visit_definition_list_item(self, node):
296
raise nodes.SkipNode # Not implemented yet
298
def depart_definition_list_item(self, node):
301
def visit_term(self, node):
302
raise nodes.SkipNode # Not implemented yet
304
def depart_term(self, node):
307
def visit_definition(self, node):
308
raise nodes.SkipNode # Not implemented yet
310
def depart_definition(self, node):
313
def visit_field_list(self, node):
314
raise nodes.SkipNode # Not implemented yet
316
def depart_field_list(self, node):
319
def visit_field(self, node):
320
raise nodes.SkipNode # Not implemented yet
322
def depart_field(self, node):
325
def visit_field_name(self, node):
326
raise nodes.SkipNode # Not implemented yet
328
def depart_field_name(self, node):
331
def visit_field_body(self, node):
332
raise nodes.SkipNode # Not implemented yet
334
def depart_field_body(self, node):
337
def visit_list_item(self, node):
338
set_item_list_collector(node, 'text')
340
def depart_list_item(self, node):
341
text = ''.join(node['text'])
342
node.parent.collect_list_item(text)
344
def visit_option_list(self, node):
345
raise nodes.SkipNode # Not implemented yet
347
def depart_option_list(self, node):
350
def visit_option_list_item(self, node):
353
def depart_option_list_item(self, node):
356
def visit_option_group(self, node):
359
def depart_option_group(self, node):
362
def visit_option(self, node):
365
def depart_option(self, node):
368
def visit_option_string(self, node):
370
def depart_option_string(self, node):
373
def visit_option_argument(self, node):
376
def depart_option_argument(self, node):
379
def visit_description(self, node):
381
def depart_description(self, node):
385
def visit_table(self, node):
386
set_item_collector(node, 'table')
388
def depart_table(self, node):
389
node.parent.collect_text(node['table'])
391
def visit_tgroup(self, node):
392
set_item_list_collector(node, 'colspec')
393
set_item_collector(node, 'head_entries')
394
set_item_collector(node, 'body_rows')
396
def depart_tgroup(self, node):
398
# The '@multitable {xxx}{xxx}' line
399
self._decorate_list(node['colspec'], header.append,
400
'{%s}', '@multitable ', '\n')
401
# The '@headitem xxx @tab yyy...' line
402
head_entries = node['head_entries']
403
if head_entries is not None:
404
# Not all tables define titles for the columns... rest parser bug ?
406
self._decorate_list(head_entries[1:], header.append,
408
'@headitem %s' % head_entries[0], '\n')
409
header = ''.join(header)
410
# The '@item xxx\n @tab yyy\n ...' lines
411
body_rows = node['body_rows']
414
self._decorate_list(r[1:], rows.append,
415
'@tab %s\n', '@item %s\n' % r[0])
416
footer = '@end multitable\n'
417
node.parent.collect_table(header + ''.join(rows) + footer)
419
def visit_colspec(self, node):
422
def depart_colspec(self, node):
423
node.parent.collect_colspec('x' * node['colwidth'])
425
def visit_thead(self, node):
426
set_item_collector(node, 'row')
428
def depart_thead(self, node):
429
node.parent.collect_head_entries(node['row'])
431
def visit_tbody(self, node):
432
set_item_list_collector(node, 'row')
434
def depart_tbody(self, node):
435
node.parent.collect_body_rows(node['row'])
437
def visit_row(self, node):
438
set_item_list_collector(node, 'entry')
440
def depart_row(self, node):
441
node.parent.collect_row(node['entry'])
443
def visit_entry(self, node):
444
set_item_list_collector(node, 'text')
445
node['par_sep_orig'] = self.paragraph_sep
446
self.paragraph_sep = ''
448
def depart_entry(self, node):
449
node.parent.collect_entry(''.join(node['text']))
450
self.paragraph_sep = node['par_sep_orig']
454
def visit_reference(self, node):
455
for c in node.children:
456
if getattr(c, 'parent', None) is None:
459
set_item_collector(node, 'text')
461
def depart_reference(self, node):
462
anchorname = node.get('anchorname', None)
463
refuri = node.get('refuri', None)
464
refid = node.get('refid', None)
465
text = ''.join(node['text'])
466
collect = getattr(node.parent, 'collect_reference', None)
467
if collect is not None:
468
if not self.in_toctree:
469
raise AssertionError('collect_reference is specific to toctree')
470
if anchorname is None:
474
collect((anchorname, refuri, text))
475
elif refuri is not None:
476
node.parent.collect_text('@uref{%s,%s}' % (refuri, text))
477
elif refid is not None:
478
# Info format requires that a reference is followed by some
479
# punctuation char ('.', ','. ')', etc). Rest is more liberal. To
480
# accommodate, we use pxref inside parenthesis.
481
node.parent.collect_text('%s (@pxref{%s})' % (text, refid))
483
def visit_footnote_reference(self, node):
484
raise nodes.SkipNode # Not implemented yet
486
def visit_citation_reference(self, node):
487
raise nodes.SkipNode # Not implemented yet
489
def visit_title_reference(self, node):
490
raise nodes.SkipNode # Not implemented yet
492
def depart_title_reference(self, node):
495
def visit_target(self, node):
496
raise nodes.SkipNode # Not implemented yet
498
def depart_target(self, node):
501
def visit_image(self, node):
502
raise nodes.SkipNode # Not implemented yet
504
# Helpers to collect data in parent node
506
def set_item_collector(node, name):
510
setattr(node, 'collect_' + name, set_item)
513
def set_item_list_collector(node, name, sep=''):
515
node[name + '_sep'] = sep
516
def append_item(item):
517
node[name].append(item)
518
setattr(node, 'collect_' + name, append_item)