mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Implement math_renderer framework
This commit is contained in:
2
CHANGES
2
CHANGES
@@ -102,6 +102,7 @@ Deprecated
|
||||
* ``sphinx.ext.mathbase.eqref`` node is deprecated
|
||||
* ``sphinx.ext.mathbase.is_in_section_title()`` is deprecated
|
||||
* ``sphinx.ext.mathbase.MathDomain`` is deprecated
|
||||
* ``sphinx.ext.mathbase.setup_math()`` is deprecated
|
||||
|
||||
For more details, see `deprecation APIs list
|
||||
<http://www.sphinx-doc.org/en/master/extdev/index.html#deprecated-apis>`_
|
||||
@@ -163,6 +164,7 @@ Features added
|
||||
* #4976: ``SphinxLoggerAdapter.info()`` now supports ``location`` parameter
|
||||
* #5122: setuptools: support nitpicky option
|
||||
* #2820: autoclass directive supports nested class
|
||||
* Add ``app.add_html_math_renderer()`` to register a math renderer for HTML
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
@@ -93,6 +93,8 @@ package.
|
||||
|
||||
.. automethod:: Sphinx.add_html_theme(name, theme_path)
|
||||
|
||||
.. automethod:: Sphinx.add_html_math_renderer(name, inline_renderers, block_renderers)
|
||||
|
||||
.. automethod:: Sphinx.add_message_catalog(catalog, locale_dir)
|
||||
|
||||
.. automethod:: Sphinx.is_parallel_allowed(typ)
|
||||
|
||||
@@ -136,6 +136,11 @@ The following is a list of deprecated interface.
|
||||
- 3.0
|
||||
- ``sphinx.domains.math.MathDomain``
|
||||
|
||||
* - ``sphinx.ext.mathbase.setup_math()``
|
||||
- 1.8
|
||||
- 3.0
|
||||
- :meth:`~sphinx.application.Sphinx.add_html_math_renderer()`
|
||||
|
||||
* - ``sphinx.ext.mathbase.is_in_section_title()``
|
||||
- 1.8
|
||||
- 3.0
|
||||
|
||||
@@ -1220,6 +1220,20 @@ class Sphinx(object):
|
||||
logger.debug('[app] adding HTML theme: %r, %r', name, theme_path)
|
||||
self.html_themes[name] = theme_path
|
||||
|
||||
def add_html_math_renderer(self, name, inline_renderers=None, block_renderers=None):
|
||||
# type: (unicode, Tuple[Callable, Callable], Tuple[Callable, Callable]) -> None
|
||||
"""Register a math renderer for HTML.
|
||||
|
||||
The *name* is a name of the math renderer. Both *inline_renderers* and
|
||||
*block_renderes* are used as visitor functions for HTML writer.
|
||||
*inline_renderers* is used for inline math node (``nodes.math`)). The
|
||||
another is used for block math node (``nodes.math_block``). About
|
||||
visitor functions, see :meth:`add_node` for more details.
|
||||
|
||||
.. versionadded:: 1.8
|
||||
"""
|
||||
self.registry.add_html_math_renderer(name, inline_renderers, block_renderers)
|
||||
|
||||
def add_message_catalog(self, catalog, locale_dir):
|
||||
# type: (unicode, unicode) -> None
|
||||
"""Register a message catalog.
|
||||
|
||||
@@ -36,7 +36,7 @@ from sphinx.deprecation import RemovedInSphinx20Warning, RemovedInSphinx30Warnin
|
||||
from sphinx.environment.adapters.asset import ImageAdapter
|
||||
from sphinx.environment.adapters.indexentries import IndexEntries
|
||||
from sphinx.environment.adapters.toctree import TocTree
|
||||
from sphinx.errors import ThemeError
|
||||
from sphinx.errors import ConfigError, ThemeError
|
||||
from sphinx.highlighting import PygmentsBridge
|
||||
from sphinx.locale import _, __
|
||||
from sphinx.search import js_index
|
||||
@@ -438,6 +438,27 @@ class StandaloneHTMLBuilder(Builder):
|
||||
else:
|
||||
return HTMLTranslator
|
||||
|
||||
@property
|
||||
def math_renderer_name(self):
|
||||
# type: () -> unicode
|
||||
name = self.get_builder_config('math_renderer', 'html')
|
||||
if name is not None:
|
||||
# use given name
|
||||
return name
|
||||
else:
|
||||
# not given: choose a math_renderer from registered ones as possible
|
||||
renderers = list(self.app.registry.html_inline_math_renderers)
|
||||
if len(renderers) == 1:
|
||||
# only default math_renderer (mathjax) is registered
|
||||
return renderers[0]
|
||||
elif len(renderers) == 2:
|
||||
# default and another math_renderer are registered; prior the another
|
||||
renderers.remove('mathjax')
|
||||
return renderers[0]
|
||||
else:
|
||||
# many math_renderers are registered. can't choose automatically!
|
||||
return None
|
||||
|
||||
def get_outdated_docs(self):
|
||||
# type: () -> Iterator[unicode]
|
||||
try:
|
||||
@@ -1625,6 +1646,19 @@ def setup_js_tag_helper(app, pagename, templatexname, context, doctree):
|
||||
context['js_tag'] = js_tag
|
||||
|
||||
|
||||
def validate_math_renderer(app):
|
||||
# type: (Sphinx) -> None
|
||||
if app.builder.format != 'html':
|
||||
return
|
||||
|
||||
name = app.builder.math_renderer_name # type: ignore
|
||||
if name is None:
|
||||
raise ConfigError(__('Many math_renderers are registered. '
|
||||
'But no math_renderer is selected.'))
|
||||
elif name not in app.registry.html_inline_math_renderers:
|
||||
raise ConfigError(__('Unknown math_renderer %r is given.') % name)
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[unicode, Any]
|
||||
# builders
|
||||
@@ -1674,12 +1708,17 @@ def setup(app):
|
||||
app.add_config_value('html_scaled_image_link', True, 'html')
|
||||
app.add_config_value('html_experimental_html5_writer', None, 'html')
|
||||
app.add_config_value('html_baseurl', '', 'html')
|
||||
app.add_config_value('html_math_renderer', None, 'env')
|
||||
|
||||
# event handlers
|
||||
app.connect('config-inited', convert_html_css_files)
|
||||
app.connect('config-inited', convert_html_js_files)
|
||||
app.connect('builder-inited', validate_math_renderer)
|
||||
app.connect('html-page-context', setup_js_tag_helper)
|
||||
|
||||
# load default math renderer
|
||||
app.setup_extension('sphinx.ext.mathjax')
|
||||
|
||||
return {
|
||||
'version': 'builtin',
|
||||
'parallel_read_safe': True,
|
||||
|
||||
@@ -22,9 +22,9 @@ from docutils import nodes
|
||||
from six import text_type
|
||||
|
||||
import sphinx
|
||||
from sphinx.errors import SphinxError, ExtensionError
|
||||
from sphinx.errors import SphinxError
|
||||
from sphinx.ext.mathbase import get_node_equation_number
|
||||
from sphinx.ext.mathbase import setup_math as mathbase_setup, wrap_displaymath
|
||||
from sphinx.ext.mathbase import wrap_displaymath
|
||||
from sphinx.locale import _, __
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.osutil import ensuredir, ENOENT, cd
|
||||
@@ -349,10 +349,9 @@ def html_visit_displaymath(self, node):
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[unicode, Any]
|
||||
try:
|
||||
mathbase_setup(app, (html_visit_math, None), (html_visit_displaymath, None))
|
||||
except ExtensionError:
|
||||
raise ExtensionError('sphinx.ext.imgmath: other math package is already loaded')
|
||||
app.add_html_math_renderer('imgmath',
|
||||
(html_visit_math, None),
|
||||
(html_visit_displaymath, None))
|
||||
|
||||
app.add_config_value('imgmath_image_format', 'png', 'html')
|
||||
app.add_config_value('imgmath_dvipng', 'dvipng', 'html')
|
||||
|
||||
@@ -15,7 +15,6 @@ from docutils import nodes
|
||||
import sphinx
|
||||
from sphinx.errors import ExtensionError
|
||||
from sphinx.ext.mathbase import get_node_equation_number
|
||||
from sphinx.ext.mathbase import setup_math as mathbase_setup
|
||||
from sphinx.locale import _
|
||||
|
||||
if False:
|
||||
@@ -61,7 +60,9 @@ def html_visit_displaymath(self, node):
|
||||
|
||||
def builder_inited(app):
|
||||
# type: (Sphinx) -> None
|
||||
if not app.config.jsmath_path:
|
||||
if app.builder.format != 'html' or app.builder.math_renderer_name != 'jsmath': # type: ignore # NOQA
|
||||
pass
|
||||
elif not app.config.jsmath_path:
|
||||
raise ExtensionError('jsmath_path config value must be set for the '
|
||||
'jsmath extension to work')
|
||||
if app.builder.format == 'html':
|
||||
@@ -70,10 +71,9 @@ def builder_inited(app):
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[unicode, Any]
|
||||
try:
|
||||
mathbase_setup(app, (html_visit_math, None), (html_visit_displaymath, None))
|
||||
except ExtensionError:
|
||||
raise ExtensionError('sphinx.ext.jsmath: other math package is already loaded')
|
||||
app.add_html_math_renderer('jsmath',
|
||||
(html_visit_math, None),
|
||||
(html_visit_displaymath, None))
|
||||
|
||||
app.add_config_value('jsmath_path', '', False)
|
||||
app.connect('builder-inited', builder_inited)
|
||||
|
||||
@@ -13,7 +13,7 @@ import warnings
|
||||
|
||||
from docutils import nodes
|
||||
|
||||
from sphinx.addnodes import math, math_block as displaymath
|
||||
from sphinx.addnodes import math, math_block as displaymath # NOQA # to keep compatibility
|
||||
from sphinx.builders.latex.nodes import math_reference as eqref # NOQA # to keep compatibility
|
||||
from sphinx.deprecation import RemovedInSphinx30Warning
|
||||
from sphinx.domains.math import MathDomain # NOQA # to keep compatibility
|
||||
@@ -44,7 +44,7 @@ def get_node_equation_number(writer, node):
|
||||
return number
|
||||
|
||||
|
||||
def wrap_displaymath(math, label, numbering):
|
||||
def wrap_displaymath(text, label, numbering):
|
||||
# type: (unicode, unicode, bool) -> unicode
|
||||
def is_equation(part):
|
||||
# type: (unicode) -> unicode
|
||||
@@ -56,7 +56,7 @@ def wrap_displaymath(math, label, numbering):
|
||||
labeldef = r'\label{%s}' % label
|
||||
numbering = True
|
||||
|
||||
parts = list(filter(is_equation, math.split('\n\n')))
|
||||
parts = list(filter(is_equation, text.split('\n\n')))
|
||||
equations = []
|
||||
if len(parts) == 0:
|
||||
return ''
|
||||
@@ -97,8 +97,9 @@ def is_in_section_title(node):
|
||||
|
||||
|
||||
def setup_math(app, htmlinlinevisitors, htmldisplayvisitors):
|
||||
# type: (Sphinx, Tuple[Callable, Any], Tuple[Callable, Any]) -> None
|
||||
app.add_node(math, override=True,
|
||||
html=htmlinlinevisitors)
|
||||
app.add_node(displaymath, override=True,
|
||||
html=htmldisplayvisitors)
|
||||
# type: (Sphinx, Tuple[Callable, Callable], Tuple[Callable, Callable]) -> None
|
||||
warnings.warn('setup_math() is deprecated. '
|
||||
'Please use app.add_html_math_renderer() instead.',
|
||||
RemovedInSphinx30Warning)
|
||||
|
||||
app.add_html_math_renderer('unknown', htmlinlinevisitors, htmldisplayvisitors)
|
||||
|
||||
@@ -16,7 +16,6 @@ from docutils import nodes
|
||||
import sphinx
|
||||
from sphinx.errors import ExtensionError
|
||||
from sphinx.ext.mathbase import get_node_equation_number
|
||||
from sphinx.ext.mathbase import setup_math as mathbase_setup
|
||||
from sphinx.locale import _
|
||||
|
||||
if False:
|
||||
@@ -69,7 +68,9 @@ def html_visit_displaymath(self, node):
|
||||
|
||||
def builder_inited(app):
|
||||
# type: (Sphinx) -> None
|
||||
if not app.config.mathjax_path:
|
||||
if app.builder.format != 'html' or app.builder.math_renderer_name != 'mathjax': # type: ignore # NOQA
|
||||
pass
|
||||
elif not app.config.mathjax_path:
|
||||
raise ExtensionError('mathjax_path config value must be set for the '
|
||||
'mathjax extension to work')
|
||||
if app.builder.format == 'html':
|
||||
@@ -81,10 +82,9 @@ def builder_inited(app):
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[unicode, Any]
|
||||
try:
|
||||
mathbase_setup(app, (html_visit_math, None), (html_visit_displaymath, None))
|
||||
except ExtensionError:
|
||||
raise ExtensionError('sphinx.ext.mathjax: other math package is already loaded')
|
||||
app.add_html_math_renderer('mathjax',
|
||||
(html_visit_math, None),
|
||||
(html_visit_displaymath, None))
|
||||
|
||||
# more information for mathjax secure url is here:
|
||||
# https://docs.mathjax.org/en/latest/start.html#secure-access-to-the-cdn
|
||||
|
||||
@@ -92,6 +92,11 @@ class SphinxComponentRegistry(object):
|
||||
#: a dict of node class -> tuple of figtype and title_getter function
|
||||
self.enumerable_nodes = {} # type: Dict[nodes.Node, Tuple[unicode, TitleGetter]]
|
||||
|
||||
#: HTML inline and block math renderers
|
||||
#: a dict of name -> tuple of visit function and depart function
|
||||
self.html_inline_math_renderers = {} # type: Dict[unicode, Tuple[Callable, Callable]] # NOQA
|
||||
self.html_block_math_renderers = {} # type: Dict[unicode, Tuple[Callable, Callable]] # NOQA
|
||||
|
||||
#: js_files; list of JS paths or URLs
|
||||
self.js_files = [] # type: List[Tuple[unicode, Dict[unicode, unicode]]]
|
||||
|
||||
@@ -439,6 +444,16 @@ class SphinxComponentRegistry(object):
|
||||
raise ExtensionError(__('enumerable_node %r already registered') % node)
|
||||
self.enumerable_nodes[node] = (figtype, title_getter)
|
||||
|
||||
def add_html_math_renderer(self, name, inline_renderers, block_renderers):
|
||||
# type: (unicode, Tuple[Callable, Callable], Tuple[Callable, Callable]) -> None
|
||||
logger.debug('[app] adding html_math_renderer: %s, %r, %r',
|
||||
name, inline_renderers, block_renderers)
|
||||
if name in self.html_inline_math_renderers:
|
||||
raise ExtensionError(__('math renderer %s is already registred') % name)
|
||||
|
||||
self.html_inline_math_renderers[name] = inline_renderers
|
||||
self.html_block_math_renderers[name] = block_renderers
|
||||
|
||||
def load_extension(self, app, extname):
|
||||
# type: (Sphinx, unicode) -> None
|
||||
"""Load a Sphinx extension."""
|
||||
|
||||
@@ -874,11 +874,29 @@ class HTMLTranslator(BaseTranslator):
|
||||
|
||||
def visit_math(self, node, math_env=''):
|
||||
# type: (nodes.Node, unicode) -> 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
|
||||
name = self.builder.math_renderer_name
|
||||
visit, _ = self.builder.app.registry.html_inline_math_renderers[name]
|
||||
visit(self, node)
|
||||
|
||||
def depart_math(self, node, math_env=''):
|
||||
# type: (nodes.Node, unicode) -> None
|
||||
name = self.builder.math_renderer_name
|
||||
_, depart = self.builder.app.registry.html_inline_math_renderers[name]
|
||||
if depart:
|
||||
depart(self, node)
|
||||
|
||||
def visit_math_block(self, node, math_env=''):
|
||||
# type: (nodes.Node, unicode) -> None
|
||||
name = self.builder.math_renderer_name
|
||||
visit, _ = self.builder.app.registry.html_block_math_renderers[name]
|
||||
visit(self, node)
|
||||
|
||||
def depart_math_block(self, node, math_env=''):
|
||||
# type: (nodes.Node, unicode) -> None
|
||||
name = self.builder.math_renderer_name
|
||||
_, depart = self.builder.app.registry.html_block_math_renderers[name]
|
||||
if depart:
|
||||
depart(self, node)
|
||||
|
||||
def unknown_visit(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
|
||||
@@ -825,11 +825,29 @@ class HTML5Translator(BaseTranslator):
|
||||
|
||||
def visit_math(self, node, math_env=''):
|
||||
# type: (nodes.Node, unicode) -> 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
|
||||
name = self.builder.math_renderer_name
|
||||
visit, _ = self.builder.app.registry.html_inline_math_renderers[name]
|
||||
visit(self, node)
|
||||
|
||||
def depart_math(self, node, math_env=''):
|
||||
# type: (nodes.Node, unicode) -> None
|
||||
name = self.builder.math_renderer_name
|
||||
_, depart = self.builder.app.registry.html_inline_math_renderers[name]
|
||||
if depart:
|
||||
depart(self, node)
|
||||
|
||||
def visit_math_block(self, node, math_env=''):
|
||||
# type: (nodes.Node, unicode) -> None
|
||||
name = self.builder.math_renderer_name
|
||||
visit, _ = self.builder.app.registry.html_block_math_renderers[name]
|
||||
visit(self, node)
|
||||
|
||||
def depart_math_block(self, node, math_env=''):
|
||||
# type: (nodes.Node, unicode) -> None
|
||||
name = self.builder.math_renderer_name
|
||||
_, depart = self.builder.app.registry.html_block_math_renderers[name]
|
||||
if depart:
|
||||
depart(self, node)
|
||||
|
||||
def unknown_visit(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
|
||||
@@ -18,6 +18,7 @@ import pytest
|
||||
from html5lib import getTreeBuilder, HTMLParser
|
||||
from six import PY3
|
||||
|
||||
from sphinx.errors import ConfigError
|
||||
from sphinx.testing.util import remove_unicode_literals, strip_escseq
|
||||
from sphinx.util.inventory import InventoryFile
|
||||
|
||||
@@ -1327,3 +1328,62 @@ def test_html_baseurl_and_html_file_suffix(app, status, warning):
|
||||
|
||||
result = (app.outdir / 'qux' / 'index.htm').text(encoding='utf8')
|
||||
assert '<link rel="canonical" href="https://example.com/subdir/qux/index.htm" />' in result
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='basic')
|
||||
def test_default_html_math_renderer(app, status, warning):
|
||||
assert app.builder.math_renderer_name == 'mathjax'
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='basic',
|
||||
confoverrides={'extensions': ['sphinx.ext.mathjax']})
|
||||
def test_html_math_renderer_is_mathjax(app, status, warning):
|
||||
assert app.builder.math_renderer_name == 'mathjax'
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='basic',
|
||||
confoverrides={'extensions': ['sphinx.ext.imgmath']})
|
||||
def test_html_math_renderer_is_imgmath(app, status, warning):
|
||||
assert app.builder.math_renderer_name == 'imgmath'
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='basic',
|
||||
confoverrides={'extensions': ['sphinx.ext.jsmath',
|
||||
'sphinx.ext.imgmath']})
|
||||
def test_html_math_renderer_is_duplicated(make_app, app_params):
|
||||
try:
|
||||
args, kwargs = app_params
|
||||
make_app(*args, **kwargs)
|
||||
assert False
|
||||
except ConfigError as exc:
|
||||
assert str(exc) == ('Many math_renderers are registered. '
|
||||
'But no math_renderer is selected.')
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='basic',
|
||||
confoverrides={'extensions': ['sphinx.ext.imgmath',
|
||||
'sphinx.ext.mathjax']})
|
||||
def test_html_math_renderer_is_duplicated2(app, status, warning):
|
||||
# case of both mathjax and another math_renderer is loaded
|
||||
assert app.builder.math_renderer_name == 'imgmath' # The another one is chosen
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='basic',
|
||||
confoverrides={'extensions': ['sphinx.ext.jsmath',
|
||||
'sphinx.ext.imgmath'],
|
||||
'html_math_renderer': 'imgmath'})
|
||||
def test_html_math_renderer_is_chosen(app, status, warning):
|
||||
assert app.builder.math_renderer_name == 'imgmath'
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='basic',
|
||||
confoverrides={'extensions': ['sphinx.ext.jsmath',
|
||||
'sphinx.ext.mathjax'],
|
||||
'html_math_renderer': 'imgmath'})
|
||||
def test_html_math_renderer_is_mismatched(make_app, app_params):
|
||||
try:
|
||||
args, kwargs = app_params
|
||||
make_app(*args, **kwargs)
|
||||
assert False
|
||||
except ConfigError as exc:
|
||||
assert str(exc) == "Unknown math_renderer 'imgmath' is given."
|
||||
|
||||
Reference in New Issue
Block a user