Version: 0.18.1b
[docutils.git] / docutils / docutils / writers / manpage.py
blobd8b366545fa403ac13560e92ef05e8c3db677bd3
1 # -*- coding: utf-8 -*-
2 # $Id$
3 # Author: Engelbert Gruber <grubert@users.sourceforge.net>
4 # Copyright: This module is put into the public domain.
6 """
7 Simple man page writer for reStructuredText.
9 Man pages (short for "manual pages") contain system documentation on unix-like
10 systems. The pages are grouped in numbered sections:
12 1 executable programs and shell commands
13 2 system calls
14 3 library functions
15 4 special files
16 5 file formats
17 6 games
18 7 miscellaneous
19 8 system administration
21 Man pages are written *troff*, a text file formatting system.
23 See http://www.tldp.org/HOWTO/Man-Page for a start.
25 Man pages have no subsection only parts.
26 Standard parts
28 NAME ,
29 SYNOPSIS ,
30 DESCRIPTION ,
31 OPTIONS ,
32 FILES ,
33 SEE ALSO ,
34 BUGS ,
36 and
38 AUTHOR .
40 A unix-like system keeps an index of the DESCRIPTIONs, which is accessible
41 by the command whatis or apropos.
43 """
45 __docformat__ = 'reStructuredText'
47 import re
48 import sys
50 if sys.version_info < (3, 0):
51 range = xrange # NOQA: F821 # flake8 do not check undefined name
53 import docutils
54 from docutils import nodes, writers, languages
55 try:
56 import roman
57 except ImportError:
58 import docutils.utils.roman as roman
60 FIELD_LIST_INDENT = 7
61 DEFINITION_LIST_INDENT = 7
62 OPTION_LIST_INDENT = 7
63 BLOCKQOUTE_INDENT = 3.5
64 LITERAL_BLOCK_INDENT = 3.5
66 # Define two macros so man/roff can calculate the
67 # indent/unindent margins by itself
68 MACRO_DEF = (r""".
69 .nr rst2man-indent-level 0
71 .de1 rstReportMargin
72 \\$1 \\n[an-margin]
73 level \\n[rst2man-indent-level]
74 level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
76 \\n[rst2man-indent0]
77 \\n[rst2man-indent1]
78 \\n[rst2man-indent2]
80 .de1 INDENT
81 .\" .rstReportMargin pre:
82 . RS \\$1
83 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
84 . nr rst2man-indent-level +1
85 .\" .rstReportMargin post:
87 .de UNINDENT
88 . RE
89 .\" indent \\n[an-margin]
90 .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
91 .nr rst2man-indent-level -1
92 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
93 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
95 """)
97 class Writer(writers.Writer):
99 supported = ('manpage',)
100 """Formats this writer supports."""
102 output = None
103 """Final translated form of `document`."""
105 def __init__(self):
106 writers.Writer.__init__(self)
107 self.translator_class = Translator
109 def translate(self):
110 visitor = self.translator_class(self.document)
111 self.document.walkabout(visitor)
112 self.output = visitor.astext()
115 class Table(object):
116 def __init__(self):
117 self._rows = []
118 self._options = ['center']
119 self._tab_char = '\t'
120 self._coldefs = []
121 def new_row(self):
122 self._rows.append([])
123 def append_separator(self, separator):
124 """Append the separator for table head."""
125 self._rows.append([separator])
126 def append_cell(self, cell_lines):
127 """cell_lines is an array of lines"""
128 start = 0
129 if len(cell_lines) > 0 and cell_lines[0] == '.sp\n':
130 start = 1
131 self._rows[-1].append(cell_lines[start:])
132 if len(self._coldefs) < len(self._rows[-1]):
133 self._coldefs.append('l')
134 def _minimize_cell(self, cell_lines):
135 """Remove leading and trailing blank and ``.sp`` lines"""
136 while cell_lines and cell_lines[0] in ('\n', '.sp\n'):
137 del cell_lines[0]
138 while cell_lines and cell_lines[-1] in ('\n', '.sp\n'):
139 del cell_lines[-1]
140 def as_list(self):
141 text = ['.TS\n']
142 text.append(' '.join(self._options) + ';\n')
143 text.append('|%s|.\n' % ('|'.join(self._coldefs)))
144 for row in self._rows:
145 # row = array of cells. cell = array of lines.
146 text.append('_\n') # line above
147 text.append('T{\n')
148 for i in range(len(row)):
149 cell = row[i]
150 self._minimize_cell(cell)
151 text.extend(cell)
152 if not text[-1].endswith('\n'):
153 text[-1] += '\n'
154 if i < len(row)-1:
155 text.append('T}'+self._tab_char+'T{\n')
156 else:
157 text.append('T}\n')
158 text.append('_\n')
159 text.append('.TE\n')
160 return text
162 class Translator(nodes.NodeVisitor):
163 """"""
165 words_and_spaces = re.compile(r'\S+| +|\n')
166 possibly_a_roff_command = re.compile(r'\.\w')
167 document_start = """Man page generated from reStructuredText."""
169 def __init__(self, document):
170 nodes.NodeVisitor.__init__(self, document)
171 self.settings = settings = document.settings
172 lcode = settings.language_code
173 self.language = languages.get_language(lcode, document.reporter)
174 self.head = []
175 self.body = []
176 self.foot = []
177 self.section_level = 0
178 self.context = []
179 self.topic_class = ''
180 self.colspecs = []
181 self.compact_p = 1
182 self.compact_simple = None
183 # the list style "*" bullet or "#" numbered
184 self._list_char = []
185 # writing the header .TH and .SH NAME is postboned after
186 # docinfo.
187 self._docinfo = {
188 "title": "", "title_upper": "",
189 "subtitle": "",
190 "manual_section": "", "manual_group": "",
191 "author": [],
192 "date": "",
193 "copyright": "",
194 "version": "",
196 self._docinfo_keys = [] # a list to keep the sequence as in source.
197 self._docinfo_names = {} # to get name from text not normalized.
198 self._in_docinfo = None
199 self._field_name = None
200 self._active_table = None
201 self._in_literal = False
202 self.header_written = 0
203 self._line_block = 0
204 self.authors = []
205 self.section_level = 0
206 self._indent = [0]
207 # central definition of simple processing rules
208 # what to output on : visit, depart
209 # Do not use paragraph requests ``.PP`` because these set indentation.
210 # use ``.sp``. Remove superfluous ``.sp`` in ``astext``.
212 # Fonts are put on a stack, the top one is used.
213 # ``.ft P`` or ``\\fP`` pop from stack.
214 # But ``.BI`` seams to fill stack with BIBIBIBIB...
215 # ``B`` bold, ``I`` italic, ``R`` roman should be available.
216 # Hopefully ``C`` courier too.
217 self.defs = {
218 'indent': ('.INDENT %.1f\n', '.UNINDENT\n'),
219 'definition_list_item': ('.TP', ''), # paragraph with hanging tag
220 'field_name': ('.TP\n.B ', '\n'),
221 'literal': ('\\fB', '\\fP'),
222 'literal_block': ('.sp\n.nf\n.ft C\n', '\n.ft P\n.fi\n'),
224 'option_list_item': ('.TP\n', ''),
226 'reference': (r'\fI\%', r'\fP'),
227 'emphasis': ('\\fI', '\\fP'),
228 'strong': ('\\fB', '\\fP'),
229 'term': ('\n.B ', '\n'),
230 'title_reference': ('\\fI', '\\fP'),
232 'topic-title': ('.SS ',),
233 'sidebar-title': ('.SS ',),
235 'problematic': ('\n.nf\n', '\n.fi\n'),
237 # NOTE do not specify the newline before a dot-command, but ensure
238 # it is there.
240 def comment_begin(self, text):
241 """Return commented version of the passed text WITHOUT end of
242 line/comment."""
243 prefix = '.\\" '
244 out_text = ''.join(
245 [(prefix + in_line + '\n')
246 for in_line in text.split('\n')])
247 return out_text
249 def comment(self, text):
250 """Return commented version of the passed text."""
251 return self.comment_begin(text)+'.\n'
253 def ensure_eol(self):
254 """Ensure the last line in body is terminated by new line."""
255 if len(self.body) > 0 and self.body[-1][-1] != '\n':
256 self.body.append('\n')
258 def astext(self):
259 """Return the final formatted document as a string."""
260 if not self.header_written:
261 # ensure we get a ".TH" as viewers require it.
262 self.append_header()
263 # filter body
264 for i in range(len(self.body)-1, 0, -1):
265 # remove superfluous vertical gaps.
266 if self.body[i] == '.sp\n':
267 if self.body[i - 1][:4] in ('.BI ', '.IP '):
268 self.body[i] = '.\n'
269 elif (self.body[i - 1][:3] == '.B ' and
270 self.body[i - 2][:4] == '.TP\n'):
271 self.body[i] = '.\n'
272 elif (self.body[i - 1] == '\n' and
273 not self.possibly_a_roff_command.match(self.body[i - 2]) and
274 (self.body[i - 3][:7] == '.TP\n.B '
275 or self.body[i - 3][:4] == '\n.B ')
277 self.body[i] = '.\n'
278 return ''.join(self.head + self.body + self.foot)
280 def deunicode(self, text):
281 text = text.replace(u'\xa0', '\\ ')
282 text = text.replace(u'\u2020', '\\(dg')
283 return text
285 def visit_Text(self, node):
286 text = node.astext()
287 text = text.replace('\\', '\\e')
288 replace_pairs = [
289 (u'-', u'\\-'),
290 (u'\'', u'\\(aq'),
291 (u'´', u"\\'"),
292 (u'`', u'\\(ga'),
293 (u'"', u'\\(dq'), # double quotes are a problem on macro lines
295 for (in_char, out_markup) in replace_pairs:
296 text = text.replace(in_char, out_markup)
297 # unicode
298 text = self.deunicode(text)
299 # prevent interpretation of "." at line start
300 if text.startswith('.'):
301 text = '\\&' + text
302 if self._in_literal:
303 text = text.replace('\n.', '\n\\&.')
304 self.body.append(text)
306 def depart_Text(self, node):
307 pass
309 def list_start(self, node):
310 class EnumChar(object):
311 enum_style = {
312 'bullet': '\\(bu',
313 'emdash': '\\(em',
316 def __init__(self, style):
317 self._style = style
318 if 'start' in node:
319 self._cnt = node['start'] - 1
320 else:
321 self._cnt = 0
322 self._indent = 2
323 if style == 'arabic':
324 # indentation depends on number of children
325 # and start value.
326 self._indent = len(str(len(node.children)))
327 self._indent += len(str(self._cnt)) + 1
328 elif style == 'loweralpha':
329 self._cnt += ord('a') - 1
330 self._indent = 3
331 elif style == 'upperalpha':
332 self._cnt += ord('A') - 1
333 self._indent = 3
334 elif style.endswith('roman'):
335 self._indent = 5
337 def __next__(self):
338 if self._style == 'bullet':
339 return self.enum_style[self._style]
340 elif self._style == 'emdash':
341 return self.enum_style[self._style]
342 self._cnt += 1
343 # TODO add prefix postfix
344 if self._style == 'arabic':
345 return "%d." % self._cnt
346 elif self._style in ('loweralpha', 'upperalpha'):
347 return "%c." % self._cnt
348 elif self._style.endswith('roman'):
349 res = roman.toRoman(self._cnt) + '.'
350 if self._style.startswith('upper'):
351 return res.upper()
352 return res.lower()
353 else:
354 return "%d." % self._cnt
356 if sys.version_info < (3, 0):
357 next = __next__
359 def get_width(self):
360 return self._indent
361 def __repr__(self):
362 return 'enum_style-%s' % list(self._style)
364 if 'enumtype' in node:
365 self._list_char.append(EnumChar(node['enumtype']))
366 else:
367 self._list_char.append(EnumChar('bullet'))
368 if len(self._list_char) > 1:
369 # indent nested lists
370 self.indent(self._list_char[-2].get_width())
371 else:
372 self.indent(self._list_char[-1].get_width())
374 def list_end(self):
375 self.dedent()
376 self._list_char.pop()
378 def header(self):
379 tmpl = (".TH \"%(title_upper)s\" %(manual_section)s"
380 " \"%(date)s\" \"%(version)s\" \"%(manual_group)s\"\n"
381 ".SH NAME\n"
382 "%(title)s \\- %(subtitle)s\n")
383 return tmpl % self._docinfo
385 def append_header(self):
386 """append header with .TH and .SH NAME"""
387 # NOTE before everything
388 # .TH title_upper section date source manual
389 # BUT macros before .TH for whatis database generators.
390 if self.header_written:
391 return
392 self.head.append(MACRO_DEF)
393 self.head.append(self.header())
394 self.header_written = 1
396 def visit_address(self, node):
397 self.visit_docinfo_item(node, 'address')
399 def depart_address(self, node):
400 pass
402 def visit_admonition(self, node, name=None):
404 # Make admonitions a simple block quote
405 # with a strong heading
407 # Using .IP/.RE doesn't preserve indentation
408 # when admonitions contain bullets, literal,
409 # and/or block quotes.
411 if name:
412 # .. admonition:: has no name
413 self.body.append('.sp\n')
414 name = '%s%s:%s\n' % (
415 self.defs['strong'][0],
416 self.language.labels.get(name, name).upper(),
417 self.defs['strong'][1],
419 self.body.append(name)
420 self.visit_block_quote(node)
422 def depart_admonition(self, node):
423 self.depart_block_quote(node)
425 def visit_attention(self, node):
426 self.visit_admonition(node, 'attention')
428 depart_attention = depart_admonition
430 def visit_docinfo_item(self, node, name):
431 if name == 'author':
432 self._docinfo[name].append(node.astext())
433 else:
434 self._docinfo[name] = node.astext()
435 self._docinfo_keys.append(name)
436 raise nodes.SkipNode
438 def depart_docinfo_item(self, node):
439 pass
441 def visit_author(self, node):
442 self.visit_docinfo_item(node, 'author')
444 depart_author = depart_docinfo_item
446 def visit_authors(self, node):
447 # _author is called anyway.
448 pass
450 def depart_authors(self, node):
451 pass
453 def visit_block_quote(self, node):
454 # BUG/HACK: indent always uses the _last_ indentation,
455 # thus we need two of them.
456 self.indent(BLOCKQOUTE_INDENT)
457 self.indent(0)
459 def depart_block_quote(self, node):
460 self.dedent()
461 self.dedent()
463 def visit_bullet_list(self, node):
464 self.list_start(node)
466 def depart_bullet_list(self, node):
467 self.list_end()
469 def visit_caption(self, node):
470 pass
472 def depart_caption(self, node):
473 pass
475 def visit_caution(self, node):
476 self.visit_admonition(node, 'caution')
478 depart_caution = depart_admonition
480 def visit_citation(self, node):
481 num = node.astext().split(None, 1)[0]
482 num = num.strip()
483 self.body.append('.IP [%s] 5\n' % num)
485 def depart_citation(self, node):
486 pass
488 def visit_citation_reference(self, node):
489 self.body.append('['+node.astext()+']')
490 raise nodes.SkipNode
492 def visit_classifier(self, node):
493 pass
495 def depart_classifier(self, node):
496 pass
498 def visit_colspec(self, node):
499 self.colspecs.append(node)
501 def depart_colspec(self, node):
502 pass
504 def write_colspecs(self):
505 self.body.append("%s.\n" % ('L '*len(self.colspecs)))
507 def visit_comment(self, node,
508 sub=re.compile('-(?=-)').sub):
509 self.body.append(self.comment(node.astext()))
510 raise nodes.SkipNode
512 def visit_contact(self, node):
513 self.visit_docinfo_item(node, 'contact')
515 depart_contact = depart_docinfo_item
517 def visit_container(self, node):
518 pass
520 def depart_container(self, node):
521 pass
523 def visit_compound(self, node):
524 pass
526 def depart_compound(self, node):
527 pass
529 def visit_copyright(self, node):
530 self.visit_docinfo_item(node, 'copyright')
532 def visit_danger(self, node):
533 self.visit_admonition(node, 'danger')
535 depart_danger = depart_admonition
537 def visit_date(self, node):
538 self.visit_docinfo_item(node, 'date')
540 def visit_decoration(self, node):
541 pass
543 def depart_decoration(self, node):
544 pass
546 def visit_definition(self, node):
547 pass
549 def depart_definition(self, node):
550 pass
552 def visit_definition_list(self, node):
553 self.indent(DEFINITION_LIST_INDENT)
555 def depart_definition_list(self, node):
556 self.dedent()
558 def visit_definition_list_item(self, node):
559 self.body.append(self.defs['definition_list_item'][0])
561 def depart_definition_list_item(self, node):
562 self.body.append(self.defs['definition_list_item'][1])
564 def visit_description(self, node):
565 pass
567 def depart_description(self, node):
568 pass
570 def visit_docinfo(self, node):
571 self._in_docinfo = 1
573 def depart_docinfo(self, node):
574 self._in_docinfo = None
575 # NOTE nothing should be written before this
576 self.append_header()
578 def visit_doctest_block(self, node):
579 self.body.append(self.defs['literal_block'][0])
580 self._in_literal = True
582 def depart_doctest_block(self, node):
583 self._in_literal = False
584 self.body.append(self.defs['literal_block'][1])
586 def visit_document(self, node):
587 # no blank line between comment and header.
588 self.head.append(self.comment(self.document_start).rstrip()+'\n')
589 # writing header is postponed
590 self.header_written = 0
592 def depart_document(self, node):
593 if self._docinfo['author']:
594 self.body.append('.SH AUTHOR\n%s\n'
595 % ', '.join(self._docinfo['author']))
596 skip = ('author', 'copyright', 'date',
597 'manual_group', 'manual_section',
598 'subtitle',
599 'title', 'title_upper', 'version')
600 for name in self._docinfo_keys:
601 if name == 'address':
602 self.body.append("\n%s:\n%s%s.nf\n%s\n.fi\n%s%s" % (
603 self.language.labels.get(name, name),
604 self.defs['indent'][0] % 0,
605 self.defs['indent'][0] % BLOCKQOUTE_INDENT,
606 self._docinfo[name],
607 self.defs['indent'][1],
608 self.defs['indent'][1]))
609 elif not name in skip:
610 if name in self._docinfo_names:
611 label = self._docinfo_names[name]
612 else:
613 label = self.language.labels.get(name, name)
614 self.body.append("\n%s: %s\n" % (label, self._docinfo[name]))
615 if self._docinfo['copyright']:
616 self.body.append('.SH COPYRIGHT\n%s\n'
617 % self._docinfo['copyright'])
618 self.body.append(self.comment(
619 'Generated by docutils manpage writer.'))
621 def visit_emphasis(self, node):
622 self.body.append(self.defs['emphasis'][0])
624 def depart_emphasis(self, node):
625 self.body.append(self.defs['emphasis'][1])
627 def visit_entry(self, node):
628 # a cell in a table row
629 if 'morerows' in node:
630 self.document.reporter.warning('"table row spanning" not supported',
631 base_node=node)
632 if 'morecols' in node:
633 self.document.reporter.warning(
634 '"table cell spanning" not supported', base_node=node)
635 self.context.append(len(self.body))
637 def depart_entry(self, node):
638 start = self.context.pop()
639 self._active_table.append_cell(self.body[start:])
640 del self.body[start:]
642 def visit_enumerated_list(self, node):
643 self.list_start(node)
645 def depart_enumerated_list(self, node):
646 self.list_end()
648 def visit_error(self, node):
649 self.visit_admonition(node, 'error')
651 depart_error = depart_admonition
653 def visit_field(self, node):
654 pass
656 def depart_field(self, node):
657 pass
659 def visit_field_body(self, node):
660 if self._in_docinfo:
661 name_normalized = self._field_name.lower().replace(" ", "_")
662 self._docinfo_names[name_normalized] = self._field_name
663 self.visit_docinfo_item(node, name_normalized)
664 raise nodes.SkipNode
666 def depart_field_body(self, node):
667 pass
669 def visit_field_list(self, node):
670 self.indent(FIELD_LIST_INDENT)
672 def depart_field_list(self, node):
673 self.dedent()
675 def visit_field_name(self, node):
676 if self._in_docinfo:
677 self._field_name = node.astext()
678 raise nodes.SkipNode
679 else:
680 self.body.append(self.defs['field_name'][0])
682 def depart_field_name(self, node):
683 self.body.append(self.defs['field_name'][1])
685 def visit_figure(self, node):
686 self.indent(2.5)
687 self.indent(0)
689 def depart_figure(self, node):
690 self.dedent()
691 self.dedent()
693 def visit_footer(self, node):
694 self.document.reporter.warning('"footer" not supported',
695 base_node=node)
697 def depart_footer(self, node):
698 pass
700 def visit_footnote(self, node):
701 num, text = node.astext().split(None, 1)
702 num = num.strip()
703 self.body.append('.IP [%s] 5\n' % self.deunicode(num))
705 def depart_footnote(self, node):
706 pass
708 def footnote_backrefs(self, node):
709 self.document.reporter.warning('"footnote_backrefs" not supported',
710 base_node=node)
712 def visit_footnote_reference(self, node):
713 self.body.append('['+self.deunicode(node.astext())+']')
714 raise nodes.SkipNode
716 def depart_footnote_reference(self, node):
717 pass
719 def visit_generated(self, node):
720 pass
722 def depart_generated(self, node):
723 pass
725 def visit_header(self, node):
726 raise NotImplementedError(node.astext())
728 def depart_header(self, node):
729 pass
731 def visit_hint(self, node):
732 self.visit_admonition(node, 'hint')
734 depart_hint = depart_admonition
736 def visit_subscript(self, node):
737 self.body.append('\\s-2\\d')
739 def depart_subscript(self, node):
740 self.body.append('\\u\\s0')
742 def visit_superscript(self, node):
743 self.body.append('\\s-2\\u')
745 def depart_superscript(self, node):
746 self.body.append('\\d\\s0')
748 def visit_attribution(self, node):
749 self.body.append('\\(em ')
751 def depart_attribution(self, node):
752 self.body.append('\n')
754 def visit_image(self, node):
755 self.document.reporter.warning('"image" not supported',
756 base_node=node)
757 text = []
758 if 'alt' in node.attributes:
759 text.append(node.attributes['alt'])
760 if 'uri' in node.attributes:
761 text.append(node.attributes['uri'])
762 self.body.append('[image: %s]\n' % ('/'.join(text)))
763 raise nodes.SkipNode
765 def visit_important(self, node):
766 self.visit_admonition(node, 'important')
768 depart_important = depart_admonition
770 def visit_inline(self, node):
771 pass
773 def depart_inline(self, node):
774 pass
776 def visit_label(self, node):
777 # footnote and citation
778 if (isinstance(node.parent, nodes.footnote)
779 or isinstance(node.parent, nodes.citation)):
780 raise nodes.SkipNode
781 self.document.reporter.warning('"unsupported "label"',
782 base_node=node)
783 self.body.append('[')
785 def depart_label(self, node):
786 self.body.append(']\n')
788 def visit_legend(self, node):
789 pass
791 def depart_legend(self, node):
792 pass
794 # WHAT should we use .INDENT, .UNINDENT ?
795 def visit_line_block(self, node):
796 self._line_block += 1
797 if self._line_block == 1:
798 # TODO: separate inline blocks from previous paragraphs
799 # see http://hg.intevation.org/mercurial/crew/rev/9c142ed9c405
800 # self.body.append('.sp\n')
801 # but it does not work for me.
802 self.body.append('.nf\n')
803 else:
804 self.body.append('.in +2\n')
806 def depart_line_block(self, node):
807 self._line_block -= 1
808 if self._line_block == 0:
809 self.body.append('.fi\n')
810 self.body.append('.sp\n')
811 else:
812 self.body.append('.in -2\n')
814 def visit_line(self, node):
815 pass
817 def depart_line(self, node):
818 self.body.append('\n')
820 def visit_list_item(self, node):
821 # man 7 man argues to use ".IP" instead of ".TP"
822 self.body.append('.IP %s %d\n' % (
823 next(self._list_char[-1]),
824 self._list_char[-1].get_width(),))
826 def depart_list_item(self, node):
827 pass
829 def visit_literal(self, node):
830 self.body.append(self.defs['literal'][0])
832 def depart_literal(self, node):
833 self.body.append(self.defs['literal'][1])
835 def visit_literal_block(self, node):
836 # BUG/HACK: indent always uses the _last_ indentation,
837 # thus we need two of them.
838 self.indent(LITERAL_BLOCK_INDENT)
839 self.indent(0)
840 self.body.append(self.defs['literal_block'][0])
841 self._in_literal = True
843 def depart_literal_block(self, node):
844 self._in_literal = False
845 self.body.append(self.defs['literal_block'][1])
846 self.dedent()
847 self.dedent()
849 def visit_math(self, node):
850 self.document.reporter.warning('"math" role not supported',
851 base_node=node)
852 self.visit_literal(node)
854 def depart_math(self, node):
855 self.depart_literal(node)
857 def visit_math_block(self, node):
858 self.document.reporter.warning('"math" directive not supported',
859 base_node=node)
860 self.visit_literal_block(node)
862 def depart_math_block(self, node):
863 self.depart_literal_block(node)
865 # <meta> shall become an optional standard node:
866 # def visit_meta(self, node):
867 # raise NotImplementedError(node.astext())
869 # def depart_meta(self, node):
870 # pass
872 def visit_note(self, node):
873 self.visit_admonition(node, 'note')
875 depart_note = depart_admonition
877 def indent(self, by=0.5):
878 # if we are in a section ".SH" there already is a .RS
879 step = self._indent[-1]
880 self._indent.append(by)
881 self.body.append(self.defs['indent'][0] % step)
883 def dedent(self):
884 self._indent.pop()
885 self.body.append(self.defs['indent'][1])
887 def visit_option_list(self, node):
888 self.indent(OPTION_LIST_INDENT)
890 def depart_option_list(self, node):
891 self.dedent()
893 def visit_option_list_item(self, node):
894 # one item of the list
895 self.body.append(self.defs['option_list_item'][0])
897 def depart_option_list_item(self, node):
898 self.body.append(self.defs['option_list_item'][1])
900 def visit_option_group(self, node):
901 # as one option could have several forms it is a group
902 # options without parameter bold only, .B, -v
903 # options with parameter bold italic, .BI, -f file
905 # we do not know if .B or .BI
906 self.context.append('.B ') # blind guess. Add blank for sphinx see docutils/bugs/380
907 self.context.append(len(self.body)) # to be able to insert later
908 self.context.append(0) # option counter
910 def depart_option_group(self, node):
911 self.context.pop() # the counter
912 start_position = self.context.pop()
913 text = self.body[start_position:]
914 del self.body[start_position:]
915 self.body.append('%s%s\n' % (self.context.pop(), ''.join(text)))
917 def visit_option(self, node):
918 # each form of the option will be presented separately
919 if self.context[-1] > 0:
920 if self.context[-3] == '.BI':
921 self.body.append('\\fR,\\fB ')
922 else:
923 self.body.append('\\fP,\\fB ')
924 if self.context[-3] == '.BI':
925 self.body.append('\\')
926 self.body.append(' ')
928 def depart_option(self, node):
929 self.context[-1] += 1
931 def visit_option_string(self, node):
932 # do not know if .B or .BI
933 pass
935 def depart_option_string(self, node):
936 pass
938 def visit_option_argument(self, node):
939 self.context[-3] = '.BI' # bold/italic alternate
940 if node['delimiter'] != ' ':
941 self.body.append('\\fB%s ' % node['delimiter'])
942 elif self.body[len(self.body)-1].endswith('='):
943 # a blank only means no blank in output, just changing font
944 self.body.append(' ')
945 else:
946 # blank backslash blank, switch font then a blank
947 self.body.append(' \\ ')
949 def depart_option_argument(self, node):
950 pass
952 def visit_organization(self, node):
953 self.visit_docinfo_item(node, 'organization')
955 def depart_organization(self, node):
956 pass
958 def first_child(self, node):
959 first = isinstance(node.parent[0], nodes.label) # skip label
960 for child in node.parent.children[first:]:
961 if isinstance(child, nodes.Invisible):
962 continue
963 if child is node:
964 return 1
965 break
966 return 0
968 def visit_paragraph(self, node):
969 # ``.PP`` : Start standard indented paragraph.
970 # ``.LP`` : Start block paragraph, all except the first.
971 # ``.P [type]`` : Start paragraph type.
972 # NOTE do not use paragraph starts because they reset indentation.
973 # ``.sp`` is only vertical space
974 self.ensure_eol()
975 if not self.first_child(node):
976 self.body.append('.sp\n')
977 # set in literal to escape dots after a new-line-character
978 self._in_literal = True
980 def depart_paragraph(self, node):
981 self._in_literal = False
982 self.body.append('\n')
984 def visit_problematic(self, node):
985 self.body.append(self.defs['problematic'][0])
987 def depart_problematic(self, node):
988 self.body.append(self.defs['problematic'][1])
990 def visit_raw(self, node):
991 if node.get('format') == 'manpage':
992 self.body.append(node.astext() + "\n")
993 # Keep non-manpage raw text out of output:
994 raise nodes.SkipNode
996 def visit_reference(self, node):
997 """E.g. link or email address."""
998 self.body.append(self.defs['reference'][0])
1000 def depart_reference(self, node):
1001 # TODO check node text is different from refuri
1002 #self.body.append("\n'UR " + node['refuri'] + "\n'UE\n")
1003 self.body.append(self.defs['reference'][1])
1005 def visit_revision(self, node):
1006 self.visit_docinfo_item(node, 'revision')
1008 depart_revision = depart_docinfo_item
1010 def visit_row(self, node):
1011 self._active_table.new_row()
1013 def depart_row(self, node):
1014 pass
1016 def visit_section(self, node):
1017 self.section_level += 1
1019 def depart_section(self, node):
1020 self.section_level -= 1
1022 def visit_status(self, node):
1023 self.visit_docinfo_item(node, 'status')
1025 depart_status = depart_docinfo_item
1027 def visit_strong(self, node):
1028 self.body.append(self.defs['strong'][0])
1030 def depart_strong(self, node):
1031 self.body.append(self.defs['strong'][1])
1033 def visit_substitution_definition(self, node):
1034 """Internal only."""
1035 raise nodes.SkipNode
1037 def visit_substitution_reference(self, node):
1038 self.document.reporter.warning('"substitution_reference" not supported',
1039 base_node=node)
1041 def visit_subtitle(self, node):
1042 if isinstance(node.parent, nodes.sidebar):
1043 self.body.append(self.defs['strong'][0])
1044 elif isinstance(node.parent, nodes.document):
1045 self.visit_docinfo_item(node, 'subtitle')
1046 elif isinstance(node.parent, nodes.section):
1047 self.body.append(self.defs['strong'][0])
1049 def depart_subtitle(self, node):
1050 # document subtitle calls SkipNode
1051 self.body.append(self.defs['strong'][1]+'\n.PP\n')
1053 def visit_system_message(self, node):
1054 # TODO add report_level
1055 #if node['level'] < self.document.reporter['writer'].report_level:
1056 # Level is too low to display:
1057 # raise nodes.SkipNode
1058 attr = {}
1059 backref_text = ''
1060 if node.hasattr('id'):
1061 attr['name'] = node['id']
1062 if node.hasattr('line'):
1063 line = ', line %s' % node['line']
1064 else:
1065 line = ''
1066 self.body.append('.IP "System Message: %s/%s (%s:%s)"\n'
1067 % (node['type'], node['level'], node['source'], line))
1069 def depart_system_message(self, node):
1070 pass
1072 def visit_table(self, node):
1073 self._active_table = Table()
1075 def depart_table(self, node):
1076 self.ensure_eol()
1077 self.body.extend(self._active_table.as_list())
1078 self._active_table = None
1080 def visit_target(self, node):
1081 # targets are in-document hyper targets, without any use for man-pages.
1082 raise nodes.SkipNode
1084 def visit_tbody(self, node):
1085 pass
1087 def depart_tbody(self, node):
1088 pass
1090 def visit_term(self, node):
1091 self.body.append(self.defs['term'][0])
1093 def depart_term(self, node):
1094 self.body.append(self.defs['term'][1])
1096 def visit_tgroup(self, node):
1097 pass
1099 def depart_tgroup(self, node):
1100 pass
1102 def visit_thead(self, node):
1103 # MAYBE double line '='
1104 pass
1106 def depart_thead(self, node):
1107 # MAYBE double line '='
1108 pass
1110 def visit_tip(self, node):
1111 self.visit_admonition(node, 'tip')
1113 depart_tip = depart_admonition
1115 def visit_title(self, node):
1116 if isinstance(node.parent, nodes.topic):
1117 self.body.append(self.defs['topic-title'][0])
1118 elif isinstance(node.parent, nodes.sidebar):
1119 self.body.append(self.defs['sidebar-title'][0])
1120 elif isinstance(node.parent, nodes.admonition):
1121 self.body.append('.IP "')
1122 elif self.section_level == 0:
1123 self._docinfo['title'] = node.astext()
1124 # document title for .TH
1125 self._docinfo['title_upper'] = node.astext().upper()
1126 raise nodes.SkipNode
1127 elif self.section_level == 1:
1128 self.body.append('.SH %s\n' % self.deunicode(node.astext().upper()))
1129 raise nodes.SkipNode
1130 else:
1131 self.body.append('.SS ')
1133 def depart_title(self, node):
1134 if isinstance(node.parent, nodes.admonition):
1135 self.body.append('"')
1136 self.body.append('\n')
1138 def visit_title_reference(self, node):
1139 """inline citation reference"""
1140 self.body.append(self.defs['title_reference'][0])
1142 def depart_title_reference(self, node):
1143 self.body.append(self.defs['title_reference'][1])
1145 def visit_topic(self, node):
1146 pass
1148 def depart_topic(self, node):
1149 pass
1151 def visit_sidebar(self, node):
1152 pass
1154 def depart_sidebar(self, node):
1155 pass
1157 def visit_rubric(self, node):
1158 pass
1160 def depart_rubric(self, node):
1161 self.body.append('\n')
1163 def visit_transition(self, node):
1164 # .PP Begin a new paragraph and reset prevailing indent.
1165 # .sp N leaves N lines of blank space.
1166 # .ce centers the next line
1167 self.body.append('\n.sp\n.ce\n----\n')
1169 def depart_transition(self, node):
1170 self.body.append('\n.ce 0\n.sp\n')
1172 def visit_version(self, node):
1173 self.visit_docinfo_item(node, 'version')
1175 def visit_warning(self, node):
1176 self.visit_admonition(node, 'warning')
1178 depart_warning = depart_admonition
1180 def unimplemented_visit(self, node):
1181 raise NotImplementedError('visiting unimplemented node type: %s'
1182 % node.__class__.__name__)
1184 # vim: set fileencoding=utf-8 et ts=4 ai :