| 1 | # Copyright: 2007 Brian Harring <ferringb@gmail.com> |
|---|
| 2 | # Copyright: ?-2006 Engelbert Gruber <grubert@users.sourceforge.net> |
|---|
| 3 | # Original Author: Englebert Gruber |
|---|
| 4 | # License: Public Domain |
|---|
| 5 | |
|---|
| 6 | """ |
|---|
| 7 | Man page formatting for reStructuredText. |
|---|
| 8 | |
|---|
| 9 | See http://www.tldp.org/HOWTO/Man-Page for a start. |
|---|
| 10 | |
|---|
| 11 | Man pages have no subsection only parts. |
|---|
| 12 | Standard parts |
|---|
| 13 | NAME , |
|---|
| 14 | SYNOPSIS , |
|---|
| 15 | DESCRIPTION , |
|---|
| 16 | OPTIONS , |
|---|
| 17 | FILES , |
|---|
| 18 | SEE ALSO , |
|---|
| 19 | BUGS , |
|---|
| 20 | and |
|---|
| 21 | AUTHOR . |
|---|
| 22 | |
|---|
| 23 | """ |
|---|
| 24 | |
|---|
| 25 | # NOTE: the macros only work when at line start, so try the rule |
|---|
| 26 | # start new lines in visit_ functions. |
|---|
| 27 | |
|---|
| 28 | __docformat__ = 'reStructuredText' |
|---|
| 29 | |
|---|
| 30 | import sys |
|---|
| 31 | import os |
|---|
| 32 | import time |
|---|
| 33 | import re |
|---|
| 34 | from types import ListType |
|---|
| 35 | |
|---|
| 36 | import docutils |
|---|
| 37 | from docutils import nodes, utils, writers, languages |
|---|
| 38 | |
|---|
| 39 | |
|---|
| 40 | class Writer(writers.Writer): |
|---|
| 41 | |
|---|
| 42 | supported = ('manpage') |
|---|
| 43 | """Formats this writer supports.""" |
|---|
| 44 | |
|---|
| 45 | output = None |
|---|
| 46 | """Final translated form of `document`.""" |
|---|
| 47 | |
|---|
| 48 | def __init__(self, debug_file): |
|---|
| 49 | writers.Writer.__init__(self) |
|---|
| 50 | self.translator_class = Translator |
|---|
| 51 | self.debug_file = debug_file |
|---|
| 52 | |
|---|
| 53 | def translate(self): |
|---|
| 54 | visitor = self.translator_class(self.document) |
|---|
| 55 | if self.debug_file: |
|---|
| 56 | open(self.debug_file, 'w').write(str(self.document)) |
|---|
| 57 | self.document.walkabout(visitor) |
|---|
| 58 | self.output = visitor.astext() |
|---|
| 59 | |
|---|
| 60 | |
|---|
| 61 | class Table: |
|---|
| 62 | def __init__(self): |
|---|
| 63 | self._rows = [] |
|---|
| 64 | self._options = ['center', ] |
|---|
| 65 | self._tab_char = '\t' |
|---|
| 66 | self._coldefs = [] |
|---|
| 67 | def new_row(self): |
|---|
| 68 | self._rows.append([]) |
|---|
| 69 | def append_cell(self, text): |
|---|
| 70 | self._rows[-1].append(text) |
|---|
| 71 | if len(self._coldefs) < len(self._rows[-1]): |
|---|
| 72 | self._coldefs.append('l') |
|---|
| 73 | def astext(self): |
|---|
| 74 | text = '.TS\n' |
|---|
| 75 | text += ' '.join(self._options) + ';\n' |
|---|
| 76 | text += '|%s|.\n' % ('|'.join(self._coldefs)) |
|---|
| 77 | for row in self._rows: |
|---|
| 78 | # row = array of cells. cell = array of lines. |
|---|
| 79 | # line above |
|---|
| 80 | text += '_\n' |
|---|
| 81 | nr_of_cells = len(row) |
|---|
| 82 | ln_cnt = -1 |
|---|
| 83 | while ln_cnt < 10: # safety belt |
|---|
| 84 | ln_cnt += 1 |
|---|
| 85 | line = [] |
|---|
| 86 | last_line = 1 |
|---|
| 87 | for cell in row: |
|---|
| 88 | if len(cell) > ln_cnt: |
|---|
| 89 | line.append(cell[ln_cnt]) |
|---|
| 90 | last_line = 0 |
|---|
| 91 | else: |
|---|
| 92 | line.append(" ") |
|---|
| 93 | if last_line: |
|---|
| 94 | break |
|---|
| 95 | text += self._tab_char.join(line) + '\n' |
|---|
| 96 | text += '_\n' |
|---|
| 97 | text += '.TE\n' |
|---|
| 98 | return text |
|---|
| 99 | |
|---|
| 100 | class Translator(nodes.NodeVisitor): |
|---|
| 101 | """""" |
|---|
| 102 | |
|---|
| 103 | words_and_spaces = re.compile(r'\S+| +|\n') |
|---|
| 104 | document_start = """Man page generated from reStructeredText.""" |
|---|
| 105 | |
|---|
| 106 | |
|---|
| 107 | def unimplemented_visit(self, node): |
|---|
| 108 | raise NotImplementedError('visiting unimplemented node type: %s' |
|---|
| 109 | % node.__class__.__name__) |
|---|
| 110 | |
|---|
| 111 | def noop(self, node): |
|---|
| 112 | pass |
|---|
| 113 | |
|---|
| 114 | simple_defs = { |
|---|
| 115 | 'definition' : ('', ''), |
|---|
| 116 | 'definition_list' : ('', ''), |
|---|
| 117 | 'definition_list_item' : ('\n.TP', ''), |
|---|
| 118 | 'description' : ('\n', ''), |
|---|
| 119 | 'field_name' : ('\n.TP\n.B ', '\n'), |
|---|
| 120 | 'literal_block' : ('\n.nf\n', '\n.fi\n'), |
|---|
| 121 | 'option_list' : ('', ''), |
|---|
| 122 | 'option_list_item' : ('\n.TP', ''), |
|---|
| 123 | 'reference' : ('', ''), |
|---|
| 124 | 'strong' : ('\n.B ', ''), |
|---|
| 125 | 'term' : ('\n.B ', '\n'), |
|---|
| 126 | } |
|---|
| 127 | |
|---|
| 128 | def f(mode, k, val): |
|---|
| 129 | def f2(self, node): |
|---|
| 130 | if val: |
|---|
| 131 | if self.body and self.body[-1].endswith('\n'): |
|---|
| 132 | self.body.append(val.lstrip('\n')) |
|---|
| 133 | else: |
|---|
| 134 | self.body.append(val) |
|---|
| 135 | return f2 |
|---|
| 136 | |
|---|
| 137 | for k,v in simple_defs.iteritems(): |
|---|
| 138 | locals()['visit_%s' % k] = f('visit', k, v[0]) |
|---|
| 139 | locals()['depart_%s' % k] = f('depart', k, v[1]) |
|---|
| 140 | |
|---|
| 141 | def __init__(self, document): |
|---|
| 142 | nodes.NodeVisitor.__init__(self, document) |
|---|
| 143 | self.settings = settings = document.settings |
|---|
| 144 | lcode = settings.language_code |
|---|
| 145 | self.language = languages.get_language(lcode) |
|---|
| 146 | self.head = [] |
|---|
| 147 | self.body = [] |
|---|
| 148 | self.foot = [] |
|---|
| 149 | self.section_level = 0 |
|---|
| 150 | self.context = [] |
|---|
| 151 | self.topic_class = '' |
|---|
| 152 | self.colspecs = [] |
|---|
| 153 | self.compact_p = 1 |
|---|
| 154 | self.compact_simple = None |
|---|
| 155 | # the list style "*" bullet or "#" numbered |
|---|
| 156 | self._list_char = [] |
|---|
| 157 | # writing the header .TH and .SH NAME is postboned after |
|---|
| 158 | # docinfo. |
|---|
| 159 | self._docinfo = { |
|---|
| 160 | "title" : "", "subtitle" : "", |
|---|
| 161 | "manual_section" : "", "manual_group" : "", |
|---|
| 162 | "author" : "", |
|---|
| 163 | "date" : "", |
|---|
| 164 | "copyright" : "", |
|---|
| 165 | "version" : "", |
|---|
| 166 | } |
|---|
| 167 | self._in_docinfo = None |
|---|
| 168 | self._active_table = None |
|---|
| 169 | self._in_entry = None |
|---|
| 170 | self.header_written = 0 |
|---|
| 171 | self.authors = [] |
|---|
| 172 | self.section_level = 0 |
|---|
| 173 | # central definition of simple processing rules |
|---|
| 174 | # what to output on : visit, depart |
|---|
| 175 | # TODO dont specify the newline before a dot-command, but ensure |
|---|
| 176 | # check it is there. |
|---|
| 177 | |
|---|
| 178 | def comment_begin(self, text): |
|---|
| 179 | """Return commented version of the passed text WITHOUT end of line/comment.""" |
|---|
| 180 | prefix = '\n.\\" ' |
|---|
| 181 | return prefix+prefix.join(text.split('\n')) |
|---|
| 182 | |
|---|
| 183 | def comment(self, text): |
|---|
| 184 | """Return commented version of the passed text.""" |
|---|
| 185 | return self.comment_begin(text)+'\n' |
|---|
| 186 | |
|---|
| 187 | def astext(self): |
|---|
| 188 | """Return the final formatted document as a string.""" |
|---|
| 189 | return ''.join(self.head + self.body + self.foot) |
|---|
| 190 | |
|---|
| 191 | def visit_Text(self, node): |
|---|
| 192 | text = node.astext().replace('-','\-') |
|---|
| 193 | text = text.replace("'","\\'") |
|---|
| 194 | self.body.append(text) |
|---|
| 195 | |
|---|
| 196 | def list_start(self, node): |
|---|
| 197 | class enum_char: |
|---|
| 198 | enum_style = { |
|---|
| 199 | 'arabic' : (3,1), |
|---|
| 200 | 'loweralpha' : (2,'a'), |
|---|
| 201 | 'upperalpha' : (2,'A'), |
|---|
| 202 | 'lowerroman' : (5,'i'), |
|---|
| 203 | 'upperroman' : (5,'I'), |
|---|
| 204 | 'bullet' : (2,'\\(bu'), |
|---|
| 205 | 'emdash' : (2,'\\(em'), |
|---|
| 206 | } |
|---|
| 207 | def __init__(self, style): |
|---|
| 208 | self._style = self.enum_style[style] |
|---|
| 209 | self._cnt = -1 |
|---|
| 210 | def next(self): |
|---|
| 211 | self._cnt += 1 |
|---|
| 212 | try: |
|---|
| 213 | return "%d." % (self._style[1] + self._cnt) |
|---|
| 214 | except: |
|---|
| 215 | if self._style[1][0] == '\\': |
|---|
| 216 | return self._style[1] |
|---|
| 217 | # BUG romans dont work |
|---|
| 218 | # BUG alpha only a...z |
|---|
| 219 | return "%c." % (ord(self._style[1])+self._cnt) |
|---|
| 220 | def get_width(self): |
|---|
| 221 | return self._style[0] |
|---|
| 222 | |
|---|
| 223 | if node.has_key('enumtype'): |
|---|
| 224 | self._list_char.append(enum_char(node['enumtype'])) |
|---|
| 225 | else: |
|---|
| 226 | self._list_char.append(enum_char('bullet')) |
|---|
| 227 | if len(self._list_char) > 1: |
|---|
| 228 | # indent nested lists |
|---|
| 229 | # BUG indentation depends on indentation of parent list. |
|---|
| 230 | self.body.append('\n.RS %d' % self._list_char[-2].get_width()) |
|---|
| 231 | |
|---|
| 232 | def list_end(self): |
|---|
| 233 | self._list_char.pop() |
|---|
| 234 | if len(self._list_char) > 0: |
|---|
| 235 | self.body.append('\n.RE\n') |
|---|
| 236 | |
|---|
| 237 | def append_header(self): |
|---|
| 238 | """append header with .TH and .SH NAME""" |
|---|
| 239 | # TODO before everything |
|---|
| 240 | # .TH title section date source manual |
|---|
| 241 | if self.header_written: |
|---|
| 242 | return |
|---|
| 243 | tmpl = (".TH %(title)s %(manual_section)s" |
|---|
| 244 | " \"%(date)s\" \"%(version)s\" \"%(manual_group)s\"\n" |
|---|
| 245 | ".SH NAME\n" |
|---|
| 246 | "%(title)s \- %(subtitle)s\n") |
|---|
| 247 | self.body.append(tmpl % self._docinfo) |
|---|
| 248 | self.header_written = 1 |
|---|
| 249 | |
|---|
| 250 | def visit_author(self, node): |
|---|
| 251 | self._docinfo['author'] = node.astext() |
|---|
| 252 | raise nodes.SkipNode |
|---|
| 253 | |
|---|
| 254 | def visit_authors(self, node): |
|---|
| 255 | self.body.append(self.comment('visit_authors')) |
|---|
| 256 | |
|---|
| 257 | def depart_authors(self, node): |
|---|
| 258 | self.body.append(self.comment('depart_authors')) |
|---|
| 259 | |
|---|
| 260 | def visit_block_quote(self, node): |
|---|
| 261 | self.body.append(self.comment('visit_block_quote')) |
|---|
| 262 | |
|---|
| 263 | def depart_block_quote(self, node): |
|---|
| 264 | self.body.append(self.comment('depart_block_quote')) |
|---|
| 265 | |
|---|
| 266 | def visit_bullet_list(self, node): |
|---|
| 267 | self.list_start(node) |
|---|
| 268 | |
|---|
| 269 | def depart_bullet_list(self, node): |
|---|
| 270 | self.list_end() |
|---|
| 271 | |
|---|
| 272 | def visit_colspec(self, node): |
|---|
| 273 | self.colspecs.append(node) |
|---|
| 274 | |
|---|
| 275 | def write_colspecs(self): |
|---|
| 276 | self.body.append("%s.\n" % ('L '*len(self.colspecs))) |
|---|
| 277 | |
|---|
| 278 | def visit_comment(self, node, |
|---|
| 279 | sub=re.compile('-(?=-)').sub): |
|---|
| 280 | self.body.append(self.comment(node.astext())) |
|---|
| 281 | raise nodes.SkipNode |
|---|
| 282 | |
|---|
| 283 | def visit_contact(self, node): |
|---|
| 284 | self.visit_docinfo_item(node, 'contact') |
|---|
| 285 | |
|---|
| 286 | def depart_contact(self, node): |
|---|
| 287 | self.depart_docinfo_item() |
|---|
| 288 | |
|---|
| 289 | def visit_copyright(self, node): |
|---|
| 290 | self._docinfo['copyright'] = node.astext() |
|---|
| 291 | raise nodes.SkipNode |
|---|
| 292 | |
|---|
| 293 | def visit_date(self, node): |
|---|
| 294 | self._docinfo['date'] = node.astext() |
|---|
| 295 | raise nodes.SkipNode |
|---|
| 296 | |
|---|
| 297 | visit_decoration = depart_decoration = noop |
|---|
| 298 | |
|---|
| 299 | def visit_docinfo(self, node): |
|---|
| 300 | self._in_docinfo = 1 |
|---|
| 301 | |
|---|
| 302 | def depart_docinfo(self, node): |
|---|
| 303 | self._in_docinfo = None |
|---|
| 304 | # TODO nothing should be written before this |
|---|
| 305 | self.append_header() |
|---|
| 306 | |
|---|
| 307 | def visit_docinfo_item(self, node, name): |
|---|
| 308 | self.body.append(self.comment('%s: ' % self.language.labels[name])) |
|---|
| 309 | if len(node): |
|---|
| 310 | return |
|---|
| 311 | if isinstance(node[0], nodes.Element): |
|---|
| 312 | node[0].set_class('first') |
|---|
| 313 | if isinstance(node[0], nodes.Element): |
|---|
| 314 | node[-1].set_class('last') |
|---|
| 315 | |
|---|
| 316 | def visit_document(self, node): |
|---|
| 317 | self.body.append(self.comment(self.document_start)) |
|---|
| 318 | # writing header is postboned |
|---|
| 319 | self.header_written = 0 |
|---|
| 320 | |
|---|
| 321 | def depart_document(self, node): |
|---|
| 322 | if self._docinfo['author']: |
|---|
| 323 | self.body.append('\n.SH AUTHOR\n%s\n' |
|---|
| 324 | % self._docinfo['author']) |
|---|
| 325 | if self._docinfo['copyright']: |
|---|
| 326 | self.body.append('\n.SH COPYRIGHT\n%s\n' |
|---|
| 327 | % self._docinfo['copyright']) |
|---|
| 328 | self.body.append( |
|---|
| 329 | self.comment( |
|---|
| 330 | 'Generated by docutils manpage writer on %s.\n' |
|---|
| 331 | % (time.strftime('%Y-%m-%d %H:%M')) ) ) |
|---|
| 332 | |
|---|
| 333 | def visit_emphasis(self, node): |
|---|
| 334 | self.body.append('\n.I ') |
|---|
| 335 | |
|---|
| 336 | def depart_emphasis(self, node): |
|---|
| 337 | self.body.append('\n') |
|---|
| 338 | |
|---|
| 339 | def visit_enumerated_list(self, node): |
|---|
| 340 | self.list_start(node) |
|---|
| 341 | |
|---|
| 342 | def depart_enumerated_list(self, node): |
|---|
| 343 | self.list_end() |
|---|
| 344 | |
|---|
| 345 | visit_field = depart_field = noop |
|---|
| 346 | |
|---|
| 347 | def visit_field_body(self, node): |
|---|
| 348 | if self._in_docinfo: |
|---|
| 349 | self._docinfo[ |
|---|
| 350 | self._field_name.lower().replace(" ","_")] = node.astext() |
|---|
| 351 | raise nodes.SkipNode |
|---|
| 352 | |
|---|
| 353 | def depart_field_body(self, node): |
|---|
| 354 | self.body.append(self.comment('depart_field_body')) |
|---|
| 355 | |
|---|
| 356 | def visit_field_list(self, node): |
|---|
| 357 | self.body.append(self.comment('visit_field_list')) |
|---|
| 358 | |
|---|
| 359 | def depart_field_list(self, node): |
|---|
| 360 | self.body.append(self.comment('depart_field_list')) |
|---|
| 361 | |
|---|
| 362 | def visit_field_name(self, node): |
|---|
| 363 | if self._in_docinfo: |
|---|
| 364 | self._field_name = node.astext() |
|---|
| 365 | raise nodes.SkipNode |
|---|
| 366 | else: |
|---|
| 367 | self.body.append(self.simple_defs['field_name'][0]) |
|---|
| 368 | |
|---|
| 369 | def depart_field_name(self, node): |
|---|
| 370 | self.body.append(self.simple_defs['field_name'][1]) |
|---|
| 371 | |
|---|
| 372 | visit_generated = depart_generated = noop |
|---|
| 373 | |
|---|
| 374 | def visit_line_block(self, node): |
|---|
| 375 | self.body.append('\n') |
|---|
| 376 | |
|---|
| 377 | def depart_line_block(self, node): |
|---|
| 378 | self.body.append('\n') |
|---|
| 379 | |
|---|
| 380 | def visit_list_item(self, node): |
|---|
| 381 | self.body.append('\n.TP %d\n%s' % ( |
|---|
| 382 | self._list_char[-1].get_width(), |
|---|
| 383 | self._list_char[-1].next(),) ) |
|---|
| 384 | |
|---|
| 385 | def visit_option(self, node): |
|---|
| 386 | # each form of the option will be presented separately |
|---|
| 387 | if self.context[-1]>0: |
|---|
| 388 | self.body.append(', ') |
|---|
| 389 | if self.context[-3] == '.BI': |
|---|
| 390 | self.body.append('\\') |
|---|
| 391 | self.body.append(' ') |
|---|
| 392 | |
|---|
| 393 | def depart_option(self, node): |
|---|
| 394 | self.context[-1] += 1 |
|---|
| 395 | |
|---|
| 396 | def visit_option_group(self, node): |
|---|
| 397 | # as one option could have several forms it is a group |
|---|
| 398 | # options without parameter bold only, .B, -v |
|---|
| 399 | # options with parameter bold italic, .BI, -f file |
|---|
| 400 | |
|---|
| 401 | # we do not know if .B or .BI |
|---|
| 402 | self.context.append('.B') # blind guess |
|---|
| 403 | self.context.append(len(self.body)) # to be able to insert later |
|---|
| 404 | self.context.append(0) # option counter |
|---|
| 405 | |
|---|
| 406 | def depart_option_group(self, node): |
|---|
| 407 | self.context.pop() # the counter |
|---|
| 408 | start_position = self.context.pop() |
|---|
| 409 | text = self.body[start_position:] |
|---|
| 410 | del self.body[start_position:] |
|---|
| 411 | self.body.append('\n%s%s' % (self.context.pop(), ''.join(text))) |
|---|
| 412 | |
|---|
| 413 | visit_option_string = depart_option_string = noop |
|---|
| 414 | |
|---|
| 415 | def visit_option_argument(self, node): |
|---|
| 416 | self.context[-3] = '.BI' |
|---|
| 417 | if self.body[len(self.body)-1].endswith('='): |
|---|
| 418 | # a blank only means no blank in output |
|---|
| 419 | self.body.append(' ') |
|---|
| 420 | else: |
|---|
| 421 | # backslash blank blank |
|---|
| 422 | self.body.append('\\ ') |
|---|
| 423 | |
|---|
| 424 | def depart_paragraph(self, node): |
|---|
| 425 | # TODO .PP or an empty line |
|---|
| 426 | if self.body and not self.body[-1].endswith("\n"): |
|---|
| 427 | self.body.append('\n') |
|---|
| 428 | |
|---|
| 429 | def visit_raw(self, node): |
|---|
| 430 | if node.get('format') == 'manpage': |
|---|
| 431 | self.body.append(node.astext()) |
|---|
| 432 | # Keep non-HTML raw text out of output: |
|---|
| 433 | raise nodes.SkipNode |
|---|
| 434 | |
|---|
| 435 | def visit_revision(self, node): |
|---|
| 436 | self.visit_docinfo_item(node, 'revision') |
|---|
| 437 | |
|---|
| 438 | def depart_revision(self, node): |
|---|
| 439 | self.depart_docinfo_item() |
|---|
| 440 | |
|---|
| 441 | def visit_row(self, node): |
|---|
| 442 | self._active_table.new_row() |
|---|
| 443 | |
|---|
| 444 | def visit_section(self, node): |
|---|
| 445 | self.section_level += 1 |
|---|
| 446 | |
|---|
| 447 | def depart_section(self, node): |
|---|
| 448 | self.section_level -= 1 |
|---|
| 449 | |
|---|
| 450 | def visit_substitution_definition(self, node): |
|---|
| 451 | """Internal only.""" |
|---|
| 452 | raise nodes.SkipNode |
|---|
| 453 | |
|---|
| 454 | def visit_substitution_reference(self, node): |
|---|
| 455 | self.unimplemented_visit(node) |
|---|
| 456 | |
|---|
| 457 | def visit_subtitle(self, node): |
|---|
| 458 | self._docinfo["subtitle"] = node.astext() |
|---|
| 459 | raise nodes.SkipNode |
|---|
| 460 | |
|---|
| 461 | def visit_system_message(self, node): |
|---|
| 462 | # TODO add report_level |
|---|
| 463 | #if node['level'] < self.document.reporter['writer'].report_level: |
|---|
| 464 | # Level is too low to display: |
|---|
| 465 | # raise nodes.SkipNode |
|---|
| 466 | self.body.append('\.SH system-message\n') |
|---|
| 467 | attr = {} |
|---|
| 468 | backref_text = '' |
|---|
| 469 | if node.hasattr('id'): |
|---|
| 470 | attr['name'] = node['id'] |
|---|
| 471 | if node.hasattr('line'): |
|---|
| 472 | line = ', line %s' % node['line'] |
|---|
| 473 | else: |
|---|
| 474 | line = '' |
|---|
| 475 | self.body.append('System Message: %s/%s (%s:%s)\n' |
|---|
| 476 | % (node['type'], node['level'], node['source'], line)) |
|---|
| 477 | |
|---|
| 478 | def depart_system_message(self, node): |
|---|
| 479 | self.body.append('\n') |
|---|
| 480 | |
|---|
| 481 | def visit_table(self, node): |
|---|
| 482 | self._active_table = Table() |
|---|
| 483 | |
|---|
| 484 | def depart_table(self, node): |
|---|
| 485 | self.body.append(self._active_table.astext()) |
|---|
| 486 | self._active_table = None |
|---|
| 487 | |
|---|
| 488 | def visit_target(self, node): |
|---|
| 489 | self.body.append(self.comment('visit_target')) |
|---|
| 490 | |
|---|
| 491 | def depart_target(self, node): |
|---|
| 492 | self.body.append(self.comment('depart_target')) |
|---|
| 493 | |
|---|
| 494 | visit_tbody = depart_tbody = noop |
|---|
| 495 | visit_tgroup = depart_tgroup = noop |
|---|
| 496 | |
|---|
| 497 | def visit_title(self, node): |
|---|
| 498 | if len(self.body) and not self.body[-1].endswith("\n"): |
|---|
| 499 | self.body.append("\n") |
|---|
| 500 | if isinstance(node.parent, nodes.topic): |
|---|
| 501 | self.body.append(self.comment('topic-title')) |
|---|
| 502 | elif isinstance(node.parent, nodes.sidebar): |
|---|
| 503 | self.body.append(self.comment('sidebar-title')) |
|---|
| 504 | elif isinstance(node.parent, nodes.admonition): |
|---|
| 505 | self.body.append(self.comment('admonition-title')) |
|---|
| 506 | elif self.section_level == 0: |
|---|
| 507 | # document title for .TH |
|---|
| 508 | self._docinfo['title'] = node.astext() |
|---|
| 509 | raise nodes.SkipNode |
|---|
| 510 | elif self.section_level == 1: |
|---|
| 511 | self.body.append('.SH ') |
|---|
| 512 | else: |
|---|
| 513 | self.body.append('.SS ') |
|---|
| 514 | |
|---|
| 515 | def depart_title(self, node): |
|---|
| 516 | if not self.body[-1].endswith("\n"): |
|---|
| 517 | self.body.append('\n') |
|---|
| 518 | |
|---|
| 519 | def visit_topic(self, node): |
|---|
| 520 | self.body.append(self.comment('topic: '+node.astext())) |
|---|
| 521 | raise nodes.SkipNode |
|---|
| 522 | ##self.topic_class = node.get('class') |
|---|
| 523 | |
|---|
| 524 | def visit_transition(self, node): |
|---|
| 525 | # .PP Begin a new paragraph and reset prevailing indent. |
|---|
| 526 | # .sp N leaves N lines of blank space. |
|---|
| 527 | # .ce centers the next line |
|---|
| 528 | self.body.append('\n.sp\n.ce\n----\n') |
|---|
| 529 | |
|---|
| 530 | def depart_transition(self, node): |
|---|
| 531 | self.body.append('\n.ce 0\n.sp\n') |
|---|
| 532 | |
|---|
| 533 | def visit_version(self, node): |
|---|
| 534 | self._docinfo["version"] = node.astext() |
|---|
| 535 | raise nodes.SkipNode |
|---|
| 536 | |
|---|
| 537 | def __getattr__(self, attr): |
|---|
| 538 | if attr.startswith("visit_"): |
|---|
| 539 | if not hasattr(self, "depart_%s" % attr[6:]): |
|---|
| 540 | obj = self.unimplemented_visit |
|---|
| 541 | else: |
|---|
| 542 | obj = self.noop |
|---|
| 543 | elif attr.startswith("depart_"): |
|---|
| 544 | if not hasattr(self, "visit_%s" % attr[6:]): |
|---|
| 545 | obj = self.unimplemented_visit |
|---|
| 546 | else: |
|---|
| 547 | obj = self.noop |
|---|
| 548 | else: |
|---|
| 549 | raise AttributeError(self, attr) |
|---|
| 550 | setattr(self, attr, obj) |
|---|
| 551 | return obj |
|---|
| 552 | |
|---|
| 553 | # vim: set et ts=4 ai : |
|---|