| """The index domain.""" |
| |
| from __future__ import annotations |
| |
| from typing import TYPE_CHECKING, Any |
| |
| from docutils import nodes |
| from docutils.parsers.rst import directives |
| |
| from sphinx import addnodes |
| from sphinx.domains import Domain |
| from sphinx.util import logging |
| from sphinx.util.docutils import ReferenceRole, SphinxDirective |
| from sphinx.util.index_entries import split_index_msg |
| from sphinx.util.nodes import process_index_entry |
| |
| if TYPE_CHECKING: |
| from collections.abc import Iterable |
| |
| from docutils.nodes import Node, system_message |
| |
| from sphinx.application import Sphinx |
| from sphinx.environment import BuildEnvironment |
| from sphinx.util.typing import OptionSpec |
| |
| |
| logger = logging.getLogger(__name__) |
| |
| |
| class IndexDomain(Domain): |
| """Mathematics domain.""" |
| name = 'index' |
| label = 'index' |
| |
| @property |
| def entries(self) -> dict[str, list[tuple[str, str, str, str, str | None]]]: |
| return self.data.setdefault('entries', {}) |
| |
| def clear_doc(self, docname: str) -> None: |
| self.entries.pop(docname, None) |
| |
| def merge_domaindata(self, docnames: Iterable[str], otherdata: dict[str, Any]) -> None: |
| for docname in docnames: |
| self.entries[docname] = otherdata['entries'][docname] |
| |
| def process_doc(self, env: BuildEnvironment, docname: str, document: Node) -> None: |
| """Process a document after it is read by the environment.""" |
| entries = self.entries.setdefault(env.docname, []) |
| for node in list(document.findall(addnodes.index)): |
| try: |
| for (entry_type, value, _target_id, _main, _category_key) in node['entries']: |
| split_index_msg(entry_type, value) |
| except ValueError as exc: |
| logger.warning(str(exc), location=node) |
| node.parent.remove(node) |
| else: |
| for entry in node['entries']: |
| entries.append(entry) |
| |
| |
| class IndexDirective(SphinxDirective): |
| """ |
| Directive to add entries to the index. |
| """ |
| has_content = False |
| required_arguments = 1 |
| optional_arguments = 0 |
| final_argument_whitespace = True |
| option_spec: OptionSpec = { |
| 'name': directives.unchanged, |
| } |
| |
| def run(self) -> list[Node]: |
| arguments = self.arguments[0].split('\n') |
| |
| if 'name' in self.options: |
| targetname = self.options['name'] |
| targetnode = nodes.target('', '', names=[targetname]) |
| else: |
| targetid = 'index-%s' % self.env.new_serialno('index') |
| targetnode = nodes.target('', '', ids=[targetid]) |
| |
| self.state.document.note_explicit_target(targetnode) |
| indexnode = addnodes.index() |
| indexnode['entries'] = [] |
| indexnode['inline'] = False |
| self.set_source_info(indexnode) |
| for entry in arguments: |
| indexnode['entries'].extend(process_index_entry(entry, targetnode['ids'][0])) |
| return [indexnode, targetnode] |
| |
| |
| class IndexRole(ReferenceRole): |
| def run(self) -> tuple[list[Node], list[system_message]]: |
| 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) |
| self.set_source_info(index) |
| return [index, target, text], [] |
| |
| |
| def setup(app: Sphinx) -> dict[str, Any]: |
| app.add_domain(IndexDomain) |
| app.add_directive('index', IndexDirective) |
| app.add_role('index', IndexRole()) |
| |
| return { |
| 'version': 'builtin', |
| 'env_version': 1, |
| 'parallel_read_safe': True, |
| 'parallel_write_safe': True, |
| } |