Add <target> to one more testcase (see r8206).
[docutils.git] / sandbox / texinfo-writer / texinfo.py
blob81b13b3361747c54772080c4729393cfe516504b
1 # -*- coding: utf-8 -*-
2 # Author: Jon Waltman <jonathan.waltman@gmail.com>
3 # Copyright: This module is put into the public domain.
5 """
6 Texinfo writer for reStructuredText.
8 Texinfo is the official documentation system of the GNU project and
9 can be used to generate multiple output formats. This writer focuses
10 on producing Texinfo that is compiled to Info by the "makeinfo"
11 program.
12 """
14 import docutils
15 from docutils import nodes, writers
17 TEMPLATE = """\
18 \\input texinfo @c -*-texinfo-*-
19 @c %%**start of header
20 @setfilename %(filename)s
21 @documentencoding UTF-8
22 @copying
23 Generated by Docutils
24 @end copying
25 @settitle %(title)s
26 @defindex ge
27 @paragraphindent %(paragraphindent)s
28 @exampleindent %(exampleindent)s
29 %(direntry)s
30 @c %%**end of header
32 @titlepage
33 @title %(title)s
34 @end titlepage
35 @contents
37 @c %%** start of user preamble
38 %(preamble)s
39 @c %%** end of user preamble
41 @ifnottex
42 @node Top
43 @top %(title)s
44 @end ifnottex
46 @c %%**start of body
47 %(body)s
48 @c %%**end of body
49 @bye
50 """
53 def find_subsections(section):
54 """Return a list of subsections for the given ``section``."""
55 result = []
56 for child in section.children:
57 if isinstance(child, nodes.section):
58 result.append(child)
59 continue
60 result.extend(find_subsections(child))
61 return result
64 ## Escaping
65 # Which characters to escape depends on the context. In some cases,
66 # namely menus and node names, it's not possible to escape certain
67 # characters.
69 def escape(s):
70 """Return a string with Texinfo command characters escaped."""
71 s = s.replace('@', '@@')
72 s = s.replace('{', '@{')
73 s = s.replace('}', '@}')
74 # Prevent "--" from being converted to an "em dash"
75 # s = s.replace('-', '@w{-}')
76 return s
78 def escape_arg(s):
79 """Return an escaped string suitable for use as an argument
80 to a Texinfo command."""
81 s = escape(s)
82 # Commas are the argument delimeters
83 s = s.replace(',', '@comma{}')
84 # Normalize white space
85 s = ' '.join(s.split()).strip()
86 return s
88 def escape_id(s):
89 """Return an escaped string suitable for node names, menu entries,
90 and xrefs anchors."""
91 bad_chars = ',:.()@{}'
92 for bc in bad_chars:
93 s = s.replace(bc, ' ')
94 s = ' '.join(s.split()).strip()
95 return s
98 class Writer(writers.Writer):
99 """Texinfo writer for generating Texinfo documents."""
100 supported = ('texinfo', 'texi')
102 settings_spec = (
103 'Texinfo Specific Options',
104 None,
106 ("Name of the resulting Info file to be created by 'makeinfo'. "
107 "Should probably end with '.info'.",
108 ['--texinfo-filename'],
109 {'default': '', 'metavar': '<file>'}),
111 ('Specify the Info dir entry category.',
112 ['--texinfo-dir-category'],
113 {'default': 'Miscellaneous', 'metavar': '<name>'}),
115 ('The name to use for the Info dir entry. '
116 'If not provided, no entry will be created.',
117 ['--texinfo-dir-entry'],
118 {'default': '', 'metavar': '<name>'}),
120 ('A brief description (one or two lines) to use for the '
121 'Info dir entry.',
122 ['--texinfo-dir-description'],
123 {'default': '', 'metavar': '<desc>'}),
127 settings_defaults = {}
128 settings_default_overrides = {'docinfo_xform': 0}
130 output = None
132 visitor_attributes = ('output', 'fragment')
134 def translate(self):
135 self.visitor = visitor = Translator(self.document)
136 self.document.walkabout(visitor)
137 visitor.finish()
138 for attr in self.visitor_attributes:
139 setattr(self, attr, getattr(visitor, attr))
142 class Translator(nodes.NodeVisitor):
144 default_elements = {
145 'filename': '',
146 'title': '',
147 'paragraphindent': 2,
148 'exampleindent': 4,
149 'direntry': '',
150 'preamble': '',
151 'body': '',
154 def __init__(self, document):
155 nodes.NodeVisitor.__init__(self, document)
156 self.init_settings()
158 self.written_ids = set() # node names and anchors in output
159 self.referenced_ids = set() # node names and anchors that should
160 # be in output
161 self.node_names = {} # node name --> node's name to display
162 self.node_menus = {} # node name --> node's menu entries
163 self.rellinks = {} # node name --> (next, previous, up)
165 self.collect_node_names()
166 self.collect_node_menus()
167 self.collect_rellinks()
169 self.short_ids = {}
170 self.body = []
171 self.previous_section = None
172 self.section_level = 0
173 self.seen_title = False
174 self.next_section_targets = []
175 self.escape_newlines = 0
176 self.curfilestack = []
178 def finish(self):
179 while self.referenced_ids:
180 # Handle xrefs with missing anchors
181 r = self.referenced_ids.pop()
182 if r not in self.written_ids:
183 self.document.reporter.info(
184 "Unknown cross-reference target: `%s'" % r)
185 self.add_text('@anchor{%s}@w{%s}\n' % (r, ' ' * 30))
186 self.fragment = ''.join(self.body).strip() + '\n'
187 self.elements['body'] = self.fragment
188 self.output = TEMPLATE % self.elements
191 ## Helper routines
193 def init_settings(self):
194 settings = self.settings = self.document.settings
195 elements = self.elements = self.default_elements.copy()
196 elements.update({
197 # if empty, the title is set to the first section title
198 'title': settings.title,
199 # if empty, use basename of input file
200 'filename': settings.texinfo_filename,
202 # Title
203 title = elements['title']
204 if not title:
205 title = self.document.next_node(nodes.title)
206 title = (title and title.astext()) or '<untitled>'
207 elements['title'] = escape_id(title) or '<untitled>'
208 # Filename
209 if not elements['filename']:
210 elements['filename'] = self.document.get('source') or 'untitled'
211 if elements['filename'][-4:] in ('.txt', '.rst'):
212 elements['filename'] = elements['filename'][:-4]
213 elements['filename'] += '.info'
214 # Direntry
215 if settings.texinfo_dir_entry:
216 elements['direntry'] = ('@dircategory %s\n'
217 '@direntry\n'
218 '* %s: (%s). %s\n'
219 '@end direntry\n') % (
220 escape_id(settings.texinfo_dir_category),
221 escape_id(settings.texinfo_dir_entry),
222 elements['filename'],
223 escape_arg(settings.texinfo_dir_description))
225 def collect_node_names(self):
226 """Generates a unique id for each section.
228 Assigns the attribute ``node_name`` to each section."""
229 self.document['node_name'] = 'Top'
230 self.node_names['Top'] = 'Top'
231 self.written_ids.update(('Top', 'top'))
233 for section in self.document.traverse(nodes.section):
234 title = section.next_node(nodes.Titular)
235 name = (title and title.astext()) or '<untitled>'
236 node_id = name = escape_id(name) or '<untitled>'
237 assert node_id and name
238 nth, suffix = 1, ''
239 while (node_id + suffix).lower() in self.written_ids:
240 nth += 1
241 suffix = '<%s>' % nth
242 node_id += suffix
243 assert node_id not in self.node_names
244 assert node_id not in self.written_ids
245 assert node_id.lower() not in self.written_ids
246 section['node_name'] = node_id
247 self.node_names[node_id] = name
248 self.written_ids.update((node_id, node_id.lower()))
250 def collect_node_menus(self):
251 """Collect the menu entries for each "node" section."""
252 node_menus = self.node_menus
253 for node in ([self.document] +
254 self.document.traverse(nodes.section)):
255 assert 'node_name' in node and node['node_name']
256 entries = tuple(s['node_name']
257 for s in find_subsections(node))
258 node_menus[node['node_name']] = entries
259 # Try to find a suitable "Top" node
260 title = self.document.next_node(nodes.title)
261 top = (title and title.parent) or self.document
262 if not isinstance(top, (nodes.document, nodes.section)):
263 top = self.document
264 if top is not self.document:
265 entries = node_menus[top['node_name']]
266 entries += node_menus['Top'][1:]
267 node_menus['Top'] = entries
268 del node_menus[top['node_name']]
269 top['node_name'] = 'Top'
271 def collect_rellinks(self):
272 """Collect the relative links (next, previous, up) for each "node"."""
273 rellinks = self.rellinks
274 node_menus = self.node_menus
275 for id, entries in node_menus.items():
276 rellinks[id] = ['', '', '']
277 # Up's
278 for id, entries in node_menus.items():
279 for e in entries:
280 rellinks[e][2] = id
281 # Next's and prev's
282 for id, entries in node_menus.items():
283 for i, id in enumerate(entries):
284 # First child's prev is empty
285 if i != 0:
286 rellinks[id][1] = entries[i-1]
287 # Last child's next is empty
288 if i != len(entries) - 1:
289 rellinks[id][0] = entries[i+1]
290 # Top's next is its first child
291 try:
292 first = node_menus['Top'][0]
293 except IndexError:
294 pass
295 else:
296 rellinks['Top'][0] = first
297 rellinks[first][1] = 'Top'
299 def add_text(self, text, fresh=False):
300 """Add some text to the output.
302 Optional argument ``fresh`` means to insert a newline before
303 the text if the last character out was not a newline."""
304 if fresh:
305 if self.body and not self.body[-1].endswith('\n'):
306 self.body.append('\n')
307 self.body.append(text)
309 def rstrip(self):
310 """Strip trailing whitespace from the current output."""
311 while self.body and not self.body[-1].strip():
312 del self.body[-1]
313 if not self.body:
314 return
315 self.body[-1] = self.body[-1].rstrip()
317 def add_menu_entries(self, entries):
318 for entry in entries:
319 name = self.node_names[entry]
320 if name == entry:
321 self.add_text('* %s::\n' % name, fresh=1)
322 else:
323 self.add_text('* %s: %s.\n' % (name, entry), fresh=1)
325 def add_menu(self, section, master=False):
326 entries = self.node_menus[section['node_name']]
327 if not entries:
328 return
329 self.add_text('\n@menu\n')
330 self.add_menu_entries(entries)
331 if master:
332 # Write the "detailed menu"
333 started_detail = False
334 for entry in entries:
335 subentries = self.node_menus[entry]
336 if not subentries:
337 continue
338 if not started_detail:
339 started_detail = True
340 self.add_text('\n@detailmenu\n'
341 ' --- The Detailed Node Listing ---\n')
342 self.add_text('\n%s\n\n' % self.node_names[entry])
343 self.add_menu_entries(subentries)
344 if started_detail:
345 self.rstrip()
346 self.add_text('\n@end detailmenu\n')
347 self.rstrip()
348 self.add_text('\n@end menu\n\n')
351 ## xref handling
353 def get_short_id(self, id):
354 """Return a shorter 'id' associated with ``id``."""
355 # Shorter ids improve paragraph filling in places
356 # that the id is hidden by Emacs.
357 try:
358 sid = self.short_ids[id]
359 except KeyError:
360 sid = hex(len(self.short_ids))[2:]
361 self.short_ids[id] = sid
362 return sid
364 def add_anchor(self, id, msg_node=None):
365 # Anchors can be referenced by their original id
366 # or by the generated shortened id
367 id = escape_id(id).lower()
368 ids = (self.get_short_id(id), id)
369 for id in ids:
370 if id not in self.written_ids:
371 self.add_text('@anchor{%s}' % id)
372 self.written_ids.add(id)
374 def add_xref(self, ref, name, node):
375 ref = self.get_short_id(escape_id(ref).lower())
376 name = ' '.join(name.split()).strip()
377 if not name or ref == name:
378 self.add_text('@pxref{%s}' % ref)
379 else:
380 self.add_text('@pxref{%s,%s}' % (ref, name))
381 self.referenced_ids.add(ref)
383 ## Visiting
385 def visit_document(self, node):
386 pass
387 def depart_document(self, node):
388 pass
390 def visit_Text(self, node):
391 s = escape(node.astext())
392 if self.escape_newlines:
393 s = s.replace('\n', ' ')
394 self.add_text(s)
395 def depart_Text(self, node):
396 pass
398 def visit_section(self, node):
399 self.next_section_targets.extend(node.get('ids', []))
400 if not self.seen_title:
401 return
402 if self.previous_section:
403 self.add_menu(self.previous_section)
404 else:
405 self.add_menu(self.document, master=True)
407 node_name = node['node_name']
408 pointers = tuple([node_name] + self.rellinks[node_name])
409 self.add_text('\n@node %s,%s,%s,%s\n' % pointers)
410 if node_name != node_name.lower():
411 self.add_text('@anchor{%s}' % node_name.lower())
412 for id in self.next_section_targets:
413 self.add_anchor(id, node)
415 self.next_section_targets = []
416 self.previous_section = node
417 self.section_level += 1
419 def depart_section(self, node):
420 self.section_level -= 1
422 headings = (
423 '@unnumbered',
424 '@chapter',
425 '@section',
426 '@subsection',
427 '@subsubsection',
430 rubrics = (
431 '@heading',
432 '@subheading',
433 '@subsubheading',
436 def visit_title(self, node):
437 if not self.seen_title:
438 self.seen_title = 1
439 raise nodes.SkipNode
440 parent = node.parent
441 if isinstance(parent, nodes.table):
442 return
443 if isinstance(parent, nodes.Admonition):
444 raise nodes.SkipNode
445 elif isinstance(parent, nodes.sidebar):
446 self.visit_rubric(node)
447 elif isinstance(parent, nodes.topic):
448 raise nodes.SkipNode
449 elif not isinstance(parent, nodes.section):
450 self.document.reporter.warning(
451 'encountered title node not in section, topic, table, '
452 'admonition or sidebar', base_node=node)
453 self.visit_rubric(node)
454 else:
455 try:
456 heading = self.headings[self.section_level]
457 except IndexError:
458 heading = self.headings[-1]
459 self.add_text('%s ' % heading, fresh=1)
461 def depart_title(self, node):
462 self.add_text('', fresh=1)
464 def visit_rubric(self, node):
465 try:
466 rubric = self.rubrics[self.section_level]
467 except IndexError:
468 rubric = self.rubrics[-1]
469 self.add_text('%s ' % rubric, fresh=1)
470 def depart_rubric(self, node):
471 self.add_text('', fresh=1)
473 def visit_subtitle(self, node):
474 self.add_text('\n\n@noindent\n')
475 def depart_subtitle(self, node):
476 self.add_text('\n\n')
478 ## References
480 def visit_target(self, node):
481 if node.get('ids'):
482 self.add_anchor(node['ids'][0], node)
483 elif node.get('refid'):
484 # Section targets need to go after the start of the section.
485 next = node.next_node(ascend=1, siblings=1)
486 while isinstance(next, nodes.target):
487 next = next.next_node(ascend=1, siblings=1)
488 if isinstance(next, nodes.section):
489 self.next_section_targets.append(node['refid'])
490 return
491 self.add_anchor(node['refid'], node)
492 elif node.get('refuri'):
493 pass
494 else:
495 self.document.reporter.error("Unknown target type: %r" % node)
497 def visit_reference(self, node):
498 if isinstance(node.parent, nodes.title):
499 return
500 if isinstance(node[0], nodes.image):
501 return
502 name = node.get('name', node.astext()).strip()
503 if node.get('refid'):
504 self.add_xref(escape_id(node['refid']),
505 escape_id(name), node)
506 raise nodes.SkipNode
507 if not node.get('refuri'):
508 self.document.reporter.error("Unknown reference type: %s" % node)
509 return
510 uri = node['refuri']
511 if uri.startswith('#'):
512 self.add_xref(escape_id(uri[1:]), escape_id(name), node)
513 elif uri.startswith('%'):
514 id = uri[1:]
515 if '#' in id:
516 src, id = uri[1:].split('#', 1)
517 assert '#' not in id
518 self.add_xref(escape_id(id), escape_id(name), node)
519 elif uri.startswith('mailto:'):
520 uri = escape_arg(uri[7:])
521 name = escape_arg(name)
522 if not name or name == uri:
523 self.add_text('@email{%s}' % uri)
524 else:
525 self.add_text('@email{%s,%s}' % (uri, name))
526 elif uri.startswith('info:'):
527 uri = uri[5:].replace('_', ' ')
528 uri = escape_arg(uri)
529 id = 'Top'
530 if '#' in uri:
531 uri, id = uri.split('#', 1)
532 id = escape_id(id)
533 name = escape_id(name)
534 if name == id:
535 self.add_text('@pxref{%s,,,%s}' % (id, uri))
536 else:
537 self.add_text('@pxref{%s,,%s,%s}' % (id, name, uri))
538 else:
539 uri = escape_arg(uri)
540 name = escape_arg(name)
541 if not name or uri == name:
542 self.add_text('@indicateurl{%s}' % uri)
543 else:
544 self.add_text('@uref{%s,%s}' % (uri, name))
545 raise nodes.SkipNode
547 def depart_reference(self, node):
548 pass
550 def visit_title_reference(self, node):
551 text = node.astext()
552 self.add_text('@cite{%s}' % escape_arg(text))
553 raise nodes.SkipNode
554 def visit_title_reference(self, node):
555 pass
557 ## Blocks
559 def visit_paragraph(self, node):
560 if 'continued' in node or isinstance(node.parent, nodes.compound):
561 self.add_text('@noindent\n', fresh=1)
562 def depart_paragraph(self, node):
563 self.add_text('\n\n')
565 def visit_block_quote(self, node):
566 self.rstrip()
567 self.add_text('\n\n@quotation\n')
568 def depart_block_quote(self, node):
569 self.rstrip()
570 self.add_text('\n@end quotation\n\n')
572 def visit_literal_block(self, node):
573 self.rstrip()
574 self.add_text('\n\n@example\n')
575 def depart_literal_block(self, node):
576 self.rstrip()
577 self.add_text('\n@end example\n\n'
578 '@noindent\n')
580 visit_doctest_block = visit_literal_block
581 depart_doctest_block = depart_literal_block
583 def visit_line_block(self, node):
584 self.add_text('@display\n', fresh=1)
585 def depart_line_block(self, node):
586 self.add_text('@end display\n', fresh=1)
588 def visit_line(self, node):
589 self.rstrip()
590 self.add_text('\n')
591 self.escape_newlines += 1
592 def depart_line(self, node):
593 self.add_text('@w{ }\n')
594 self.escape_newlines -= 1
596 ## Inline
598 def visit_strong(self, node):
599 self.add_text('@strong{')
600 def depart_strong(self, node):
601 self.add_text('}')
603 def visit_emphasis(self, node):
604 self.add_text('@emph{')
605 def depart_emphasis(self, node):
606 self.add_text('}')
608 def visit_literal(self, node):
609 self.add_text('@code{')
610 def depart_literal(self, node):
611 self.add_text('}')
613 def visit_superscript(self, node):
614 self.add_text('@w{^')
615 def depart_superscript(self, node):
616 self.add_text('}')
618 def visit_subscript(self, node):
619 self.add_text('@w{[')
620 def depart_subscript(self, node):
621 self.add_text(']}')
623 ## Footnotes
625 def visit_footnote(self, node):
626 self.visit_block_quote(node)
627 def depart_footnote(self, node):
628 self.depart_block_quote(node)
630 def visit_footnote_reference(self, node):
631 self.add_text('@w{(')
632 def depart_footnote_reference(self, node):
633 self.add_text(')}')
635 visit_citation = visit_footnote
636 depart_citation = depart_footnote
638 def visit_citation_reference(self, node):
639 self.add_text('@w{[')
640 def depart_citation_reference(self, node):
641 self.add_text(']}')
643 ## Lists
645 def visit_bullet_list(self, node):
646 bullet = node.get('bullet', '*')
647 self.rstrip()
648 self.add_text('\n\n@itemize %s\n' % bullet)
649 def depart_bullet_list(self, node):
650 self.rstrip()
651 self.add_text('\n@end itemize\n\n')
653 def visit_enumerated_list(self, node):
654 # Doesn't support Roman numerals
655 enum = node.get('enumtype', 'arabic')
656 starters = {'arabic': '',
657 'loweralpha': 'a',
658 'upperalpha': 'A',}
659 start = node.get('start', starters.get(enum, ''))
660 self.rstrip()
661 self.add_text('\n\n@enumerate %s\n' % start)
662 def depart_enumerated_list(self, node):
663 self.rstrip()
664 self.add_text('\n@end enumerate\n\n')
666 def visit_list_item(self, node):
667 self.rstrip()
668 self.add_text('\n@item\n')
669 def depart_list_item(self, node):
670 pass
672 ## Option List
674 def visit_option_list(self, node):
675 self.add_text('\n@table @option\n')
676 def depart_option_list(self, node):
677 self.rstrip()
678 self.add_text('\n@end table\n\n')
680 def visit_option_list_item(self, node):
681 pass
682 def depart_option_list_item(self, node):
683 pass
685 def visit_option_group(self, node):
686 self.at_item_x = '@item'
687 def depart_option_group(self, node):
688 pass
690 def visit_option(self, node):
691 self.add_text(self.at_item_x + ' ', fresh=1)
692 self.at_item_x = '@itemx'
693 def depart_option(self, node):
694 pass
696 def visit_option_string(self, node):
697 pass
698 def depart_option_string(self, node):
699 pass
701 def visit_option_argument(self, node):
702 self.add_text(node.get('delimiter', ' '))
703 def depart_option_argument(self, node):
704 pass
706 def visit_description(self, node):
707 self.add_text('', fresh=1)
708 def depart_description(self, node):
709 pass
711 ## Definitions
713 def visit_definition_list(self, node):
714 self.add_text('\n@table @asis\n')
715 def depart_definition_list(self, node):
716 self.rstrip()
717 self.add_text('\n@end table\n\n')
719 def visit_definition_list_item(self, node):
720 self.at_item_x = '@item'
721 def depart_definition_list_item(self, node):
722 pass
724 def visit_term(self, node):
725 if node.get('ids') and node['ids'][0]:
726 self.add_anchor(node['ids'][0], node)
727 self.add_text(self.at_item_x + ' ', fresh=1)
728 self.at_item_x = '@itemx'
729 def depart_term(self, node):
730 pass
732 def visit_classifier(self, node):
733 self.add_text(' : ')
734 def depart_classifier(self, node):
735 pass
737 def visit_definition(self, node):
738 self.add_text('', fresh=1)
739 def depart_definition(self, node):
740 pass
742 ## Tables
744 def visit_table(self, node):
745 self.entry_sep = '@item'
746 def depart_table(self, node):
747 self.rstrip()
748 self.add_text('\n@end multitable\n\n')
750 def visit_tabular_col_spec(self, node):
751 pass
752 def depart_tabular_col_spec(self, node):
753 pass
755 def visit_colspec(self, node):
756 self.colwidths.append(node['colwidth'])
757 if len(self.colwidths) != self.n_cols:
758 return
759 self.add_text('@multitable ', fresh=1)
760 for i, n in enumerate(self.colwidths):
761 self.add_text('{%s} ' %('x' * (n+2)))
762 def depart_colspec(self, node):
763 pass
765 def visit_tgroup(self, node):
766 self.colwidths = []
767 self.n_cols = node['cols']
768 def depart_tgroup(self, node):
769 pass
771 def visit_thead(self, node):
772 self.entry_sep = '@headitem'
773 def depart_thead(self, node):
774 pass
776 def visit_tbody(self, node):
777 pass
778 def depart_tbody(self, node):
779 pass
781 def visit_row(self, node):
782 pass
783 def depart_row(self, node):
784 self.entry_sep = '@item'
786 def visit_entry(self, node):
787 self.rstrip()
788 self.add_text('\n%s ' % self.entry_sep)
789 self.entry_sep = '@tab'
790 def depart_entry(self, node):
791 for i in xrange(node.get('morecols', 0)):
792 self.add_text('@tab\n', fresh=1)
793 self.add_text('', fresh=1)
795 ## Field Lists
797 def visit_field_list(self, node):
798 self.add_text('\n@itemize @w\n')
799 def depart_field_list(self, node):
800 self.rstrip()
801 self.add_text('\n@end itemize\n\n')
803 def visit_field(self, node):
804 if not isinstance(node.parent, nodes.field_list):
805 self.visit_field_list(None)
806 def depart_field(self, node):
807 if not isinstance(node.parent, nodes.field_list):
808 self.depart_field_list(None)
810 def visit_field_name(self, node):
811 self.add_text('@item ', fresh=1)
812 def depart_field_name(self, node):
813 self.add_text(':')
815 def visit_field_body(self, node):
816 self.add_text('', fresh=1)
817 def depart_field_body(self, node):
818 pass
820 ## Admonitions
822 def visit_admonition(self, node):
823 title = escape(node[0].astext())
824 self.add_text('\n@cartouche\n'
825 '@quotation %s\n' % title)
826 def depart_admonition(self, node):
827 self.rstrip()
828 self.add_text('\n@end quotation\n'
829 '@end cartouche\n\n')
831 def _make_visit_admonition(typ):
832 def visit(self, node):
833 title = escape(typ)
834 self.add_text('\n@cartouche\n'
835 '@quotation %s\n' % title)
836 return visit
838 visit_attention = _make_visit_admonition('Attention')
839 visit_caution = _make_visit_admonition('Caution')
840 visit_danger = _make_visit_admonition('Danger')
841 visit_error = _make_visit_admonition('Error')
842 visit_important = _make_visit_admonition('Important')
843 visit_note = _make_visit_admonition('Note')
844 visit_tip = _make_visit_admonition('Tip')
845 visit_hint = _make_visit_admonition('Hint')
846 visit_warning = _make_visit_admonition('Warning')
848 depart_attention = depart_admonition
849 depart_caution = depart_admonition
850 depart_danger = depart_admonition
851 depart_error = depart_admonition
852 depart_important = depart_admonition
853 depart_note = depart_admonition
854 depart_tip = depart_admonition
855 depart_hint = depart_admonition
856 depart_warning = depart_admonition
858 ## Misc
860 def visit_docinfo(self, node):
861 # No 'docinfo_xform'
862 raise nodes.SkipNode
864 def visit_topic(self, node):
865 # Ignore TOC's since we have to have a "menu" anyway
866 if 'contents' in node.get('classes', []):
867 raise nodes.SkipNode
868 title = node[0]
869 self.visit_rubric(title)
870 self.add_text('%s\n' % escape(title.astext()))
871 self.visit_block_quote(node)
872 def depart_topic(self, node):
873 self.depart_block_quote(node)
875 def visit_generated(self, node):
876 raise nodes.SkipNode
877 def depart_generated(self, node):
878 pass
880 def visit_transition(self, node):
881 self.add_text('\n\n@noindent\n'
882 '@exdent @w{%s}\n\n'
883 '@noindent\n' % ('_' * 70))
884 def depart_transition(self, node):
885 pass
887 def visit_attribution(self, node):
888 self.add_text('@flushright\n', fresh=1)
889 def depart_attribution(self, node):
890 self.add_text('@end flushright\n', fresh=1)
892 def visit_raw(self, node):
893 format = node.get('format', '').split()
894 if 'texinfo' in format or 'texi' in format:
895 self.add_text(node.astext())
896 raise nodes.SkipNode
897 def depart_raw(self, node):
898 pass
900 def visit_figure(self, node):
901 self.add_text('\n@float Figure\n')
902 def depart_figure(self, node):
903 self.rstrip()
904 self.add_text('\n@end float\n\n')
906 def visit_caption(self, node):
907 if not isinstance(node.parent, nodes.figure):
908 self.document.reporter.warning('Caption not inside a figure.',
909 base_node=node)
910 return
911 self.add_text('@caption{', fresh=1)
912 def depart_caption(self, node):
913 if isinstance(node.parent, nodes.figure):
914 self.rstrip()
915 self.add_text('}\n')
917 def visit_image(self, node):
918 self.add_text('@w{[image]}')
919 raise nodes.SkipNode
920 def depart_image(self, node):
921 pass
923 def visit_compound(self, node):
924 pass
925 def depart_compound(self, node):
926 pass
928 def visit_sidebar(self, node):
929 pass
930 def depart_sidebar(self, node):
931 pass
933 def visit_label(self, node):
934 self.add_text('@w{(')
935 def depart_label(self, node):
936 self.add_text(')} ')
938 def visit_legend(self, node):
939 pass
940 def depart_legend(self, node):
941 pass
943 def visit_substitution_reference(self, node):
944 pass
945 def depart_substitution_reference(self, node):
946 pass
948 def visit_substitution_definition(self, node):
949 raise nodes.SkipNode
950 def depart_substitution_definition(self, node):
951 pass
953 def visit_system_message(self, node):
954 self.add_text('\n@format\n'
955 '---------- SYSTEM MESSAGE -----------\n')
956 def depart_system_message(self, node):
957 self.rstrip()
958 self.add_text('\n------------------------------------\n'
959 '@end format\n')
961 def visit_comment(self, node):
962 for line in node.astext().splitlines():
963 self.add_text('@c %s\n' % line, fresh=1)
964 raise nodes.SkipNode
965 def depart_comment(self, node):
966 pass
968 def visit_problematic(self, node):
969 self.add_text('>')
970 def depart_problematic(self, node):
971 self.add_text('<')
973 def unimplemented_visit(self, node):
974 self.document.reporter.error("Unimplemented node type: `%s'"
975 % node.__class__.__name__, base_node=node)
977 def unknown_visit(self, node):
978 self.document.reporter.error("Unknown node type: `%s'"
979 % node.__class__.__name__, base_node=node)
980 def unknown_departure(self, node):
981 pass