summaryrefslogtreecommitdiffstats
path: root/sphinx/roles.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/roles.py')
-rw-r--r--sphinx/roles.py160
1 files changed, 103 insertions, 57 deletions
diff --git a/sphinx/roles.py b/sphinx/roles.py
index d734429..2fa242f 100644
--- a/sphinx/roles.py
+++ b/sphinx/roles.py
@@ -22,7 +22,7 @@ if TYPE_CHECKING:
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
- from sphinx.util.typing import RoleFunction
+ from sphinx.util.typing import ExtensionMetadata, RoleFunction
generic_docroles = {
@@ -31,7 +31,6 @@ generic_docroles = {
'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
@@ -41,6 +40,7 @@ generic_docroles = {
# -- generic cross-reference role ----------------------------------------------
+
class XRefRole(ReferenceRole):
"""
A generic cross-referencing role. To create a callable that can be used as
@@ -68,10 +68,14 @@ class XRefRole(ReferenceRole):
nodeclass: type[Element] = addnodes.pending_xref
innernodeclass: type[TextElement] = nodes.literal
- def __init__(self, fix_parens: bool = False, lowercase: bool = False,
- nodeclass: type[Element] | None = None,
- innernodeclass: type[TextElement] | None = None,
- warn_dangling: bool = False) -> None:
+ def __init__(
+ self,
+ fix_parens: bool = False,
+ lowercase: bool = False,
+ nodeclass: type[Element] | None = None,
+ innernodeclass: type[TextElement] | None = None,
+ warn_dangling: bool = False,
+ ) -> None:
self.fix_parens = fix_parens
self.lowercase = lowercase
self.warn_dangling = warn_dangling
@@ -112,7 +116,7 @@ class XRefRole(ReferenceRole):
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, "")
+ 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)
@@ -126,17 +130,20 @@ class XRefRole(ReferenceRole):
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}
+ 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)
+ 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)
@@ -144,8 +151,14 @@ class XRefRole(ReferenceRole):
# methods that can be overwritten
- def process_link(self, env: BuildEnvironment, refnode: Element, has_explicit_title: bool,
- title: str, target: str) -> tuple[str, str]:
+ 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)``
@@ -153,8 +166,9 @@ class XRefRole(ReferenceRole):
"""
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]]:
+ 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)``
@@ -164,8 +178,14 @@ class XRefRole(ReferenceRole):
class AnyXRefRole(XRefRole):
- def process_link(self, env: BuildEnvironment, refnode: Element, has_explicit_title: bool,
- title: str, target: str) -> tuple[str, str]:
+ 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)
@@ -175,8 +195,15 @@ class AnyXRefRole(XRefRole):
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)]
+ entries = [
+ (
+ 'single',
+ _('Python Enhancement Proposals; PEP %s') % self.target,
+ target_id,
+ '',
+ None,
+ )
+ ]
index = addnodes.index(entries=entries)
target = nodes.target('', '', ids=[target_id])
@@ -188,11 +215,12 @@ class PEP(ReferenceRole):
if self.has_explicit_title:
reference += nodes.strong(self.title, self.title)
else:
- title = "PEP " + self.title
+ 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)
+ 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]
@@ -222,11 +250,12 @@ class RFC(ReferenceRole):
if self.has_explicit_title:
reference += nodes.strong(self.title, self.title)
else:
- title = "RFC " + self.title
+ 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)
+ 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]
@@ -241,9 +270,6 @@ class RFC(ReferenceRole):
return base_url + self.inliner.rfc_url % int(ret[0])
-_amp_re = re.compile(r'(?<!&)&(?![&\s])')
-
-
class GUILabel(SphinxRole):
amp_re = re.compile(r'(?<!&)&(?![&\s])')
@@ -270,17 +296,14 @@ class MenuSelection(GUILabel):
return super().run()
-_litvar_re = re.compile('{([^}]+)}')
-parens_re = re.compile(r'(\\*{|\\*})')
-
-
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])
+ node = nodes.literal(
+ self.rawtext, '', *children, role=self.name.lower(), classes=[self.name]
+ )
return [node], []
@@ -292,14 +315,13 @@ class EmphasizedLiteral(SphinxRole):
if part == '\\\\': # escaped backslash
stack[-1] += '\\'
elif part == '{':
- if len(stack) >= 2 and stack[-2] == "{": # nested
- stack[-1] += "{"
+ if len(stack) >= 2 and stack[-2] == '{': # nested
+ stack[-1] += '{'
else:
# start emphasis
- stack.append('{')
- stack.append('')
+ stack.extend(('{', ''))
elif part == '}':
- if len(stack) == 3 and stack[1] == "{" and len(stack[2]) > 0:
+ if len(stack) == 3 and stack[1] == '{' and len(stack[2]) > 0:
# emphasized word found
if stack[0]:
result.append(nodes.Text(stack[0]))
@@ -324,17 +346,14 @@ class EmphasizedLiteral(SphinxRole):
return result
-_abbr_re = re.compile(r'\((.*)\)$', re.S)
-
-
class Abbreviation(SphinxRole):
- abbr_re = re.compile(r'\((.*)\)$', re.S)
+ abbr_re = re.compile(r'\((.*)\)$', re.DOTALL)
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()
+ text = self.text[: matched.start()].strip()
options['explanation'] = matched.group(1)
else:
text = self.text
@@ -342,6 +361,28 @@ class Abbreviation(SphinxRole):
return [nodes.abbreviation(self.rawtext, text, **options)], []
+class Manpage(ReferenceRole):
+ _manpage_re = re.compile(r'^(?P<path>(?P<page>.+)[(.](?P<section>[1-9]\w*)?\)?)$')
+
+ def run(self) -> tuple[list[Node], list[system_message]]:
+ manpage = ws_re.sub(' ', self.target)
+ if m := self._manpage_re.match(manpage):
+ info = m.groupdict()
+ else:
+ info = {'path': manpage, 'page': manpage, 'section': ''}
+
+ inner: nodes.Node
+ text = self.title[1:] if self.disabled else self.title
+ if not self.disabled and self.config.manpages_url:
+ uri = self.config.manpages_url.format(**info)
+ inner = nodes.reference('', text, classes=[self.name], refuri=uri)
+ else:
+ inner = nodes.Text(text)
+ node = addnodes.manpage(self.rawtext, '', inner, classes=[self.name], **info)
+
+ return [node], []
+
+
# Sphinx provides the `code-block` directive for highlighting code blocks.
# Docutils provides the `code` role which in theory can be used similarly by
# defining a custom role for a given programming language:
@@ -366,10 +407,15 @@ class Abbreviation(SphinxRole):
# way as the Sphinx `code-block` directive.
#
# TODO: Change to use `SphinxRole` once SphinxRole is fixed to support options.
-def code_role(name: str, rawtext: str, text: str, lineno: int,
- inliner: docutils.parsers.rst.states.Inliner,
- options: dict | None = None, content: Sequence[str] = (),
- ) -> tuple[list[Node], list[system_message]]:
+def code_role(
+ name: str,
+ rawtext: str,
+ text: str,
+ lineno: int,
+ inliner: docutils.parsers.rst.states.Inliner,
+ options: dict[str, Any] | None = None,
+ content: Sequence[str] = (),
+) -> tuple[list[Node], list[system_message]]:
if options is None:
options = {}
options = options.copy()
@@ -400,7 +446,6 @@ specific_docroles: dict[str, RoleFunction] = {
'download': XRefRole(nodeclass=addnodes.download_reference),
# links to anything
'any': AnyXRefRole(warn_dangling=True),
-
'pep': PEP(),
'rfc': RFC(),
'guilabel': GUILabel(),
@@ -408,23 +453,24 @@ specific_docroles: dict[str, RoleFunction] = {
'file': EmphasizedLiteral(),
'samp': EmphasizedLiteral(),
'abbr': Abbreviation(),
+ 'manpage': Manpage(),
}
-def setup(app: Sphinx) -> dict[str, Any]:
+def setup(app: Sphinx) -> ExtensionMetadata:
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)
+ role = roles.CustomRole(rolename, generic, {'classes': [rolename]}) # type: ignore[arg-type]
+ roles.register_local_role(rolename, role) # type: ignore[arg-type]
for rolename, func in specific_docroles.items():
- roles.register_local_role(rolename, func)
+ roles.register_local_role(rolename, func) # type: ignore[arg-type]
# Since docutils registers it as a canonical role, override it as a
# canonical role as well.
- roles.register_canonical_role('code', code_role)
+ roles.register_canonical_role('code', code_role) # type: ignore[arg-type]
return {
'version': 'builtin',