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