/doc/manpage.py

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