From cf7da1843c45a4c2df7a749f7886a2d2ba0ee92a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 19:25:40 +0200 Subject: Adding upstream version 7.2.6. Signed-off-by: Daniel Baumann --- sphinx/domains/rst.py | 299 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 sphinx/domains/rst.py (limited to 'sphinx/domains/rst.py') diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py new file mode 100644 index 0000000..480aba5 --- /dev/null +++ b/sphinx/domains/rst.py @@ -0,0 +1,299 @@ +"""The reStructuredText domain.""" + +from __future__ import annotations + +import re +from typing import TYPE_CHECKING, Any, cast + +from docutils.parsers.rst import directives + +from sphinx import addnodes +from sphinx.directives import ObjectDescription +from sphinx.domains import Domain, ObjType +from sphinx.locale import _, __ +from sphinx.roles import XRefRole +from sphinx.util import logging +from sphinx.util.nodes import make_id, make_refnode + +if TYPE_CHECKING: + from collections.abc import Iterator + + from docutils.nodes import Element + + from sphinx.addnodes import desc_signature, pending_xref + from sphinx.application import Sphinx + from sphinx.builders import Builder + from sphinx.environment import BuildEnvironment + from sphinx.util.typing import OptionSpec + +logger = logging.getLogger(__name__) + +dir_sig_re = re.compile(r'\.\. (.+?)::(.*)$') + + +class ReSTMarkup(ObjectDescription[str]): + """ + Description of generic reST markup. + """ + option_spec: OptionSpec = { + 'no-index': directives.flag, + 'no-index-entry': directives.flag, + 'no-contents-entry': directives.flag, + 'no-typesetting': directives.flag, + 'noindex': directives.flag, + 'noindexentry': directives.flag, + 'nocontentsentry': directives.flag, + } + + def add_target_and_index(self, name: str, sig: str, signode: desc_signature) -> None: + node_id = make_id(self.env, self.state.document, self.objtype, name) + signode['ids'].append(node_id) + self.state.document.note_explicit_target(signode) + + domain = cast(ReSTDomain, self.env.get_domain('rst')) + domain.note_object(self.objtype, name, node_id, location=signode) + + if 'no-index-entry' not in self.options: + indextext = self.get_index_text(self.objtype, name) + if indextext: + self.indexnode['entries'].append(('single', indextext, node_id, '', None)) + + def get_index_text(self, objectname: str, name: str) -> str: + return '' + + def _object_hierarchy_parts(self, sig_node: desc_signature) -> tuple[str, ...]: + if 'fullname' not in sig_node: + return () + directive_names = [] + for parent in self.env.ref_context.get('rst:directives', ()): + directive_names += parent.split(':') + name = sig_node['fullname'] + return tuple(directive_names + name.split(':')) + + def _toc_entry_name(self, sig_node: desc_signature) -> str: + if not sig_node.get('_toc_parts'): + return '' + + config = self.env.app.config + objtype = sig_node.parent.get('objtype') + *parents, name = sig_node['_toc_parts'] + if objtype == 'directive:option': + return f':{name}:' + if config.toc_object_entries_show_parents in {'domain', 'all'}: + name = ':'.join(sig_node['_toc_parts']) + if objtype == 'role': + return f':{name}:' + if objtype == 'directive': + return f'.. {name}::' + return '' + + +def parse_directive(d: str) -> tuple[str, str]: + """Parse a directive signature. + + Returns (directive, arguments) string tuple. If no arguments are given, + returns (directive, ''). + """ + dir = d.strip() + if not dir.startswith('.'): + # Assume it is a directive without syntax + return (dir, '') + m = dir_sig_re.match(dir) + if not m: + return (dir, '') + parsed_dir, parsed_args = m.groups() + if parsed_args.strip(): + return (parsed_dir.strip(), ' ' + parsed_args.strip()) + else: + return (parsed_dir.strip(), '') + + +class ReSTDirective(ReSTMarkup): + """ + Description of a reST directive. + """ + def handle_signature(self, sig: str, signode: desc_signature) -> str: + name, args = parse_directive(sig) + desc_name = f'.. {name}::' + signode['fullname'] = name.strip() + signode += addnodes.desc_name(desc_name, desc_name) + if len(args) > 0: + signode += addnodes.desc_addname(args, args) + return name + + def get_index_text(self, objectname: str, name: str) -> str: + return _('%s (directive)') % name + + def before_content(self) -> None: + if self.names: + directives = self.env.ref_context.setdefault('rst:directives', []) + directives.append(self.names[0]) + + def after_content(self) -> None: + directives = self.env.ref_context.setdefault('rst:directives', []) + if directives: + directives.pop() + + +class ReSTDirectiveOption(ReSTMarkup): + """ + Description of an option for reST directive. + """ + option_spec: OptionSpec = ReSTMarkup.option_spec.copy() + option_spec.update({ + 'type': directives.unchanged, + }) + + def handle_signature(self, sig: str, signode: desc_signature) -> str: + try: + name, argument = re.split(r'\s*:\s+', sig.strip(), maxsplit=1) + except ValueError: + name, argument = sig, None + + desc_name = f':{name}:' + signode['fullname'] = name.strip() + signode += addnodes.desc_name(desc_name, desc_name) + if argument: + signode += addnodes.desc_annotation(' ' + argument, ' ' + argument) + if self.options.get('type'): + text = ' (%s)' % self.options['type'] + signode += addnodes.desc_annotation(text, text) + return name + + def add_target_and_index(self, name: str, sig: str, signode: desc_signature) -> None: + domain = cast(ReSTDomain, self.env.get_domain('rst')) + + directive_name = self.current_directive + if directive_name: + prefix = '-'.join([self.objtype, directive_name]) + objname = ':'.join([directive_name, name]) + else: + prefix = self.objtype + objname = name + + node_id = make_id(self.env, self.state.document, prefix, name) + signode['ids'].append(node_id) + self.state.document.note_explicit_target(signode) + domain.note_object(self.objtype, objname, node_id, location=signode) + + if directive_name: + key = name[0].upper() + pair = [_('%s (directive)') % directive_name, + _(':%s: (directive option)') % name] + self.indexnode['entries'].append(('pair', '; '.join(pair), node_id, '', key)) + else: + key = name[0].upper() + text = _(':%s: (directive option)') % name + self.indexnode['entries'].append(('single', text, node_id, '', key)) + + @property + def current_directive(self) -> str: + directives = self.env.ref_context.get('rst:directives') + if directives: + return directives[-1] + else: + return '' + + +class ReSTRole(ReSTMarkup): + """ + Description of a reST role. + """ + def handle_signature(self, sig: str, signode: desc_signature) -> str: + desc_name = f':{sig}:' + signode['fullname'] = sig.strip() + signode += addnodes.desc_name(desc_name, desc_name) + return sig + + def get_index_text(self, objectname: str, name: str) -> str: + return _('%s (role)') % name + + +class ReSTDomain(Domain): + """ReStructuredText domain.""" + name = 'rst' + label = 'reStructuredText' + + object_types = { + 'directive': ObjType(_('directive'), 'dir'), + 'directive:option': ObjType(_('directive-option'), 'dir'), + 'role': ObjType(_('role'), 'role'), + } + directives = { + 'directive': ReSTDirective, + 'directive:option': ReSTDirectiveOption, + 'role': ReSTRole, + } + roles = { + 'dir': XRefRole(), + 'role': XRefRole(), + } + initial_data: dict[str, dict[tuple[str, str], str]] = { + 'objects': {}, # fullname -> docname, objtype + } + + @property + def objects(self) -> dict[tuple[str, str], tuple[str, str]]: + return self.data.setdefault('objects', {}) # (objtype, fullname) -> (docname, node_id) + + def note_object(self, objtype: str, name: str, node_id: str, location: Any = None) -> None: + if (objtype, name) in self.objects: + docname, node_id = self.objects[objtype, name] + logger.warning(__('duplicate description of %s %s, other instance in %s') % + (objtype, name, docname), location=location) + + self.objects[objtype, name] = (self.env.docname, node_id) + + def clear_doc(self, docname: str) -> None: + for (typ, name), (doc, _node_id) in list(self.objects.items()): + if doc == docname: + del self.objects[typ, name] + + def merge_domaindata(self, docnames: list[str], otherdata: dict[str, Any]) -> None: + # XXX check duplicates + for (typ, name), (doc, node_id) in otherdata['objects'].items(): + if doc in docnames: + self.objects[typ, name] = (doc, node_id) + + def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, + typ: str, target: str, node: pending_xref, contnode: Element, + ) -> Element | None: + objtypes = self.objtypes_for_role(typ) + if not objtypes: + return None + for objtype in objtypes: + result = self.objects.get((objtype, target)) + if result: + todocname, node_id = result + return make_refnode(builder, fromdocname, todocname, node_id, + contnode, target + ' ' + objtype) + return None + + def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, + target: str, node: pending_xref, contnode: Element, + ) -> list[tuple[str, Element]]: + results: list[tuple[str, Element]] = [] + for objtype in self.object_types: + result = self.objects.get((objtype, target)) + if result: + todocname, node_id = result + results.append( + ('rst:' + self.role_for_objtype(objtype), # type: ignore[operator] + make_refnode(builder, fromdocname, todocname, node_id, + contnode, target + ' ' + objtype))) + return results + + def get_objects(self) -> Iterator[tuple[str, str, str, str, str, int]]: + for (typ, name), (docname, node_id) in self.data['objects'].items(): + yield name, name, typ, docname, node_id, 1 + + +def setup(app: Sphinx) -> dict[str, Any]: + app.add_domain(ReSTDomain) + + return { + 'version': 'builtin', + 'env_version': 2, + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } -- cgit v1.2.3