diff --git a/CHANGES b/CHANGES index 3f5dc787c..6e38020e8 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,8 @@ Incompatible changes Deprecated ---------- +* ``sphinx.domains.math.MathDomain.add_equation()`` +* ``sphinx.domains.math.MathDomain.get_next_equation_number()`` * The ``info`` and ``warn`` arguments of ``sphinx.ext.autosummary.generate.generate_autosummary_docs()`` * ``sphinx.ext.autosummary.generate._simple_info()`` diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index f83235b92..e09a6b1f0 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -26,6 +26,16 @@ The following is a list of deprecated interfaces. - (will be) Removed - Alternatives + * - ``sphinx.domains.math.MathDomain.add_equation()`` + - 2.2 + - 4.0 + - ``sphinx.domains.math.MathDomain.note_equation()`` + + * - ``sphinx.domains.math.MathDomain.get_next_equation_number()`` + - 2.2 + - 4.0 + - ``sphinx.domains.math.MathDomain.note_equation()`` + * - The ``info`` and ``warn`` arguments of ``sphinx.ext.autosummary.generate.generate_autosummary_docs()`` - 2.2 diff --git a/sphinx/directives/patches.py b/sphinx/directives/patches.py index b4b1664ab..7bc13bf87 100644 --- a/sphinx/directives/patches.py +++ b/sphinx/directives/patches.py @@ -16,6 +16,7 @@ from docutils.parsers.rst.directives import images, html, tables from sphinx import addnodes from sphinx.directives import optional_int +from sphinx.domains.math import MathDomain from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import set_source_info @@ -194,18 +195,15 @@ class MathDirective(SphinxDirective): return # register label to domain - domain = self.env.get_domain('math') - try: - eqno = domain.add_equation(self.env, self.env.docname, node['label']) # type: ignore # NOQA - node['number'] = eqno + domain = cast(MathDomain, self.env.get_domain('math')) + domain.note_equation(self.env.docname, node['label'], location=node) + node['number'] = domain.get_equation_number_for(node['label']) - # add target node - node_id = make_id('equation-%s' % node['label']) - target = nodes.target('', '', ids=[node_id]) - self.state.document.note_explicit_target(target) - ret.insert(0, target) - except UserWarning as exc: - self.state_machine.reporter.warning(exc, line=self.lineno) + # add target node + node_id = make_id('equation-%s' % node['label']) + target = nodes.target('', '', ids=[node_id]) + self.state.document.note_explicit_target(target) + ret.insert(0, target) def setup(app: "Sphinx") -> Dict[str, Any]: diff --git a/sphinx/domains/math.py b/sphinx/domains/math.py index bc0ef8328..45e33ea6f 100644 --- a/sphinx/domains/math.py +++ b/sphinx/domains/math.py @@ -8,10 +8,13 @@ :license: BSD, see LICENSE for details. """ +import warnings + from docutils import nodes from docutils.nodes import make_id from sphinx.addnodes import math_block as displaymath +from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.domains import Domain from sphinx.locale import __ from sphinx.roles import XRefRole @@ -44,7 +47,7 @@ class MathDomain(Domain): initial_data = { 'objects': {}, # labelid -> (docname, eqno) 'has_equations': {}, # docname -> bool - } # type: Dict[str, Dict[str, Tuple[str, int]]] + } # type: Dict dangling_warnings = { 'eq': 'equation not found: %(target)s', } @@ -56,6 +59,27 @@ class MathDomain(Domain): 'numref': MathReferenceRole(), } + @property + def equations(self): + # type: () -> Dict[str, Tuple[str, int]] + return self.data.setdefault('objects', {}) # labelid -> (docname, eqno) + + def note_equation(self, docname, labelid, location=None): + # type: (str, str, Any) -> 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): + # type: (str) -> int + if labelid in self.equations: + return self.equations[labelid][1] + else: + return None + def process_doc(self, env, docname, document): # type: (BuildEnvironment, str, nodes.document) -> None def math_node(node): @@ -66,9 +90,9 @@ class MathDomain(Domain): def clear_doc(self, docname): # type: (str) -> None - for equation_id, (doc, eqno) in list(self.data['objects'].items()): + for equation_id, (doc, eqno) in list(self.equations.items()): if doc == docname: - del self.data['objects'][equation_id] + del self.equations[equation_id] self.data['has_equations'].pop(docname, None) @@ -76,7 +100,7 @@ class MathDomain(Domain): # type: (Iterable[str], Dict) -> None for labelid, (doc, eqno) in otherdata['objects'].items(): if doc in docnames: - self.data['objects'][labelid] = (doc, eqno) + self.equations[labelid] = (doc, eqno) for docname in docnames: self.data['has_equations'][docname] = otherdata['has_equations'][docname] @@ -84,19 +108,22 @@ class MathDomain(Domain): def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA assert typ in ('eq', 'numref') - docname, number = self.data['objects'].get(target, (None, None)) + docname, number = self.equations.get(target, (None, None)) if docname: # 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: - number = env.toc_fignumbers[docname]['displaymath'].get(node_id, ()) - number = '.'.join(map(str, number)) + numbers = env.toc_fignumbers[docname]['displaymath'].get(node_id, ()) + eqno = '.'.join(map(str, numbers)) else: - number = '' + eqno = '' + else: + eqno = str(number) + try: eqref_format = env.config.math_eqref_format or "({number})" - title = nodes.Text(eqref_format.format(number=number)) + title = nodes.Text(eqref_format.format(number=eqno)) except KeyError as exc: logger.warning(__('Invalid math_eqref_format: %r'), exc, location=node) @@ -120,19 +147,22 @@ class MathDomain(Domain): def add_equation(self, env, docname, labelid): # type: (BuildEnvironment, str, str) -> int - equations = self.data['objects'] - if labelid in equations: - path = env.doc2path(equations[labelid][0]) + warnings.warn('MathDomain.add_equation() is deprecated.', + RemovedInSphinx40Warning) + if labelid in self.equations: + path = env.doc2path(self.equations[labelid][0]) msg = __('duplicate label of equation %s, other instance in %s') % (labelid, path) raise UserWarning(msg) else: eqno = self.get_next_equation_number(docname) - equations[labelid] = (docname, eqno) + self.equations[labelid] = (docname, eqno) return eqno def get_next_equation_number(self, docname): # type: (str) -> int - targets = [eq for eq in self.data['objects'].values() if eq[0] == docname] + warnings.warn('MathDomain.get_next_equation_number() is deprecated.', + RemovedInSphinx40Warning) + targets = [eq for eq in self.equations.values() if eq[0] == docname] return len(targets) + 1 def has_equations(self): diff --git a/tests/test_directive_patch.py b/tests/test_directive_patch.py index 4f61f2d0b..9304b18e3 100644 --- a/tests/test_directive_patch.py +++ b/tests/test_directive_patch.py @@ -52,3 +52,35 @@ def test_code_directive(app): doctree = restructuredtext.parse(app, text) assert_node(doctree, [nodes.document, nodes.literal_block, 'print("hello world")']) assert_node(doctree[0], language="python", linenos=True, highlight_args={'linenostart': 5}) + + +def test_math_directive(app): + # normal case + text = '.. math:: E = mc^2' + doctree = restructuredtext.parse(app, text) + assert_node(doctree, [nodes.document, nodes.math_block, 'E = mc^2\n\n']) + + # :name: option + text = ('.. math:: E = mc^2\n' + ' :name: eq1\n') + doctree = restructuredtext.parse(app, text) + assert_node(doctree, [nodes.document, (nodes.target, + [nodes.math_block, "E = mc^2\n\n"])]) + assert_node(doctree[1], nodes.math_block, docname='index', label="eq1", number=1) + + # :label: option + text = ('.. math:: E = mc^2\n' + ' :label: eq2\n') + doctree = restructuredtext.parse(app, text) + assert_node(doctree, [nodes.document, (nodes.target, + [nodes.math_block, 'E = mc^2\n\n'])]) + assert_node(doctree[1], nodes.math_block, docname='index', label="eq2", number=2) + + # :label: option without value + text = ('.. math:: E = mc^2\n' + ' :label:\n') + doctree = restructuredtext.parse(app, text) + assert_node(doctree, [nodes.document, (nodes.target, + [nodes.math_block, 'E = mc^2\n\n'])]) + assert_node(doctree[1], nodes.math_block, ids=['equation-index-0'], + docname='index', label="index:0", number=3)