PageRenderTime 90ms CodeModel.GetById 20ms app.highlight 61ms RepoModel.GetById 1ms app.codeStats 0ms

/doc/manpage.py

https://bitbucket.org/jpellerin/nose/
Python | 1119 lines | 1029 code | 58 blank | 32 comment | 11 complexity | 457da5d9623cf8f18d23611f00e08915 MD5 | raw file
   1# $Id: manpage.py 5901 2009-04-07 13:26:48Z 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, document.reporter)
 159        self.head = []
 160        self.body = []
 161        self.foot = []
 162        self.section_level = -1
 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 = 1 # FIXME docinfo not being found?
 181        self._active_table = None
 182        self._in_entry = None
 183        self.header_written = 0
 184        self.authors = []
 185        self.section_level = -1
 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                'problematic' : ('\n.nf\n', '\n.fi\n'),
 214                # docinfo fields.
 215                'address' : ('\n.nf\n', '\n.fi\n'),
 216                'organization' : ('\n.nf\n', '\n.fi\n'),
 217                    }
 218        # TODO dont specify the newline before a dot-command, but ensure
 219        # check it is there.
 220
 221    def comment_begin(self, text):
 222        """Return commented version of the passed text WITHOUT end of line/comment."""
 223        prefix = '\n.\\" '
 224        return prefix+prefix.join(text.split('\n'))
 225
 226    def comment(self, text):
 227        """Return commented version of the passed text."""
 228        return self.comment_begin(text)+'\n'
 229
 230    def astext(self):
 231        """Return the final formatted document as a string."""
 232        if not self.header_written:
 233            # ensure we get a ".TH" as viewers require it.
 234            self.head.append(self.header())
 235        return ''.join(self.head + self.body + self.foot)
 236
 237    def visit_Text(self, node):
 238        text = node.astext().replace('-','\-')
 239        text = text.replace("'","\\'")
 240        self.body.append(text)
 241
 242    def depart_Text(self, node):
 243        pass
 244
 245    def list_start(self, node):
 246        class enum_char:
 247            enum_style = {
 248                    'arabic'     : (3,1),
 249                    'loweralpha' : (3,'a'),
 250                    'upperalpha' : (3,'A'),
 251                    'lowerroman' : (5,'i'),
 252                    'upperroman' : (5,'I'),
 253                    'bullet'     : (2,'\\(bu'),
 254                    'emdash'     : (2,'\\(em'),
 255                     }
 256            def __init__(self, style):
 257                if style == 'arabic':
 258                    if node.has_key('start'):
 259                        start = node['start']
 260                    else:
 261                        start = 1
 262                    self._style = (
 263                            len(str(len(node.children)))+2,
 264                            start )
 265                # BUG: fix start for alpha
 266                else:
 267                    self._style = self.enum_style[style]
 268                self._cnt = -1
 269            def next(self):
 270                self._cnt += 1
 271                # BUG add prefix postfix
 272                try:
 273                    return "%d." % (self._style[1] + self._cnt)
 274                except:
 275                    if self._style[1][0] == '\\':
 276                        return self._style[1]
 277                    # BUG romans dont work
 278                    # BUG alpha only a...z
 279                    return "%c." % (ord(self._style[1])+self._cnt)
 280            def get_width(self):
 281                return self._style[0]
 282            def __repr__(self):
 283                return 'enum_style%r' % list(self._style)
 284
 285        if node.has_key('enumtype'):
 286            self._list_char.append(enum_char(node['enumtype']))
 287        else:
 288            self._list_char.append(enum_char('bullet'))
 289        if len(self._list_char) > 1:
 290            # indent nested lists
 291            # BUG indentation depends on indentation of parent list.
 292            self.indent(self._list_char[-2].get_width())
 293        else:
 294            self.indent(self._list_char[-1].get_width())
 295
 296    def list_end(self):
 297        self.dedent()
 298        self._list_char.pop()
 299
 300    def header(self):
 301        tmpl = (".TH %(title)s %(manual_section)s"
 302                " \"%(date)s\" \"%(version)s\" \"%(manual_group)s\"\n"
 303                ".SH NAME\n"
 304                "%(title)s \- %(subtitle)s\n")
 305        return tmpl % self._docinfo
 306
 307    def append_header(self):
 308        """append header with .TH and .SH NAME"""
 309        # TODO before everything
 310        # .TH title section date source manual
 311        if self.header_written:
 312            return
 313        self.body.append(self.header())
 314        self.body.append(MACRO_DEF)
 315        self.header_written = 1
 316
 317    def visit_address(self, node):
 318        self._docinfo['address'] = node.astext()
 319        raise nodes.SkipNode
 320
 321    def depart_address(self, node):
 322        pass
 323
 324    def visit_admonition(self, node, name):
 325        self.visit_block_quote(node)
 326
 327    def depart_admonition(self):
 328        self.depart_block_quote(None)
 329
 330    def visit_attention(self, node):
 331        self.visit_admonition(node, 'attention')
 332
 333    def depart_attention(self, node):
 334        self.depart_admonition()
 335
 336    def visit_author(self, node):
 337        self._docinfo['author'] = node.astext()
 338        raise nodes.SkipNode
 339
 340    def depart_author(self, node):
 341        pass
 342
 343    def visit_authors(self, node):
 344        self.body.append(self.comment('visit_authors'))
 345
 346    def depart_authors(self, node):
 347        self.body.append(self.comment('depart_authors'))
 348
 349    def visit_block_quote(self, node):
 350        #self.body.append(self.comment('visit_block_quote'))
 351        # BUG/HACK: indent alway uses the _last_ indention,
 352        # thus we need two of them.
 353        self.indent(BLOCKQOUTE_INDENT)
 354        self.indent(0)
 355
 356    def depart_block_quote(self, node):
 357        #self.body.append(self.comment('depart_block_quote'))
 358        self.dedent()
 359        self.dedent()
 360
 361    def visit_bullet_list(self, node):
 362        self.list_start(node)
 363
 364    def depart_bullet_list(self, node):
 365        self.list_end()
 366
 367    def visit_caption(self, node):
 368        raise NotImplementedError, node.astext()
 369        self.body.append(self.starttag(node, 'p', '', CLASS='caption'))
 370
 371    def depart_caption(self, node):
 372        raise NotImplementedError, node.astext()
 373        self.body.append('</p>\n')
 374
 375    def visit_caution(self, node):
 376        self.visit_admonition(node, 'caution')
 377
 378    def depart_caution(self, node):
 379        self.depart_admonition()
 380
 381    def visit_citation(self, node):
 382        raise NotImplementedError, node.astext()
 383        self.body.append(self.starttag(node, 'table', CLASS='citation',
 384                                       frame="void", rules="none"))
 385        self.body.append('<colgroup><col class="label" /><col /></colgroup>\n'
 386                         '<col />\n'
 387                         '<tbody valign="top">\n'
 388                         '<tr>')
 389        self.footnote_backrefs(node)
 390
 391    def depart_citation(self, node):
 392        raise NotImplementedError, node.astext()
 393        self.body.append('</td></tr>\n'
 394                         '</tbody>\n</table>\n')
 395
 396    def visit_citation_reference(self, node):
 397        raise NotImplementedError, node.astext()
 398        href = ''
 399        if node.has_key('refid'):
 400            href = '#' + node['refid']
 401        elif node.has_key('refname'):
 402            href = '#' + self.document.nameids[node['refname']]
 403        self.body.append(self.starttag(node, 'a', '[', href=href,
 404                                       CLASS='citation-reference'))
 405
 406    def depart_citation_reference(self, node):
 407        raise NotImplementedError, node.astext()
 408        self.body.append(']</a>')
 409
 410    def visit_classifier(self, node):
 411        raise NotImplementedError, node.astext()
 412        self.body.append(' <span class="classifier-delimiter">:</span> ')
 413        self.body.append(self.starttag(node, 'span', '', CLASS='classifier'))
 414
 415    def depart_classifier(self, node):
 416        raise NotImplementedError, node.astext()
 417        self.body.append('</span>')
 418
 419    def visit_colspec(self, node):
 420        self.colspecs.append(node)
 421
 422    def depart_colspec(self, node):
 423        pass
 424
 425    def write_colspecs(self):
 426        self.body.append("%s.\n" % ('L '*len(self.colspecs)))
 427
 428    def visit_comment(self, node,
 429                      sub=re.compile('-(?=-)').sub):
 430        self.body.append(self.comment(node.astext()))
 431        raise nodes.SkipNode
 432
 433    def visit_contact(self, node):
 434        self.visit_docinfo_item(node, 'contact')
 435
 436    def depart_contact(self, node):
 437        self.depart_docinfo_item()
 438
 439    def visit_copyright(self, node):
 440        self._docinfo['copyright'] = node.astext()
 441        raise nodes.SkipNode
 442
 443    def visit_danger(self, node):
 444        self.visit_admonition(node, 'danger')
 445
 446    def depart_danger(self, node):
 447        self.depart_admonition()
 448
 449    def visit_date(self, node):
 450        self._docinfo['date'] = node.astext()
 451        raise nodes.SkipNode
 452
 453    def visit_decoration(self, node):
 454        pass
 455
 456    def depart_decoration(self, node):
 457        pass
 458
 459    def visit_definition(self, node):
 460        self.body.append(self.defs['definition'][0])
 461
 462    def depart_definition(self, node):
 463        self.body.append(self.defs['definition'][1])
 464
 465    def visit_definition_list(self, node):
 466        self.indent(DEFINITION_LIST_INDENT)
 467
 468    def depart_definition_list(self, node):
 469        self.dedent()
 470
 471    def visit_definition_list_item(self, node):
 472        self.body.append(self.defs['definition_list_item'][0])
 473
 474    def depart_definition_list_item(self, node):
 475        self.body.append(self.defs['definition_list_item'][1])
 476
 477    def visit_description(self, node):
 478        self.body.append(self.defs['description'][0])
 479
 480    def depart_description(self, node):
 481        self.body.append(self.defs['description'][1])
 482
 483    def visit_docinfo(self, node):
 484        self._in_docinfo = 1
 485
 486    def depart_docinfo(self, node):
 487        self._in_docinfo = None
 488        # TODO nothing should be written before this
 489        self.append_header()
 490
 491    def visit_docinfo_item(self, node, name):
 492        self.body.append(self.comment('%s: ' % self.language.labels[name]))
 493        if len(node):
 494            return
 495            if isinstance(node[0], nodes.Element):
 496                node[0].set_class('first')
 497            if isinstance(node[0], nodes.Element):
 498                node[-1].set_class('last')
 499
 500    def depart_docinfo_item(self):
 501        pass
 502
 503    def visit_doctest_block(self, node):
 504        raise NotImplementedError, node.astext()
 505        self.body.append(self.starttag(node, 'pre', CLASS='doctest-block'))
 506
 507    def depart_doctest_block(self, node):
 508        raise NotImplementedError, node.astext()
 509        self.body.append('\n</pre>\n')
 510
 511    def visit_document(self, node):
 512        self.body.append(self.comment(self.document_start).lstrip())
 513        # writing header is postboned
 514        self.header_written = 0
 515
 516    def depart_document(self, node):
 517        if self._docinfo['author']:
 518            self.body.append('\n.SH AUTHOR\n%s\n' 
 519                    % self._docinfo['author'])
 520        if 'organization' in self._docinfo:
 521            self.body.append(self.defs['organization'][0])
 522            self.body.append(self._docinfo['organization'])
 523            self.body.append(self.defs['organization'][1])
 524        if 'address' in self._docinfo:
 525            self.body.append(self.defs['address'][0])
 526            self.body.append(self._docinfo['address'])
 527            self.body.append(self.defs['address'][1])
 528        if self._docinfo['copyright']:
 529            self.body.append('\n.SH COPYRIGHT\n%s\n' 
 530                    % self._docinfo['copyright'])
 531        self.body.append(
 532                self.comment(
 533                        'Generated by docutils manpage writer on %s.\n' 
 534                        % (time.strftime('%Y-%m-%d %H:%M')) ) )
 535
 536    def visit_emphasis(self, node):
 537        self.body.append(self.defs['emphasis'][0])
 538
 539    def depart_emphasis(self, node):
 540        self.body.append(self.defs['emphasis'][1])
 541
 542    def visit_entry(self, node):
 543        # BUG entries have to be on one line separated by tab force it.
 544        self.context.append(len(self.body))
 545        self._in_entry = 1
 546
 547    def depart_entry(self, node):
 548        start = self.context.pop()
 549        self._active_table.append_cell(self.body[start:])
 550        del self.body[start:]
 551        self._in_entry = 0
 552
 553    def visit_enumerated_list(self, node):
 554        self.list_start(node)
 555
 556    def depart_enumerated_list(self, node):
 557        self.list_end()
 558
 559    def visit_error(self, node):
 560        self.visit_admonition(node, 'error')
 561
 562    def depart_error(self, node):
 563        self.depart_admonition()
 564
 565    def visit_field(self, node):
 566        #self.body.append(self.comment('visit_field'))
 567        pass
 568
 569    def depart_field(self, node):
 570        #self.body.append(self.comment('depart_field'))
 571        pass
 572
 573    def visit_field_body(self, node):
 574        #self.body.append(self.comment('visit_field_body'))
 575        if self._in_docinfo:
 576            self._docinfo[
 577                    self._field_name.lower().replace(" ","_")] = node.astext()
 578            raise nodes.SkipNode
 579
 580    def depart_field_body(self, node):
 581        pass
 582
 583    def visit_field_list(self, node):
 584        self.indent(FIELD_LIST_INDENT)
 585
 586    def depart_field_list(self, node):
 587        self.dedent('depart_field_list')
 588
 589    def visit_field_name(self, node):
 590        if self._in_docinfo:
 591            self._in_docinfo = 1
 592            self._field_name = node.astext()
 593            raise nodes.SkipNode
 594        else:
 595            self.body.append(self.defs['field_name'][0])
 596
 597    def depart_field_name(self, node):
 598        self.body.append(self.defs['field_name'][1])
 599
 600    def visit_figure(self, node):
 601        raise NotImplementedError, node.astext()
 602
 603    def depart_figure(self, node):
 604        raise NotImplementedError, node.astext()
 605
 606    def visit_footer(self, node):
 607        raise NotImplementedError, node.astext()
 608
 609    def depart_footer(self, node):
 610        raise NotImplementedError, node.astext()
 611        start = self.context.pop()
 612        footer = (['<hr class="footer"/>\n',
 613                   self.starttag(node, 'div', CLASS='footer')]
 614                  + self.body[start:] + ['</div>\n'])
 615        self.body_suffix[:0] = footer
 616        del self.body[start:]
 617
 618    def visit_footnote(self, node):
 619        raise NotImplementedError, node.astext()
 620        self.body.append(self.starttag(node, 'table', CLASS='footnote',
 621                                       frame="void", rules="none"))
 622        self.body.append('<colgroup><col class="label" /><col /></colgroup>\n'
 623                         '<tbody valign="top">\n'
 624                         '<tr>')
 625        self.footnote_backrefs(node)
 626
 627    def footnote_backrefs(self, node):
 628        raise NotImplementedError, node.astext()
 629        if self.settings.footnote_backlinks and node.hasattr('backrefs'):
 630            backrefs = node['backrefs']
 631            if len(backrefs) == 1:
 632                self.context.append('')
 633                self.context.append('<a class="fn-backref" href="#%s" '
 634                                    'name="%s">' % (backrefs[0], node['id']))
 635            else:
 636                i = 1
 637                backlinks = []
 638                for backref in backrefs:
 639                    backlinks.append('<a class="fn-backref" href="#%s">%s</a>'
 640                                     % (backref, i))
 641                    i += 1
 642                self.context.append('<em>(%s)</em> ' % ', '.join(backlinks))
 643                self.context.append('<a name="%s">' % node['id'])
 644        else:
 645            self.context.append('')
 646            self.context.append('<a name="%s">' % node['id'])
 647
 648    def depart_footnote(self, node):
 649        raise NotImplementedError, node.astext()
 650        self.body.append('</td></tr>\n'
 651                         '</tbody>\n</table>\n')
 652
 653    def visit_footnote_reference(self, node):
 654        raise NotImplementedError, node.astext()
 655        href = ''
 656        if node.has_key('refid'):
 657            href = '#' + node['refid']
 658        elif node.has_key('refname'):
 659            href = '#' + self.document.nameids[node['refname']]
 660        format = self.settings.footnote_references
 661        if format == 'brackets':
 662            suffix = '['
 663            self.context.append(']')
 664        elif format == 'superscript':
 665            suffix = '<sup>'
 666            self.context.append('</sup>')
 667        else:                           # shouldn't happen
 668            suffix = '???'
 669            self.content.append('???')
 670        self.body.append(self.starttag(node, 'a', suffix, href=href,
 671                                       CLASS='footnote-reference'))
 672
 673    def depart_footnote_reference(self, node):
 674        raise NotImplementedError, node.astext()
 675        self.body.append(self.context.pop() + '</a>')
 676
 677    def visit_generated(self, node):
 678        pass
 679
 680    def depart_generated(self, node):
 681        pass
 682
 683    def visit_header(self, node):
 684        raise NotImplementedError, node.astext()
 685        self.context.append(len(self.body))
 686
 687    def depart_header(self, node):
 688        raise NotImplementedError, node.astext()
 689        start = self.context.pop()
 690        self.body_prefix.append(self.starttag(node, 'div', CLASS='header'))
 691        self.body_prefix.extend(self.body[start:])
 692        self.body_prefix.append('<hr />\n</div>\n')
 693        del self.body[start:]
 694
 695    def visit_hint(self, node):
 696        self.visit_admonition(node, 'hint')
 697
 698    def depart_hint(self, node):
 699        self.depart_admonition()
 700
 701    def visit_image(self, node):
 702        raise NotImplementedError, node.astext()
 703        atts = node.attributes.copy()
 704        atts['src'] = atts['uri']
 705        del atts['uri']
 706        if not atts.has_key('alt'):
 707            atts['alt'] = atts['src']
 708        if isinstance(node.parent, nodes.TextElement):
 709            self.context.append('')
 710        else:
 711            self.body.append('<p>')
 712            self.context.append('</p>\n')
 713        self.body.append(self.emptytag(node, 'img', '', **atts))
 714
 715    def depart_image(self, node):
 716        raise NotImplementedError, node.astext()
 717        self.body.append(self.context.pop())
 718
 719    def visit_important(self, node):
 720        self.visit_admonition(node, 'important')
 721
 722    def depart_important(self, node):
 723        self.depart_admonition()
 724
 725    def visit_label(self, node):
 726        raise NotImplementedError, node.astext()
 727        self.body.append(self.starttag(node, 'td', '%s[' % self.context.pop(),
 728                                       CLASS='label'))
 729
 730    def depart_label(self, node):
 731        raise NotImplementedError, node.astext()
 732        self.body.append(']</a></td><td>%s' % self.context.pop())
 733
 734    def visit_legend(self, node):
 735        raise NotImplementedError, node.astext()
 736        self.body.append(self.starttag(node, 'div', CLASS='legend'))
 737
 738    def depart_legend(self, node):
 739        raise NotImplementedError, node.astext()
 740        self.body.append('</div>\n')
 741
 742    def visit_line_block(self, node):
 743        self.body.append('\n')
 744
 745    def depart_line_block(self, node):
 746        self.body.append('\n')
 747
 748    def visit_line(self, node):
 749        pass
 750
 751    def depart_line(self, node):
 752        self.body.append('\n.br\n')
 753
 754    def visit_list_item(self, node):
 755        # man 7 man argues to use ".IP" instead of ".TP"
 756        self.body.append('\n.IP %s %d\n' % (
 757                self._list_char[-1].next(),
 758                self._list_char[-1].get_width(),) )
 759
 760    def depart_list_item(self, node):
 761        pass
 762
 763    def visit_literal(self, node):
 764        self.body.append(self.defs['literal'][0])
 765
 766    def depart_literal(self, node):
 767        self.body.append(self.defs['literal'][1])
 768
 769    def visit_literal_block(self, node):
 770        self.body.append(self.defs['literal_block'][0])
 771
 772    def depart_literal_block(self, node):
 773        self.body.append(self.defs['literal_block'][1])
 774
 775    def visit_meta(self, node):
 776        raise NotImplementedError, node.astext()
 777        self.head.append(self.emptytag(node, 'meta', **node.attributes))
 778
 779    def depart_meta(self, node):
 780        pass
 781
 782    def visit_note(self, node):
 783        self.visit_admonition(node, 'note')
 784
 785    def depart_note(self, node):
 786        self.depart_admonition()
 787
 788    def indent(self, by=0.5):
 789        # if we are in a section ".SH" there already is a .RS
 790        #self.body.append('\n[[debug: listchar: %r]]\n' % map(repr, self._list_char))
 791        #self.body.append('\n[[debug: indent %r]]\n' % self._indent)
 792        step = self._indent[-1]
 793        self._indent.append(by)
 794        self.body.append(self.defs['indent'][0] % step)
 795
 796    def dedent(self, name=''):
 797        #self.body.append('\n[[debug: dedent %s %r]]\n' % (name, self._indent))
 798        self._indent.pop()
 799        self.body.append(self.defs['indent'][1])
 800
 801    def visit_option_list(self, node):
 802        self.indent(OPTION_LIST_INDENT)
 803
 804    def depart_option_list(self, node):
 805        self.dedent()
 806
 807    def visit_option_list_item(self, node):
 808        # one item of the list
 809        self.body.append(self.defs['option_list_item'][0])
 810
 811    def depart_option_list_item(self, node):
 812        self.body.append(self.defs['option_list_item'][1])
 813
 814    def visit_option_group(self, node):
 815        # as one option could have several forms it is a group
 816        # options without parameter bold only, .B, -v
 817        # options with parameter bold italic, .BI, -f file
 818        
 819        # we do not know if .B or .BI
 820        self.context.append('.B')           # blind guess
 821        self.context.append(len(self.body)) # to be able to insert later
 822        self.context.append(0)              # option counter
 823
 824    def depart_option_group(self, node):
 825        self.context.pop()  # the counter
 826        start_position = self.context.pop()
 827        text = self.body[start_position:]
 828        del self.body[start_position:]
 829        self.body.append('\n%s%s' % (self.context.pop(), ''.join(text)))
 830
 831    def visit_option(self, node):
 832        # each form of the option will be presented separately
 833        if self.context[-1]>0:
 834            self.body.append(' ,')
 835        if self.context[-3] == '.BI':
 836            self.body.append('\\')
 837        self.body.append(' ')
 838
 839    def depart_option(self, node):
 840        self.context[-1] += 1
 841
 842    def visit_option_string(self, node):
 843        # do not know if .B or .BI
 844        pass
 845
 846    def depart_option_string(self, node):
 847        pass
 848
 849    def visit_option_argument(self, node):
 850        self.context[-3] = '.BI' # bold/italic alternate
 851        if node['delimiter'] != ' ':
 852            self.body.append('\\fn%s ' % node['delimiter'] )
 853        elif self.body[len(self.body)-1].endswith('='):
 854            # a blank only means no blank in output, just changing font
 855            self.body.append(' ')
 856        else:
 857            # backslash blank blank
 858            self.body.append('\\  ')
 859
 860    def depart_option_argument(self, node):
 861        pass
 862
 863    def visit_organization(self, node):
 864        self._docinfo['organization'] = node.astext()
 865        raise nodes.SkipNode
 866
 867    def depart_organization(self, node):
 868        pass
 869
 870    def visit_paragraph(self, node):
 871        # BUG every but the first paragraph in a list must be intended
 872        # TODO .PP or new line
 873        return
 874
 875    def depart_paragraph(self, node):
 876        # TODO .PP or an empty line
 877        if not self._in_entry:
 878            self.body.append('\n\n')
 879
 880    def visit_problematic(self, node):
 881        self.body.append(self.defs['problematic'][0])
 882
 883    def depart_problematic(self, node):
 884        self.body.append(self.defs['problematic'][1])
 885
 886    def visit_raw(self, node):
 887        if node.get('format') == 'manpage':
 888            self.body.append(node.astext())
 889        # Keep non-manpage raw text out of output:
 890        raise nodes.SkipNode
 891
 892    def visit_reference(self, node):
 893        """E.g. link or email address."""
 894        self.body.append(self.defs['reference'][0])
 895
 896    def depart_reference(self, node):
 897        self.body.append(self.defs['reference'][1])
 898
 899    def visit_revision(self, node):
 900        self.visit_docinfo_item(node, 'revision')
 901
 902    def depart_revision(self, node):
 903        self.depart_docinfo_item()
 904
 905    def visit_row(self, node):
 906        self._active_table.new_row()
 907
 908    def depart_row(self, node):
 909        pass
 910
 911    def visit_section(self, node):
 912        self.section_level += 1
 913
 914    def depart_section(self, node):
 915        self.section_level -= 1    
 916
 917    def visit_status(self, node):
 918        raise NotImplementedError, node.astext()
 919        self.visit_docinfo_item(node, 'status', meta=None)
 920
 921    def depart_status(self, node):
 922        self.depart_docinfo_item()
 923
 924    def visit_strong(self, node):
 925        self.body.append(self.defs['strong'][1])
 926
 927    def depart_strong(self, node):
 928        self.body.append(self.defs['strong'][1])
 929
 930    def visit_substitution_definition(self, node):
 931        """Internal only."""
 932        raise nodes.SkipNode
 933
 934    def visit_substitution_reference(self, node):
 935        self.unimplemented_visit(node)
 936
 937    def visit_subtitle(self, node):
 938        self._docinfo["subtitle"] = node.astext()
 939        raise nodes.SkipNode
 940
 941    def visit_system_message(self, node):
 942        # TODO add report_level
 943        #if node['level'] < self.document.reporter['writer'].report_level:
 944            # Level is too low to display:
 945        #    raise nodes.SkipNode
 946        self.body.append('\.SH system-message\n')
 947        attr = {}
 948        backref_text = ''
 949        if node.hasattr('id'):
 950            attr['name'] = node['id']
 951        if node.hasattr('line'):
 952            line = ', line %s' % node['line']
 953        else:
 954            line = ''
 955        self.body.append('System Message: %s/%s (%s:%s)\n'
 956                         % (node['type'], node['level'], node['source'], line))
 957
 958    def depart_system_message(self, node):
 959        self.body.append('\n')
 960
 961    def visit_table(self, node):
 962        self._active_table = Table()
 963
 964    def depart_table(self, node):
 965        self.body.append(self._active_table.astext())
 966        self._active_table = None
 967
 968    def visit_target(self, node):
 969        self.body.append(self.comment('visit_target'))
 970        #self.body.append(self.defs['target'][0])
 971        #self.body.append(node['refuri'])
 972
 973    def depart_target(self, node):
 974        self.body.append(self.comment('depart_target'))
 975        #self.body.append(self.defs['target'][1])
 976
 977    def visit_tbody(self, node):
 978        pass
 979
 980    def depart_tbody(self, node):
 981        pass
 982
 983    def visit_term(self, node):
 984        self.body.append(self.defs['term'][0])
 985
 986    def depart_term(self, node):
 987        self.body.append(self.defs['term'][1])
 988
 989    def visit_tgroup(self, node):
 990        pass
 991
 992    def depart_tgroup(self, node):
 993        pass
 994
 995    def visit_compound(self, node):
 996        pass
 997
 998    def depart_compound(self, node):
 999        pass
1000
1001    def visit_thead(self, node):
1002        raise NotImplementedError, node.astext()
1003        self.write_colspecs()
1004        self.body.append(self.context.pop()) # '</colgroup>\n'
1005        # There may or may not be a <thead>; this is for <tbody> to use:
1006        self.context.append('')
1007        self.body.append(self.starttag(node, 'thead', valign='bottom'))
1008
1009    def depart_thead(self, node):
1010        raise NotImplementedError, node.astext()
1011        self.body.append('</thead>\n')
1012
1013    def visit_tip(self, node):
1014        self.visit_admonition(node, 'tip')
1015
1016    def depart_tip(self, node):
1017        self.depart_admonition()
1018
1019    def visit_title(self, node):
1020        if isinstance(node.parent, nodes.topic):
1021            self.body.append(self.comment('topic-title'))
1022        elif isinstance(node.parent, nodes.sidebar):
1023            self.body.append(self.comment('sidebar-title'))
1024        elif isinstance(node.parent, nodes.admonition):
1025            self.body.append(self.comment('admonition-title'))
1026        elif self.section_level == 0:
1027            # document title for .TH
1028            self._docinfo['title'] = node.astext()
1029            raise nodes.SkipNode
1030        elif self.section_level == 1:
1031            self._docinfo['subtitle'] = node.astext()
1032            raise nodes.SkipNode
1033        elif self.section_level == 2:
1034            self.body.append('\n.SH ')
1035        else:
1036            self.body.append('\n.SS ')
1037
1038    def depart_title(self, node):
1039        self.body.append('\n')
1040
1041    def visit_title_reference(self, node):
1042        """inline citation reference"""
1043        self.body.append(self.defs['title_reference'][0])
1044
1045    def depart_title_reference(self, node):
1046        self.body.append(self.defs['title_reference'][1])
1047
1048    def visit_topic(self, node):
1049        self.body.append(self.comment('topic: '+node.astext()))
1050        raise nodes.SkipNode
1051        ##self.topic_class = node.get('class')
1052
1053    def depart_topic(self, node):
1054        ##self.topic_class = ''
1055        pass
1056
1057    def visit_transition(self, node):
1058        # .PP      Begin a new paragraph and reset prevailing indent.
1059        # .sp N    leaves N lines of blank space.
1060        # .ce      centers the next line
1061        self.body.append('\n.sp\n.ce\n----\n')
1062
1063    def depart_transition(self, node):
1064        self.body.append('\n.ce 0\n.sp\n')
1065
1066    def visit_version(self, node):
1067        self._docinfo["version"] = node.astext()
1068        raise nodes.SkipNode
1069
1070    def visit_warning(self, node):
1071        self.visit_admonition(node, 'warning')
1072
1073    def depart_warning(self, node):
1074        self.depart_admonition()
1075
1076    def visit_index(self, node):
1077        pass
1078
1079    def depart_index(self, node):
1080        pass
1081
1082    def visit_desc(self, node):
1083        pass
1084
1085    def depart_desc(self, node):
1086        pass
1087
1088    def visit_desc_signature(self, node):
1089        # .. cmdoption makes options look like this
1090        self.body.append('\n')
1091        self.body.append('.TP')
1092        self.body.append('\n')
1093
1094    def depart_desc_signature(self, node):
1095        pass
1096
1097    def visit_desc_name(self, node):
1098        self.body.append(r'\fB') # option name
1099
1100    def depart_desc_name(self, node):
1101        self.body.append(r'\fR')
1102
1103    def visit_desc_addname(self, node):
1104        self.body.append(r'\fR')
1105
1106    def depart_desc_addname(self, node):
1107        # self.body.append(r'\fR')
1108        pass
1109
1110    def visit_desc_content(self, node):
1111        self.body.append('\n') # option help
1112
1113    def depart_desc_content(self, node):
1114        pass
1115
1116    def unimplemented_visit(self, node):
1117        pass
1118
1119# vim: set et ts=4 ai :