diff options
Diffstat (limited to '')
-rw-r--r-- | sphinx/roles.py | 160 |
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', |