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