From 4e04bff4f50bc382251437d5faafff9c34c492cb Mon Sep 17 00:00:00 2001
From: Takeshi KOMIYA
Date: Tue, 15 May 2018 10:57:07 +0900
Subject: [PATCH 1/5] Enable math node rendering by default (without HTML
builders)
Nowadays, math elements (inline and block level equations) are
integrated into reST spec by default. But, in Sphinx, they are
not enabled by default. For this reason, users have to enable
one of math extensions even if target builder supports math
elements directly.
This change starts to enable them by default. As a first step,
this replaces math node and its structure by docutils based one.
---
CHANGES | 2 +
doc/extdev/index.rst | 10 ++++
sphinx/addnodes.py | 25 ++++++++++
sphinx/application.py | 1 +
sphinx/ext/imgmath.py | 19 +++++---
sphinx/ext/jsmath.py | 2 +-
sphinx/ext/mathbase.py | 53 +++-----------------
sphinx/ext/mathjax.py | 2 +-
sphinx/transforms/post_transforms/compat.py | 54 +++++++++++++++++++++
sphinx/writers/latex.py | 10 +++-
sphinx/writers/manpage.py | 10 +++-
sphinx/writers/texinfo.py | 7 ++-
sphinx/writers/text.py | 10 +++-
tests/roots/test-ext-math-compat/conf.py | 14 ++++++
tests/roots/test-ext-math-compat/index.rst | 25 ++++++++++
tests/test_ext_math.py | 22 +++++++++
16 files changed, 203 insertions(+), 63 deletions(-)
create mode 100644 sphinx/transforms/post_transforms/compat.py
create mode 100644 tests/roots/test-ext-math-compat/conf.py
create mode 100644 tests/roots/test-ext-math-compat/index.rst
diff --git a/CHANGES b/CHANGES
index faabb32ad..e2fa3d60b 100644
--- a/CHANGES
+++ b/CHANGES
@@ -72,6 +72,8 @@ Deprecated
* ``BuildEnvironment.dump()`` is deprecated
* ``BuildEnvironment.dumps()`` is deprecated
* ``BuildEnvironment.topickle()`` is deprecated
+* ``sphinx.ext.mathbase.math`` node is deprecated
+* ``sphinx.ext.mathbase.is_in_section_title()`` is deprecated
For more details, see `deprecation APIs list
`_
diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst
index b089bdcea..08de4b095 100644
--- a/doc/extdev/index.rst
+++ b/doc/extdev/index.rst
@@ -126,6 +126,16 @@ The following is a list of deprecated interface.
- 4.0
- :meth:`~sphinx.application.Sphinx.add_css_file()`
+ * - ``sphinx.ext.mathbase.is_in_section_title()``
+ - 1.8
+ - 3.0
+ - N/A
+
+ * - ``sphinx.ext.mathbase.math`` (node)
+ - 1.8
+ - 3.0
+ - ``docutils.nodes.math``
+
* - ``viewcode_import`` (config value)
- 1.8
- 3.0
diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py
index e6999bd16..56f23bba3 100644
--- a/sphinx/addnodes.py
+++ b/sphinx/addnodes.py
@@ -9,8 +9,12 @@
:license: BSD, see LICENSE for details.
"""
+import warnings
+
from docutils import nodes
+from sphinx.deprecation import RemovedInSphinx30Warning
+
if False:
# For type annotation
from typing import List, Sequence # NOQA
@@ -183,6 +187,27 @@ class production(nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for a single grammar production rule."""
+# math node
+
+
+class math(nodes.math):
+ """Node for inline equations.
+
+ .. deprecated:: 1.8
+ Use ``docutils.nodes.math`` instead.
+ """
+
+ def __getitem__(self, key):
+ """Special accessor for supporting ``node['latex']``."""
+ if key == 'latex' and 'latex' not in self.attributes:
+ warnings.warn("math node for Sphinx was replaced by docutils'. "
+ "Therefore please use ``node.astext()`` to get an equation instead.",
+ RemovedInSphinx30Warning)
+ return self.astext()
+ else:
+ return nodes.math.__getitem__(self, key)
+
+
# other directive-level nodes
class index(nodes.Invisible, nodes.Inline, nodes.TextElement):
diff --git a/sphinx/application.py b/sphinx/application.py
index 9d3d5de9f..f36135cf8 100644
--- a/sphinx/application.py
+++ b/sphinx/application.py
@@ -97,6 +97,7 @@ builtin_extensions = (
'sphinx.roles',
'sphinx.transforms.post_transforms',
'sphinx.transforms.post_transforms.images',
+ 'sphinx.transforms.post_transforms.compat',
'sphinx.util.compat',
# collectors should be loaded by specific order
'sphinx.environment.collectors.dependencies',
diff --git a/sphinx/ext/imgmath.py b/sphinx/ext/imgmath.py
index a1faf2c1b..0e43abc74 100644
--- a/sphinx/ext/imgmath.py
+++ b/sphinx/ext/imgmath.py
@@ -37,7 +37,7 @@ if False:
from sphinx.application import Sphinx # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.config import Config # NOQA
- from sphinx.ext.mathbase import math as math_node, displaymath # NOQA
+ from sphinx.ext.mathbase import displaymath # NOQA
logger = logging.getLogger(__name__)
@@ -285,27 +285,30 @@ def cleanup_tempdir(app, exc):
def get_tooltip(self, node):
- # type: (nodes.NodeVisitor, math_node) -> unicode
+ # type: (nodes.NodeVisitor, nodes.math) -> unicode
if self.builder.config.imgmath_add_tooltips:
- return ' alt="%s"' % self.encode(node['latex']).strip()
+ if len(node) == 1:
+ return ' alt="%s"' % self.encode(node.astext()).strip()
+ else:
+ return ' alt="%s"' % self.encode(node['latex']).strip()
return ''
def html_visit_math(self, node):
- # type: (nodes.NodeVisitor, math_node) -> None
+ # type: (nodes.NodeVisitor, nodes.math) -> None
try:
- fname, depth = render_math(self, '$' + node['latex'] + '$')
+ fname, depth = render_math(self, '$' + node.astext() + '$')
except MathExtError as exc:
msg = text_type(exc)
sm = nodes.system_message(msg, type='WARNING', level=2,
- backrefs=[], source=node['latex'])
+ backrefs=[], source=node.astext())
sm.walkabout(self)
- logger.warning(__('display latex %r: %s'), node['latex'], msg)
+ logger.warning(__('display latex %r: %s'), node.astext(), msg)
raise nodes.SkipNode
if fname is None:
# something failed -- use text-only as a bad substitute
self.body.append('%s' %
- self.encode(node['latex']).strip())
+ self.encode(node.astext()).strip())
else:
c = ('
None
self.body.append(self.starttag(node, 'span', '', CLASS='math notranslate nohighlight'))
- self.body.append(self.encode(node['latex']) + '')
+ self.body.append(self.encode(node.astext()) + '')
raise nodes.SkipNode
diff --git a/sphinx/ext/mathbase.py b/sphinx/ext/mathbase.py
index e6a6929e6..d40d461c2 100644
--- a/sphinx/ext/mathbase.py
+++ b/sphinx/ext/mathbase.py
@@ -9,11 +9,15 @@
:license: BSD, see LICENSE for details.
"""
-from docutils import nodes, utils
+import warnings
+
+from docutils import nodes
from docutils.nodes import make_id
from docutils.parsers.rst import directives
+from sphinx.addnodes import math
from sphinx.config import string_classes
+from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.domains import Domain
from sphinx.locale import __
from sphinx.roles import XRefRole
@@ -33,10 +37,6 @@ if False:
logger = logging.getLogger(__name__)
-class math(nodes.Inline, nodes.TextElement):
- pass
-
-
class displaymath(nodes.Part, nodes.Element):
pass
@@ -195,17 +195,14 @@ def wrap_displaymath(math, label, numbering):
return '%s\n%s%s' % (begin, ''.join(equations), end)
-def math_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
- # type: (unicode, unicode, unicode, int, Inliner, Dict, List[unicode]) -> Tuple[List[nodes.Node], List[nodes.Node]] # NOQA
- latex = utils.unescape(text, restore_backslashes=True)
- return [math(latex=latex)], []
-
-
def is_in_section_title(node):
# type: (nodes.Node) -> bool
"""Determine whether the node is in a section title"""
from sphinx.util.nodes import traverse_parent
+ warnings.warn('is_in_section_title() is deprecated.',
+ RemovedInSphinx30Warning)
+
for ancestor in traverse_parent(node):
if isinstance(ancestor, nodes.title) and \
isinstance(ancestor.parent, nodes.section):
@@ -275,17 +272,6 @@ class MathDirective(SphinxDirective):
self.state_machine.reporter.warning(exc.args[0], line=self.lineno)
-def latex_visit_math(self, node):
- # type: (nodes.NodeVisitor, math) -> None
- if is_in_section_title(node):
- protect = r'\protect'
- else:
- protect = ''
- equation = protect + r'\(' + node['latex'] + protect + r'\)'
- self.body.append(equation)
- raise nodes.SkipNode
-
-
def latex_visit_displaymath(self, node):
# type: (nodes.NodeVisitor, displaymath) -> None
if not node['label']:
@@ -320,12 +306,6 @@ def latex_visit_eqref(self, node):
raise nodes.SkipNode
-def text_visit_math(self, node):
- # type: (nodes.NodeVisitor, math) -> None
- self.add_text(node['latex'])
- raise nodes.SkipNode
-
-
def text_visit_displaymath(self, node):
# type: (nodes.NodeVisitor, displaymath) -> None
self.new_state()
@@ -334,12 +314,6 @@ def text_visit_displaymath(self, node):
raise nodes.SkipNode
-def man_visit_math(self, node):
- # type: (nodes.NodeVisitor, math) -> None
- self.body.append(node['latex'])
- raise nodes.SkipNode
-
-
def man_visit_displaymath(self, node):
# type: (nodes.NodeVisitor, displaymath) -> None
self.visit_centered(node)
@@ -350,12 +324,6 @@ def man_depart_displaymath(self, node):
self.depart_centered(node)
-def texinfo_visit_math(self, node):
- # type: (nodes.NodeVisitor, math) -> None
- self.body.append('@math{' + self.escape_arg(node['latex']) + '}')
- raise nodes.SkipNode
-
-
def texinfo_visit_displaymath(self, node):
# type: (nodes.NodeVisitor, displaymath) -> None
if node.get('label'):
@@ -376,10 +344,6 @@ def setup_math(app, htmlinlinevisitors, htmldisplayvisitors):
app.add_config_value('math_numfig', True, 'env')
app.add_domain(MathDomain)
app.add_node(math, override=True,
- latex=(latex_visit_math, None),
- text=(text_visit_math, None),
- man=(man_visit_math, None),
- texinfo=(texinfo_visit_math, None),
html=htmlinlinevisitors)
app.add_node(displaymath,
latex=(latex_visit_displaymath, None),
@@ -388,6 +352,5 @@ def setup_math(app, htmlinlinevisitors, htmldisplayvisitors):
texinfo=(texinfo_visit_displaymath, texinfo_depart_displaymath),
html=htmldisplayvisitors)
app.add_node(eqref, latex=(latex_visit_eqref, None))
- app.add_role('math', math_role)
app.add_role('eq', EqXRefRole(warn_dangling=True))
app.add_directive('math', MathDirective)
diff --git a/sphinx/ext/mathjax.py b/sphinx/ext/mathjax.py
index 2bb7eec09..f6566a583 100644
--- a/sphinx/ext/mathjax.py
+++ b/sphinx/ext/mathjax.py
@@ -29,7 +29,7 @@ def html_visit_math(self, node):
# type: (nodes.NodeVisitor, nodes.Node) -> None
self.body.append(self.starttag(node, 'span', '', CLASS='math notranslate nohighlight'))
self.body.append(self.builder.config.mathjax_inline[0] +
- self.encode(node['latex']) +
+ self.encode(node.astext()) +
self.builder.config.mathjax_inline[1] + '')
raise nodes.SkipNode
diff --git a/sphinx/transforms/post_transforms/compat.py b/sphinx/transforms/post_transforms/compat.py
new file mode 100644
index 000000000..0f0b2367d
--- /dev/null
+++ b/sphinx/transforms/post_transforms/compat.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+"""
+ sphinx.transforms.post_transforms.compat
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Post transforms for compatibility
+
+ :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import warnings
+from typing import TYPE_CHECKING
+
+from docutils import nodes
+
+from sphinx.deprecation import RemovedInSphinx30Warning
+from sphinx.transforms import SphinxTransform
+from sphinx.util import logging
+
+if TYPE_CHECKING:
+ from typing import Any, Callable, Dict, Iterable, List, Tuple # NOQA
+ from docutils.parsers.rst.states import Inliner # NOQA
+ from docutils.writers.html4css1 import Writer # NOQA
+ from sphinx.application import Sphinx # NOQA
+ from sphinx.builders import Builder # NOQA
+ from sphinx.environment import BuildEnvironment # NOQA
+
+logger = logging.getLogger(__name__)
+
+
+class MathNodeMigrator(SphinxTransform):
+ """Migrate a math node to docutils'.
+
+ For a long time, Sphinx uses an original node for math. Since 1.8,
+ Sphinx starts to use a math node of docutils'. This transform converts
+ old and new nodes to keep compatibility.
+ """
+ default_priority = 999
+
+ def apply(self):
+ # type: () -> None
+ for node in self.document.traverse(nodes.math):
+ if len(node) == 0:
+ # convert an old styled node to new one
+ warnings.warn("math node for Sphinx was replaced by docutils'. "
+ "Please use ``docutils.nodes.math`` instead.",
+ RemovedInSphinx30Warning)
+ equation = node['latex']
+ node += nodes.Text(equation, equation)
+
+
+def setup(app):
+ app.add_post_transform(MathNodeMigrator)
diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py
index 6c4b66881..15f72566b 100644
--- a/sphinx/writers/latex.py
+++ b/sphinx/writers/latex.py
@@ -2540,6 +2540,14 @@ class LaTeXTranslator(nodes.NodeVisitor):
self.body.append('\n')
def visit_math(self, node):
+ # type: (nodes.Node) -> None
+ if self.in_title:
+ self.body.append(r'\protect\(%s\protect\)' % node.astext())
+ else:
+ self.body.append(r'\(%s\)' % node.astext())
+ raise nodes.SkipNode
+
+ def visit_math_block(self, node):
# type: (nodes.Node) -> None
logger.warning(__('using "math" markup without a Sphinx math extension '
'active, please use one of the math extensions '
@@ -2547,8 +2555,6 @@ class LaTeXTranslator(nodes.NodeVisitor):
location=(self.curfilestack[-1], node.line))
raise nodes.SkipNode
- visit_math_block = visit_math
-
def unknown_visit(self, node):
# type: (nodes.Node) -> None
raise NotImplementedError('Unknown node: ' + node.__class__.__name__)
diff --git a/sphinx/writers/manpage.py b/sphinx/writers/manpage.py
index c6c8723dd..1f49ced9e 100644
--- a/sphinx/writers/manpage.py
+++ b/sphinx/writers/manpage.py
@@ -512,14 +512,20 @@ class ManualPageTranslator(BaseTranslator):
pass
def visit_math(self, node):
+ # type: (nodes.Node) -> None
+ pass
+
+ def depart_math(self, node):
+ # type: (nodes.Node) -> None
+ pass
+
+ def visit_math_block(self, node):
# type: (nodes.Node) -> None
logger.warning(__('using "math" markup without a Sphinx math extension '
'active, please use one of the math extensions '
'described at http://sphinx-doc.org/en/master/ext/math.html'))
raise nodes.SkipNode
- visit_math_block = visit_math
-
def unknown_visit(self, node):
# type: (nodes.Node) -> None
raise NotImplementedError('Unknown node: ' + node.__class__.__name__)
diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py
index e58d659ab..3b2dd8915 100644
--- a/sphinx/writers/texinfo.py
+++ b/sphinx/writers/texinfo.py
@@ -1730,10 +1730,13 @@ class TexinfoTranslator(nodes.NodeVisitor):
pass
def visit_math(self, node):
+ # type: (nodes.Node) -> None
+ self.body.append('@math{' + self.escape_arg(node.astext()) + '}')
+ raise nodes.SkipNode
+
+ def visit_math_block(self, node):
# type: (nodes.Node) -> None
logger.warning(__('using "math" markup without a Sphinx math extension '
'active, please use one of the math extensions '
'described at http://sphinx-doc.org/en/master/ext/math.html'))
raise nodes.SkipNode
-
- visit_math_block = visit_math
diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py
index 379f06b46..677a10df3 100644
--- a/sphinx/writers/text.py
+++ b/sphinx/writers/text.py
@@ -1178,6 +1178,14 @@ class TextTranslator(nodes.NodeVisitor):
raise nodes.SkipNode
def visit_math(self, node):
+ # type: (nodes.Node) -> None
+ pass
+
+ def depart_math(self, node):
+ # type: (nodes.Node) -> None
+ pass
+
+ def visit_math_block(self, node):
# type: (nodes.Node) -> None
logger.warning(__('using "math" markup without a Sphinx math extension '
'active, please use one of the math extensions '
@@ -1185,8 +1193,6 @@ class TextTranslator(nodes.NodeVisitor):
location=(self.builder.current_docname, node.line))
raise nodes.SkipNode
- visit_math_block = visit_math
-
def unknown_visit(self, node):
# type: (nodes.Node) -> None
raise NotImplementedError('Unknown node: ' + node.__class__.__name__)
diff --git a/tests/roots/test-ext-math-compat/conf.py b/tests/roots/test-ext-math-compat/conf.py
new file mode 100644
index 000000000..fc3b3d0b6
--- /dev/null
+++ b/tests/roots/test-ext-math-compat/conf.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+
+from sphinx.ext.mathbase import math
+
+master_doc = 'index'
+extensions = ['sphinx.ext.mathjax']
+
+
+def my_math_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
+ return [math(latex='E = mc^2')], []
+
+
+def setup(app):
+ app.add_role('my_math', my_math_role)
diff --git a/tests/roots/test-ext-math-compat/index.rst b/tests/roots/test-ext-math-compat/index.rst
new file mode 100644
index 000000000..d5fc071f1
--- /dev/null
+++ b/tests/roots/test-ext-math-compat/index.rst
@@ -0,0 +1,25 @@
+Test Math
+=========
+
+inline
+------
+
+Inline: :math:`E=mc^2`
+Inline my math: :my_math:`:-)`
+
+block
+-----
+
+.. math:: a^2+b^2=c^2
+
+Second math
+
+.. math:: e^{i\pi}+1=0
+
+Multi math equations
+
+.. math::
+
+ S &= \pi r^2
+
+ V &= \frac{4}{3} \pi r^3
diff --git a/tests/test_ext_math.py b/tests/test_ext_math.py
index 28ce094a8..d3580e986 100644
--- a/tests/test_ext_math.py
+++ b/tests/test_ext_math.py
@@ -12,8 +12,12 @@
import errno
import re
import subprocess
+import warnings
import pytest
+from docutils import nodes
+
+from sphinx.testing.util import assert_node
def has_binary(binary):
@@ -208,3 +212,21 @@ def test_imgmath_numfig_html(app, status, warning):
'href="math.html#equation-foo">(1) and '
'(3).
')
assert html in content
+
+
+@pytest.mark.sphinx('dummy', testroot='ext-math-compat')
+def test_math_compat(app, status, warning):
+ with warnings.catch_warnings(record=True):
+ app.builder.build_all()
+ doctree = app.env.get_and_resolve_doctree('index', app.builder)
+
+ assert_node(doctree,
+ [nodes.document, nodes.section, (nodes.title,
+ [nodes.section, (nodes.title,
+ nodes.paragraph)],
+ nodes.section)])
+ assert_node(doctree[0][1][1],
+ ('Inline: ',
+ [nodes.math, "E=mc^2"],
+ '\nInline my math: ',
+ [nodes.math, "E = mc^2"]))
From e675ad2ec91407d516a51304f6bd7fd683f2371c Mon Sep 17 00:00:00 2001
From: Takeshi KOMIYA
Date: Fri, 2 Mar 2018 22:52:39 +0900
Subject: [PATCH 2/5] Enable math_block node rendering by default (without HTML
builders)
---
CHANGES | 1 +
doc/extdev/index.rst | 5 +
sphinx/addnodes.py | 25 +++-
sphinx/config.py | 1 +
sphinx/directives/patches.py | 59 +++++++++
sphinx/ext/imgmath.py | 15 +--
sphinx/ext/jsmath.py | 4 +-
sphinx/ext/mathbase.py | 130 +-------------------
sphinx/ext/mathjax.py | 4 +-
sphinx/transforms/post_transforms/compat.py | 34 ++++-
sphinx/writers/latex.py | 21 +++-
sphinx/writers/manpage.py | 11 +-
sphinx/writers/texinfo.py | 7 +-
sphinx/writers/text.py | 12 +-
tests/roots/test-ext-math-compat/conf.py | 10 +-
tests/roots/test-ext-math-compat/index.rst | 6 +-
tests/test_ext_math.py | 7 ++
17 files changed, 187 insertions(+), 165 deletions(-)
diff --git a/CHANGES b/CHANGES
index e2fa3d60b..a55bc2fff 100644
--- a/CHANGES
+++ b/CHANGES
@@ -73,6 +73,7 @@ Deprecated
* ``BuildEnvironment.dumps()`` is deprecated
* ``BuildEnvironment.topickle()`` is deprecated
* ``sphinx.ext.mathbase.math`` node is deprecated
+* ``sphinx.ext.mathbase.displaymath`` node is deprecated
* ``sphinx.ext.mathbase.is_in_section_title()`` is deprecated
For more details, see `deprecation APIs list
diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst
index 08de4b095..17effd7bd 100644
--- a/doc/extdev/index.rst
+++ b/doc/extdev/index.rst
@@ -136,6 +136,11 @@ The following is a list of deprecated interface.
- 3.0
- ``docutils.nodes.math``
+ * - ``sphinx.ext.mathbase.displaymath`` (node)
+ - 1.8
+ - 3.0
+ - ``docutils.nodes.math_block``
+
* - ``viewcode_import`` (config value)
- 1.8
- 3.0
diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py
index 56f23bba3..37e54cfc7 100644
--- a/sphinx/addnodes.py
+++ b/sphinx/addnodes.py
@@ -187,7 +187,7 @@ class production(nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for a single grammar production rule."""
-# math node
+# math nodes
class math(nodes.math):
@@ -208,6 +208,29 @@ class math(nodes.math):
return nodes.math.__getitem__(self, key)
+class math_block(nodes.math_block):
+ """Node for block level equations.
+
+ .. deprecated:: 1.8
+ """
+
+ def __getitem__(self, key):
+ if key == 'latex' and 'latex' not in self.attributes:
+ warnings.warn("displaymath node for Sphinx was replaced by docutils'. "
+ "Therefore please use ``node.astext()`` to get an equation instead.",
+ RemovedInSphinx30Warning)
+ return self.astext()
+ else:
+ return nodes.math_block.__getitem__(self, key)
+
+
+class displaymath(math_block):
+ """Node for block level equations.
+
+ .. deprecated:: 1.8
+ """
+
+
# other directive-level nodes
class index(nodes.Invisible, nodes.Inline, nodes.TextElement):
diff --git a/sphinx/config.py b/sphinx/config.py
index 1184f1891..74b97cbeb 100644
--- a/sphinx/config.py
+++ b/sphinx/config.py
@@ -139,6 +139,7 @@ class Config(object):
numfig_secnum_depth = (1, 'env', []),
numfig_format = ({}, 'env', []), # will be initialized in init_numfig_format()
+ math_number_all = (False, 'env', []),
tls_verify = (True, 'env', []),
tls_cacerts = (None, 'env', []),
smartquotes = (True, 'env', []),
diff --git a/sphinx/directives/patches.py b/sphinx/directives/patches.py
index 00be5584d..f84f082bc 100644
--- a/sphinx/directives/patches.py
+++ b/sphinx/directives/patches.py
@@ -8,6 +8,7 @@
"""
from docutils import nodes
+from docutils.nodes import make_id
from docutils.parsers.rst import directives
from docutils.parsers.rst.directives import images, html, tables
@@ -105,6 +106,63 @@ class ListTable(tables.ListTable):
return title, message
+class MathDirective(SphinxDirective):
+
+ has_content = True
+ required_arguments = 0
+ optional_arguments = 1
+ final_argument_whitespace = True
+ option_spec = {
+ 'label': directives.unchanged,
+ 'name': directives.unchanged,
+ 'nowrap': directives.flag,
+ }
+
+ def run(self):
+ # type: () -> List[nodes.Node]
+ latex = '\n'.join(self.content)
+ if self.arguments and self.arguments[0]:
+ latex = self.arguments[0] + '\n\n' + latex
+ node = nodes.math_block(latex, latex,
+ docname=self.state.document.settings.env.docname,
+ number=self.options.get('name'),
+ label=self.options.get('label'),
+ nowrap='nowrap' in self.options)
+ ret = [node]
+ set_source_info(self, node)
+ if hasattr(self, 'src'):
+ node.source = self.src
+ self.add_target(ret)
+ return ret
+
+ def add_target(self, ret):
+ # type: (List[nodes.Node]) -> None
+ node = ret[0]
+
+ # assign label automatically if math_number_all enabled
+ if node['label'] == '' or (self.config.math_number_all and not node['label']):
+ seq = self.env.new_serialno('sphinx.ext.math#equations')
+ node['label'] = "%s:%d" % (self.env.docname, seq)
+
+ # no targets and numbers are needed
+ if not node['label']:
+ 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
+
+ # 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.args[0], line=self.lineno)
+
+
def setup(app):
# type: (Sphinx) -> Dict
directives.register_directive('figure', Figure)
@@ -112,6 +170,7 @@ def setup(app):
directives.register_directive('table', RSTTable)
directives.register_directive('csv-table', CSVTable)
directives.register_directive('list-table', ListTable)
+ directives.register_directive('math', MathDirective)
return {
'version': 'builtin',
diff --git a/sphinx/ext/imgmath.py b/sphinx/ext/imgmath.py
index 0e43abc74..0cdc9aba4 100644
--- a/sphinx/ext/imgmath.py
+++ b/sphinx/ext/imgmath.py
@@ -287,10 +287,7 @@ def cleanup_tempdir(app, exc):
def get_tooltip(self, node):
# type: (nodes.NodeVisitor, nodes.math) -> unicode
if self.builder.config.imgmath_add_tooltips:
- if len(node) == 1:
- return ' alt="%s"' % self.encode(node.astext()).strip()
- else:
- return ' alt="%s"' % self.encode(node['latex']).strip()
+ return ' alt="%s"' % self.encode(node.astext()).strip()
return ''
@@ -320,18 +317,18 @@ def html_visit_math(self, node):
def html_visit_displaymath(self, node):
# type: (nodes.NodeVisitor, displaymath) -> None
if node['nowrap']:
- latex = node['latex']
+ latex = node.astext()
else:
- latex = wrap_displaymath(node['latex'], None,
+ latex = wrap_displaymath(node.astext(), None,
self.builder.config.math_number_all)
try:
fname, depth = render_math(self, latex)
except MathExtError as exc:
msg = text_type(exc)
sm = nodes.system_message(msg, type='WARNING', level=2,
- backrefs=[], source=node['latex'])
+ backrefs=[], source=node.astext())
sm.walkabout(self)
- logger.warning(__('inline latex %r: %s'), node['latex'], msg)
+ logger.warning(__('inline latex %r: %s'), node.astext(), msg)
raise nodes.SkipNode
self.body.append(self.starttag(node, 'div', CLASS='math'))
self.body.append('')
@@ -343,7 +340,7 @@ def html_visit_displaymath(self, node):
if fname is None:
# something failed -- use text-only as a bad substitute
self.body.append('%s
\n' %
- self.encode(node['latex']).strip())
+ self.encode(node.astext()).strip())
else:
self.body.append(('
\n')
diff --git a/sphinx/ext/jsmath.py b/sphinx/ext/jsmath.py
index fa9eb7913..7381da42b 100644
--- a/sphinx/ext/jsmath.py
+++ b/sphinx/ext/jsmath.py
@@ -35,10 +35,10 @@ def html_visit_displaymath(self, node):
# type: (nodes.NodeVisitor, nodes.Node) -> None
if node['nowrap']:
self.body.append(self.starttag(node, 'div', CLASS='math notranslate nohighlight'))
- self.body.append(self.encode(node['latex']))
+ self.body.append(self.encode(node.astext()))
self.body.append('')
raise nodes.SkipNode
- for i, part in enumerate(node['latex'].split('\n\n')):
+ for i, part in enumerate(node.astext().split('\n\n')):
part = self.encode(part)
if i == 0:
# necessary to e.g. set the id property correctly
diff --git a/sphinx/ext/mathbase.py b/sphinx/ext/mathbase.py
index d40d461c2..89c6e9286 100644
--- a/sphinx/ext/mathbase.py
+++ b/sphinx/ext/mathbase.py
@@ -13,22 +13,19 @@ import warnings
from docutils import nodes
from docutils.nodes import make_id
-from docutils.parsers.rst import directives
-from sphinx.addnodes import math
+from sphinx.addnodes import math, math_block as displaymath
from sphinx.config import string_classes
from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.domains import Domain
from sphinx.locale import __
from sphinx.roles import XRefRole
from sphinx.util import logging
-from sphinx.util.docutils import SphinxDirective
-from sphinx.util.nodes import make_refnode, set_source_info
+from sphinx.util.nodes import make_refnode
if False:
# For type annotation
from typing import Any, Callable, Dict, Iterable, List, Tuple # NOQA
- from docutils.parsers.rst.states import Inliner # NOQA
from docutils.writers.html4css1 import Writer # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.builders import Builder # NOQA
@@ -37,10 +34,6 @@ if False:
logger = logging.getLogger(__name__)
-class displaymath(nodes.Part, nodes.Element):
- pass
-
-
class eqref(nodes.Inline, nodes.TextElement):
pass
@@ -65,6 +58,7 @@ class MathDomain(Domain):
}
enumerable_nodes = { # node_class -> (figtype, title_getter)
displaymath: ('displaymath', None),
+ nodes.math_block: ('displaymath', None),
} # type: Dict[nodes.Node, Tuple[unicode, Callable]]
def clear_doc(self, docname):
@@ -210,85 +204,6 @@ def is_in_section_title(node):
return False
-class MathDirective(SphinxDirective):
-
- has_content = True
- required_arguments = 0
- optional_arguments = 1
- final_argument_whitespace = True
- option_spec = {
- 'label': directives.unchanged,
- 'name': directives.unchanged,
- 'nowrap': directives.flag,
- }
-
- def run(self):
- # type: () -> List[nodes.Node]
- latex = '\n'.join(self.content)
- if self.arguments and self.arguments[0]:
- latex = self.arguments[0] + '\n\n' + latex
- node = displaymath()
- node['latex'] = latex
- node['number'] = None
- node['label'] = None
- if 'name' in self.options:
- node['label'] = self.options['name']
- if 'label' in self.options:
- node['label'] = self.options['label']
- node['nowrap'] = 'nowrap' in self.options
- node['docname'] = self.env.docname
- ret = [node]
- set_source_info(self, node)
- if hasattr(self, 'src'):
- node.source = self.src
- self.add_target(ret)
- return ret
-
- def add_target(self, ret):
- # type: (List[nodes.Node]) -> None
- node = ret[0]
-
- # assign label automatically if math_number_all enabled
- if node['label'] == '' or (self.config.math_number_all and not node['label']):
- seq = self.env.new_serialno('sphinx.ext.math#equations')
- node['label'] = "%s:%d" % (self.env.docname, seq)
-
- # no targets and numbers are needed
- if not node['label']:
- 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
-
- # 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.args[0], line=self.lineno)
-
-
-def latex_visit_displaymath(self, node):
- # type: (nodes.NodeVisitor, displaymath) -> None
- if not node['label']:
- label = None
- else:
- label = "equation:%s:%s" % (node['docname'], node['label'])
-
- if node['nowrap']:
- if label:
- self.body.append(r'\label{%s}' % label)
- self.body.append(node['latex'])
- else:
- self.body.append(wrap_displaymath(node['latex'], label,
- self.builder.config.math_number_all))
- raise nodes.SkipNode
-
-
def latex_visit_eqref(self, node):
# type: (nodes.NodeVisitor, eqref) -> None
label = "equation:%s:%s" % (node['docname'], node['target'])
@@ -306,51 +221,14 @@ def latex_visit_eqref(self, node):
raise nodes.SkipNode
-def text_visit_displaymath(self, node):
- # type: (nodes.NodeVisitor, displaymath) -> None
- self.new_state()
- self.add_text(node['latex'])
- self.end_state()
- raise nodes.SkipNode
-
-
-def man_visit_displaymath(self, node):
- # type: (nodes.NodeVisitor, displaymath) -> None
- self.visit_centered(node)
-
-
-def man_depart_displaymath(self, node):
- # type: (nodes.NodeVisitor, displaymath) -> None
- self.depart_centered(node)
-
-
-def texinfo_visit_displaymath(self, node):
- # type: (nodes.NodeVisitor, displaymath) -> None
- if node.get('label'):
- self.add_anchor(node['label'], node)
- self.body.append('\n\n@example\n%s\n@end example\n\n' %
- self.escape_arg(node['latex']))
-
-
-def texinfo_depart_displaymath(self, node):
- # type: (nodes.NodeVisitor, displaymath) -> None
- pass
-
-
def setup_math(app, htmlinlinevisitors, htmldisplayvisitors):
# type: (Sphinx, Tuple[Callable, Any], Tuple[Callable, Any]) -> None
- app.add_config_value('math_number_all', False, 'env')
app.add_config_value('math_eqref_format', None, 'env', string_classes)
app.add_config_value('math_numfig', True, 'env')
app.add_domain(MathDomain)
app.add_node(math, override=True,
html=htmlinlinevisitors)
- app.add_node(displaymath,
- latex=(latex_visit_displaymath, None),
- text=(text_visit_displaymath, None),
- man=(man_visit_displaymath, man_depart_displaymath),
- texinfo=(texinfo_visit_displaymath, texinfo_depart_displaymath),
+ app.add_node(displaymath, override=True,
html=htmldisplayvisitors)
app.add_node(eqref, latex=(latex_visit_eqref, None))
app.add_role('eq', EqXRefRole(warn_dangling=True))
- app.add_directive('math', MathDirective)
diff --git a/sphinx/ext/mathjax.py b/sphinx/ext/mathjax.py
index f6566a583..39fbb8539 100644
--- a/sphinx/ext/mathjax.py
+++ b/sphinx/ext/mathjax.py
@@ -38,7 +38,7 @@ def html_visit_displaymath(self, node):
# type: (nodes.NodeVisitor, nodes.Node) -> None
self.body.append(self.starttag(node, 'div', CLASS='math notranslate nohighlight'))
if node['nowrap']:
- self.body.append(self.encode(node['latex']))
+ self.body.append(self.encode(node.astext()))
self.body.append('')
raise nodes.SkipNode
@@ -49,7 +49,7 @@ def html_visit_displaymath(self, node):
self.add_permalink_ref(node, _('Permalink to this equation'))
self.body.append('')
self.body.append(self.builder.config.mathjax_display[0])
- parts = [prt for prt in node['latex'].split('\n\n') if prt.strip()]
+ parts = [prt for prt in node.astext().split('\n\n') if prt.strip()]
if len(parts) > 1: # Add alignment if there are more than 1 equation
self.body.append(r' \begin{align}\begin{aligned}')
for i, part in enumerate(parts):
diff --git a/sphinx/transforms/post_transforms/compat.py b/sphinx/transforms/post_transforms/compat.py
index 0f0b2367d..94360f038 100644
--- a/sphinx/transforms/post_transforms/compat.py
+++ b/sphinx/transforms/post_transforms/compat.py
@@ -13,7 +13,9 @@ import warnings
from typing import TYPE_CHECKING
from docutils import nodes
+from docutils.writers.docutils_xml import XMLTranslator
+from sphinx.addnodes import math_block, displaymath
from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.transforms import SphinxTransform
from sphinx.util import logging
@@ -41,14 +43,44 @@ class MathNodeMigrator(SphinxTransform):
def apply(self):
# type: () -> None
for node in self.document.traverse(nodes.math):
+ # case: old styled ``math`` node generated by old extensions
if len(node) == 0:
- # convert an old styled node to new one
warnings.warn("math node for Sphinx was replaced by docutils'. "
"Please use ``docutils.nodes.math`` instead.",
RemovedInSphinx30Warning)
equation = node['latex']
node += nodes.Text(equation, equation)
+ translator = self.app.builder.get_translator_class()
+ if hasattr(translator, 'visit_displaymath') and translator != XMLTranslator:
+ # case: old translators which does not support ``math_block`` node
+ warnings.warn("Translator for %s does not support math_block node'. "
+ "Please update your extension." % translator,
+ RemovedInSphinx30Warning)
+ for node in self.document.traverse(math_block):
+ alt = displaymath(latex=node.astext(),
+ number=node.get('number'),
+ label=node.get('label'),
+ nowrap=node.get('nowrap'),
+ docname=node.get('docname'))
+ node.replace(alt)
+ else:
+ # case: old styled ``displaymath`` node generated by old extensions
+ for node in self.document.traverse(math_block):
+ if len(node) == 0:
+ warnings.warn("math node for Sphinx was replaced by docutils'. "
+ "Please use ``docutils.nodes.math_block`` instead.",
+ RemovedInSphinx30Warning)
+ latex = node['latex']
+ node += nodes.Text(latex, latex)
+
def setup(app):
+ # type: (Sphinx) -> Dict[unicode, Any]
app.add_post_transform(MathNodeMigrator)
+
+ return {
+ 'version': 'builtin',
+ 'parallel_read_safe': True,
+ 'parallel_write_safe': True,
+ }
diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py
index 15f72566b..d4abacd38 100644
--- a/sphinx/writers/latex.py
+++ b/sphinx/writers/latex.py
@@ -2549,10 +2549,23 @@ class LaTeXTranslator(nodes.NodeVisitor):
def visit_math_block(self, node):
# type: (nodes.Node) -> None
- logger.warning(__('using "math" markup without a Sphinx math extension '
- 'active, please use one of the math extensions '
- 'described at http://sphinx-doc.org/en/master/ext/math.html'),
- location=(self.curfilestack[-1], node.line))
+ if node.get('label'):
+ label = "equation:%s:%s" % (node['docname'], node['label'])
+ else:
+ label = None
+
+ if node.get('nowrap'):
+ if label:
+ self.body.append(r'\label{%s}' % label)
+ self.body.append(node.astext())
+ else:
+ def is_equation(part):
+ # type: (unicode) -> unicode
+ return part.strip()
+
+ from sphinx.ext.mathbase import wrap_displaymath
+ self.body.append(wrap_displaymath(node.astext(), label,
+ self.builder.config.math_number_all))
raise nodes.SkipNode
def unknown_visit(self, node):
diff --git a/sphinx/writers/manpage.py b/sphinx/writers/manpage.py
index 1f49ced9e..02540a53d 100644
--- a/sphinx/writers/manpage.py
+++ b/sphinx/writers/manpage.py
@@ -18,7 +18,7 @@ from docutils.writers.manpage import (
import sphinx.util.docutils
from sphinx import addnodes
-from sphinx.locale import admonitionlabels, _, __
+from sphinx.locale import admonitionlabels, _
from sphinx.util import logging
from sphinx.util.i18n import format_date
@@ -521,10 +521,11 @@ class ManualPageTranslator(BaseTranslator):
def visit_math_block(self, node):
# type: (nodes.Node) -> None
- logger.warning(__('using "math" markup without a Sphinx math extension '
- 'active, please use one of the math extensions '
- 'described at http://sphinx-doc.org/en/master/ext/math.html'))
- raise nodes.SkipNode
+ self.visit_centered(node)
+
+ def depart_math_block(self, node):
+ # type: (nodes.Node) -> None
+ self.depart_centered(node)
def unknown_visit(self, node):
# type: (nodes.Node) -> None
diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py
index 3b2dd8915..4f76c6cf9 100644
--- a/sphinx/writers/texinfo.py
+++ b/sphinx/writers/texinfo.py
@@ -1736,7 +1736,8 @@ class TexinfoTranslator(nodes.NodeVisitor):
def visit_math_block(self, node):
# type: (nodes.Node) -> None
- logger.warning(__('using "math" markup without a Sphinx math extension '
- 'active, please use one of the math extensions '
- 'described at http://sphinx-doc.org/en/master/ext/math.html'))
+ if node.get('label'):
+ self.add_anchor(node['label'], node)
+ self.body.append('\n\n@example\n%s\n@end example\n\n' %
+ self.escape_arg(node.astext()))
raise nodes.SkipNode
diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py
index 677a10df3..0f9b45fbe 100644
--- a/sphinx/writers/text.py
+++ b/sphinx/writers/text.py
@@ -18,7 +18,7 @@ from docutils.utils import column_width
from six.moves import zip_longest
from sphinx import addnodes
-from sphinx.locale import admonitionlabels, _, __
+from sphinx.locale import admonitionlabels, _
from sphinx.util import logging
if False:
@@ -1187,11 +1187,11 @@ class TextTranslator(nodes.NodeVisitor):
def visit_math_block(self, node):
# type: (nodes.Node) -> None
- logger.warning(__('using "math" markup without a Sphinx math extension '
- 'active, please use one of the math extensions '
- 'described at http://sphinx-doc.org/en/master/ext/math.html'),
- location=(self.builder.current_docname, node.line))
- raise nodes.SkipNode
+ self.new_state()
+
+ def depart_math_block(self, node):
+ # type: (nodes.Node) -> None
+ self.end_state()
def unknown_visit(self, node):
# type: (nodes.Node) -> None
diff --git a/tests/roots/test-ext-math-compat/conf.py b/tests/roots/test-ext-math-compat/conf.py
index fc3b3d0b6..c4f6005ea 100644
--- a/tests/roots/test-ext-math-compat/conf.py
+++ b/tests/roots/test-ext-math-compat/conf.py
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
-from sphinx.ext.mathbase import math
+from docutils.parsers.rst import Directive
+
+from sphinx.ext.mathbase import math, displaymath
master_doc = 'index'
extensions = ['sphinx.ext.mathjax']
@@ -10,5 +12,11 @@ def my_math_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
return [math(latex='E = mc^2')], []
+class MyMathDirective(Directive):
+ def run(self):
+ return [displaymath(latex='E = mc^2')]
+
+
def setup(app):
app.add_role('my_math', my_math_role)
+ app.add_directive('my-math', MyMathDirective)
diff --git a/tests/roots/test-ext-math-compat/index.rst b/tests/roots/test-ext-math-compat/index.rst
index d5fc071f1..208878c36 100644
--- a/tests/roots/test-ext-math-compat/index.rst
+++ b/tests/roots/test-ext-math-compat/index.rst
@@ -18,8 +18,4 @@ Second math
Multi math equations
-.. math::
-
- S &= \pi r^2
-
- V &= \frac{4}{3} \pi r^3
+.. my-math::
diff --git a/tests/test_ext_math.py b/tests/test_ext_math.py
index d3580e986..47465f07e 100644
--- a/tests/test_ext_math.py
+++ b/tests/test_ext_math.py
@@ -230,3 +230,10 @@ def test_math_compat(app, status, warning):
[nodes.math, "E=mc^2"],
'\nInline my math: ',
[nodes.math, "E = mc^2"]))
+ assert_node(doctree[0][2],
+ ([nodes.title, "block"],
+ [nodes.math_block, "a^2+b^2=c^2\n\n"],
+ [nodes.paragraph, "Second math"],
+ [nodes.math_block, "e^{i\\pi}+1=0\n\n"],
+ [nodes.paragraph, "Multi math equations"],
+ [nodes.math_block, "E = mc^2"]))
From a4c7be6fcdf6d6fcef8d781707a86a7eb8216bb5 Mon Sep 17 00:00:00 2001
From: Takeshi KOMIYA
Date: Tue, 15 May 2018 23:21:01 +0900
Subject: [PATCH 3/5] Move MathDomain to sphinx.domains.math
---
CHANGES | 1 +
doc/extdev/index.rst | 5 ++
sphinx/application.py | 1 +
sphinx/domains/math.py | 130 +++++++++++++++++++++++++++++++++++++++++
sphinx/ext/mathbase.py | 98 +------------------------------
5 files changed, 139 insertions(+), 96 deletions(-)
create mode 100644 sphinx/domains/math.py
diff --git a/CHANGES b/CHANGES
index a55bc2fff..cd00e52d6 100644
--- a/CHANGES
+++ b/CHANGES
@@ -75,6 +75,7 @@ Deprecated
* ``sphinx.ext.mathbase.math`` node is deprecated
* ``sphinx.ext.mathbase.displaymath`` node is deprecated
* ``sphinx.ext.mathbase.is_in_section_title()`` is deprecated
+* ``sphinx.ext.mathbase.MathDomain`` is deprecated
For more details, see `deprecation APIs list
`_
diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst
index 17effd7bd..c48f93012 100644
--- a/doc/extdev/index.rst
+++ b/doc/extdev/index.rst
@@ -126,6 +126,11 @@ The following is a list of deprecated interface.
- 4.0
- :meth:`~sphinx.application.Sphinx.add_css_file()`
+ * - ``sphinx.ext.mathbase.MathDomain``
+ - 1.8
+ - 3.0
+ - ``sphinx.domains.math.MathDomain``
+
* - ``sphinx.ext.mathbase.is_in_section_title()``
- 1.8
- 3.0
diff --git a/sphinx/application.py b/sphinx/application.py
index f36135cf8..782415e89 100644
--- a/sphinx/application.py
+++ b/sphinx/application.py
@@ -83,6 +83,7 @@ builtin_extensions = (
'sphinx.domains.c',
'sphinx.domains.cpp',
'sphinx.domains.javascript',
+ 'sphinx.domains.math',
'sphinx.domains.python',
'sphinx.domains.rst',
'sphinx.domains.std',
diff --git a/sphinx/domains/math.py b/sphinx/domains/math.py
new file mode 100644
index 000000000..4230eb7a9
--- /dev/null
+++ b/sphinx/domains/math.py
@@ -0,0 +1,130 @@
+# -*- coding: utf-8 -*-
+"""
+ sphinx.domains.math
+ ~~~~~~~~~~~~~~~~~~~
+
+ The math domain.
+
+ :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+from docutils import nodes
+from docutils.nodes import make_id
+
+from sphinx.addnodes import math_block as displaymath
+from sphinx.domains import Domain
+from sphinx.ext.mathbase import eqref
+from sphinx.locale import __
+from sphinx.util import logging
+from sphinx.util.nodes import make_refnode
+
+if False:
+ # For type annotation
+ from typing import Any, Callable, Dict, Iterable, List, Tuple # NOQA
+ from sphinx.application import Sphinx # NOQA
+ from sphinx.builders import Builder # NOQA
+ from sphinx.environment import BuildEnvironment # NOQA
+
+logger = logging.getLogger(__name__)
+
+
+class MathDomain(Domain):
+ """Mathematics domain."""
+ name = 'math'
+ label = 'mathematics'
+
+ initial_data = {
+ 'objects': {}, # labelid -> (docname, eqno)
+ } # type: Dict[unicode, Dict[unicode, Tuple[unicode, int]]]
+ dangling_warnings = {
+ 'eq': 'equation not found: %(target)s',
+ }
+ enumerable_nodes = { # node_class -> (figtype, title_getter)
+ displaymath: ('displaymath', None),
+ nodes.math_block: ('displaymath', None),
+ } # type: Dict[nodes.Node, Tuple[unicode, Callable]]
+
+ def clear_doc(self, docname):
+ # type: (unicode) -> None
+ for equation_id, (doc, eqno) in list(self.data['objects'].items()):
+ if doc == docname:
+ del self.data['objects'][equation_id]
+
+ def merge_domaindata(self, docnames, otherdata):
+ # type: (Iterable[unicode], Dict) -> None
+ for labelid, (doc, eqno) in otherdata['objects'].items():
+ if doc in docnames:
+ self.data['objects'][labelid] = (doc, eqno)
+
+ def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
+ # type: (BuildEnvironment, unicode, Builder, unicode, unicode, nodes.Node, nodes.Node) -> nodes.Node # NOQA
+ assert typ == 'eq'
+ docname, number = self.data['objects'].get(target, (None, None))
+ if docname:
+ if builder.name == 'latex':
+ newnode = eqref('', **node.attributes)
+ newnode['docname'] = docname
+ newnode['target'] = target
+ return newnode
+ else:
+ # 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))
+ else:
+ number = ''
+ try:
+ eqref_format = env.config.math_eqref_format or "({number})"
+ title = nodes.Text(eqref_format.format(number=number))
+ 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, fromdocname, builder, target, node, contnode):
+ # type: (BuildEnvironment, unicode, Builder, unicode, nodes.Node, nodes.Node) -> List[nodes.Node] # NOQA
+ refnode = self.resolve_xref(env, fromdocname, builder, 'eq', target, node, contnode)
+ if refnode is None:
+ return []
+ else:
+ return [refnode]
+
+ def get_objects(self):
+ # type: () -> List
+ return []
+
+ def add_equation(self, env, docname, labelid):
+ # type: (BuildEnvironment, unicode, unicode) -> int
+ equations = self.data['objects']
+ if labelid in equations:
+ path = env.doc2path(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)
+ return eqno
+
+ def get_next_equation_number(self, docname):
+ # type: (unicode) -> int
+ targets = [eq for eq in self.data['objects'].values() if eq[0] == docname]
+ return len(targets) + 1
+
+
+def setup(app):
+ # type: (Sphinx) -> Dict[unicode, Any]
+ app.add_domain(MathDomain)
+
+ return {
+ 'version': 'builtin',
+ 'env_version': 1,
+ 'parallel_read_safe': True,
+ 'parallel_write_safe': True,
+ }
diff --git a/sphinx/ext/mathbase.py b/sphinx/ext/mathbase.py
index 89c6e9286..86c810341 100644
--- a/sphinx/ext/mathbase.py
+++ b/sphinx/ext/mathbase.py
@@ -12,24 +12,20 @@
import warnings
from docutils import nodes
-from docutils.nodes import make_id
from sphinx.addnodes import math, math_block as displaymath
from sphinx.config import string_classes
from sphinx.deprecation import RemovedInSphinx30Warning
-from sphinx.domains import Domain
+from sphinx.domains.math import MathDomain # NOQA # to keep compatibility
from sphinx.locale import __
from sphinx.roles import XRefRole
from sphinx.util import logging
-from sphinx.util.nodes import make_refnode
if False:
# For type annotation
- from typing import Any, Callable, Dict, Iterable, List, Tuple # NOQA
+ from typing import Any, Callable, List, Tuple # NOQA
from docutils.writers.html4css1 import Writer # NOQA
from sphinx.application import Sphinx # NOQA
- from sphinx.builders import Builder # NOQA
- from sphinx.environment import BuildEnvironment # NOQA
logger = logging.getLogger(__name__)
@@ -45,95 +41,6 @@ class EqXRefRole(XRefRole):
return [node], []
-class MathDomain(Domain):
- """Mathematics domain."""
- name = 'math'
- label = 'mathematics'
-
- initial_data = {
- 'objects': {}, # labelid -> (docname, eqno)
- } # type: Dict[unicode, Dict[unicode, Tuple[unicode, int]]]
- dangling_warnings = {
- 'eq': 'equation not found: %(target)s',
- }
- enumerable_nodes = { # node_class -> (figtype, title_getter)
- displaymath: ('displaymath', None),
- nodes.math_block: ('displaymath', None),
- } # type: Dict[nodes.Node, Tuple[unicode, Callable]]
-
- def clear_doc(self, docname):
- # type: (unicode) -> None
- for equation_id, (doc, eqno) in list(self.data['objects'].items()):
- if doc == docname:
- del self.data['objects'][equation_id]
-
- def merge_domaindata(self, docnames, otherdata):
- # type: (Iterable[unicode], Dict) -> None
- for labelid, (doc, eqno) in otherdata['objects'].items():
- if doc in docnames:
- self.data['objects'][labelid] = (doc, eqno)
-
- def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
- # type: (BuildEnvironment, unicode, Builder, unicode, unicode, nodes.Node, nodes.Node) -> nodes.Node # NOQA
- assert typ == 'eq'
- docname, number = self.data['objects'].get(target, (None, None))
- if docname:
- if builder.name == 'latex':
- newnode = eqref('', **node.attributes)
- newnode['docname'] = docname
- newnode['target'] = target
- return newnode
- else:
- # 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))
- else:
- number = ''
- try:
- eqref_format = env.config.math_eqref_format or "({number})"
- title = nodes.Text(eqref_format.format(number=number))
- 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, fromdocname, builder, target, node, contnode):
- # type: (BuildEnvironment, unicode, Builder, unicode, nodes.Node, nodes.Node) -> List[nodes.Node] # NOQA
- refnode = self.resolve_xref(env, fromdocname, builder, 'eq', target, node, contnode)
- if refnode is None:
- return []
- else:
- return [refnode]
-
- def get_objects(self):
- # type: () -> List
- return []
-
- def add_equation(self, env, docname, labelid):
- # type: (BuildEnvironment, unicode, unicode) -> int
- equations = self.data['objects']
- if labelid in equations:
- path = env.doc2path(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)
- return eqno
-
- def get_next_equation_number(self, docname):
- # type: (unicode) -> int
- targets = [eq for eq in self.data['objects'].values() if eq[0] == docname]
- return len(targets) + 1
-
-
def get_node_equation_number(writer, node):
# type: (Writer, nodes.Node) -> unicode
if writer.builder.config.math_numfig and writer.builder.config.numfig:
@@ -225,7 +132,6 @@ def setup_math(app, htmlinlinevisitors, htmldisplayvisitors):
# type: (Sphinx, Tuple[Callable, Any], Tuple[Callable, Any]) -> None
app.add_config_value('math_eqref_format', None, 'env', string_classes)
app.add_config_value('math_numfig', True, 'env')
- app.add_domain(MathDomain)
app.add_node(math, override=True,
html=htmlinlinevisitors)
app.add_node(displaymath, override=True,
From 5f31f9dbab0cc1207ab89f109037b14e014d1e03 Mon Sep 17 00:00:00 2001
From: Takeshi KOMIYA
Date: Tue, 15 May 2018 22:26:16 +0900
Subject: [PATCH 4/5] Enable eq role by default
---
CHANGES | 1 +
doc/extdev/index.rst | 5 +++++
sphinx/addnodes.py | 5 +++++
sphinx/config.py | 2 ++
sphinx/domains/math.py | 14 +++++++++++---
sphinx/ext/mathbase.py | 40 ++--------------------------------------
sphinx/writers/latex.py | 19 +++++++++++++++++++
7 files changed, 45 insertions(+), 41 deletions(-)
diff --git a/CHANGES b/CHANGES
index cd00e52d6..a26226e85 100644
--- a/CHANGES
+++ b/CHANGES
@@ -74,6 +74,7 @@ Deprecated
* ``BuildEnvironment.topickle()`` is deprecated
* ``sphinx.ext.mathbase.math`` node is deprecated
* ``sphinx.ext.mathbase.displaymath`` node is deprecated
+* ``sphinx.ext.mathbase.eqref`` node is deprecated
* ``sphinx.ext.mathbase.is_in_section_title()`` is deprecated
* ``sphinx.ext.mathbase.MathDomain`` is deprecated
diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst
index c48f93012..5b9838244 100644
--- a/doc/extdev/index.rst
+++ b/doc/extdev/index.rst
@@ -146,6 +146,11 @@ The following is a list of deprecated interface.
- 3.0
- ``docutils.nodes.math_block``
+ * - ``sphinx.ext.mathbase.eqref`` (node)
+ - 1.8
+ - 3.0
+ - ``sphinx.addnodes.math_reference``
+
* - ``viewcode_import`` (config value)
- 1.8
- 3.0
diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py
index 37e54cfc7..40390f346 100644
--- a/sphinx/addnodes.py
+++ b/sphinx/addnodes.py
@@ -231,6 +231,11 @@ class displaymath(math_block):
"""
+class math_reference(nodes.Inline, nodes.Referential, nodes.TextElement):
+ """Node for a reference for equation."""
+ pass
+
+
# other directive-level nodes
class index(nodes.Invisible, nodes.Inline, nodes.TextElement):
diff --git a/sphinx/config.py b/sphinx/config.py
index 74b97cbeb..4ad09c08b 100644
--- a/sphinx/config.py
+++ b/sphinx/config.py
@@ -140,6 +140,8 @@ class Config(object):
numfig_format = ({}, 'env', []), # will be initialized in init_numfig_format()
math_number_all = (False, 'env', []),
+ math_eqref_format = (None, 'env', string_classes),
+ math_numfig = (True, 'env', []),
tls_verify = (True, 'env', []),
tls_cacerts = (None, 'env', []),
smartquotes = (True, 'env', []),
diff --git a/sphinx/domains/math.py b/sphinx/domains/math.py
index 4230eb7a9..a5de8a892 100644
--- a/sphinx/domains/math.py
+++ b/sphinx/domains/math.py
@@ -12,10 +12,10 @@
from docutils import nodes
from docutils.nodes import make_id
-from sphinx.addnodes import math_block as displaymath
+from sphinx.addnodes import math_block as displaymath, math_reference
from sphinx.domains import Domain
-from sphinx.ext.mathbase import eqref
from sphinx.locale import __
+from sphinx.roles import XRefRole
from sphinx.util import logging
from sphinx.util.nodes import make_refnode
@@ -29,6 +29,13 @@ if False:
logger = logging.getLogger(__name__)
+class MathReferenceRole(XRefRole):
+ def result_nodes(self, document, env, node, is_ref):
+ # type: (nodes.Node, BuildEnvironment, nodes.Node, bool) -> Tuple[List[nodes.Node], List[nodes.Node]] # NOQA
+ node['refdomain'] = 'math'
+ return [node], []
+
+
class MathDomain(Domain):
"""Mathematics domain."""
name = 'math'
@@ -63,7 +70,7 @@ class MathDomain(Domain):
docname, number = self.data['objects'].get(target, (None, None))
if docname:
if builder.name == 'latex':
- newnode = eqref('', **node.attributes)
+ newnode = math_reference('', **node.attributes)
newnode['docname'] = docname
newnode['target'] = target
return newnode
@@ -121,6 +128,7 @@ class MathDomain(Domain):
def setup(app):
# type: (Sphinx) -> Dict[unicode, Any]
app.add_domain(MathDomain)
+ app.add_role('eq', MathReferenceRole(warn_dangling=True))
return {
'version': 'builtin',
diff --git a/sphinx/ext/mathbase.py b/sphinx/ext/mathbase.py
index 86c810341..448f329b2 100644
--- a/sphinx/ext/mathbase.py
+++ b/sphinx/ext/mathbase.py
@@ -14,12 +14,10 @@ import warnings
from docutils import nodes
from sphinx.addnodes import math, math_block as displaymath
-from sphinx.config import string_classes
+from sphinx.addnodes import math_reference as eqref # NOQA # to keep compatibility
from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.domains.math import MathDomain # NOQA # to keep compatibility
-from sphinx.locale import __
-from sphinx.roles import XRefRole
-from sphinx.util import logging
+from sphinx.domains.math import MathReferenceRole as EqXRefRole # NOQA # to keep compatibility
if False:
# For type annotation
@@ -27,19 +25,6 @@ if False:
from docutils.writers.html4css1 import Writer # NOQA
from sphinx.application import Sphinx # NOQA
-logger = logging.getLogger(__name__)
-
-
-class eqref(nodes.Inline, nodes.TextElement):
- pass
-
-
-class EqXRefRole(XRefRole):
- def result_nodes(self, document, env, node, is_ref):
- # type: (nodes.Node, BuildEnvironment, nodes.Node, bool) -> Tuple[List[nodes.Node], List[nodes.Node]] # NOQA
- node['refdomain'] = 'math'
- return [node], []
-
def get_node_equation_number(writer, node):
# type: (Writer, nodes.Node) -> unicode
@@ -111,30 +96,9 @@ def is_in_section_title(node):
return False
-def latex_visit_eqref(self, node):
- # type: (nodes.NodeVisitor, eqref) -> None
- label = "equation:%s:%s" % (node['docname'], node['target'])
- eqref_format = self.builder.config.math_eqref_format
- if eqref_format:
- try:
- ref = '\\ref{%s}' % label
- self.body.append(eqref_format.format(number=ref))
- except KeyError as exc:
- logger.warning(__('Invalid math_eqref_format: %r'), exc,
- location=node)
- self.body.append('\\eqref{%s}' % label)
- else:
- self.body.append('\\eqref{%s}' % label)
- raise nodes.SkipNode
-
-
def setup_math(app, htmlinlinevisitors, htmldisplayvisitors):
# type: (Sphinx, Tuple[Callable, Any], Tuple[Callable, Any]) -> None
- app.add_config_value('math_eqref_format', None, 'env', string_classes)
- app.add_config_value('math_numfig', True, 'env')
app.add_node(math, override=True,
html=htmlinlinevisitors)
app.add_node(displaymath, override=True,
html=htmldisplayvisitors)
- app.add_node(eqref, latex=(latex_visit_eqref, None))
- app.add_role('eq', EqXRefRole(warn_dangling=True))
diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py
index d4abacd38..f0a5b1195 100644
--- a/sphinx/writers/latex.py
+++ b/sphinx/writers/latex.py
@@ -2568,6 +2568,25 @@ class LaTeXTranslator(nodes.NodeVisitor):
self.builder.config.math_number_all))
raise nodes.SkipNode
+ def visit_math_reference(self, node):
+ # type: (nodes.Node) -> None
+ label = "equation:%s:%s" % (node['docname'], node['target'])
+ eqref_format = self.builder.config.math_eqref_format
+ if eqref_format:
+ try:
+ ref = r'\ref{%s}' % label
+ self.body.append(eqref_format.format(number=ref))
+ except KeyError as exc:
+ logger.warning(__('Invalid math_eqref_format: %r'), exc,
+ location=node)
+ self.body.append(r'\eqref{%s}' % label)
+ else:
+ self.body.append(r'\eqref{%s}' % label)
+
+ def depart_math_reference(self, node):
+ # type: (nodes.Node) -> None
+ pass
+
def unknown_visit(self, node):
# type: (nodes.Node) -> None
raise NotImplementedError('Unknown node: ' + node.__class__.__name__)
From adbda06eca8d2538afc44e9296a07477b0de6a07 Mon Sep 17 00:00:00 2001
From: Takeshi KOMIYA
Date: Wed, 16 May 2018 01:49:53 +0900
Subject: [PATCH 5/5] Fix make_app() fixture fails to rollback registered roles
on creating multiple apps
---
sphinx/testing/fixtures.py | 2 +-
tests/test_config.py | 6 ------
2 files changed, 1 insertion(+), 7 deletions(-)
diff --git a/sphinx/testing/fixtures.py b/sphinx/testing/fixtures.py
index 76ed154fd..94ac9a6e7 100644
--- a/sphinx/testing/fixtures.py
+++ b/sphinx/testing/fixtures.py
@@ -163,7 +163,7 @@ def make_app(test_params, monkeypatch):
yield make
sys.path[:] = syspath
- for app_ in apps:
+ for app_ in reversed(apps): # clean up applications from the new ones
app_.cleanup()
diff --git a/tests/test_config.py b/tests/test_config.py
index e3b79c835..6a910ecfc 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -173,25 +173,19 @@ def test_needs_sphinx(make_app_with_empty_project):
make_app = make_app_with_empty_project
# micro version
app = make_app(confoverrides={'needs_sphinx': '1.3.3'}) # OK: less
- app.cleanup()
app = make_app(confoverrides={'needs_sphinx': '1.3.4'}) # OK: equals
- app.cleanup()
with pytest.raises(VersionRequirementError):
make_app(confoverrides={'needs_sphinx': '1.3.5'}) # NG: greater
# minor version
app = make_app(confoverrides={'needs_sphinx': '1.2'}) # OK: less
- app.cleanup()
app = make_app(confoverrides={'needs_sphinx': '1.3'}) # OK: equals
- app.cleanup()
with pytest.raises(VersionRequirementError):
make_app(confoverrides={'needs_sphinx': '1.4'}) # NG: greater
# major version
app = make_app(confoverrides={'needs_sphinx': '0'}) # OK: less
- app.cleanup()
app = make_app(confoverrides={'needs_sphinx': '1'}) # OK: equals
- app.cleanup()
with pytest.raises(VersionRequirementError):
make_app(confoverrides={'needs_sphinx': '2'}) # NG: greater