mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #4975 from tk0miya/refactor_math
Fix #4333: Enable directives and roles for math by default
This commit is contained in:
commit
ac523c637a
5
CHANGES
5
CHANGES
@ -72,6 +72,11 @@ Deprecated
|
|||||||
* ``BuildEnvironment.dump()`` is deprecated
|
* ``BuildEnvironment.dump()`` is deprecated
|
||||||
* ``BuildEnvironment.dumps()`` is deprecated
|
* ``BuildEnvironment.dumps()`` is deprecated
|
||||||
* ``BuildEnvironment.topickle()`` is 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
|
||||||
|
|
||||||
For more details, see `deprecation APIs list
|
For more details, see `deprecation APIs list
|
||||||
<http://www.sphinx-doc.org/en/master/extdev/index.html#deprecated-apis>`_
|
<http://www.sphinx-doc.org/en/master/extdev/index.html#deprecated-apis>`_
|
||||||
|
@ -126,6 +126,31 @@ The following is a list of deprecated interface.
|
|||||||
- 4.0
|
- 4.0
|
||||||
- :meth:`~sphinx.application.Sphinx.add_css_file()`
|
- :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
|
||||||
|
- N/A
|
||||||
|
|
||||||
|
* - ``sphinx.ext.mathbase.math`` (node)
|
||||||
|
- 1.8
|
||||||
|
- 3.0
|
||||||
|
- ``docutils.nodes.math``
|
||||||
|
|
||||||
|
* - ``sphinx.ext.mathbase.displaymath`` (node)
|
||||||
|
- 1.8
|
||||||
|
- 3.0
|
||||||
|
- ``docutils.nodes.math_block``
|
||||||
|
|
||||||
|
* - ``sphinx.ext.mathbase.eqref`` (node)
|
||||||
|
- 1.8
|
||||||
|
- 3.0
|
||||||
|
- ``sphinx.addnodes.math_reference``
|
||||||
|
|
||||||
* - ``viewcode_import`` (config value)
|
* - ``viewcode_import`` (config value)
|
||||||
- 1.8
|
- 1.8
|
||||||
- 3.0
|
- 3.0
|
||||||
|
@ -9,8 +9,12 @@
|
|||||||
:license: BSD, see LICENSE for details.
|
:license: BSD, see LICENSE for details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
|
||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
|
|
||||||
|
from sphinx.deprecation import RemovedInSphinx30Warning
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
# For type annotation
|
# For type annotation
|
||||||
from typing import List, Sequence # NOQA
|
from typing import List, Sequence # NOQA
|
||||||
@ -183,6 +187,55 @@ class production(nodes.Part, nodes.Inline, nodes.FixedTextElement):
|
|||||||
"""Node for a single grammar production rule."""
|
"""Node for a single grammar production rule."""
|
||||||
|
|
||||||
|
|
||||||
|
# math nodes
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class math_reference(nodes.Inline, nodes.Referential, nodes.TextElement):
|
||||||
|
"""Node for a reference for equation."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# other directive-level nodes
|
# other directive-level nodes
|
||||||
|
|
||||||
class index(nodes.Invisible, nodes.Inline, nodes.TextElement):
|
class index(nodes.Invisible, nodes.Inline, nodes.TextElement):
|
||||||
|
@ -83,6 +83,7 @@ builtin_extensions = (
|
|||||||
'sphinx.domains.c',
|
'sphinx.domains.c',
|
||||||
'sphinx.domains.cpp',
|
'sphinx.domains.cpp',
|
||||||
'sphinx.domains.javascript',
|
'sphinx.domains.javascript',
|
||||||
|
'sphinx.domains.math',
|
||||||
'sphinx.domains.python',
|
'sphinx.domains.python',
|
||||||
'sphinx.domains.rst',
|
'sphinx.domains.rst',
|
||||||
'sphinx.domains.std',
|
'sphinx.domains.std',
|
||||||
@ -97,6 +98,7 @@ builtin_extensions = (
|
|||||||
'sphinx.roles',
|
'sphinx.roles',
|
||||||
'sphinx.transforms.post_transforms',
|
'sphinx.transforms.post_transforms',
|
||||||
'sphinx.transforms.post_transforms.images',
|
'sphinx.transforms.post_transforms.images',
|
||||||
|
'sphinx.transforms.post_transforms.compat',
|
||||||
'sphinx.util.compat',
|
'sphinx.util.compat',
|
||||||
# collectors should be loaded by specific order
|
# collectors should be loaded by specific order
|
||||||
'sphinx.environment.collectors.dependencies',
|
'sphinx.environment.collectors.dependencies',
|
||||||
|
@ -139,6 +139,9 @@ class Config(object):
|
|||||||
numfig_secnum_depth = (1, 'env', []),
|
numfig_secnum_depth = (1, 'env', []),
|
||||||
numfig_format = ({}, 'env', []), # will be initialized in init_numfig_format()
|
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_verify = (True, 'env', []),
|
||||||
tls_cacerts = (None, 'env', []),
|
tls_cacerts = (None, 'env', []),
|
||||||
smartquotes = (True, 'env', []),
|
smartquotes = (True, 'env', []),
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
|
from docutils.nodes import make_id
|
||||||
from docutils.parsers.rst import directives
|
from docutils.parsers.rst import directives
|
||||||
from docutils.parsers.rst.directives import images, html, tables
|
from docutils.parsers.rst.directives import images, html, tables
|
||||||
|
|
||||||
@ -105,6 +106,63 @@ class ListTable(tables.ListTable):
|
|||||||
return title, message
|
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):
|
def setup(app):
|
||||||
# type: (Sphinx) -> Dict
|
# type: (Sphinx) -> Dict
|
||||||
directives.register_directive('figure', Figure)
|
directives.register_directive('figure', Figure)
|
||||||
@ -112,6 +170,7 @@ def setup(app):
|
|||||||
directives.register_directive('table', RSTTable)
|
directives.register_directive('table', RSTTable)
|
||||||
directives.register_directive('csv-table', CSVTable)
|
directives.register_directive('csv-table', CSVTable)
|
||||||
directives.register_directive('list-table', ListTable)
|
directives.register_directive('list-table', ListTable)
|
||||||
|
directives.register_directive('math', MathDirective)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'version': 'builtin',
|
'version': 'builtin',
|
||||||
|
138
sphinx/domains/math.py
Normal file
138
sphinx/domains/math.py
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
# -*- 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, math_reference
|
||||||
|
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 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 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'
|
||||||
|
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 = math_reference('', **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)
|
||||||
|
app.add_role('eq', MathReferenceRole(warn_dangling=True))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'version': 'builtin',
|
||||||
|
'env_version': 1,
|
||||||
|
'parallel_read_safe': True,
|
||||||
|
'parallel_write_safe': True,
|
||||||
|
}
|
@ -37,7 +37,7 @@ if False:
|
|||||||
from sphinx.application import Sphinx # NOQA
|
from sphinx.application import Sphinx # NOQA
|
||||||
from sphinx.builders import Builder # NOQA
|
from sphinx.builders import Builder # NOQA
|
||||||
from sphinx.config import Config # 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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -285,27 +285,27 @@ def cleanup_tempdir(app, exc):
|
|||||||
|
|
||||||
|
|
||||||
def get_tooltip(self, node):
|
def get_tooltip(self, node):
|
||||||
# type: (nodes.NodeVisitor, math_node) -> unicode
|
# type: (nodes.NodeVisitor, nodes.math) -> unicode
|
||||||
if self.builder.config.imgmath_add_tooltips:
|
if self.builder.config.imgmath_add_tooltips:
|
||||||
return ' alt="%s"' % self.encode(node['latex']).strip()
|
return ' alt="%s"' % self.encode(node.astext()).strip()
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
def html_visit_math(self, node):
|
def html_visit_math(self, node):
|
||||||
# type: (nodes.NodeVisitor, math_node) -> None
|
# type: (nodes.NodeVisitor, nodes.math) -> None
|
||||||
try:
|
try:
|
||||||
fname, depth = render_math(self, '$' + node['latex'] + '$')
|
fname, depth = render_math(self, '$' + node.astext() + '$')
|
||||||
except MathExtError as exc:
|
except MathExtError as exc:
|
||||||
msg = text_type(exc)
|
msg = text_type(exc)
|
||||||
sm = nodes.system_message(msg, type='WARNING', level=2,
|
sm = nodes.system_message(msg, type='WARNING', level=2,
|
||||||
backrefs=[], source=node['latex'])
|
backrefs=[], source=node.astext())
|
||||||
sm.walkabout(self)
|
sm.walkabout(self)
|
||||||
logger.warning(__('display latex %r: %s'), node['latex'], msg)
|
logger.warning(__('display latex %r: %s'), node.astext(), msg)
|
||||||
raise nodes.SkipNode
|
raise nodes.SkipNode
|
||||||
if fname is None:
|
if fname is None:
|
||||||
# something failed -- use text-only as a bad substitute
|
# something failed -- use text-only as a bad substitute
|
||||||
self.body.append('<span class="math">%s</span>' %
|
self.body.append('<span class="math">%s</span>' %
|
||||||
self.encode(node['latex']).strip())
|
self.encode(node.astext()).strip())
|
||||||
else:
|
else:
|
||||||
c = ('<img class="math" src="%s"' % fname) + get_tooltip(self, node)
|
c = ('<img class="math" src="%s"' % fname) + get_tooltip(self, node)
|
||||||
if depth is not None:
|
if depth is not None:
|
||||||
@ -317,18 +317,18 @@ def html_visit_math(self, node):
|
|||||||
def html_visit_displaymath(self, node):
|
def html_visit_displaymath(self, node):
|
||||||
# type: (nodes.NodeVisitor, displaymath) -> None
|
# type: (nodes.NodeVisitor, displaymath) -> None
|
||||||
if node['nowrap']:
|
if node['nowrap']:
|
||||||
latex = node['latex']
|
latex = node.astext()
|
||||||
else:
|
else:
|
||||||
latex = wrap_displaymath(node['latex'], None,
|
latex = wrap_displaymath(node.astext(), None,
|
||||||
self.builder.config.math_number_all)
|
self.builder.config.math_number_all)
|
||||||
try:
|
try:
|
||||||
fname, depth = render_math(self, latex)
|
fname, depth = render_math(self, latex)
|
||||||
except MathExtError as exc:
|
except MathExtError as exc:
|
||||||
msg = text_type(exc)
|
msg = text_type(exc)
|
||||||
sm = nodes.system_message(msg, type='WARNING', level=2,
|
sm = nodes.system_message(msg, type='WARNING', level=2,
|
||||||
backrefs=[], source=node['latex'])
|
backrefs=[], source=node.astext())
|
||||||
sm.walkabout(self)
|
sm.walkabout(self)
|
||||||
logger.warning(__('inline latex %r: %s'), node['latex'], msg)
|
logger.warning(__('inline latex %r: %s'), node.astext(), msg)
|
||||||
raise nodes.SkipNode
|
raise nodes.SkipNode
|
||||||
self.body.append(self.starttag(node, 'div', CLASS='math'))
|
self.body.append(self.starttag(node, 'div', CLASS='math'))
|
||||||
self.body.append('<p>')
|
self.body.append('<p>')
|
||||||
@ -340,7 +340,7 @@ def html_visit_displaymath(self, node):
|
|||||||
if fname is None:
|
if fname is None:
|
||||||
# something failed -- use text-only as a bad substitute
|
# something failed -- use text-only as a bad substitute
|
||||||
self.body.append('<span class="math">%s</span></p>\n</div>' %
|
self.body.append('<span class="math">%s</span></p>\n</div>' %
|
||||||
self.encode(node['latex']).strip())
|
self.encode(node.astext()).strip())
|
||||||
else:
|
else:
|
||||||
self.body.append(('<img src="%s"' % fname) + get_tooltip(self, node) +
|
self.body.append(('<img src="%s"' % fname) + get_tooltip(self, node) +
|
||||||
'/></p>\n</div>')
|
'/></p>\n</div>')
|
||||||
|
@ -27,7 +27,7 @@ if False:
|
|||||||
def html_visit_math(self, node):
|
def html_visit_math(self, node):
|
||||||
# type: (nodes.NodeVisitor, nodes.Node) -> None
|
# type: (nodes.NodeVisitor, nodes.Node) -> None
|
||||||
self.body.append(self.starttag(node, 'span', '', CLASS='math notranslate nohighlight'))
|
self.body.append(self.starttag(node, 'span', '', CLASS='math notranslate nohighlight'))
|
||||||
self.body.append(self.encode(node['latex']) + '</span>')
|
self.body.append(self.encode(node.astext()) + '</span>')
|
||||||
raise nodes.SkipNode
|
raise nodes.SkipNode
|
||||||
|
|
||||||
|
|
||||||
@ -35,10 +35,10 @@ def html_visit_displaymath(self, node):
|
|||||||
# type: (nodes.NodeVisitor, nodes.Node) -> None
|
# type: (nodes.NodeVisitor, nodes.Node) -> None
|
||||||
if node['nowrap']:
|
if node['nowrap']:
|
||||||
self.body.append(self.starttag(node, 'div', CLASS='math notranslate nohighlight'))
|
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('</div>')
|
self.body.append('</div>')
|
||||||
raise nodes.SkipNode
|
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)
|
part = self.encode(part)
|
||||||
if i == 0:
|
if i == 0:
|
||||||
# necessary to e.g. set the id property correctly
|
# necessary to e.g. set the id property correctly
|
||||||
|
@ -9,135 +9,21 @@
|
|||||||
:license: BSD, see LICENSE for details.
|
:license: BSD, see LICENSE for details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from docutils import nodes, utils
|
import warnings
|
||||||
from docutils.nodes import make_id
|
|
||||||
from docutils.parsers.rst import directives
|
|
||||||
|
|
||||||
from sphinx.config import string_classes
|
from docutils import nodes
|
||||||
from sphinx.domains import Domain
|
|
||||||
from sphinx.locale import __
|
from sphinx.addnodes import math, math_block as displaymath
|
||||||
from sphinx.roles import XRefRole
|
from sphinx.addnodes import math_reference as eqref # NOQA # to keep compatibility
|
||||||
from sphinx.util import logging
|
from sphinx.deprecation import RemovedInSphinx30Warning
|
||||||
from sphinx.util.docutils import SphinxDirective
|
from sphinx.domains.math import MathDomain # NOQA # to keep compatibility
|
||||||
from sphinx.util.nodes import make_refnode, set_source_info
|
from sphinx.domains.math import MathReferenceRole as EqXRefRole # NOQA # to keep compatibility
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
# For type annotation
|
# For type annotation
|
||||||
from typing import Any, Callable, Dict, Iterable, List, Tuple # NOQA
|
from typing import Any, Callable, List, Tuple # NOQA
|
||||||
from docutils.parsers.rst.states import Inliner # NOQA
|
|
||||||
from docutils.writers.html4css1 import Writer # NOQA
|
from docutils.writers.html4css1 import Writer # NOQA
|
||||||
from sphinx.application import Sphinx # NOQA
|
from sphinx.application import Sphinx # NOQA
|
||||||
from sphinx.builders import Builder # NOQA
|
|
||||||
from sphinx.environment import BuildEnvironment # NOQA
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class math(nodes.Inline, nodes.TextElement):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class displaymath(nodes.Part, nodes.Element):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
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], []
|
|
||||||
|
|
||||||
|
|
||||||
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),
|
|
||||||
} # 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):
|
def get_node_equation_number(writer, node):
|
||||||
@ -195,17 +81,14 @@ def wrap_displaymath(math, label, numbering):
|
|||||||
return '%s\n%s%s' % (begin, ''.join(equations), end)
|
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):
|
def is_in_section_title(node):
|
||||||
# type: (nodes.Node) -> bool
|
# type: (nodes.Node) -> bool
|
||||||
"""Determine whether the node is in a section title"""
|
"""Determine whether the node is in a section title"""
|
||||||
from sphinx.util.nodes import traverse_parent
|
from sphinx.util.nodes import traverse_parent
|
||||||
|
|
||||||
|
warnings.warn('is_in_section_title() is deprecated.',
|
||||||
|
RemovedInSphinx30Warning)
|
||||||
|
|
||||||
for ancestor in traverse_parent(node):
|
for ancestor in traverse_parent(node):
|
||||||
if isinstance(ancestor, nodes.title) and \
|
if isinstance(ancestor, nodes.title) and \
|
||||||
isinstance(ancestor.parent, nodes.section):
|
isinstance(ancestor.parent, nodes.section):
|
||||||
@ -213,181 +96,9 @@ def is_in_section_title(node):
|
|||||||
return False
|
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_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']:
|
|
||||||
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'])
|
|
||||||
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 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()
|
|
||||||
self.add_text(node['latex'])
|
|
||||||
self.end_state()
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
def man_depart_displaymath(self, node):
|
|
||||||
# type: (nodes.NodeVisitor, displaymath) -> None
|
|
||||||
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'):
|
|
||||||
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):
|
def setup_math(app, htmlinlinevisitors, htmldisplayvisitors):
|
||||||
# type: (Sphinx, Tuple[Callable, Any], Tuple[Callable, Any]) -> None
|
# 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,
|
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)
|
html=htmlinlinevisitors)
|
||||||
app.add_node(displaymath,
|
app.add_node(displaymath, override=True,
|
||||||
latex=(latex_visit_displaymath, None),
|
|
||||||
text=(text_visit_displaymath, None),
|
|
||||||
man=(man_visit_displaymath, man_depart_displaymath),
|
|
||||||
texinfo=(texinfo_visit_displaymath, texinfo_depart_displaymath),
|
|
||||||
html=htmldisplayvisitors)
|
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)
|
|
||||||
|
@ -29,7 +29,7 @@ def html_visit_math(self, node):
|
|||||||
# type: (nodes.NodeVisitor, nodes.Node) -> None
|
# type: (nodes.NodeVisitor, nodes.Node) -> None
|
||||||
self.body.append(self.starttag(node, 'span', '', CLASS='math notranslate nohighlight'))
|
self.body.append(self.starttag(node, 'span', '', CLASS='math notranslate nohighlight'))
|
||||||
self.body.append(self.builder.config.mathjax_inline[0] +
|
self.body.append(self.builder.config.mathjax_inline[0] +
|
||||||
self.encode(node['latex']) +
|
self.encode(node.astext()) +
|
||||||
self.builder.config.mathjax_inline[1] + '</span>')
|
self.builder.config.mathjax_inline[1] + '</span>')
|
||||||
raise nodes.SkipNode
|
raise nodes.SkipNode
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ def html_visit_displaymath(self, node):
|
|||||||
# type: (nodes.NodeVisitor, nodes.Node) -> None
|
# type: (nodes.NodeVisitor, nodes.Node) -> None
|
||||||
self.body.append(self.starttag(node, 'div', CLASS='math notranslate nohighlight'))
|
self.body.append(self.starttag(node, 'div', CLASS='math notranslate nohighlight'))
|
||||||
if node['nowrap']:
|
if node['nowrap']:
|
||||||
self.body.append(self.encode(node['latex']))
|
self.body.append(self.encode(node.astext()))
|
||||||
self.body.append('</div>')
|
self.body.append('</div>')
|
||||||
raise nodes.SkipNode
|
raise nodes.SkipNode
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ def html_visit_displaymath(self, node):
|
|||||||
self.add_permalink_ref(node, _('Permalink to this equation'))
|
self.add_permalink_ref(node, _('Permalink to this equation'))
|
||||||
self.body.append('</span>')
|
self.body.append('</span>')
|
||||||
self.body.append(self.builder.config.mathjax_display[0])
|
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
|
if len(parts) > 1: # Add alignment if there are more than 1 equation
|
||||||
self.body.append(r' \begin{align}\begin{aligned}')
|
self.body.append(r' \begin{align}\begin{aligned}')
|
||||||
for i, part in enumerate(parts):
|
for i, part in enumerate(parts):
|
||||||
|
@ -163,7 +163,7 @@ def make_app(test_params, monkeypatch):
|
|||||||
yield make
|
yield make
|
||||||
|
|
||||||
sys.path[:] = syspath
|
sys.path[:] = syspath
|
||||||
for app_ in apps:
|
for app_ in reversed(apps): # clean up applications from the new ones
|
||||||
app_.cleanup()
|
app_.cleanup()
|
||||||
|
|
||||||
|
|
||||||
|
86
sphinx/transforms/post_transforms/compat.py
Normal file
86
sphinx/transforms/post_transforms/compat.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
# -*- 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 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
|
||||||
|
|
||||||
|
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):
|
||||||
|
# case: old styled ``math`` node generated by old extensions
|
||||||
|
if len(node) == 0:
|
||||||
|
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,
|
||||||
|
}
|
@ -2542,13 +2542,51 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
|
|
||||||
def visit_math(self, node):
|
def visit_math(self, node):
|
||||||
# type: (nodes.Node) -> None
|
# type: (nodes.Node) -> None
|
||||||
logger.warning(__('using "math" markup without a Sphinx math extension '
|
if self.in_title:
|
||||||
'active, please use one of the math extensions '
|
self.body.append(r'\protect\(%s\protect\)' % node.astext())
|
||||||
'described at http://sphinx-doc.org/en/master/ext/math.html'),
|
else:
|
||||||
location=(self.curfilestack[-1], node.line))
|
self.body.append(r'\(%s\)' % node.astext())
|
||||||
raise nodes.SkipNode
|
raise nodes.SkipNode
|
||||||
|
|
||||||
visit_math_block = visit_math
|
def visit_math_block(self, node):
|
||||||
|
# type: (nodes.Node) -> None
|
||||||
|
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 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):
|
def unknown_visit(self, node):
|
||||||
# type: (nodes.Node) -> None
|
# type: (nodes.Node) -> None
|
||||||
|
@ -18,7 +18,7 @@ from docutils.writers.manpage import (
|
|||||||
|
|
||||||
import sphinx.util.docutils
|
import sphinx.util.docutils
|
||||||
from sphinx import addnodes
|
from sphinx import addnodes
|
||||||
from sphinx.locale import admonitionlabels, _, __
|
from sphinx.locale import admonitionlabels, _
|
||||||
from sphinx.util import logging
|
from sphinx.util import logging
|
||||||
from sphinx.util.i18n import format_date
|
from sphinx.util.i18n import format_date
|
||||||
|
|
||||||
@ -513,12 +513,19 @@ class ManualPageTranslator(BaseTranslator):
|
|||||||
|
|
||||||
def visit_math(self, node):
|
def visit_math(self, node):
|
||||||
# type: (nodes.Node) -> None
|
# type: (nodes.Node) -> None
|
||||||
logger.warning(__('using "math" markup without a Sphinx math extension '
|
pass
|
||||||
'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 depart_math(self, node):
|
||||||
|
# type: (nodes.Node) -> None
|
||||||
|
pass
|
||||||
|
|
||||||
|
def visit_math_block(self, node):
|
||||||
|
# type: (nodes.Node) -> None
|
||||||
|
self.visit_centered(node)
|
||||||
|
|
||||||
|
def depart_math_block(self, node):
|
||||||
|
# type: (nodes.Node) -> None
|
||||||
|
self.depart_centered(node)
|
||||||
|
|
||||||
def unknown_visit(self, node):
|
def unknown_visit(self, node):
|
||||||
# type: (nodes.Node) -> None
|
# type: (nodes.Node) -> None
|
||||||
|
@ -1731,9 +1731,13 @@ class TexinfoTranslator(nodes.NodeVisitor):
|
|||||||
|
|
||||||
def visit_math(self, node):
|
def visit_math(self, node):
|
||||||
# type: (nodes.Node) -> None
|
# type: (nodes.Node) -> None
|
||||||
logger.warning(__('using "math" markup without a Sphinx math extension '
|
self.body.append('@math{' + self.escape_arg(node.astext()) + '}')
|
||||||
'active, please use one of the math extensions '
|
|
||||||
'described at http://sphinx-doc.org/en/master/ext/math.html'))
|
|
||||||
raise nodes.SkipNode
|
raise nodes.SkipNode
|
||||||
|
|
||||||
visit_math_block = visit_math
|
def visit_math_block(self, node):
|
||||||
|
# type: (nodes.Node) -> 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.astext()))
|
||||||
|
raise nodes.SkipNode
|
||||||
|
@ -18,7 +18,7 @@ from docutils.utils import column_width
|
|||||||
from six.moves import zip_longest
|
from six.moves import zip_longest
|
||||||
|
|
||||||
from sphinx import addnodes
|
from sphinx import addnodes
|
||||||
from sphinx.locale import admonitionlabels, _, __
|
from sphinx.locale import admonitionlabels, _
|
||||||
from sphinx.util import logging
|
from sphinx.util import logging
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
@ -1179,13 +1179,19 @@ class TextTranslator(nodes.NodeVisitor):
|
|||||||
|
|
||||||
def visit_math(self, node):
|
def visit_math(self, node):
|
||||||
# type: (nodes.Node) -> None
|
# type: (nodes.Node) -> None
|
||||||
logger.warning(__('using "math" markup without a Sphinx math extension '
|
pass
|
||||||
'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
|
|
||||||
|
|
||||||
visit_math_block = visit_math
|
def depart_math(self, node):
|
||||||
|
# type: (nodes.Node) -> None
|
||||||
|
pass
|
||||||
|
|
||||||
|
def visit_math_block(self, node):
|
||||||
|
# type: (nodes.Node) -> None
|
||||||
|
self.new_state()
|
||||||
|
|
||||||
|
def depart_math_block(self, node):
|
||||||
|
# type: (nodes.Node) -> None
|
||||||
|
self.end_state()
|
||||||
|
|
||||||
def unknown_visit(self, node):
|
def unknown_visit(self, node):
|
||||||
# type: (nodes.Node) -> None
|
# type: (nodes.Node) -> None
|
||||||
|
22
tests/roots/test-ext-math-compat/conf.py
Normal file
22
tests/roots/test-ext-math-compat/conf.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from docutils.parsers.rst import Directive
|
||||||
|
|
||||||
|
from sphinx.ext.mathbase import math, displaymath
|
||||||
|
|
||||||
|
master_doc = 'index'
|
||||||
|
extensions = ['sphinx.ext.mathjax']
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
21
tests/roots/test-ext-math-compat/index.rst
Normal file
21
tests/roots/test-ext-math-compat/index.rst
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
.. my-math::
|
@ -173,25 +173,19 @@ def test_needs_sphinx(make_app_with_empty_project):
|
|||||||
make_app = make_app_with_empty_project
|
make_app = make_app_with_empty_project
|
||||||
# micro version
|
# micro version
|
||||||
app = make_app(confoverrides={'needs_sphinx': '1.3.3'}) # OK: less
|
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 = make_app(confoverrides={'needs_sphinx': '1.3.4'}) # OK: equals
|
||||||
app.cleanup()
|
|
||||||
with pytest.raises(VersionRequirementError):
|
with pytest.raises(VersionRequirementError):
|
||||||
make_app(confoverrides={'needs_sphinx': '1.3.5'}) # NG: greater
|
make_app(confoverrides={'needs_sphinx': '1.3.5'}) # NG: greater
|
||||||
|
|
||||||
# minor version
|
# minor version
|
||||||
app = make_app(confoverrides={'needs_sphinx': '1.2'}) # OK: less
|
app = make_app(confoverrides={'needs_sphinx': '1.2'}) # OK: less
|
||||||
app.cleanup()
|
|
||||||
app = make_app(confoverrides={'needs_sphinx': '1.3'}) # OK: equals
|
app = make_app(confoverrides={'needs_sphinx': '1.3'}) # OK: equals
|
||||||
app.cleanup()
|
|
||||||
with pytest.raises(VersionRequirementError):
|
with pytest.raises(VersionRequirementError):
|
||||||
make_app(confoverrides={'needs_sphinx': '1.4'}) # NG: greater
|
make_app(confoverrides={'needs_sphinx': '1.4'}) # NG: greater
|
||||||
|
|
||||||
# major version
|
# major version
|
||||||
app = make_app(confoverrides={'needs_sphinx': '0'}) # OK: less
|
app = make_app(confoverrides={'needs_sphinx': '0'}) # OK: less
|
||||||
app.cleanup()
|
|
||||||
app = make_app(confoverrides={'needs_sphinx': '1'}) # OK: equals
|
app = make_app(confoverrides={'needs_sphinx': '1'}) # OK: equals
|
||||||
app.cleanup()
|
|
||||||
with pytest.raises(VersionRequirementError):
|
with pytest.raises(VersionRequirementError):
|
||||||
make_app(confoverrides={'needs_sphinx': '2'}) # NG: greater
|
make_app(confoverrides={'needs_sphinx': '2'}) # NG: greater
|
||||||
|
|
||||||
|
@ -12,8 +12,12 @@
|
|||||||
import errno
|
import errno
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from docutils import nodes
|
||||||
|
|
||||||
|
from sphinx.testing.util import assert_node
|
||||||
|
|
||||||
|
|
||||||
def has_binary(binary):
|
def has_binary(binary):
|
||||||
@ -208,3 +212,28 @@ def test_imgmath_numfig_html(app, status, warning):
|
|||||||
'href="math.html#equation-foo">(1)</a> and '
|
'href="math.html#equation-foo">(1)</a> and '
|
||||||
'<a class="reference internal" href="#equation-bar">(3)</a>.</p>')
|
'<a class="reference internal" href="#equation-bar">(3)</a>.</p>')
|
||||||
assert html in content
|
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"]))
|
||||||
|
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"]))
|
||||||
|
Loading…
Reference in New Issue
Block a user