comparison env/lib/python3.9/site-packages/docutils/writers/manpage.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 # -*- coding: utf-8 -*-
2 # $Id: manpage.py 8451 2019-12-30 14:42:44Z grubert $
3 # Author: Engelbert Gruber <grubert@users.sourceforge.net>
4 # Copyright: This module is put into the public domain.
5
6 """
7 Simple man page writer for reStructuredText.
8
9 Man pages (short for "manual pages") contain system documentation on unix-like
10 systems. The pages are grouped in numbered sections:
11
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
20
21 Man pages are written *troff*, a text file formatting system.
22
23 See http://www.tldp.org/HOWTO/Man-Page for a start.
24
25 Man pages have no subsection only parts.
26 Standard parts
27
28 NAME ,
29 SYNOPSIS ,
30 DESCRIPTION ,
31 OPTIONS ,
32 FILES ,
33 SEE ALSO ,
34 BUGS ,
35
36 and
37
38 AUTHOR .
39
40 A unix-like system keeps an index of the DESCRIPTIONs, which is accessible
41 by the command whatis or apropos.
42
43 """
44
45 __docformat__ = 'reStructuredText'
46
47 import re
48 import sys
49
50 if sys.version_info < (3, 0):
51 range = xrange
52
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
59
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
65
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
70 .
71 .de1 rstReportMargin
72 \\$1 \\n[an-margin]
73 level \\n[rst2man-indent-level]
74 level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
75 -
76 \\n[rst2man-indent0]
77 \\n[rst2man-indent1]
78 \\n[rst2man-indent2]
79 ..
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:
86 ..
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
94 ..
95 """)
96
97 class Writer(writers.Writer):
98
99 supported = ('manpage',)
100 """Formats this writer supports."""
101
102 output = None
103 """Final translated form of `document`."""
104
105 def __init__(self):
106 writers.Writer.__init__(self)
107 self.translator_class = Translator
108
109 def translate(self):
110 visitor = self.translator_class(self.document)
111 self.document.walkabout(visitor)
112 self.output = visitor.astext()
113
114
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
161
162 class Translator(nodes.NodeVisitor):
163 """"""
164
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."""
168
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": "",
195 }
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._active_table = None
200 self._in_literal = False
201 self.header_written = 0
202 self._line_block = 0
203 self.authors = []
204 self.section_level = 0
205 self._indent = [0]
206 # central definition of simple processing rules
207 # what to output on : visit, depart
208 # Do not use paragraph requests ``.PP`` because these set indentation.
209 # use ``.sp``. Remove superfluous ``.sp`` in ``astext``.
210 #
211 # Fonts are put on a stack, the top one is used.
212 # ``.ft P`` or ``\\fP`` pop from stack.
213 # But ``.BI`` seams to fill stack with BIBIBIBIB...
214 # ``B`` bold, ``I`` italic, ``R`` roman should be available.
215 # Hopefully ``C`` courier too.
216 self.defs = {
217 'indent': ('.INDENT %.1f\n', '.UNINDENT\n'),
218 'definition_list_item': ('.TP', ''),
219 'field_name': ('.TP\n.B ', '\n'),
220 'literal': ('\\fB', '\\fP'),
221 'literal_block': ('.sp\n.nf\n.ft C\n', '\n.ft P\n.fi\n'),
222
223 'option_list_item': ('.TP\n', ''),
224
225 'reference': (r'\fI\%', r'\fP'),
226 'emphasis': ('\\fI', '\\fP'),
227 'strong': ('\\fB', '\\fP'),
228 'term': ('\n.B ', '\n'),
229 'title_reference': ('\\fI', '\\fP'),
230
231 'topic-title': ('.SS ',),
232 'sidebar-title': ('.SS ',),
233
234 'problematic': ('\n.nf\n', '\n.fi\n'),
235 }
236 # NOTE do not specify the newline before a dot-command, but ensure
237 # it is there.
238
239 def comment_begin(self, text):
240 """Return commented version of the passed text WITHOUT end of
241 line/comment."""
242 prefix = '.\\" '
243 out_text = ''.join(
244 [(prefix + in_line + '\n')
245 for in_line in text.split('\n')])
246 return out_text
247
248 def comment(self, text):
249 """Return commented version of the passed text."""
250 return self.comment_begin(text)+'.\n'
251
252 def ensure_eol(self):
253 """Ensure the last line in body is terminated by new line."""
254 if len(self.body) > 0 and self.body[-1][-1] != '\n':
255 self.body.append('\n')
256
257 def astext(self):
258 """Return the final formatted document as a string."""
259 if not self.header_written:
260 # ensure we get a ".TH" as viewers require it.
261 self.append_header()
262 # filter body
263 for i in range(len(self.body)-1, 0, -1):
264 # remove superfluous vertical gaps.
265 if self.body[i] == '.sp\n':
266 if self.body[i - 1][:4] in ('.BI ', '.IP '):
267 self.body[i] = '.\n'
268 elif (self.body[i - 1][:3] == '.B ' and
269 self.body[i - 2][:4] == '.TP\n'):
270 self.body[i] = '.\n'
271 elif (self.body[i - 1] == '\n' and
272 not self.possibly_a_roff_command.match(self.body[i - 2]) and
273 (self.body[i - 3][:7] == '.TP\n.B '
274 or self.body[i - 3][:4] == '\n.B ')
275 ):
276 self.body[i] = '.\n'
277 return ''.join(self.head + self.body + self.foot)
278
279 def deunicode(self, text):
280 text = text.replace(u'\xa0', '\\ ')
281 text = text.replace(u'\u2020', '\\(dg')
282 return text
283
284 def visit_Text(self, node):
285 text = node.astext()
286 text = text.replace('\\', '\\e')
287 replace_pairs = [
288 (u'-', u'\\-'),
289 (u'\'', u'\\(aq'),
290 (u'ยด', u"\\'"),
291 (u'`', u'\\(ga'),
292 ]
293 for (in_char, out_markup) in replace_pairs:
294 text = text.replace(in_char, out_markup)
295 # unicode
296 text = self.deunicode(text)
297 # prevent interpretation of "." at line start
298 if text.startswith('.'):
299 text = '\\&' + text
300 if self._in_literal:
301 text = text.replace('\n.', '\n\\&.')
302 self.body.append(text)
303
304 def depart_Text(self, node):
305 pass
306
307 def list_start(self, node):
308 class enum_char(object):
309 enum_style = {
310 'bullet': '\\(bu',
311 'emdash': '\\(em',
312 }
313
314 def __init__(self, style):
315 self._style = style
316 if 'start' in node:
317 self._cnt = node['start'] - 1
318 else:
319 self._cnt = 0
320 self._indent = 2
321 if style == 'arabic':
322 # indentation depends on number of children
323 # and start value.
324 self._indent = len(str(len(node.children)))
325 self._indent += len(str(self._cnt)) + 1
326 elif style == 'loweralpha':
327 self._cnt += ord('a') - 1
328 self._indent = 3
329 elif style == 'upperalpha':
330 self._cnt += ord('A') - 1
331 self._indent = 3
332 elif style.endswith('roman'):
333 self._indent = 5
334
335 def __next__(self):
336 if self._style == 'bullet':
337 return self.enum_style[self._style]
338 elif self._style == 'emdash':
339 return self.enum_style[self._style]
340 self._cnt += 1
341 # TODO add prefix postfix
342 if self._style == 'arabic':
343 return "%d." % self._cnt
344 elif self._style in ('loweralpha', 'upperalpha'):
345 return "%c." % self._cnt
346 elif self._style.endswith('roman'):
347 res = roman.toRoman(self._cnt) + '.'
348 if self._style.startswith('upper'):
349 return res.upper()
350 return res.lower()
351 else:
352 return "%d." % self._cnt
353
354 if sys.version_info < (3, 0):
355 next = __next__
356
357 def get_width(self):
358 return self._indent
359 def __repr__(self):
360 return 'enum_style-%s' % list(self._style)
361
362 if 'enumtype' in node:
363 self._list_char.append(enum_char(node['enumtype']))
364 else:
365 self._list_char.append(enum_char('bullet'))
366 if len(self._list_char) > 1:
367 # indent nested lists
368 self.indent(self._list_char[-2].get_width())
369 else:
370 self.indent(self._list_char[-1].get_width())
371
372 def list_end(self):
373 self.dedent()
374 self._list_char.pop()
375
376 def header(self):
377 tmpl = (".TH %(title_upper)s %(manual_section)s"
378 " \"%(date)s\" \"%(version)s\" \"%(manual_group)s\"\n"
379 ".SH NAME\n"
380 "%(title)s \\- %(subtitle)s\n")
381 return tmpl % self._docinfo
382
383 def append_header(self):
384 """append header with .TH and .SH NAME"""
385 # NOTE before everything
386 # .TH title_upper section date source manual
387 if self.header_written:
388 return
389 self.head.append(self.header())
390 self.head.append(MACRO_DEF)
391 self.header_written = 1
392
393 def visit_address(self, node):
394 self.visit_docinfo_item(node, 'address')
395
396 def depart_address(self, node):
397 pass
398
399 def visit_admonition(self, node, name=None):
400 #
401 # Make admonitions a simple block quote
402 # with a strong heading
403 #
404 # Using .IP/.RE doesn't preserve indentation
405 # when admonitions contain bullets, literal,
406 # and/or block quotes.
407 #
408 if name:
409 # .. admonition:: has no name
410 self.body.append('.sp\n')
411 name = '%s%s:%s\n' % (
412 self.defs['strong'][0],
413 self.language.labels.get(name, name).upper(),
414 self.defs['strong'][1],
415 )
416 self.body.append(name)
417 self.visit_block_quote(node)
418
419 def depart_admonition(self, node):
420 self.depart_block_quote(node)
421
422 def visit_attention(self, node):
423 self.visit_admonition(node, 'attention')
424
425 depart_attention = depart_admonition
426
427 def visit_docinfo_item(self, node, name):
428 if name == 'author':
429 self._docinfo[name].append(node.astext())
430 else:
431 self._docinfo[name] = node.astext()
432 self._docinfo_keys.append(name)
433 raise nodes.SkipNode
434
435 def depart_docinfo_item(self, node):
436 pass
437
438 def visit_author(self, node):
439 self.visit_docinfo_item(node, 'author')
440
441 depart_author = depart_docinfo_item
442
443 def visit_authors(self, node):
444 # _author is called anyway.
445 pass
446
447 def depart_authors(self, node):
448 pass
449
450 def visit_block_quote(self, node):
451 # BUG/HACK: indent always uses the _last_ indention,
452 # thus we need two of them.
453 self.indent(BLOCKQOUTE_INDENT)
454 self.indent(0)
455
456 def depart_block_quote(self, node):
457 self.dedent()
458 self.dedent()
459
460 def visit_bullet_list(self, node):
461 self.list_start(node)
462
463 def depart_bullet_list(self, node):
464 self.list_end()
465
466 def visit_caption(self, node):
467 pass
468
469 def depart_caption(self, node):
470 pass
471
472 def visit_caution(self, node):
473 self.visit_admonition(node, 'caution')
474
475 depart_caution = depart_admonition
476
477 def visit_citation(self, node):
478 num, text = node.astext().split(None, 1)
479 num = num.strip()
480 self.body.append('.IP [%s] 5\n' % num)
481
482 def depart_citation(self, node):
483 pass
484
485 def visit_citation_reference(self, node):
486 self.body.append('['+node.astext()+']')
487 raise nodes.SkipNode
488
489 def visit_classifier(self, node):
490 pass
491
492 def depart_classifier(self, node):
493 pass
494
495 def visit_colspec(self, node):
496 self.colspecs.append(node)
497
498 def depart_colspec(self, node):
499 pass
500
501 def write_colspecs(self):
502 self.body.append("%s.\n" % ('L '*len(self.colspecs)))
503
504 def visit_comment(self, node,
505 sub=re.compile('-(?=-)').sub):
506 self.body.append(self.comment(node.astext()))
507 raise nodes.SkipNode
508
509 def visit_contact(self, node):
510 self.visit_docinfo_item(node, 'contact')
511
512 depart_contact = depart_docinfo_item
513
514 def visit_container(self, node):
515 pass
516
517 def depart_container(self, node):
518 pass
519
520 def visit_compound(self, node):
521 pass
522
523 def depart_compound(self, node):
524 pass
525
526 def visit_copyright(self, node):
527 self.visit_docinfo_item(node, 'copyright')
528
529 def visit_danger(self, node):
530 self.visit_admonition(node, 'danger')
531
532 depart_danger = depart_admonition
533
534 def visit_date(self, node):
535 self.visit_docinfo_item(node, 'date')
536
537 def visit_decoration(self, node):
538 pass
539
540 def depart_decoration(self, node):
541 pass
542
543 def visit_definition(self, node):
544 pass
545
546 def depart_definition(self, node):
547 pass
548
549 def visit_definition_list(self, node):
550 self.indent(DEFINITION_LIST_INDENT)
551
552 def depart_definition_list(self, node):
553 self.dedent()
554
555 def visit_definition_list_item(self, node):
556 self.body.append(self.defs['definition_list_item'][0])
557
558 def depart_definition_list_item(self, node):
559 self.body.append(self.defs['definition_list_item'][1])
560
561 def visit_description(self, node):
562 pass
563
564 def depart_description(self, node):
565 pass
566
567 def visit_docinfo(self, node):
568 self._in_docinfo = 1
569
570 def depart_docinfo(self, node):
571 self._in_docinfo = None
572 # NOTE nothing should be written before this
573 self.append_header()
574
575 def visit_doctest_block(self, node):
576 self.body.append(self.defs['literal_block'][0])
577 self._in_literal = True
578
579 def depart_doctest_block(self, node):
580 self._in_literal = False
581 self.body.append(self.defs['literal_block'][1])
582
583 def visit_document(self, node):
584 # no blank line between comment and header.
585 self.head.append(self.comment(self.document_start).rstrip()+'\n')
586 # writing header is postponed
587 self.header_written = 0
588
589 def depart_document(self, node):
590 if self._docinfo['author']:
591 self.body.append('.SH AUTHOR\n%s\n'
592 % ', '.join(self._docinfo['author']))
593 skip = ('author', 'copyright', 'date',
594 'manual_group', 'manual_section',
595 'subtitle',
596 'title', 'title_upper', 'version')
597 for name in self._docinfo_keys:
598 if name == 'address':
599 self.body.append("\n%s:\n%s%s.nf\n%s\n.fi\n%s%s" % (
600 self.language.labels.get(name, name),
601 self.defs['indent'][0] % 0,
602 self.defs['indent'][0] % BLOCKQOUTE_INDENT,
603 self._docinfo[name],
604 self.defs['indent'][1],
605 self.defs['indent'][1]))
606 elif not name in skip:
607 if name in self._docinfo_names:
608 label = self._docinfo_names[name]
609 else:
610 label = self.language.labels.get(name, name)
611 self.body.append("\n%s: %s\n" % (label, self._docinfo[name]))
612 if self._docinfo['copyright']:
613 self.body.append('.SH COPYRIGHT\n%s\n'
614 % self._docinfo['copyright'])
615 self.body.append(self.comment(
616 'Generated by docutils manpage writer.'))
617
618 def visit_emphasis(self, node):
619 self.body.append(self.defs['emphasis'][0])
620
621 def depart_emphasis(self, node):
622 self.body.append(self.defs['emphasis'][1])
623
624 def visit_entry(self, node):
625 # a cell in a table row
626 if 'morerows' in node:
627 self.document.reporter.warning('"table row spanning" not supported',
628 base_node=node)
629 if 'morecols' in node:
630 self.document.reporter.warning(
631 '"table cell spanning" not supported', base_node=node)
632 self.context.append(len(self.body))
633
634 def depart_entry(self, node):
635 start = self.context.pop()
636 self._active_table.append_cell(self.body[start:])
637 del self.body[start:]
638
639 def visit_enumerated_list(self, node):
640 self.list_start(node)
641
642 def depart_enumerated_list(self, node):
643 self.list_end()
644
645 def visit_error(self, node):
646 self.visit_admonition(node, 'error')
647
648 depart_error = depart_admonition
649
650 def visit_field(self, node):
651 pass
652
653 def depart_field(self, node):
654 pass
655
656 def visit_field_body(self, node):
657 if self._in_docinfo:
658 name_normalized = self._field_name.lower().replace(" ", "_")
659 self._docinfo_names[name_normalized] = self._field_name
660 self.visit_docinfo_item(node, name_normalized)
661 raise nodes.SkipNode
662
663 def depart_field_body(self, node):
664 pass
665
666 def visit_field_list(self, node):
667 self.indent(FIELD_LIST_INDENT)
668
669 def depart_field_list(self, node):
670 self.dedent()
671
672 def visit_field_name(self, node):
673 if self._in_docinfo:
674 self._field_name = node.astext()
675 raise nodes.SkipNode
676 else:
677 self.body.append(self.defs['field_name'][0])
678
679 def depart_field_name(self, node):
680 self.body.append(self.defs['field_name'][1])
681
682 def visit_figure(self, node):
683 self.indent(2.5)
684 self.indent(0)
685
686 def depart_figure(self, node):
687 self.dedent()
688 self.dedent()
689
690 def visit_footer(self, node):
691 self.document.reporter.warning('"footer" not supported',
692 base_node=node)
693
694 def depart_footer(self, node):
695 pass
696
697 def visit_footnote(self, node):
698 num, text = node.astext().split(None, 1)
699 num = num.strip()
700 self.body.append('.IP [%s] 5\n' % self.deunicode(num))
701
702 def depart_footnote(self, node):
703 pass
704
705 def footnote_backrefs(self, node):
706 self.document.reporter.warning('"footnote_backrefs" not supported',
707 base_node=node)
708
709 def visit_footnote_reference(self, node):
710 self.body.append('['+self.deunicode(node.astext())+']')
711 raise nodes.SkipNode
712
713 def depart_footnote_reference(self, node):
714 pass
715
716 def visit_generated(self, node):
717 pass
718
719 def depart_generated(self, node):
720 pass
721
722 def visit_header(self, node):
723 raise NotImplementedError(node.astext())
724
725 def depart_header(self, node):
726 pass
727
728 def visit_hint(self, node):
729 self.visit_admonition(node, 'hint')
730
731 depart_hint = depart_admonition
732
733 def visit_subscript(self, node):
734 self.body.append('\\s-2\\d')
735
736 def depart_subscript(self, node):
737 self.body.append('\\u\\s0')
738
739 def visit_superscript(self, node):
740 self.body.append('\\s-2\\u')
741
742 def depart_superscript(self, node):
743 self.body.append('\\d\\s0')
744
745 def visit_attribution(self, node):
746 self.body.append('\\(em ')
747
748 def depart_attribution(self, node):
749 self.body.append('\n')
750
751 def visit_image(self, node):
752 self.document.reporter.warning('"image" not supported',
753 base_node=node)
754 text = []
755 if 'alt' in node.attributes:
756 text.append(node.attributes['alt'])
757 if 'uri' in node.attributes:
758 text.append(node.attributes['uri'])
759 self.body.append('[image: %s]\n' % ('/'.join(text)))
760 raise nodes.SkipNode
761
762 def visit_important(self, node):
763 self.visit_admonition(node, 'important')
764
765 depart_important = depart_admonition
766
767 def visit_inline(self, node):
768 pass
769
770 def depart_inline(self, node):
771 pass
772
773 def visit_label(self, node):
774 # footnote and citation
775 if (isinstance(node.parent, nodes.footnote)
776 or isinstance(node.parent, nodes.citation)):
777 raise nodes.SkipNode
778 self.document.reporter.warning('"unsupported "label"',
779 base_node=node)
780 self.body.append('[')
781
782 def depart_label(self, node):
783 self.body.append(']\n')
784
785 def visit_legend(self, node):
786 pass
787
788 def depart_legend(self, node):
789 pass
790
791 # WHAT should we use .INDENT, .UNINDENT ?
792 def visit_line_block(self, node):
793 self._line_block += 1
794 if self._line_block == 1:
795 # TODO: separate inline blocks from previous paragraphs
796 # see http://hg.intevation.org/mercurial/crew/rev/9c142ed9c405
797 # self.body.append('.sp\n')
798 # but it does not work for me.
799 self.body.append('.nf\n')
800 else:
801 self.body.append('.in +2\n')
802
803 def depart_line_block(self, node):
804 self._line_block -= 1
805 if self._line_block == 0:
806 self.body.append('.fi\n')
807 self.body.append('.sp\n')
808 else:
809 self.body.append('.in -2\n')
810
811 def visit_line(self, node):
812 pass
813
814 def depart_line(self, node):
815 self.body.append('\n')
816
817 def visit_list_item(self, node):
818 # man 7 man argues to use ".IP" instead of ".TP"
819 self.body.append('.IP %s %d\n' % (
820 next(self._list_char[-1]),
821 self._list_char[-1].get_width(),))
822
823 def depart_list_item(self, node):
824 pass
825
826 def visit_literal(self, node):
827 self.body.append(self.defs['literal'][0])
828
829 def depart_literal(self, node):
830 self.body.append(self.defs['literal'][1])
831
832 def visit_literal_block(self, node):
833 # BUG/HACK: indent always uses the _last_ indention,
834 # thus we need two of them.
835 self.indent(LITERAL_BLOCK_INDENT)
836 self.indent(0)
837 self.body.append(self.defs['literal_block'][0])
838 self._in_literal = True
839
840 def depart_literal_block(self, node):
841 self._in_literal = False
842 self.body.append(self.defs['literal_block'][1])
843 self.dedent()
844 self.dedent()
845
846 def visit_math(self, node):
847 self.document.reporter.warning('"math" role not supported',
848 base_node=node)
849 self.visit_literal(node)
850
851 def depart_math(self, node):
852 self.depart_literal(node)
853
854 def visit_math_block(self, node):
855 self.document.reporter.warning('"math" directive not supported',
856 base_node=node)
857 self.visit_literal_block(node)
858
859 def depart_math_block(self, node):
860 self.depart_literal_block(node)
861
862 def visit_meta(self, node):
863 raise NotImplementedError(node.astext())
864
865 def depart_meta(self, node):
866 pass
867
868 def visit_note(self, node):
869 self.visit_admonition(node, 'note')
870
871 depart_note = depart_admonition
872
873 def indent(self, by=0.5):
874 # if we are in a section ".SH" there already is a .RS
875 step = self._indent[-1]
876 self._indent.append(by)
877 self.body.append(self.defs['indent'][0] % step)
878
879 def dedent(self):
880 self._indent.pop()
881 self.body.append(self.defs['indent'][1])
882
883 def visit_option_list(self, node):
884 self.indent(OPTION_LIST_INDENT)
885
886 def depart_option_list(self, node):
887 self.dedent()
888
889 def visit_option_list_item(self, node):
890 # one item of the list
891 self.body.append(self.defs['option_list_item'][0])
892
893 def depart_option_list_item(self, node):
894 self.body.append(self.defs['option_list_item'][1])
895
896 def visit_option_group(self, node):
897 # as one option could have several forms it is a group
898 # options without parameter bold only, .B, -v
899 # options with parameter bold italic, .BI, -f file
900 #
901 # we do not know if .B or .BI
902 self.context.append('.B') # blind guess
903 self.context.append(len(self.body)) # to be able to insert later
904 self.context.append(0) # option counter
905
906 def depart_option_group(self, node):
907 self.context.pop() # the counter
908 start_position = self.context.pop()
909 text = self.body[start_position:]
910 del self.body[start_position:]
911 self.body.append('%s%s\n' % (self.context.pop(), ''.join(text)))
912
913 def visit_option(self, node):
914 # each form of the option will be presented separately
915 if self.context[-1] > 0:
916 if self.context[-3] == '.BI':
917 self.body.append('\\fR,\\fB ')
918 else:
919 self.body.append('\\fP,\\fB ')
920 if self.context[-3] == '.BI':
921 self.body.append('\\')
922 self.body.append(' ')
923
924 def depart_option(self, node):
925 self.context[-1] += 1
926
927 def visit_option_string(self, node):
928 # do not know if .B or .BI
929 pass
930
931 def depart_option_string(self, node):
932 pass
933
934 def visit_option_argument(self, node):
935 self.context[-3] = '.BI' # bold/italic alternate
936 if node['delimiter'] != ' ':
937 self.body.append('\\fB%s ' % node['delimiter'])
938 elif self.body[len(self.body)-1].endswith('='):
939 # a blank only means no blank in output, just changing font
940 self.body.append(' ')
941 else:
942 # blank backslash blank, switch font then a blank
943 self.body.append(' \\ ')
944
945 def depart_option_argument(self, node):
946 pass
947
948 def visit_organization(self, node):
949 self.visit_docinfo_item(node, 'organization')
950
951 def depart_organization(self, node):
952 pass
953
954 def first_child(self, node):
955 first = isinstance(node.parent[0], nodes.label) # skip label
956 for child in node.parent.children[first:]:
957 if isinstance(child, nodes.Invisible):
958 continue
959 if child is node:
960 return 1
961 break
962 return 0
963
964 def visit_paragraph(self, node):
965 # ``.PP`` : Start standard indented paragraph.
966 # ``.LP`` : Start block paragraph, all except the first.
967 # ``.P [type]`` : Start paragraph type.
968 # NOTE do not use paragraph starts because they reset indentation.
969 # ``.sp`` is only vertical space
970 self.ensure_eol()
971 if not self.first_child(node):
972 self.body.append('.sp\n')
973 # set in literal to escape dots after a new-line-character
974 self._in_literal = True
975
976 def depart_paragraph(self, node):
977 self._in_literal = False
978 self.body.append('\n')
979
980 def visit_problematic(self, node):
981 self.body.append(self.defs['problematic'][0])
982
983 def depart_problematic(self, node):
984 self.body.append(self.defs['problematic'][1])
985
986 def visit_raw(self, node):
987 if node.get('format') == 'manpage':
988 self.body.append(node.astext() + "\n")
989 # Keep non-manpage raw text out of output:
990 raise nodes.SkipNode
991
992 def visit_reference(self, node):
993 """E.g. link or email address."""
994 self.body.append(self.defs['reference'][0])
995
996 def depart_reference(self, node):
997 self.body.append(self.defs['reference'][1])
998
999 def visit_revision(self, node):
1000 self.visit_docinfo_item(node, 'revision')
1001
1002 depart_revision = depart_docinfo_item
1003
1004 def visit_row(self, node):
1005 self._active_table.new_row()
1006
1007 def depart_row(self, node):
1008 pass
1009
1010 def visit_section(self, node):
1011 self.section_level += 1
1012
1013 def depart_section(self, node):
1014 self.section_level -= 1
1015
1016 def visit_status(self, node):
1017 self.visit_docinfo_item(node, 'status')
1018
1019 depart_status = depart_docinfo_item
1020
1021 def visit_strong(self, node):
1022 self.body.append(self.defs['strong'][0])
1023
1024 def depart_strong(self, node):
1025 self.body.append(self.defs['strong'][1])
1026
1027 def visit_substitution_definition(self, node):
1028 """Internal only."""
1029 raise nodes.SkipNode
1030
1031 def visit_substitution_reference(self, node):
1032 self.document.reporter.warning('"substitution_reference" not supported',
1033 base_node=node)
1034
1035 def visit_subtitle(self, node):
1036 if isinstance(node.parent, nodes.sidebar):
1037 self.body.append(self.defs['strong'][0])
1038 elif isinstance(node.parent, nodes.document):
1039 self.visit_docinfo_item(node, 'subtitle')
1040 elif isinstance(node.parent, nodes.section):
1041 self.body.append(self.defs['strong'][0])
1042
1043 def depart_subtitle(self, node):
1044 # document subtitle calls SkipNode
1045 self.body.append(self.defs['strong'][1]+'\n.PP\n')
1046
1047 def visit_system_message(self, node):
1048 # TODO add report_level
1049 #if node['level'] < self.document.reporter['writer'].report_level:
1050 # Level is too low to display:
1051 # raise nodes.SkipNode
1052 attr = {}
1053 backref_text = ''
1054 if node.hasattr('id'):
1055 attr['name'] = node['id']
1056 if node.hasattr('line'):
1057 line = ', line %s' % node['line']
1058 else:
1059 line = ''
1060 self.body.append('.IP "System Message: %s/%s (%s:%s)"\n'
1061 % (node['type'], node['level'], node['source'], line))
1062
1063 def depart_system_message(self, node):
1064 pass
1065
1066 def visit_table(self, node):
1067 self._active_table = Table()
1068
1069 def depart_table(self, node):
1070 self.ensure_eol()
1071 self.body.extend(self._active_table.as_list())
1072 self._active_table = None
1073
1074 def visit_target(self, node):
1075 # targets are in-document hyper targets, without any use for man-pages.
1076 raise nodes.SkipNode
1077
1078 def visit_tbody(self, node):
1079 pass
1080
1081 def depart_tbody(self, node):
1082 pass
1083
1084 def visit_term(self, node):
1085 self.body.append(self.defs['term'][0])
1086
1087 def depart_term(self, node):
1088 self.body.append(self.defs['term'][1])
1089
1090 def visit_tgroup(self, node):
1091 pass
1092
1093 def depart_tgroup(self, node):
1094 pass
1095
1096 def visit_thead(self, node):
1097 # MAYBE double line '='
1098 pass
1099
1100 def depart_thead(self, node):
1101 # MAYBE double line '='
1102 pass
1103
1104 def visit_tip(self, node):
1105 self.visit_admonition(node, 'tip')
1106
1107 depart_tip = depart_admonition
1108
1109 def visit_title(self, node):
1110 if isinstance(node.parent, nodes.topic):
1111 self.body.append(self.defs['topic-title'][0])
1112 elif isinstance(node.parent, nodes.sidebar):
1113 self.body.append(self.defs['sidebar-title'][0])
1114 elif isinstance(node.parent, nodes.admonition):
1115 self.body.append('.IP "')
1116 elif self.section_level == 0:
1117 self._docinfo['title'] = node.astext()
1118 # document title for .TH
1119 self._docinfo['title_upper'] = node.astext().upper()
1120 raise nodes.SkipNode
1121 elif self.section_level == 1:
1122 self.body.append('.SH %s\n' % self.deunicode(node.astext().upper()))
1123 raise nodes.SkipNode
1124 else:
1125 self.body.append('.SS ')
1126
1127 def depart_title(self, node):
1128 if isinstance(node.parent, nodes.admonition):
1129 self.body.append('"')
1130 self.body.append('\n')
1131
1132 def visit_title_reference(self, node):
1133 """inline citation reference"""
1134 self.body.append(self.defs['title_reference'][0])
1135
1136 def depart_title_reference(self, node):
1137 self.body.append(self.defs['title_reference'][1])
1138
1139 def visit_topic(self, node):
1140 pass
1141
1142 def depart_topic(self, node):
1143 pass
1144
1145 def visit_sidebar(self, node):
1146 pass
1147
1148 def depart_sidebar(self, node):
1149 pass
1150
1151 def visit_rubric(self, node):
1152 pass
1153
1154 def depart_rubric(self, node):
1155 pass
1156
1157 def visit_transition(self, node):
1158 # .PP Begin a new paragraph and reset prevailing indent.
1159 # .sp N leaves N lines of blank space.
1160 # .ce centers the next line
1161 self.body.append('\n.sp\n.ce\n----\n')
1162
1163 def depart_transition(self, node):
1164 self.body.append('\n.ce 0\n.sp\n')
1165
1166 def visit_version(self, node):
1167 self.visit_docinfo_item(node, 'version')
1168
1169 def visit_warning(self, node):
1170 self.visit_admonition(node, 'warning')
1171
1172 depart_warning = depart_admonition
1173
1174 def unimplemented_visit(self, node):
1175 raise NotImplementedError('visiting unimplemented node type: %s'
1176 % node.__class__.__name__)
1177
1178 # vim: set fileencoding=utf-8 et ts=4 ai :