mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
318 lines
12 KiB
Python
318 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
sphinx.roles
|
|
~~~~~~~~~~~~
|
|
|
|
Handlers for additional ReST roles.
|
|
|
|
:copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
|
|
:license: BSD, see LICENSE for details.
|
|
"""
|
|
|
|
import re
|
|
|
|
from docutils import nodes, utils
|
|
from docutils.parsers.rst import roles
|
|
|
|
from sphinx import addnodes
|
|
from sphinx.locale import _
|
|
from sphinx.util import ws_re
|
|
from sphinx.util.nodes import split_explicit_title, process_index_entry, \
|
|
set_role_source_info
|
|
|
|
|
|
generic_docroles = {
|
|
'command' : addnodes.literal_strong,
|
|
'dfn' : nodes.emphasis,
|
|
'kbd' : nodes.literal,
|
|
'mailheader' : addnodes.literal_emphasis,
|
|
'makevar' : addnodes.literal_strong,
|
|
'manpage' : addnodes.literal_emphasis,
|
|
'mimetype' : addnodes.literal_emphasis,
|
|
'newsgroup' : addnodes.literal_emphasis,
|
|
'program' : addnodes.literal_strong, # XXX should be an x-ref
|
|
'regexp' : nodes.literal,
|
|
}
|
|
|
|
for rolename, nodeclass in generic_docroles.iteritems():
|
|
generic = roles.GenericRole(rolename, nodeclass)
|
|
role = roles.CustomRole(rolename, generic, {'classes': [rolename]})
|
|
roles.register_local_role(rolename, role)
|
|
|
|
# -- generic cross-reference role ----------------------------------------------
|
|
|
|
class XRefRole(object):
|
|
"""
|
|
A generic cross-referencing role. To create a callable that can be used as
|
|
a role function, create an instance of this class.
|
|
|
|
The general features of this role are:
|
|
|
|
* Automatic creation of a reference and a content node.
|
|
* Optional separation of title and target with `title <target>`.
|
|
* The implementation is a class rather than a function to make
|
|
customization easier.
|
|
|
|
Customization can be done in two ways:
|
|
|
|
* Supplying constructor parameters:
|
|
* `fix_parens` to normalize parentheses (strip from target, and add to
|
|
title if configured)
|
|
* `lowercase` to lowercase the target
|
|
* `nodeclass` and `innernodeclass` select the node classes for
|
|
the reference and the content node
|
|
|
|
* Subclassing and overwriting `process_link()` and/or `result_nodes()`.
|
|
"""
|
|
|
|
nodeclass = addnodes.pending_xref
|
|
innernodeclass = nodes.literal
|
|
|
|
def __init__(self, fix_parens=False, lowercase=False,
|
|
nodeclass=None, innernodeclass=None, warn_dangling=False):
|
|
self.fix_parens = fix_parens
|
|
self.lowercase = lowercase
|
|
self.warn_dangling = warn_dangling
|
|
if nodeclass is not None:
|
|
self.nodeclass = nodeclass
|
|
if innernodeclass is not None:
|
|
self.innernodeclass = innernodeclass
|
|
|
|
def _fix_parens(self, env, has_explicit_title, title, target):
|
|
if not has_explicit_title:
|
|
if title.endswith('()'):
|
|
# remove parentheses
|
|
title = title[:-2]
|
|
if env.config.add_function_parentheses:
|
|
# add them back to all occurrences if configured
|
|
title += '()'
|
|
# remove parentheses from the target too
|
|
if target.endswith('()'):
|
|
target = target[:-2]
|
|
return title, target
|
|
|
|
def __call__(self, typ, rawtext, text, lineno, inliner,
|
|
options={}, content=[]):
|
|
env = inliner.document.settings.env
|
|
if not typ:
|
|
typ = env.config.default_role
|
|
else:
|
|
typ = typ.lower()
|
|
if ':' not in typ:
|
|
domain, role = '', typ
|
|
classes = ['xref', role]
|
|
else:
|
|
domain, role = typ.split(':', 1)
|
|
classes = ['xref', domain, '%s-%s' % (domain, role)]
|
|
# if the first character is a bang, don't cross-reference at all
|
|
if text[0:1] == '!':
|
|
text = utils.unescape(text)[1:]
|
|
if self.fix_parens:
|
|
text, tgt = self._fix_parens(env, False, text, "")
|
|
innernode = self.innernodeclass(rawtext, text, classes=classes)
|
|
return self.result_nodes(inliner.document, env, innernode,
|
|
is_ref=False)
|
|
# split title and target in role content
|
|
has_explicit_title, title, target = split_explicit_title(text)
|
|
title = utils.unescape(title)
|
|
target = utils.unescape(target)
|
|
# fix-up title and target
|
|
if self.lowercase:
|
|
target = target.lower()
|
|
if self.fix_parens:
|
|
title, target = self._fix_parens(
|
|
env, has_explicit_title, title, target)
|
|
# create the reference node
|
|
refnode = self.nodeclass(rawtext, reftype=role, refdomain=domain,
|
|
refexplicit=has_explicit_title)
|
|
# we may need the line number for warnings
|
|
set_role_source_info(inliner, lineno, refnode)
|
|
title, target = self.process_link(
|
|
env, refnode, has_explicit_title, title, target)
|
|
# now that the target and title are finally determined, set them
|
|
refnode['reftarget'] = target
|
|
refnode += self.innernodeclass(rawtext, title, classes=classes)
|
|
# we also need the source document
|
|
refnode['refdoc'] = env.docname
|
|
refnode['refwarn'] = self.warn_dangling
|
|
# result_nodes allow further modification of return values
|
|
return self.result_nodes(inliner.document, env, refnode, is_ref=True)
|
|
|
|
# methods that can be overwritten
|
|
|
|
def process_link(self, env, refnode, has_explicit_title, title, target):
|
|
"""Called after parsing title and target text, and creating the
|
|
reference node (given in *refnode*). This method can alter the
|
|
reference node and must return a new (or the same) ``(title, target)``
|
|
tuple.
|
|
"""
|
|
return title, ws_re.sub(' ', target)
|
|
|
|
def result_nodes(self, document, env, node, is_ref):
|
|
"""Called before returning the finished nodes. *node* is the reference
|
|
node if one was created (*is_ref* is then true), else the content node.
|
|
This method can add other nodes and must return a ``(nodes, messages)``
|
|
tuple (the usual return value of a role function).
|
|
"""
|
|
return [node], []
|
|
|
|
|
|
def indexmarkup_role(typ, rawtext, etext, lineno, inliner,
|
|
options={}, content=[]):
|
|
"""Role for PEP/RFC references that generate an index entry."""
|
|
env = inliner.document.settings.env
|
|
if not typ:
|
|
typ = env.config.default_role
|
|
else:
|
|
typ = typ.lower()
|
|
text = utils.unescape(etext)
|
|
targetid = 'index-%s' % env.new_serialno('index')
|
|
indexnode = addnodes.index()
|
|
targetnode = nodes.target('', '', ids=[targetid])
|
|
inliner.document.note_explicit_target(targetnode)
|
|
if typ == 'pep':
|
|
indexnode['entries'] = [
|
|
('single', _('Python Enhancement Proposals; PEP %s') % text,
|
|
targetid, '')]
|
|
anchor = ''
|
|
anchorindex = text.find('#')
|
|
if anchorindex > 0:
|
|
text, anchor = text[:anchorindex], text[anchorindex:]
|
|
try:
|
|
pepnum = int(text)
|
|
except ValueError:
|
|
msg = inliner.reporter.error('invalid PEP number %s' % text,
|
|
line=lineno)
|
|
prb = inliner.problematic(rawtext, rawtext, msg)
|
|
return [prb], [msg]
|
|
ref = inliner.document.settings.pep_base_url + 'pep-%04d' % pepnum
|
|
sn = nodes.strong('PEP '+text, 'PEP '+text)
|
|
rn = nodes.reference('', '', internal=False, refuri=ref+anchor,
|
|
classes=[typ])
|
|
rn += sn
|
|
return [indexnode, targetnode, rn], []
|
|
elif typ == 'rfc':
|
|
indexnode['entries'] = [('single', 'RFC; RFC %s' % text, targetid, '')]
|
|
anchor = ''
|
|
anchorindex = text.find('#')
|
|
if anchorindex > 0:
|
|
text, anchor = text[:anchorindex], text[anchorindex:]
|
|
try:
|
|
rfcnum = int(text)
|
|
except ValueError:
|
|
msg = inliner.reporter.error('invalid RFC number %s' % text,
|
|
line=lineno)
|
|
prb = inliner.problematic(rawtext, rawtext, msg)
|
|
return [prb], [msg]
|
|
ref = inliner.document.settings.rfc_base_url + inliner.rfc_url % rfcnum
|
|
sn = nodes.strong('RFC '+text, 'RFC '+text)
|
|
rn = nodes.reference('', '', internal=False, refuri=ref+anchor,
|
|
classes=[typ])
|
|
rn += sn
|
|
return [indexnode, targetnode, rn], []
|
|
|
|
|
|
_amp_re = re.compile(r'(?<!&)&(?![&\s])')
|
|
|
|
def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
|
|
text = utils.unescape(text)
|
|
if typ == 'menuselection':
|
|
text = text.replace('-->', u'\N{TRIANGULAR BULLET}')
|
|
spans = _amp_re.split(text)
|
|
|
|
node = nodes.emphasis(rawtext=rawtext)
|
|
for i, span in enumerate(spans):
|
|
span = span.replace('&&', '&')
|
|
if i == 0:
|
|
if len(span) > 0:
|
|
textnode = nodes.Text(span)
|
|
node += textnode
|
|
continue
|
|
accel_node = nodes.inline()
|
|
letter_node = nodes.Text(span[0])
|
|
accel_node += letter_node
|
|
accel_node['classes'].append('accelerator')
|
|
node += accel_node
|
|
textnode = nodes.Text(span[1:])
|
|
node += textnode
|
|
|
|
node['classes'].append(typ)
|
|
return [node], []
|
|
|
|
_litvar_re = re.compile('{([^}]+)}')
|
|
|
|
def emph_literal_role(typ, rawtext, text, lineno, inliner,
|
|
options={}, content=[]):
|
|
text = utils.unescape(text)
|
|
pos = 0
|
|
retnode = nodes.literal(role=typ.lower(), classes=[typ])
|
|
for m in _litvar_re.finditer(text):
|
|
if m.start() > pos:
|
|
txt = text[pos:m.start()]
|
|
retnode += nodes.Text(txt, txt)
|
|
retnode += nodes.emphasis(m.group(1), m.group(1))
|
|
pos = m.end()
|
|
if pos < len(text):
|
|
retnode += nodes.Text(text[pos:], text[pos:])
|
|
return [retnode], []
|
|
|
|
|
|
_abbr_re = re.compile('\((.*)\)$', re.S)
|
|
|
|
def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
|
|
text = utils.unescape(text)
|
|
m = _abbr_re.search(text)
|
|
if m is None:
|
|
return [addnodes.abbreviation(text, text)], []
|
|
abbr = text[:m.start()].strip()
|
|
expl = m.group(1)
|
|
return [addnodes.abbreviation(abbr, abbr, explanation=expl)], []
|
|
|
|
|
|
def index_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
|
|
# create new reference target
|
|
env = inliner.document.settings.env
|
|
targetid = 'index-%s' % env.new_serialno('index')
|
|
targetnode = nodes.target('', '', ids=[targetid])
|
|
# split text and target in role content
|
|
has_explicit_title, title, target = split_explicit_title(text)
|
|
title = utils.unescape(title)
|
|
target = utils.unescape(target)
|
|
# if an explicit target is given, we can process it as a full entry
|
|
if has_explicit_title:
|
|
entries = process_index_entry(target, targetid)
|
|
# otherwise we just create a "single" entry
|
|
else:
|
|
# but allow giving main entry
|
|
main = ''
|
|
if target.startswith('!'):
|
|
target = target[1:]
|
|
title = title[1:]
|
|
main = 'main'
|
|
entries = [('single', target, targetid, main)]
|
|
indexnode = addnodes.index()
|
|
indexnode['entries'] = entries
|
|
set_role_source_info(inliner, lineno, indexnode)
|
|
textnode = nodes.Text(title, title)
|
|
return [indexnode, targetnode, textnode], []
|
|
|
|
|
|
specific_docroles = {
|
|
# links to download references
|
|
'download': XRefRole(nodeclass=addnodes.download_reference),
|
|
# links to documents
|
|
'doc': XRefRole(warn_dangling=True),
|
|
|
|
'pep': indexmarkup_role,
|
|
'rfc': indexmarkup_role,
|
|
'guilabel': menusel_role,
|
|
'menuselection': menusel_role,
|
|
'file': emph_literal_role,
|
|
'samp': emph_literal_role,
|
|
'abbr': abbr_role,
|
|
'index': index_role,
|
|
}
|
|
|
|
for rolename, func in specific_docroles.iteritems():
|
|
roles.register_local_role(rolename, func)
|