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