153 lines
5.4 KiB
Python
153 lines
5.4 KiB
Python
"""The math domain."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from docutils import nodes
|
|
from docutils.nodes import Element, Node, make_id, system_message
|
|
|
|
from sphinx.domains import Domain
|
|
from sphinx.locale import __
|
|
from sphinx.roles import XRefRole
|
|
from sphinx.util import logging
|
|
from sphinx.util.nodes import make_refnode
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Iterable
|
|
|
|
from sphinx.addnodes import pending_xref
|
|
from sphinx.application import Sphinx
|
|
from sphinx.builders import Builder
|
|
from sphinx.environment import BuildEnvironment
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class MathReferenceRole(XRefRole):
|
|
def result_nodes(self, document: nodes.document, env: BuildEnvironment, node: Element,
|
|
is_ref: bool) -> tuple[list[Node], list[system_message]]:
|
|
node['refdomain'] = 'math'
|
|
return [node], []
|
|
|
|
|
|
class MathDomain(Domain):
|
|
"""Mathematics domain."""
|
|
name = 'math'
|
|
label = 'mathematics'
|
|
|
|
initial_data: dict[str, Any] = {
|
|
'objects': {}, # labelid -> (docname, eqno)
|
|
'has_equations': {}, # docname -> bool
|
|
}
|
|
dangling_warnings = {
|
|
'eq': 'equation not found: %(target)s',
|
|
}
|
|
enumerable_nodes = { # node_class -> (figtype, title_getter)
|
|
nodes.math_block: ('displaymath', None),
|
|
}
|
|
roles = {
|
|
'numref': MathReferenceRole(),
|
|
}
|
|
|
|
@property
|
|
def equations(self) -> dict[str, tuple[str, int]]:
|
|
return self.data.setdefault('objects', {}) # labelid -> (docname, eqno)
|
|
|
|
def note_equation(self, docname: str, labelid: str, location: Any = None) -> None:
|
|
if labelid in self.equations:
|
|
other = self.equations[labelid][0]
|
|
logger.warning(__('duplicate label of equation %s, other instance in %s') %
|
|
(labelid, other), location=location)
|
|
|
|
self.equations[labelid] = (docname, self.env.new_serialno('eqno') + 1)
|
|
|
|
def get_equation_number_for(self, labelid: str) -> int | None:
|
|
if labelid in self.equations:
|
|
return self.equations[labelid][1]
|
|
else:
|
|
return None
|
|
|
|
def process_doc(self, env: BuildEnvironment, docname: str,
|
|
document: nodes.document) -> None:
|
|
def math_node(node: Node) -> bool:
|
|
return isinstance(node, (nodes.math, nodes.math_block))
|
|
|
|
self.data['has_equations'][docname] = any(document.findall(math_node))
|
|
|
|
def clear_doc(self, docname: str) -> None:
|
|
for equation_id, (doc, _eqno) in list(self.equations.items()):
|
|
if doc == docname:
|
|
del self.equations[equation_id]
|
|
|
|
self.data['has_equations'].pop(docname, None)
|
|
|
|
def merge_domaindata(self, docnames: Iterable[str], otherdata: dict[str, Any]) -> None:
|
|
for labelid, (doc, eqno) in otherdata['objects'].items():
|
|
if doc in docnames:
|
|
self.equations[labelid] = (doc, eqno)
|
|
|
|
for docname in docnames:
|
|
self.data['has_equations'][docname] = otherdata['has_equations'][docname]
|
|
|
|
def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
|
|
typ: str, target: str, node: pending_xref, contnode: Element,
|
|
) -> Element | None:
|
|
assert typ in ('eq', 'numref')
|
|
result = self.equations.get(target)
|
|
if result:
|
|
docname, number = result
|
|
# TODO: perhaps use rather a sphinx-core provided prefix here?
|
|
node_id = make_id('equation-%s' % target)
|
|
if env.config.math_numfig and env.config.numfig:
|
|
if docname in env.toc_fignumbers:
|
|
numbers = env.toc_fignumbers[docname]['displaymath'].get(node_id, ())
|
|
eqno = '.'.join(map(str, numbers))
|
|
else:
|
|
eqno = ''
|
|
else:
|
|
eqno = str(number)
|
|
|
|
try:
|
|
eqref_format = env.config.math_eqref_format or "({number})"
|
|
title = nodes.Text(eqref_format.format(number=eqno))
|
|
except KeyError as exc:
|
|
logger.warning(__('Invalid math_eqref_format: %r'), exc,
|
|
location=node)
|
|
title = nodes.Text("(%d)" % number)
|
|
title = nodes.Text("(%d)" % number)
|
|
return make_refnode(builder, fromdocname, docname, node_id, title)
|
|
else:
|
|
return None
|
|
|
|
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, 'eq', target, node, contnode)
|
|
if refnode is None:
|
|
return []
|
|
else:
|
|
return [('eq', refnode)]
|
|
|
|
def get_objects(self) -> Iterable[tuple[str, str, str, str, str, int]]:
|
|
return []
|
|
|
|
def has_equations(self, docname: str | None = None) -> bool:
|
|
if docname:
|
|
return self.data['has_equations'].get(docname, False)
|
|
else:
|
|
return any(self.data['has_equations'].values())
|
|
|
|
|
|
def setup(app: Sphinx) -> dict[str, Any]:
|
|
app.add_domain(MathDomain)
|
|
app.add_role('eq', MathReferenceRole(warn_dangling=True))
|
|
|
|
return {
|
|
'version': 'builtin',
|
|
'env_version': 2,
|
|
'parallel_read_safe': True,
|
|
'parallel_write_safe': True,
|
|
}
|