Change LGPL-2.0+ to LGPL-2.1-or-later
[platform/upstream/python-nose.git] / doc / manpage.py
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 """
6 Simple man page writer for reStructuredText.
7
8 Man pages (short for "manual pages") contain system documentation on unix-like
9 systems. 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
20 Man pages are written *troff*, a text file formatting system.
21
22 See http://www.tldp.org/HOWTO/Man-Page for a start.
23
24 Man pages have no subsection only parts.
25 Standard parts
26
27   NAME ,
28   SYNOPSIS ,
29   DESCRIPTION ,
30   OPTIONS ,
31   FILES ,
32   SEE ALSO ,
33   BUGS ,
34
35 and
36
37   AUTHOR .
38
39 A unix-like system keeps an index of the DESCRIPTIONs, which is accesable
40 by 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
49 import sys
50 import os
51 import time
52 import re
53 from types import ListType
54
55 import docutils
56 from docutils import nodes, utils, writers, languages
57
58 FIELD_LIST_INDENT = 7
59 DEFINITION_LIST_INDENT = 7
60 OPTION_LIST_INDENT = 7
61 BLOCKQOUTE_INDENT = 3.5
62
63 # Define two macros so man/roff can calculate the
64 # indent/unindent margins by itself
65 MACRO_DEF = (r"""
66 .nr rst2man-indent-level 0
67 .
68 .de1 rstReportMargin
69 \\$1 \\n[an-margin]
70 level \\n[rst2man-indent-level]
71 level 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
94 class Writer(writers.Writer):
95
96     supported = ('manpage')
97     """Formats this writer supports."""
98
99     output = None
100     """Final translated form of `document`."""
101
102     def __init__(self):
103         writers.Writer.__init__(self)
104         self.translator_class = Translator
105
106     def translate(self):
107         visitor = self.translator_class(self.document)
108         self.document.walkabout(visitor)
109         self.output = visitor.astext()
110
111
112 class Table:
113     def __init__(self):
114         self._rows = []
115         self._options = ['center', ]
116         self._tab_char = '\t'
117         self._coldefs = []
118     def new_row(self):
119         self._rows.append([])
120     def append_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
148 class 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 :