You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
631 lines
24 KiB
631 lines
24 KiB
4 years ago
|
"""
|
||
|
sphinx.roles
|
||
|
~~~~~~~~~~~~
|
||
|
|
||
|
Handlers for additional ReST roles.
|
||
|
|
||
|
:copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
|
||
|
:license: BSD, see LICENSE for details.
|
||
|
"""
|
||
|
|
||
|
import re
|
||
|
import warnings
|
||
|
from typing import Any, Dict, List, Tuple
|
||
|
|
||
|
from docutils import nodes, utils
|
||
|
from docutils.nodes import Element, Node, TextElement, system_message
|
||
|
from docutils.parsers.rst.states import Inliner
|
||
|
|
||
|
from sphinx import addnodes
|
||
|
from sphinx.deprecation import RemovedInSphinx40Warning
|
||
|
from sphinx.locale import _
|
||
|
from sphinx.util import ws_re
|
||
|
from sphinx.util.docutils import ReferenceRole, SphinxRole
|
||
|
from sphinx.util.nodes import process_index_entry, set_role_source_info, split_explicit_title
|
||
|
from sphinx.util.typing import RoleFunction
|
||
|
|
||
|
if False:
|
||
|
# For type annotation
|
||
|
from typing import Type # for python3.5.1
|
||
|
|
||
|
from sphinx.application import Sphinx
|
||
|
from sphinx.environment import BuildEnvironment
|
||
|
|
||
|
|
||
|
generic_docroles = {
|
||
|
'command': addnodes.literal_strong,
|
||
|
'dfn': nodes.emphasis,
|
||
|
'kbd': nodes.literal,
|
||
|
'mailheader': addnodes.literal_emphasis,
|
||
|
'makevar': addnodes.literal_strong,
|
||
|
'manpage': addnodes.manpage,
|
||
|
'mimetype': addnodes.literal_emphasis,
|
||
|
'newsgroup': addnodes.literal_emphasis,
|
||
|
'program': addnodes.literal_strong, # XXX should be an x-ref
|
||
|
'regexp': nodes.literal,
|
||
|
}
|
||
|
|
||
|
|
||
|
# -- generic cross-reference role ----------------------------------------------
|
||
|
|
||
|
class XRefRole(ReferenceRole):
|
||
|
"""
|
||
|
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 # type: Type[Element]
|
||
|
innernodeclass = nodes.literal # type: Type[TextElement]
|
||
|
|
||
|
def __init__(self, fix_parens: bool = False, lowercase: bool = False,
|
||
|
nodeclass: "Type[Element]" = None, innernodeclass: "Type[TextElement]" = None,
|
||
|
warn_dangling: bool = False) -> None:
|
||
|
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
|
||
|
|
||
|
super().__init__()
|
||
|
|
||
|
def _fix_parens(self, env: "BuildEnvironment", has_explicit_title: bool, title: str,
|
||
|
target: str) -> Tuple[str, str]:
|
||
|
warnings.warn('XRefRole._fix_parens() is deprecated.',
|
||
|
RemovedInSphinx40Warning, stacklevel=2)
|
||
|
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 update_title_and_target(self, title: str, target: str) -> Tuple[str, str]:
|
||
|
if not self.has_explicit_title:
|
||
|
if title.endswith('()'):
|
||
|
# remove parentheses
|
||
|
title = title[:-2]
|
||
|
if self.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 run(self) -> Tuple[List[Node], List[system_message]]:
|
||
|
if ':' not in self.name:
|
||
|
self.refdomain, self.reftype = '', self.name
|
||
|
self.classes = ['xref', self.reftype]
|
||
|
else:
|
||
|
self.refdomain, self.reftype = self.name.split(':', 1)
|
||
|
self.classes = ['xref', self.refdomain, '%s-%s' % (self.refdomain, self.reftype)]
|
||
|
|
||
|
if self.disabled:
|
||
|
return self.create_non_xref_node()
|
||
|
else:
|
||
|
return self.create_xref_node()
|
||
|
|
||
|
def create_non_xref_node(self) -> Tuple[List[Node], List[system_message]]:
|
||
|
text = utils.unescape(self.text[1:])
|
||
|
if self.fix_parens:
|
||
|
self.has_explicit_title = False # treat as implicit
|
||
|
text, target = self.update_title_and_target(text, "")
|
||
|
|
||
|
node = self.innernodeclass(self.rawtext, text, classes=self.classes)
|
||
|
return self.result_nodes(self.inliner.document, self.env, node, is_ref=False)
|
||
|
|
||
|
def create_xref_node(self) -> Tuple[List[Node], List[system_message]]:
|
||
|
target = self.target
|
||
|
title = self.title
|
||
|
if self.lowercase:
|
||
|
target = target.lower()
|
||
|
if self.fix_parens:
|
||
|
title, target = self.update_title_and_target(title, target)
|
||
|
|
||
|
# create the reference node
|
||
|
options = {'refdoc': self.env.docname,
|
||
|
'refdomain': self.refdomain,
|
||
|
'reftype': self.reftype,
|
||
|
'refexplicit': self.has_explicit_title,
|
||
|
'refwarn': self.warn_dangling}
|
||
|
refnode = self.nodeclass(self.rawtext, **options)
|
||
|
self.set_source_info(refnode)
|
||
|
|
||
|
# determine the target and title for the class
|
||
|
title, target = self.process_link(self.env, refnode, self.has_explicit_title,
|
||
|
title, target)
|
||
|
refnode['reftarget'] = target
|
||
|
refnode += self.innernodeclass(self.rawtext, title, classes=self.classes)
|
||
|
|
||
|
return self.result_nodes(self.inliner.document, self.env, refnode, is_ref=True)
|
||
|
|
||
|
# methods that can be overwritten
|
||
|
|
||
|
def process_link(self, env: "BuildEnvironment", refnode: Element, has_explicit_title: bool,
|
||
|
title: str, target: str) -> Tuple[str, str]:
|
||
|
"""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: nodes.document, env: "BuildEnvironment", node: Element,
|
||
|
is_ref: bool) -> Tuple[List[Node], List[system_message]]:
|
||
|
"""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], []
|
||
|
|
||
|
|
||
|
class AnyXRefRole(XRefRole):
|
||
|
def process_link(self, env: "BuildEnvironment", refnode: Element, has_explicit_title: bool,
|
||
|
title: str, target: str) -> Tuple[str, str]:
|
||
|
result = super().process_link(env, refnode, has_explicit_title, title, target)
|
||
|
# add all possible context info (i.e. std:program, py:module etc.)
|
||
|
refnode.attributes.update(env.ref_context)
|
||
|
return result
|
||
|
|
||
|
|
||
|
def indexmarkup_role(typ: str, rawtext: str, text: str, lineno: int, inliner: Inliner,
|
||
|
options: Dict = {}, content: List[str] = []
|
||
|
) -> Tuple[List[Node], List[system_message]]:
|
||
|
"""Role for PEP/RFC references that generate an index entry."""
|
||
|
warnings.warn('indexmarkup_role() is deprecated. Please use PEP or RFC class instead.',
|
||
|
RemovedInSphinx40Warning, stacklevel=2)
|
||
|
env = inliner.document.settings.env
|
||
|
if not typ:
|
||
|
assert env.temp_data['default_role']
|
||
|
typ = env.temp_data['default_role'].lower()
|
||
|
else:
|
||
|
typ = typ.lower()
|
||
|
|
||
|
has_explicit_title, title, target = split_explicit_title(text)
|
||
|
title = utils.unescape(title)
|
||
|
target = utils.unescape(target)
|
||
|
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') % target,
|
||
|
targetid, '', None)]
|
||
|
anchor = ''
|
||
|
anchorindex = target.find('#')
|
||
|
if anchorindex > 0:
|
||
|
target, anchor = target[:anchorindex], target[anchorindex:]
|
||
|
if not has_explicit_title:
|
||
|
title = "PEP " + utils.unescape(title)
|
||
|
try:
|
||
|
pepnum = int(target)
|
||
|
except ValueError:
|
||
|
msg = inliner.reporter.error('invalid PEP number %s' % target,
|
||
|
line=lineno)
|
||
|
prb = inliner.problematic(rawtext, rawtext, msg)
|
||
|
return [prb], [msg]
|
||
|
ref = inliner.document.settings.pep_base_url + 'pep-%04d' % pepnum
|
||
|
sn = nodes.strong(title, title)
|
||
|
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' % target, targetid, '', None)]
|
||
|
anchor = ''
|
||
|
anchorindex = target.find('#')
|
||
|
if anchorindex > 0:
|
||
|
target, anchor = target[:anchorindex], target[anchorindex:]
|
||
|
if not has_explicit_title:
|
||
|
title = "RFC " + utils.unescape(title)
|
||
|
try:
|
||
|
rfcnum = int(target)
|
||
|
except ValueError:
|
||
|
msg = inliner.reporter.error('invalid RFC number %s' % target,
|
||
|
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(title, title)
|
||
|
rn = nodes.reference('', '', internal=False, refuri=ref + anchor,
|
||
|
classes=[typ])
|
||
|
rn += sn
|
||
|
return [indexnode, targetnode, rn], []
|
||
|
else:
|
||
|
raise ValueError('unknown role type: %s' % typ)
|
||
|
|
||
|
|
||
|
class PEP(ReferenceRole):
|
||
|
def run(self) -> Tuple[List[Node], List[system_message]]:
|
||
|
target_id = 'index-%s' % self.env.new_serialno('index')
|
||
|
entries = [('single', _('Python Enhancement Proposals; PEP %s') % self.target,
|
||
|
target_id, '', None)]
|
||
|
|
||
|
index = addnodes.index(entries=entries)
|
||
|
target = nodes.target('', '', ids=[target_id])
|
||
|
self.inliner.document.note_explicit_target(target)
|
||
|
|
||
|
try:
|
||
|
refuri = self.build_uri()
|
||
|
reference = nodes.reference('', '', internal=False, refuri=refuri, classes=['pep'])
|
||
|
if self.has_explicit_title:
|
||
|
reference += nodes.strong(self.title, self.title)
|
||
|
else:
|
||
|
title = "PEP " + self.title
|
||
|
reference += nodes.strong(title, title)
|
||
|
except ValueError:
|
||
|
msg = self.inliner.reporter.error('invalid PEP number %s' % self.target,
|
||
|
line=self.lineno)
|
||
|
prb = self.inliner.problematic(self.rawtext, self.rawtext, msg)
|
||
|
return [prb], [msg]
|
||
|
|
||
|
return [index, target, reference], []
|
||
|
|
||
|
def build_uri(self) -> str:
|
||
|
base_url = self.inliner.document.settings.pep_base_url
|
||
|
ret = self.target.split('#', 1)
|
||
|
if len(ret) == 2:
|
||
|
return base_url + 'pep-%04d#%s' % (int(ret[0]), ret[1])
|
||
|
else:
|
||
|
return base_url + 'pep-%04d' % int(ret[0])
|
||
|
|
||
|
|
||
|
class RFC(ReferenceRole):
|
||
|
def run(self) -> Tuple[List[Node], List[system_message]]:
|
||
|
target_id = 'index-%s' % self.env.new_serialno('index')
|
||
|
entries = [('single', 'RFC; RFC %s' % self.target, target_id, '', None)]
|
||
|
|
||
|
index = addnodes.index(entries=entries)
|
||
|
target = nodes.target('', '', ids=[target_id])
|
||
|
self.inliner.document.note_explicit_target(target)
|
||
|
|
||
|
try:
|
||
|
refuri = self.build_uri()
|
||
|
reference = nodes.reference('', '', internal=False, refuri=refuri, classes=['rfc'])
|
||
|
if self.has_explicit_title:
|
||
|
reference += nodes.strong(self.title, self.title)
|
||
|
else:
|
||
|
title = "RFC " + self.title
|
||
|
reference += nodes.strong(title, title)
|
||
|
except ValueError:
|
||
|
msg = self.inliner.reporter.error('invalid RFC number %s' % self.target,
|
||
|
line=self.lineno)
|
||
|
prb = self.inliner.problematic(self.rawtext, self.rawtext, msg)
|
||
|
return [prb], [msg]
|
||
|
|
||
|
return [index, target, reference], []
|
||
|
|
||
|
def build_uri(self) -> str:
|
||
|
base_url = self.inliner.document.settings.rfc_base_url
|
||
|
ret = self.target.split('#', 1)
|
||
|
if len(ret) == 2:
|
||
|
return base_url + self.inliner.rfc_url % int(ret[0]) + '#' + ret[1]
|
||
|
else:
|
||
|
return base_url + self.inliner.rfc_url % int(ret[0])
|
||
|
|
||
|
|
||
|
_amp_re = re.compile(r'(?<!&)&(?![&\s])')
|
||
|
|
||
|
|
||
|
def menusel_role(typ: str, rawtext: str, text: str, lineno: int, inliner: Inliner,
|
||
|
options: Dict = {}, content: List[str] = []
|
||
|
) -> Tuple[List[Node], List[system_message]]:
|
||
|
warnings.warn('menusel_role() is deprecated. '
|
||
|
'Please use MenuSelection or GUILabel class instead.',
|
||
|
RemovedInSphinx40Warning, stacklevel=2)
|
||
|
env = inliner.document.settings.env
|
||
|
if not typ:
|
||
|
assert env.temp_data['default_role']
|
||
|
typ = env.temp_data['default_role'].lower()
|
||
|
else:
|
||
|
typ = typ.lower()
|
||
|
|
||
|
text = utils.unescape(text)
|
||
|
if typ == 'menuselection':
|
||
|
text = text.replace('-->', '\N{TRIANGULAR BULLET}')
|
||
|
spans = _amp_re.split(text)
|
||
|
|
||
|
node = nodes.inline(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], []
|
||
|
|
||
|
|
||
|
class GUILabel(SphinxRole):
|
||
|
amp_re = re.compile(r'(?<!&)&(?![&\s])')
|
||
|
|
||
|
def run(self) -> Tuple[List[Node], List[system_message]]:
|
||
|
node = nodes.inline(rawtext=self.rawtext, classes=[self.name])
|
||
|
spans = self.amp_re.split(self.text)
|
||
|
node += nodes.Text(spans.pop(0))
|
||
|
for span in spans:
|
||
|
span = span.replace('&&', '&')
|
||
|
|
||
|
letter = nodes.Text(span[0])
|
||
|
accelerator = nodes.inline('', '', letter, classes=['accelerator'])
|
||
|
node += accelerator
|
||
|
node += nodes.Text(span[1:])
|
||
|
|
||
|
return [node], []
|
||
|
|
||
|
|
||
|
class MenuSelection(GUILabel):
|
||
|
BULLET_CHARACTER = '\N{TRIANGULAR BULLET}'
|
||
|
|
||
|
def run(self) -> Tuple[List[Node], List[system_message]]:
|
||
|
self.text = self.text.replace('-->', self.BULLET_CHARACTER)
|
||
|
return super().run()
|
||
|
|
||
|
|
||
|
_litvar_re = re.compile('{([^}]+)}')
|
||
|
parens_re = re.compile(r'(\\*{|\\*})')
|
||
|
|
||
|
|
||
|
def emph_literal_role(typ: str, rawtext: str, text: str, lineno: int, inliner: Inliner,
|
||
|
options: Dict = {}, content: List[str] = []
|
||
|
) -> Tuple[List[Node], List[system_message]]:
|
||
|
warnings.warn('emph_literal_role() is deprecated. '
|
||
|
'Please use EmphasizedLiteral class instead.',
|
||
|
RemovedInSphinx40Warning, stacklevel=2)
|
||
|
env = inliner.document.settings.env
|
||
|
if not typ:
|
||
|
assert env.temp_data['default_role']
|
||
|
typ = env.temp_data['default_role'].lower()
|
||
|
else:
|
||
|
typ = typ.lower()
|
||
|
|
||
|
retnode = nodes.literal(role=typ.lower(), classes=[typ])
|
||
|
parts = list(parens_re.split(utils.unescape(text)))
|
||
|
stack = ['']
|
||
|
for part in parts:
|
||
|
matched = parens_re.match(part)
|
||
|
if matched:
|
||
|
backslashes = len(part) - 1
|
||
|
if backslashes % 2 == 1: # escaped
|
||
|
stack[-1] += "\\" * int((backslashes - 1) / 2) + part[-1]
|
||
|
elif part[-1] == '{': # rparen
|
||
|
stack[-1] += "\\" * int(backslashes / 2)
|
||
|
if len(stack) >= 2 and stack[-2] == "{":
|
||
|
# nested
|
||
|
stack[-1] += "{"
|
||
|
else:
|
||
|
# start emphasis
|
||
|
stack.append('{')
|
||
|
stack.append('')
|
||
|
else: # lparen
|
||
|
stack[-1] += "\\" * int(backslashes / 2)
|
||
|
if len(stack) == 3 and stack[1] == "{" and len(stack[2]) > 0:
|
||
|
# emphasized word found
|
||
|
if stack[0]:
|
||
|
retnode += nodes.Text(stack[0], stack[0])
|
||
|
retnode += nodes.emphasis(stack[2], stack[2])
|
||
|
stack = ['']
|
||
|
else:
|
||
|
# emphasized word not found; the rparen is not a special symbol
|
||
|
stack.append('}')
|
||
|
stack = [''.join(stack)]
|
||
|
else:
|
||
|
stack[-1] += part
|
||
|
if ''.join(stack):
|
||
|
# remaining is treated as Text
|
||
|
text = ''.join(stack)
|
||
|
retnode += nodes.Text(text, text)
|
||
|
|
||
|
return [retnode], []
|
||
|
|
||
|
|
||
|
class EmphasizedLiteral(SphinxRole):
|
||
|
parens_re = re.compile(r'(\\\\|\\{|\\}|{|})')
|
||
|
|
||
|
def run(self) -> Tuple[List[Node], List[system_message]]:
|
||
|
children = self.parse(self.text)
|
||
|
node = nodes.literal(self.rawtext, '', *children,
|
||
|
role=self.name.lower(), classes=[self.name])
|
||
|
|
||
|
return [node], []
|
||
|
|
||
|
def parse(self, text: str) -> List[Node]:
|
||
|
result = [] # type: List[Node]
|
||
|
|
||
|
stack = ['']
|
||
|
for part in self.parens_re.split(text):
|
||
|
if part == '\\\\': # escaped backslash
|
||
|
stack[-1] += '\\'
|
||
|
elif part == '{':
|
||
|
if len(stack) >= 2 and stack[-2] == "{": # nested
|
||
|
stack[-1] += "{"
|
||
|
else:
|
||
|
# start emphasis
|
||
|
stack.append('{')
|
||
|
stack.append('')
|
||
|
elif part == '}':
|
||
|
if len(stack) == 3 and stack[1] == "{" and len(stack[2]) > 0:
|
||
|
# emphasized word found
|
||
|
if stack[0]:
|
||
|
result.append(nodes.Text(stack[0], stack[0]))
|
||
|
result.append(nodes.emphasis(stack[2], stack[2]))
|
||
|
stack = ['']
|
||
|
else:
|
||
|
# emphasized word not found; the rparen is not a special symbol
|
||
|
stack.append('}')
|
||
|
stack = [''.join(stack)]
|
||
|
elif part == '\\{': # escaped left-brace
|
||
|
stack[-1] += '{'
|
||
|
elif part == '\\}': # escaped right-brace
|
||
|
stack[-1] += '}'
|
||
|
else: # others (containing escaped braces)
|
||
|
stack[-1] += part
|
||
|
|
||
|
if ''.join(stack):
|
||
|
# remaining is treated as Text
|
||
|
text = ''.join(stack)
|
||
|
result.append(nodes.Text(text, text))
|
||
|
|
||
|
return result
|
||
|
|
||
|
|
||
|
_abbr_re = re.compile(r'\((.*)\)$', re.S)
|
||
|
|
||
|
|
||
|
def abbr_role(typ: str, rawtext: str, text: str, lineno: int, inliner: Inliner,
|
||
|
options: Dict = {}, content: List[str] = []
|
||
|
) -> Tuple[List[Node], List[system_message]]:
|
||
|
warnings.warn('abbr_role() is deprecated. Please use Abbrevation class instead.',
|
||
|
RemovedInSphinx40Warning, stacklevel=2)
|
||
|
text = utils.unescape(text)
|
||
|
m = _abbr_re.search(text)
|
||
|
if m is None:
|
||
|
return [nodes.abbreviation(text, text, **options)], []
|
||
|
abbr = text[:m.start()].strip()
|
||
|
expl = m.group(1)
|
||
|
options = options.copy()
|
||
|
options['explanation'] = expl
|
||
|
return [nodes.abbreviation(abbr, abbr, **options)], []
|
||
|
|
||
|
|
||
|
class Abbreviation(SphinxRole):
|
||
|
abbr_re = re.compile(r'\((.*)\)$', re.S)
|
||
|
|
||
|
def run(self) -> Tuple[List[Node], List[system_message]]:
|
||
|
options = self.options.copy()
|
||
|
matched = self.abbr_re.search(self.text)
|
||
|
if matched:
|
||
|
text = self.text[:matched.start()].strip()
|
||
|
options['explanation'] = matched.group(1)
|
||
|
else:
|
||
|
text = self.text
|
||
|
|
||
|
return [nodes.abbreviation(self.rawtext, text, **options)], []
|
||
|
|
||
|
|
||
|
def index_role(typ: str, rawtext: str, text: str, lineno: int, inliner: Inliner,
|
||
|
options: Dict = {}, content: List[str] = []
|
||
|
) -> Tuple[List[Node], List[system_message]]:
|
||
|
warnings.warn('index_role() is deprecated. Please use Index class instead.',
|
||
|
RemovedInSphinx40Warning, stacklevel=2)
|
||
|
# 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, None)]
|
||
|
indexnode = addnodes.index()
|
||
|
indexnode['entries'] = entries
|
||
|
set_role_source_info(inliner, lineno, indexnode)
|
||
|
textnode = nodes.Text(title, title)
|
||
|
return [indexnode, targetnode, textnode], []
|
||
|
|
||
|
|
||
|
class Index(ReferenceRole):
|
||
|
def run(self) -> Tuple[List[Node], List[system_message]]:
|
||
|
warnings.warn('Index role is deprecated.', RemovedInSphinx40Warning, stacklevel=2)
|
||
|
target_id = 'index-%s' % self.env.new_serialno('index')
|
||
|
if self.has_explicit_title:
|
||
|
# if an explicit target is given, process it as a full entry
|
||
|
title = self.title
|
||
|
entries = process_index_entry(self.target, target_id)
|
||
|
else:
|
||
|
# otherwise we just create a single entry
|
||
|
if self.target.startswith('!'):
|
||
|
title = self.title[1:]
|
||
|
entries = [('single', self.target[1:], target_id, 'main', None)]
|
||
|
else:
|
||
|
title = self.title
|
||
|
entries = [('single', self.target, target_id, '', None)]
|
||
|
|
||
|
index = addnodes.index(entries=entries)
|
||
|
target = nodes.target('', '', ids=[target_id])
|
||
|
text = nodes.Text(title, title)
|
||
|
self.set_source_info(index)
|
||
|
return [index, target, text], []
|
||
|
|
||
|
|
||
|
specific_docroles = {
|
||
|
# links to download references
|
||
|
'download': XRefRole(nodeclass=addnodes.download_reference),
|
||
|
# links to anything
|
||
|
'any': AnyXRefRole(warn_dangling=True),
|
||
|
|
||
|
'pep': PEP(),
|
||
|
'rfc': RFC(),
|
||
|
'guilabel': GUILabel(),
|
||
|
'menuselection': MenuSelection(),
|
||
|
'file': EmphasizedLiteral(),
|
||
|
'samp': EmphasizedLiteral(),
|
||
|
'abbr': Abbreviation(),
|
||
|
} # type: Dict[str, RoleFunction]
|
||
|
|
||
|
|
||
|
def setup(app: "Sphinx") -> Dict[str, Any]:
|
||
|
from docutils.parsers.rst import roles
|
||
|
|
||
|
for rolename, nodeclass in generic_docroles.items():
|
||
|
generic = roles.GenericRole(rolename, nodeclass)
|
||
|
role = roles.CustomRole(rolename, generic, {'classes': [rolename]})
|
||
|
roles.register_local_role(rolename, role)
|
||
|
|
||
|
for rolename, func in specific_docroles.items():
|
||
|
roles.register_local_role(rolename, func)
|
||
|
|
||
|
return {
|
||
|
'version': 'builtin',
|
||
|
'parallel_read_safe': True,
|
||
|
'parallel_write_safe': True,
|
||
|
}
|