diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:25:40 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:25:40 +0000 |
commit | cf7da1843c45a4c2df7a749f7886a2d2ba0ee92a (patch) | |
tree | 18dcde1a8d1f5570a77cd0c361de3b490d02c789 /sphinx/domains/citation.py | |
parent | Initial commit. (diff) | |
download | sphinx-upstream/7.2.6.tar.xz sphinx-upstream/7.2.6.zip |
Adding upstream version 7.2.6.upstream/7.2.6
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sphinx/domains/citation.py')
-rw-r--r-- | sphinx/domains/citation.py | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/sphinx/domains/citation.py b/sphinx/domains/citation.py new file mode 100644 index 0000000..d12c0f1 --- /dev/null +++ b/sphinx/domains/citation.py @@ -0,0 +1,154 @@ +"""The citation domain.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, cast + +from docutils import nodes + +from sphinx.addnodes import pending_xref +from sphinx.domains import Domain +from sphinx.locale import __ +from sphinx.transforms import SphinxTransform +from sphinx.util import logging +from sphinx.util.nodes import copy_source_info, make_refnode + +if TYPE_CHECKING: + from docutils.nodes import Element + + from sphinx.application import Sphinx + from sphinx.builders import Builder + from sphinx.environment import BuildEnvironment + + +logger = logging.getLogger(__name__) + + +class CitationDomain(Domain): + """Domain for citations.""" + + name = 'citation' + label = 'citation' + + dangling_warnings = { + 'ref': 'citation not found: %(target)s', + } + + @property + def citations(self) -> dict[str, tuple[str, str, int]]: + return self.data.setdefault('citations', {}) + + @property + def citation_refs(self) -> dict[str, set[str]]: + return self.data.setdefault('citation_refs', {}) + + def clear_doc(self, docname: str) -> None: + for key, (fn, _l, _lineno) in list(self.citations.items()): + if fn == docname: + del self.citations[key] + for key, docnames in list(self.citation_refs.items()): + if docnames == {docname}: + del self.citation_refs[key] + elif docname in docnames: + docnames.remove(docname) + + def merge_domaindata(self, docnames: list[str], otherdata: dict[str, Any]) -> None: + # XXX duplicates? + for key, data in otherdata['citations'].items(): + if data[0] in docnames: + self.citations[key] = data + for key, data in otherdata['citation_refs'].items(): + citation_refs = self.citation_refs.setdefault(key, set()) + for docname in data: + if docname in docnames: + citation_refs.add(docname) + + def note_citation(self, node: nodes.citation) -> None: + label = node[0].astext() + if label in self.citations: + path = self.env.doc2path(self.citations[label][0]) + logger.warning(__('duplicate citation %s, other instance in %s'), label, path, + location=node, type='ref', subtype='citation') + self.citations[label] = (node['docname'], node['ids'][0], node.line) + + def note_citation_reference(self, node: pending_xref) -> None: + docnames = self.citation_refs.setdefault(node['reftarget'], set()) + docnames.add(self.env.docname) + + def check_consistency(self) -> None: + for name, (docname, _labelid, lineno) in self.citations.items(): + if name not in self.citation_refs: + logger.warning(__('Citation [%s] is not referenced.'), name, + type='ref', subtype='citation', location=(docname, lineno)) + + def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, + typ: str, target: str, node: pending_xref, contnode: Element, + ) -> Element | None: + docname, labelid, lineno = self.citations.get(target, ('', '', 0)) + if not docname: + return None + + return make_refnode(builder, fromdocname, docname, + labelid, contnode) + + def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, + target: str, node: pending_xref, contnode: Element, + ) -> list[tuple[str, Element]]: + refnode = self.resolve_xref(env, fromdocname, builder, 'ref', target, node, contnode) + if refnode is None: + return [] + else: + return [('ref', refnode)] + + +class CitationDefinitionTransform(SphinxTransform): + """Mark citation definition labels as not smartquoted.""" + default_priority = 619 + + def apply(self, **kwargs: Any) -> None: + domain = cast(CitationDomain, self.env.get_domain('citation')) + for node in self.document.findall(nodes.citation): + # register citation node to domain + node['docname'] = self.env.docname + domain.note_citation(node) + + # mark citation labels as not smartquoted + label = cast(nodes.label, node[0]) + label['support_smartquotes'] = False + + +class CitationReferenceTransform(SphinxTransform): + """ + Replace citation references by pending_xref nodes before the default + docutils transform tries to resolve them. + """ + default_priority = 619 + + def apply(self, **kwargs: Any) -> None: + domain = cast(CitationDomain, self.env.get_domain('citation')) + for node in self.document.findall(nodes.citation_reference): + target = node.astext() + ref = pending_xref(target, refdomain='citation', reftype='ref', + reftarget=target, refwarn=True, + support_smartquotes=False, + ids=node["ids"], + classes=node.get('classes', [])) + ref += nodes.inline(target, '[%s]' % target) + copy_source_info(node, ref) + node.replace_self(ref) + + # register reference node to domain + domain.note_citation_reference(ref) + + +def setup(app: Sphinx) -> dict[str, Any]: + app.add_domain(CitationDomain) + app.add_transform(CitationDefinitionTransform) + app.add_transform(CitationReferenceTransform) + + return { + 'version': 'builtin', + 'env_version': 1, + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } |