Files
sphinx/converter/restwriter.py

963 lines
36 KiB
Python
Raw Normal View History

2007-07-23 09:02:25 +00:00
# -*- coding: utf-8 -*-
"""
Python documentation ReST writer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
How the converter works
=======================
A LaTeX document is tokenized by a `Tokenizer`. The tokens are processed by
the `DocParser` class which emits a tree of `DocNode`\s. The `RestWriter`
now walks this node tree and generates ReST from that.
There are some intricacies while writing ReST:
- Paragraph text must be rewrapped in order to avoid ragged lines. The
`textwrap` module does that nicely, but it must obviously operate on a
whole paragraph at a time. Therefore the contents of the current paragraph
are cached in `self.curpar`. Every time a block level element is
encountered, its node handler calls `self.flush_par()` which writes out a
paragraph. Because this can be detrimental for the markup at several
stages, the `self.noflush` context manager can be used to forbid paragraph
flushing temporarily, which means that no block level nodes can be
processed.
- There are no inline comments in ReST. Therefore comments are stored in
`self.comments` and written out every time the paragraph is flushed.
- A similar thing goes for footnotes: `self.footnotes`.
- Some inline markup cannot contain nested markup. Therefore the function
`textonly()` exists which returns a node similar to its argument, but
stripped of inline markup.
- Some constructs need to format non-block-level nodes, but without writing
the result to the current paragraph. These use `self.get_node_text()`
which writes to a temporary paragraph and returns the resulting markup.
- Indentation is important. The `self.indent` context manager helps keeping
track of indentation levels.
- Some blocks, like lists, need to prevent the first line from being
indented because the indentation space is already filled (e.g. by a
bullet). Therefore the `self.indent` context manager accepts a
`firstline` flag which can be set to ``False``, resulting in the first
line not being indented.
There are some restrictions on markup compared to LaTeX:
- Table cells may not contain blocks.
- Hard line breaks don't exist.
- Block level markup inside "alltt" environments doesn't work.
:copyright: 2007 by Georg Brandl.
:license: Python license.
"""
# yay!
from __future__ import with_statement
import re
import StringIO
import textwrap
WIDTH = 80
INDENT = 3
new_wordsep_re = re.compile(
r'(\s+|' # any whitespace
r'(?<=\s)(?::[a-z-]+:)?`\S+|' # interpreted text start
r'[^\s\w]*\w+[a-zA-Z]-(?=\w+[a-zA-Z])|' # hyphenated words
r'(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w))') # em-dash
import textwrap
# monkey-patch...
textwrap.TextWrapper.wordsep_re = new_wordsep_re
wrapper = textwrap.TextWrapper(width=WIDTH, break_long_words=False)
from .docnodes import RootNode, TextNode, NodeList, InlineNode, \
CommentNode, EmptyNode
from .util import fixup_text, empty, text, my_make_id, \
repair_bad_inline_markup
from .filenamemap import includes_mapping
class WriterError(Exception):
pass
class Indenter(object):
""" Context manager factory for indentation. """
def __init__(self, writer):
class IndenterManager(object):
def __init__(self, indentlevel, flush, firstline):
self.indentlevel = indentlevel
self.flush = flush
self.firstline = firstline
def __enter__(self):
writer.indentation += (self.indentlevel * ' ')
writer.indentfirstline = self.firstline
return self
def __exit__(self, *ignored):
if self.flush:
writer.flush_par()
writer.indentation = writer.indentation[:-self.indentlevel]
self.manager = IndenterManager
def __call__(self, indentlevel=INDENT, flush=True, firstline=True):
return self.manager(indentlevel, flush, firstline)
class NoFlush(object):
""" Convenience context manager. """
def __init__(self, writer):
self.writer = writer
def __enter__(self):
self.writer.no_flushing += 1
def __exit__(self, *ignored):
self.writer.no_flushing -= 1
class SectionMeta(object):
def __init__(self):
self.modname = ''
self.platform = ''
self.synopsis = []
self.modauthors = []
self.sectauthors = []
class RestWriter(object):
""" Write ReST from a node tree. """
def __init__(self, fp, splitchap=False, toctree=None, deflang=None, labelprefix=''):
self.splitchap = splitchap # split output at chapters?
if splitchap:
self.fp = StringIO.StringIO() # dummy one
self.chapters = [self.fp]
else:
self.fp = fp # file pointer
self.toctree = toctree # entries for the TOC tree
self.deflang = deflang # default highlighting language
self.labelprefix = labelprefix # prefix for all label names
# indentation tools
self.indentation = '' # current indentation string
self.indentfirstline = True # indent the first line of next paragraph?
self.indented = Indenter(self) # convenience context manager
# paragraph flushing tools
self.flush_cb = None # callback run on next paragraph flush, used
# for properly separating field lists from
# the following paragraph
self.no_flushing = 0 # raise an error on paragraph flush?
self.noflush = NoFlush(self) # convenience context manager
# collected items to output later
self.curpar = [] # text in current paragraph
self.comments = [] # comments to be output after flushing
self.indexentries = [] # indexentries to be output before flushing
self.footnotes = [] # footnotes to be output at document end
self.warnings = [] # warnings while writing
# specials
self.sectionlabel = '' # most recent \label command
self.thisclass = '' # most recent classdesc name
self.sectionmeta = None # current section metadata
self.noescape = 0 # don't escape text nodes
self.indexsubitem = '' # current \withsubitem text
def write_document(self, rootnode):
""" Write a document, represented by a RootNode. """
assert type(rootnode) is RootNode
if self.deflang:
self.write_directive('highlightlang', self.deflang)
self.visit_node(rootnode)
self.write_footnotes()
def new_chapter(self):
""" Called if self.splitchap is True. Create a new file pointer
and set self.fp to it. """
new_fp = StringIO.StringIO()
self.chapters.append(new_fp)
self.fp = new_fp
def write(self, text='', nl=True, first=False):
""" Write a string to the output file. """
if first:
self.fp.write((self.indentation if self.indentfirstline else '') + text)
self.indentfirstline = True
elif text: # don't write indentation only
self.fp.write(self.indentation + text)
if nl:
self.fp.write('\n')
def write_footnotes(self):
""" Write the current footnotes, if any. """
self.flush_par()
if self.footnotes:
self.write('.. rubric:: Footnotes\n')
footnotes = self.footnotes
self.footnotes = [] # first clear since indented() will flush
for footnode in footnotes:
self.write('.. [#] ', nl=False)
with self.indented(3, firstline=False):
self.visit_node(footnode)
def write_directive(self, name, args='', node=None, spabove=False, spbelow=True):
""" Helper to write a ReST directive. """
if spabove:
self.write()
self.write('.. %s::%s' % (name, args and ' '+args))
if spbelow:
self.write()
with self.indented():
if node is not None:
self.visit_node(node)
def write_sectionmeta(self):
mod = self.sectionmeta
self.sectionmeta = None
if not mod:
return
if mod.modname:
self.write('.. module:: %s' % mod.modname)
if mod.platform:
self.write(' :platform: %s' % mod.platform)
if mod.synopsis:
self.write(' :synopsis: %s' % mod.synopsis[0])
for line in mod.synopsis[1:]:
self.write(' %s' % line)
if mod.modauthors:
for author in mod.modauthors:
self.write('.. moduleauthor:: %s' % author)
if mod.sectauthors:
for author in mod.sectauthors:
self.write('.. sectionauthor:: %s' % author)
self.write()
self.write()
indexentry_mapping = {
'index': 'single',
'indexii': 'pair',
'indexiii': 'triple',
'indexiv': 'quadruple',
'stindex': 'statement',
'ttindex': 'single',
'obindex': 'object',
'opindex': 'operator',
'kwindex': 'keyword',
'exindex': 'exception',
'bifuncindex': 'builtin',
'refmodindex': 'module',
'refbimodindex': 'module',
'refexmodindex': 'module',
'refstmodindex': 'module',
}
def get_indexentries(self, entries):
""" Return a list of lines for the index entries. """
def format_entry(cmdname, args, subitem):
textargs = []
for arg in args:
if isinstance(arg, TextNode):
textarg = text(arg)
else:
textarg = self.get_node_text(self.get_textonly_node(arg, warn=0))
if ';' in textarg:
raise WriterError("semicolon in index args: " + textarg)
textarg += subitem
textarg = textarg.replace('!', '; ')
textargs.append(textarg)
return '%s: %s' % (self.indexentry_mapping[cmdname],
'; '.join(textarg for textarg in textargs
if not empty(arg)))
ret = []
if len(entries) == 1:
ret.append('.. index:: %s' % format_entry(*entries[0]))
else:
ret.append('.. index::')
for entry in entries:
ret.append(' %s' % format_entry(*entry))
return ret
def get_par(self, wrap, width=None):
""" Get the contents of the current paragraph.
Returns a list if wrap and not indent, else a string. """
if not self.curpar:
if wrap:
return []
else:
return ''
text = ''.join(self.curpar).lstrip()
text = repair_bad_inline_markup(text)
self.curpar = []
if wrap:
# returns a list!
wrapper.width = width or WIDTH
return wrapper.wrap(text)
else:
return text
no_warn_textonly = set((
'var', 'code', 'textrm', 'emph', 'keyword', 'textit', 'programopt',
'cfunction', 'texttt', 'email', 'constant',
))
def get_textonly_node(self, node, cmd='', warn=1):
""" Return a similar Node or NodeList that only has TextNode subnodes.
Warning values:
- 0: never warn
- 1: warn for markup losing information
"""
if cmd == 'code':
warn = 0
def do(subnode):
if isinstance(subnode, TextNode):
return subnode
if isinstance(subnode, NodeList):
return NodeList(do(subsubnode) for subsubnode in subnode)
if isinstance(subnode, CommentNode):
# loses comments, but huh
return EmptyNode()
if isinstance(subnode, InlineNode):
if subnode.cmdname in ('filevar', 'envvar'):
return NodeList([TextNode('{'), do(subnode.args[0]), TextNode('}')])
2007-07-23 09:02:25 +00:00
if subnode.cmdname == 'optional':
# this is not mapped to ReST markup
return subnode
if len(subnode.args) == 1:
if warn == 1 and subnode.cmdname not in self.no_warn_textonly:
self.warnings.append('%r: Discarding %s markup in %r' %
(cmd, subnode.cmdname, node))
return do(subnode.args[0])
elif len(subnode.args) == 0:
# should only happen for IndexNodes which stay in
return subnode
elif len(subnode.args) == 2 and subnode.cmdname == 'refmodule':
if not warn:
return do(subnode.args[1])
raise WriterError('get_textonly_node() failed for %r' % subnode)
return do(node)
def get_node_text(self, node, wrap=False, width=None):
""" Write the node to a temporary paragraph and return the result
as a string. """
with self.noflush:
self._old_curpar = self.curpar
self.curpar = []
self.visit_node(node)
ret = self.get_par(wrap, width=width)
self.curpar = self._old_curpar
return ret
def flush_par(self, nocb=False, nocomments=False):
""" Write the current paragraph to the output file.
Prepend index entries, append comments and footnotes. """
if self.no_flushing:
raise WriterError('called flush_par() while noflush active')
if self.indexentries:
for line in self.get_indexentries(self.indexentries):
self.write(line)
self.write()
self.indexentries = []
if self.flush_cb and not nocb:
self.flush_cb()
self.flush_cb = None
par = self.get_par(wrap=True)
if par:
for i, line in enumerate(par):
self.write(line, first=(i==0))
self.write()
if self.comments and not nocomments:
for comment in self.comments:
self.write('.. % ' + comment)
self.write()
self.comments = []
def visit_wrapped(self, pre, node, post, noescape=False):
""" Write a node within a paragraph, wrapped with pre and post strings. """
if noescape:
self.noescape += 1
self.curpar.append(pre)
with self.noflush:
self.visit_node(node)
self.curpar.append(post)
if noescape:
self.noescape -= 1
def visit_node(self, node):
""" "Write" a node (appends to curpar or writes something). """
visitfunc = getattr(self, 'visit_' + node.__class__.__name__, None)
if not visitfunc:
raise WriterError('no visit function for %s node' % node.__class__)
visitfunc(node)
# ------------------------- node handlers -----------------------------
def visit_RootNode(self, node):
if node.params.get('title'):
title = self.get_node_text(node.params['title'])
hl = len(title)
self.write('*' * (hl+4))
self.write(' %s ' % title)
self.write('*' * (hl+4))
self.write()
if node.params.get('author'):
self.write(':Author: %s%s' %
(self.get_node_text(node.params['author']),
(' <%s>' % self.get_node_text(node.params['authoremail'])
if 'authoremail' in node.params else '')))
self.write()
if node.params.get('date'):
self.write(':Date: %s' % self.get_node_text(node.params['date']))
self.write()
if node.params.get('release'):
self.write('.. |release| replace:: %s' %
self.get_node_text(node.params['release']))
self.write()
self.visit_NodeList(node.children)
def visit_NodeList(self, nodelist):
for node in nodelist:
self.visit_node(node)
def visit_CommentNode(self, node):
# no inline comments -> they are all output at the start of a new paragraph
self.comments.append(node.comment.strip())
sectchars = {
'chapter': '*',
'chapter*': '*',
'section': '=',
'subsection': '-',
'subsubsection': '^',
'paragraph': '"',
}
sectdoubleline = [
'chapter',
'chapter*',
]
def visit_SectioningNode(self, node):
self.flush_par()
self.sectionlabel = ''
self.thisclass = ''
self.write()
if self.splitchap and node.cmdname.startswith('chapter'):
self.write_footnotes()
self.new_chapter()
heading = self.get_node_text(node.args[0]).strip()
if self.sectionlabel:
self.write('.. _%s:\n' % self.sectionlabel)
hl = len(heading)
if node.cmdname in self.sectdoubleline:
self.write(self.sectchars[node.cmdname] * hl)
self.write(heading)
self.write(self.sectchars[node.cmdname] * hl)
self.write()
def visit_EnvironmentNode(self, node):
self.flush_par()
envname = node.envname
if envname == 'notice':
type = text(node.args[0]) or 'note'
self.write_directive(type, '', node.content)
elif envname in ('seealso', 'seealso*'):
self.write_directive('seealso', '', node.content, spabove=True)
elif envname == 'abstract':
self.write_directive('topic', 'Abstract', node.content, spabove=True)
elif envname == 'quote':
with self.indented():
self.visit_node(node.content)
self.write()
elif envname == 'quotation':
self.write_directive('epigraph', '', node.content, spabove=True)
else:
raise WriterError('no handler for %s environment' % envname)
descmap = {
'funcdesc': ('function', '0(1)'),
'funcdescni': ('function', '0(1)'),
'classdesc': ('class', '0(1)'),
'classdesc*': ('class', '0'),
'methoddesc': ('method', '0.1(2)'),
'methoddescni': ('method', '0.1(2)'),
'excdesc': ('exception', '0'),
'excclassdesc': ('exception', '0(1)'),
'datadesc': ('data', '0'),
'datadescni': ('data', '0'),
'memberdesc': ('attribute', '0.1'),
'memberdescni': ('attribute', '0.1'),
'opcodedesc': ('opcode', '0 (1)'),
'cfuncdesc': ('cfunction', '0 1(2)'),
'cmemberdesc': ('cmember', '1 0.2'),
'csimplemacrodesc': ('cmacro', '0'),
'ctypedesc': ('ctype', '1'),
'cvardesc': ('cvar', '0 1'),
}
def _write_sig(self, spec, args):
# don't escape "*" in signatures
self.noescape += 1
for c in spec:
if c.isdigit():
self.visit_node(self.get_textonly_node(args[int(c)]))
else:
self.curpar.append(c)
self.noescape -= 1
def visit_DescEnvironmentNode(self, node):
envname = node.envname
if envname not in self.descmap:
raise WriterError('no handler for %s environment' % envname)
self.flush_par()
# automatically fill in the class name if not given
if envname[:9] == 'classdesc' or envname[:12] == 'excclassdesc':
self.thisclass = text(node.args[0])
elif envname[:10] in ('methoddesc', 'memberdesc') and not \
text(node.args[0]):
if not self.thisclass:
raise WriterError('No current class for %s member' %
text(node.args[1]))
node.args[0] = TextNode(self.thisclass)
directivename, sigspec = self.descmap[envname]
self._write_sig(sigspec, node.args)
signature = self.get_par(wrap=False)
self.write()
self.write('.. %s:: %s' % (directivename, signature))
if node.additional:
for cmdname, add in node.additional:
entry = self.descmap[cmdname.replace('line', 'desc')]
if envname[:10] in ('methoddesc', 'memberdesc') and not \
text(add[0]):
if not self.thisclass:
raise WriterError('No current class for %s member' %
text(add[1]))
add[0] = TextNode(self.thisclass)
self._write_sig(entry[1], add)
signature = self.get_par(wrap=False)
self.write(' %s%s' % (' ' * (len(directivename) - 2),
signature))
if envname.endswith('ni'):
self.write(' :noindex:')
self.write()
with self.indented():
self.visit_node(node.content)
def visit_CommandNode(self, node):
cmdname = node.cmdname
if cmdname == 'label':
labelname = self.labelprefix + text(node.args[0]).lower()
if self.no_flushing:
# in section
self.sectionlabel = labelname
else:
self.flush_par()
self.write('.. _%s:\n' % labelname)
return
elif cmdname in ('declaremodule', 'modulesynopsis',
'moduleauthor', 'sectionauthor', 'platform'):
self.flush_par(nocb=True, nocomments=True)
if not self.sectionmeta:
self.sectionmeta = SectionMeta()
if cmdname == 'declaremodule':
self.sectionmeta.modname = text(node.args[2])
elif cmdname == 'modulesynopsis':
self.sectionmeta.synopsis = self.get_node_text(
self.get_textonly_node(node.args[0], warn=0), wrap=True)
elif cmdname == 'moduleauthor':
email = text(node.args[1])
self.sectionmeta.modauthors.append(
'%s%s' % (text(node.args[0]), (email and ' <%s>' % email)))
elif cmdname == 'sectionauthor':
email = text(node.args[1])
self.sectionmeta.sectauthors.append(
'%s%s' % (text(node.args[0]), (email and ' <%s>' % email)))
elif cmdname == 'platform':
self.sectionmeta.platform = text(node.args[0])
self.flush_cb = lambda: self.write_sectionmeta()
return
self.flush_par()
if cmdname.startswith('see'):
i = 2
if cmdname == 'seemodule':
self.write('Module :mod:`%s`' % text(node.args[1]))
elif cmdname == 'seelink':
linktext = self.get_node_text(node.args[1])
self.write('`%s <%s>`_' % (linktext, text(node.args[0])))
elif cmdname == 'seepep':
self.write(':pep:`%s` - %s' % (text(node.args[0]),
self.get_node_text(node.args[1])))
elif cmdname == 'seerfc':
self.write(':rfc:`%s` - %s' % (text(node.args[0]),
text(node.args[1])))
elif cmdname == 'seetitle':
if empty(node.args[0]):
self.write('%s' % text(node.args[1]))
else:
self.write('`%s <%s>`_' % (text(node.args[1]),
text(node.args[0])))
elif cmdname == 'seeurl':
i = 1
self.write('%s' % text(node.args[0]))
elif cmdname == 'seetext':
self.visit_node(node.args[0])
return
with self.indented():
self.visit_node(node.args[i])
elif cmdname in ('versionchanged', 'versionadded'):
self.write('.. %s:: %s' % (cmdname, text(node.args[1])))
if not empty(node.args[0]):
with self.indented():
self.visit_node(node.args[0])
self.curpar.append('.')
else:
self.write()
elif cmdname == 'deprecated':
self.write_directive('deprecated', text(node.args[0]), node.args[1],
spbelow=False)
elif cmdname == 'localmoduletable':
if self.toctree is not None:
2007-07-23 09:02:25 +00:00
self.write_directive('toctree', '', spbelow=True, spabove=True)
with self.indented():
for entry in self.toctree:
self.write(entry + '.rst')
else:
self.warnings.append('no toctree given, but \\localmoduletable in file')
elif cmdname == 'verbatiminput':
inclname = text(node.args[0])
newname = includes_mapping.get(inclname, '../includes/' + inclname)
if newname is None:
self.write()
self.write('.. XXX includefile %s' % inclname)
return
self.write()
self.write('.. include:: %s' % newname)
self.write(' :literal:')
self.write()
elif cmdname == 'input':
inclname = text(node.args[0])
newname = includes_mapping.get(inclname, None)
if newname is None:
self.write('X' 'XX: input{%s} :XX' 'X' % inclname)
return
self.write_directive('include', newname, spabove=True)
elif cmdname == 'centerline':
self.write_directive('centered', self.get_node_text(node.args[0]),
spabove=True, spbelow=True)
elif cmdname == 'XX' 'X':
self.visit_wrapped(r'**\*\*** ', node.args[0], ' **\*\***')
else:
raise WriterError('no handler for %s command' % cmdname)
def visit_DescLineCommandNode(self, node):
# these have already been written as arguments of the corresponding
# DescEnvironmentNode
pass
def visit_ParaSepNode(self, node):
self.flush_par()
def visit_VerbatimNode(self, node):
if self.comments:
# these interfer with the literal block
self.flush_par()
if self.curpar:
last = self.curpar[-1].rstrip(' ')
if last.endswith(':'):
self.curpar[-1] = last + ':'
else:
self.curpar.append(' ::')
else:
self.curpar.append('::')
self.flush_par()
with self.indented():
if isinstance(node.content, TextNode):
# verbatim
lines = textwrap.dedent(text(node.content).lstrip('\n')).split('\n')
if not lines:
return
else:
# alltt, possibly with inline formats
lines = self.get_node_text(self.get_textonly_node(
node.content, warn=0)).split('\n') + ['']
# discard leading blank links
while not lines[0].strip():
del lines[0]
for line in lines:
self.write(line)
note_re = re.compile('^\(\d\)$')
def visit_TableNode(self, node):
self.flush_par()
lines = node.lines[:]
lines.insert(0, node.headings)
fmted_rows = []
width = WIDTH - len(self.indentation)
realwidths = [0] * node.numcols
colwidth = (width / node.numcols) + 5
# don't allow paragraphs in table cells for now
with self.noflush:
for line in lines:
cells = []
for i, cell in enumerate(line):
par = self.get_node_text(cell, wrap=True, width=colwidth)
if len(par) == 1 and self.note_re.match(par[0].strip()):
# special case: escape "(1)" to avoid enumeration
par[0] = '\\' + par[0]
maxwidth = max(map(len, par)) if par else 0
realwidths[i] = max(realwidths[i], maxwidth)
cells.append(par)
fmted_rows.append(cells)
def writesep(char='-'):
out = ['+']
for width in realwidths:
out.append(char * (width+2))
out.append('+')
self.write(''.join(out))
def writerow(row):
lines = map(None, *row)
for line in lines:
out = ['|']
for i, cell in enumerate(line):
if cell:
out.append(' ' + cell.ljust(realwidths[i]+1))
else:
out.append(' ' * (realwidths[i] + 2))
out.append('|')
self.write(''.join(out))
writesep('-')
writerow(fmted_rows[0])
writesep('=')
for row in fmted_rows[1:]:
writerow(row)
writesep('-')
self.write()
def visit_ItemizeNode(self, node):
self.flush_par()
for title, content in node.items:
if not empty(title):
# do it like in a description list
self.write(self.get_node_text(title))
with self.indented():
self.visit_node(content)
else:
self.curpar.append('* ')
with self.indented(2, firstline=False):
self.visit_node(content)
def visit_EnumerateNode(self, node):
self.flush_par()
for title, content in node.items:
assert empty(title)
self.curpar.append('#. ')
with self.indented(3, firstline=False):
self.visit_node(content)
def visit_DescriptionNode(self, node):
self.flush_par()
for title, content in node.items:
self.write(self.get_node_text(title))
with self.indented():
self.visit_node(content)
visit_DefinitionsNode = visit_DescriptionNode
def visit_ProductionListNode(self, node):
self.flush_par()
arg = text(node.arg)
self.write('.. productionlist::%s' % (' '+arg if arg else ''))
with self.indented():
for item in node.items:
if not empty(item[0]):
lasttext = text(item[0])
self.write('%s: %s' % (
text(item[0]).ljust(len(lasttext)),
self.get_node_text(item[1])))
self.write()
def visit_EmptyNode(self, node):
pass
def visit_TextNode(self, node):
if self.noescape:
self.curpar.append(node.text)
else:
self.curpar.append(fixup_text(node.text))
visit_NbspNode = visit_TextNode
visit_SimpleCmdNode = visit_TextNode
def visit_BreakNode(self, node):
# XXX: linebreaks in ReST?
self.curpar.append(' --- ')
def visit_IndexNode(self, node):
if node.cmdname == 'withsubitem':
self.indexsubitem = ' ' + text(node.indexargs[0])
self.visit_node(node.indexargs[1])
self.indexsubitem = ''
else:
self.indexentries.append((node.cmdname, node.indexargs,
self.indexsubitem))
# maps argumentless commands to text
simplecmd_mapping = {
'NULL': '*NULL*',
2007-07-23 09:02:25 +00:00
'shortversion': '|version|',
'version': '|release|',
'today': '|today|',
}
# map LaTeX command names to roles: shorter names!
role_mapping = {
'cfunction': 'cfunc',
'constant': 'const',
'csimplemacro': 'cmacro',
'exception': 'exc',
'function': 'func',
'grammartoken': 'token',
'member': 'attr',
'method': 'meth',
'module': 'mod',
'programopt': 'option',
'filenq': 'file',
2007-07-23 09:02:25 +00:00
# these mean: no change
'cdata': '',
'class': '',
'command': '',
'ctype': '',
'data': '', # NEW
'dfn': '',
'envvar': '',
'file': '',
'guilabel': '',
'kbd': '',
'keyword': '',
'mailheader': '',
'makevar': '',
'menuselection': '',
'mimetype': '',
'newsgroup': '',
'option': '',
'pep': '',
'program': '',
'ref': '',
'rfc': '',
}
# do not warn about nested inline markup in these roles
role_no_warn = set((
'cdata', 'cfunction', 'class', 'constant', 'csimplemacro', 'ctype',
'data', 'exception', 'function', 'member', 'method', 'module',
))
def visit_InlineNode(self, node):
# XXX: no nested markup -- docutils doesn't support it
cmdname = node.cmdname
if not node.args:
self.curpar.append(self.simplecmd_mapping[cmdname])
return
content = node.args[0]
if cmdname in ('code', 'bfcode', 'samp', 'texttt', 'regexp'):
self.visit_wrapped('``', self.get_textonly_node(content, 'code',
warn=1), '``', noescape=True)
elif cmdname in ('emph', 'textit', 'var'):
2007-07-23 09:02:25 +00:00
self.visit_wrapped('*', self.get_textonly_node(content, 'emph',
warn=1), '*')
elif cmdname in ('strong', 'textbf'):
self.visit_wrapped('**', self.get_textonly_node(content, 'strong',
warn=1), '**')
elif cmdname in ('b', 'textrm', 'email'):
self.visit_node(content)
elif cmdname == 'token':
2007-07-23 09:02:25 +00:00
# \token appears in productionlists only
self.visit_wrapped('`', self.get_textonly_node(content, 'var',
warn=1), '`')
elif cmdname == 'ref':
self.curpar.append(':ref:`%s%s`' % (self.labelprefix,
text(node.args[0]).lower()))
elif cmdname == 'refmodule':
self.visit_wrapped(':mod:`', node.args[1], '`', noescape=True)
elif cmdname == 'optional':
self.visit_wrapped('[', content, ']')
elif cmdname == 'url':
self.visit_node(content)
elif cmdname == 'ulink':
target = text(node.args[1])
if target.startswith('..'):
self.visit_wrapped('', content, ' (X' + 'XX reference: %s)' % target)
elif not target.startswith(('http:', 'mailto:')):
#self.warnings.append('Local \\ulink to %s, use \\ref instead' % target)
self.visit_wrapped('', content, ' (X' 'XX reference: %s)' % target)
else:
self.visit_wrapped('`', self.get_textonly_node(content, 'ulink', warn=1),
' <%s>`_' % target)
elif cmdname == 'citetitle':
target = text(content)
if not target:
self.visit_node(node.args[1])
elif target.startswith('..'):
self.visit_wrapped('', node.args[1],
' (X' + 'XX reference: %s)' % target)
else:
self.visit_wrapped('`', self.get_textonly_node(node.args[1],
'citetitle', warn=1),
' <%s>`_' % target)
elif cmdname == 'character':
# ``'a'`` is not longer than :character:`a`
self.visit_wrapped("``'", content, "'``", noescape=True)
elif cmdname == 'manpage':
self.curpar.append(':manpage:`')
self.visit_node(self.get_textonly_node(content, warn=0))
self.visit_wrapped('(', self.get_textonly_node(node.args[1], warn=0), ')')
self.curpar.append('`')
elif cmdname == 'footnote':
self.curpar.append(' [#]_')
self.footnotes.append(content)
elif cmdname == 'frac':
self.visit_wrapped('(', node.args[0], ')/')
self.visit_wrapped('(', node.args[1], ')')
elif cmdname == 'longprogramopt':
self.visit_wrapped(':option:`--', content, '`')
elif cmdname == 'filevar':
self.visit_wrapped(':file:`{', content, '}`')
2007-07-23 09:02:25 +00:00
elif cmdname == '':
self.visit_node(content)
# stray commands from distutils
elif cmdname in ('argument name', 'value', 'attribute', 'option name'):
self.visit_wrapped('*', content, '*')
2007-07-23 09:02:25 +00:00
else:
self.visit_wrapped(':%s:`' % (self.role_mapping[cmdname] or cmdname),
self.get_textonly_node(
content, cmdname, warn=(cmdname not in self.role_no_warn)), '`')