Merge branch '5.x'

# Conflicts:
#	sphinx/locale/__init__.py
This commit is contained in:
Adam Turner 2022-09-30 16:15:24 +01:00
commit 63dea6172a
27 changed files with 184 additions and 73 deletions

View File

@ -1,16 +0,0 @@
version: 2
jobs:
build:
docker:
- image: sphinxdoc/docker-ci
environment:
DO_EPUBCHECK: 1
working_directory: /sphinx
steps:
- checkout
- run: /python3.8/bin/pip install -U pip setuptools
- run: /python3.8/bin/pip install -U .[test]
- run: mkdir -p test-reports/pytest
- run: make test PYTHON=/python3.8/bin/python TEST="--junitxml=test-reports/pytest/results.xml -vv"
- store_test_results:
path: test-reports

24
.github/workflows/latex.yml vendored Normal file
View File

@ -0,0 +1,24 @@
name: CI (LaTeX)
on: [push, pull_request]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-18.04
name: Python 3.8
container:
image: sphinxdoc/docker-ci
env:
DO_EPUBCHECK: 1
PATH: /python3.8/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
steps:
- uses: actions/checkout@v3
- name: Check Python version
run: python --version
- name: Install dependencies
run: pip install -U pip tox
- name: Run Tox
run: tox -e py -- -vv

10
CHANGES
View File

@ -38,12 +38,22 @@ Deprecated
Features added
--------------
* #10840: One can cross-reference including an option value: ``:option:`--module=foobar```.
Patch by Martin Liska.
Bugs fixed
----------
Testing
--------
Release 5.2.3 (released Sep 30, 2022)
=====================================
* #10878: Fix base64 image embedding in ``sphinx.ext.imgmath``
* #10886: Add ``:nocontentsentry:`` flag and global domain table of contents
entry control option. Patch by Adam Turner
Release 5.2.2 (released Sep 27, 2022)
=====================================

View File

@ -233,7 +233,7 @@ Keys that you may want to override include:
.. code-block:: python
latex_elements = {
'packages': r'\usepackage{isodate}'
'extrapackages': r'\usepackage{isodate}'
}
The specified LaTeX packages will be loaded before

View File

@ -678,6 +678,11 @@ General configuration
:term:`object` names (for object types where a "module" of some kind is
defined), e.g. for :rst:dir:`py:function` directives. Default is ``True``.
.. confval:: toc_object_entries
Create table of contents entries for domain objects (e.g. functions, classes,
attributes, etc.). Default is ``True``.
.. confval:: toc_object_entries_show_parents
A string that determines how domain objects (e.g. functions, classes,

View File

@ -42,11 +42,15 @@ Basic Markup
Most domains provide a number of :dfn:`object description directives`, used to
describe specific objects provided by modules. Each directive requires one or
more signatures to provide basic information about what is being described, and
the content should be the description. A domain will typically keep an
internal index of all entities to aid cross-referencing. Typically it will
also add entries in the shown general index.
the content should be the description.
A domain will typically keep an internal index of all entities to aid
cross-referencing.
Typically it will also add entries in the shown general index.
If you want to suppress the addition of an entry in the shown index, you can
give the directive option flag ``:noindexentry:``.
If you want to exclude the object description from the table of contents, you
can give the directive option flag ``:nocontentsentry:``.
If you want to typeset an object description, without even making it available
for cross-referencing, you can give the directive option flag ``:noindex:``
(which implies ``:noindexentry:``).
@ -57,6 +61,10 @@ options.
The directive option ``noindexentry`` in the Python, C, C++, and Javascript
domains.
.. versionadded:: 5.2.3
The directive option ``:nocontentsentry:`` in the Python, C, C++, Javascript,
and reStructuredText domains.
An example using a Python domain directive::
.. py:function:: spam(eggs)
@ -851,15 +859,19 @@ Example::
This will be rendered as:
.. c:struct:: Data
:nocontentsentry:
:noindexentry:
.. c:union:: @data
:nocontentsentry:
:noindexentry:
.. c:var:: int a
:nocontentsentry:
:noindexentry:
.. c:var:: double b
:nocontentsentry:
:noindexentry:
Explicit ref: :c:var:`Data.@data.a`. Short-hand ref: :c:var:`Data.a`.
@ -942,9 +954,11 @@ Inline Expressions and Types
will be rendered as follows:
.. c:var:: int a = 42
:nocontentsentry:
:noindexentry:
.. c:function:: int f(int i)
:nocontentsentry:
:noindexentry:
An expression: :c:expr:`a * f(a)` (or as text: :c:texpr:`a * f(a)`).
@ -1155,23 +1169,27 @@ visibility statement (``public``, ``private`` or ``protected``).
The example are rendered as follows.
.. cpp:type:: std::vector<int> MyList
:noindex:
:nocontentsentry:
:noindexentry:
A typedef-like declaration of a type.
.. cpp:type:: MyContainer::const_iterator
:noindex:
:nocontentsentry:
:noindexentry:
Declaration of a type alias with unspecified type.
.. cpp:type:: MyType = std::unordered_map<int, std::string>
:noindex:
:nocontentsentry:
:noindexentry:
Declaration of a type alias.
.. cpp:type:: template<typename T> \
MyContainer = std::vector<T>
:noindex:
:nocontentsentry:
:noindexentry:
.. rst:directive:: .. cpp:enum:: unscoped enum declaration
.. cpp:enum-struct:: scoped enum declaration
@ -1266,7 +1284,7 @@ Options
Some directives support options:
- ``:noindexentry:``, see :ref:`basic-domain-markup`.
- ``:noindexentry:`` and ``:nocontentsentry:``, see :ref:`basic-domain-markup`.
- ``:tparam-line-spec:``, for templated declarations.
If specified, each template parameter will be rendered on a separate line.
@ -1298,15 +1316,19 @@ Example::
This will be rendered as:
.. cpp:class:: Data
:nocontentsentry:
:noindexentry:
.. cpp:union:: @data
:nocontentsentry:
:noindexentry:
.. cpp:var:: int a
:nocontentsentry:
:noindexentry:
.. cpp:var:: double b
:nocontentsentry:
:noindexentry:
Explicit ref: :cpp:var:`Data::@data::a`. Short-hand ref: :cpp:var:`Data::a`.
@ -1413,11 +1435,13 @@ introduction` instead of a template parameter list::
They are rendered as follows.
.. cpp:function:: std::Iterator{It} void advance(It &it)
:nocontentsentry:
:noindexentry:
A function template with a template parameter constrained to be an Iterator.
.. cpp:class:: std::LessThanComparable{T} MySortedContainer
:nocontentsentry:
:noindexentry:
A class template with a template parameter constrained to be
@ -1448,9 +1472,11 @@ Inline Expressions and Types
will be rendered as follows:
.. cpp:var:: int a = 42
:nocontentsentry:
:noindexentry:
.. cpp:function:: int f(int i)
:nocontentsentry:
:noindexentry:
An expression: :cpp:expr:`a * f(a)` (or as text: :cpp:texpr:`a * f(a)`).
@ -1764,6 +1790,10 @@ There is a set of directives allowing documenting command-line programs:
referenceable by :rst:role:`option` (in the example case, you'd use something
like ``:option:`dest_dir```, ``:option:`-m```, or ``:option:`--module```).
.. versionchanged:: 5.3
One can cross-reference including an option value: ``:option:`--module=foobar```.
Use :confval:`option_emphasise_placeholders` for parsing of
"variable part" of a literal text (similarly to the :rst:role:`samp` role).

View File

@ -183,18 +183,17 @@ strict_optional = false
[[tool.mypy.overrides]]
module = [
"sphinx.application",
"sphinx.builders.*",
"sphinx.cmd.*",
"sphinx.builders._epub_base",
"sphinx.builders.html",
"sphinx.builders.linkcheck",
"sphinx.cmd.quickstart",
"sphinx.config",
"sphinx.deprecation",
"sphinx.domains.*",
"sphinx.environment.*",
"sphinx.events",
"sphinx.ext.*",
"sphinx.highlighting",
"sphinx.jinja2glue",
"sphinx.locale",
"sphinx.pycode.*",
"sphinx.registry",
"sphinx.roles",
"sphinx.search.*",

View File

@ -201,7 +201,7 @@ class HyperlinkAvailabilityChecker:
self.config = config
self.env = env
self.rate_limits: Dict[str, RateLimit] = {}
self.rqueue: Queue = Queue()
self.rqueue: Queue[CheckResult] = Queue()
self.workers: List[Thread] = []
self.wqueue: PriorityQueue[CheckRequest] = PriorityQueue()
@ -246,8 +246,8 @@ class HyperlinkAvailabilityChecker:
class HyperlinkAvailabilityCheckWorker(Thread):
"""A worker class for checking the availability of hyperlinks."""
def __init__(self, env: BuildEnvironment, config: Config, rqueue: Queue,
wqueue: Queue, rate_limits: Dict[str, RateLimit]) -> None:
def __init__(self, env: BuildEnvironment, config: Config, rqueue: 'Queue[CheckResult]',
wqueue: 'Queue[CheckRequest]', rate_limits: Dict[str, RateLimit]) -> None:
self.config = config
self.env = env
self.rate_limits = rate_limits
@ -428,7 +428,7 @@ class HyperlinkAvailabilityCheckWorker(Thread):
uri, docname, lineno = hyperlink
except ValueError:
# old styled check_request (will be deprecated in Sphinx-5.0)
next_check, uri, docname, lineno = check_request
next_check, uri, docname, lineno = check_request # type: ignore[misc]
if uri is None:
break

View File

@ -9,7 +9,7 @@ import pdb
import sys
import traceback
from os import path
from typing import IO, Any, List, Optional, TextIO
from typing import Any, List, Optional, TextIO
from docutils.utils import SystemMessage
@ -25,7 +25,7 @@ from sphinx.util.osutil import abspath, ensuredir
def handle_exception(
app: Optional[Sphinx], args: Any, exception: BaseException, stderr: IO = sys.stderr
app: Optional[Sphinx], args: Any, exception: BaseException, stderr: TextIO = sys.stderr
) -> None:
if isinstance(exception, bdb.BdbQuit):
return

View File

@ -177,7 +177,7 @@ class QuickstartRenderer(SphinxRenderer):
else:
return False
def render(self, template_name: str, context: Dict) -> str:
def render(self, template_name: str, context: Dict[str, Any]) -> str:
if self._has_custom_template(template_name):
custom_template = path.join(self.templatedir, path.basename(template_name))
return self.render_from_file(custom_template, context)

View File

@ -106,6 +106,7 @@ class Config:
'default_role': (None, 'env', [str]),
'add_function_parentheses': (True, 'env', []),
'add_module_names': (True, 'env', []),
'toc_object_entries': (True, 'env', [bool]),
'toc_object_entries_show_parents': ('domain', 'env',
ENUM('domain', 'all', 'hide')),
'trim_footnote_reference_space': (False, 'env', []),

View File

@ -52,10 +52,10 @@ class _ModuleWrapper:
return self._objects[name]
class DeprecatedDict(dict):
class DeprecatedDict(Dict[str, Any]):
"""A deprecated dict which warns on each access."""
def __init__(self, data: Dict, message: str, warning: Type[Warning]) -> None:
def __init__(self, data: Dict[str, Any], message: str, warning: Type[Warning]) -> None:
self.message = message
self.warning = warning
super().__init__(data)
@ -68,7 +68,7 @@ class DeprecatedDict(dict):
warnings.warn(self.message, self.warning, stacklevel=2)
return super().setdefault(key, default)
def __getitem__(self, key: str) -> None:
def __getitem__(self, key: str) -> Any:
warnings.warn(self.message, self.warning, stacklevel=2)
return super().__getitem__(key)
@ -76,6 +76,6 @@ class DeprecatedDict(dict):
warnings.warn(self.message, self.warning, stacklevel=2)
return super().get(key, default)
def update(self, other: Dict) -> None: # type: ignore
def update(self, other: Dict[str, Any]) -> None: # type: ignore
warnings.warn(self.message, self.warning, stacklevel=2)
super().update(other)

View File

@ -51,6 +51,8 @@ class ObjectDescription(SphinxDirective, Generic[T]):
final_argument_whitespace = True
option_spec: OptionSpec = {
'noindex': directives.flag,
'noindexentry': directives.flag,
'nocontentsentry': directives.flag,
}
# types of doc fields that this directive handles, see sphinx.util.docfields
@ -211,6 +213,7 @@ class ObjectDescription(SphinxDirective, Generic[T]):
node['objtype'] = node['desctype'] = self.objtype
node['noindex'] = noindex = ('noindex' in self.options)
node['noindexentry'] = ('noindexentry' in self.options)
node['nocontentsentry'] = ('nocontentsentry' in self.options)
if self.domain:
node['classes'].append(self.domain)
node['classes'].append(node['objtype'])
@ -236,8 +239,12 @@ class ObjectDescription(SphinxDirective, Generic[T]):
finally:
# Private attributes for ToC generation. Will be modified or removed
# without notice.
signode['_toc_parts'] = self._object_hierarchy_parts(signode)
signode['_toc_name'] = self._toc_entry_name(signode)
if self.env.app.config.toc_object_entries:
signode['_toc_parts'] = self._object_hierarchy_parts(signode)
signode['_toc_name'] = self._toc_entry_name(signode)
else:
signode['_toc_parts'] = ()
signode['_toc_name'] = ''
if name not in self.names:
self.names.append(name)
if not noindex:

View File

@ -3142,8 +3142,8 @@ class CObject(ObjectDescription[ASTDeclaration]):
"""
option_spec: OptionSpec = {
'noindex': directives.flag,
'noindexentry': directives.flag,
'nocontentsentry': directives.flag,
}
def _add_enumerator_to_parent(self, ast: ASTDeclaration) -> None:

View File

@ -7186,8 +7186,8 @@ class CPPObject(ObjectDescription[ASTDeclaration]):
]
option_spec: OptionSpec = {
'noindex': directives.flag,
'noindexentry': directives.flag,
'nocontentsentry': directives.flag,
'tparam-line-spec': directives.flag,
}

View File

@ -40,6 +40,7 @@ class JSObject(ObjectDescription[Tuple[str, str]]):
option_spec: OptionSpec = {
'noindex': directives.flag,
'noindexentry': directives.flag,
'nocontentsentry': directives.flag,
}
def get_display_prefix(self) -> List[Node]:
@ -284,7 +285,8 @@ class JSModule(SphinxDirective):
optional_arguments = 0
final_argument_whitespace = False
option_spec: OptionSpec = {
'noindex': directives.flag
'noindex': directives.flag,
'nocontentsentry': directives.flag,
}
def run(self) -> List[Node]:

View File

@ -412,6 +412,7 @@ class PyObject(ObjectDescription[Tuple[str, str]]):
option_spec: OptionSpec = {
'noindex': directives.flag,
'noindexentry': directives.flag,
'nocontentsentry': directives.flag,
'module': directives.unchanged,
'canonical': directives.unchanged,
'annotation': directives.unchanged,
@ -978,6 +979,7 @@ class PyModule(SphinxDirective):
'platform': lambda x: x,
'synopsis': lambda x: x,
'noindex': directives.flag,
'nocontentsentry': directives.flag,
'deprecated': directives.flag,
}

View File

@ -31,6 +31,7 @@ class ReSTMarkup(ObjectDescription[str]):
option_spec: OptionSpec = {
'noindex': directives.flag,
'noindexentry': directives.flag,
'nocontentsentry': directives.flag,
}
def add_target_and_index(self, name: str, sig: str, signode: desc_signature) -> None:

View File

@ -780,7 +780,9 @@ class StandardDomain(Domain):
self.labels[name] = docname, labelid, sectname
def add_program_option(self, program: str, name: str, docname: str, labelid: str) -> None:
self.progoptions[program, name] = (docname, labelid)
# prefer first command option entry
if (program, name) not in self.progoptions:
self.progoptions[program, name] = (docname, labelid)
def build_reference_node(self, fromdocname: str, builder: "Builder", docname: str,
labelid: str, sectname: str, rolename: str, **options: Any
@ -941,6 +943,10 @@ class StandardDomain(Domain):
progname = node.get('std:program')
target = target.strip()
docname, labelid = self.progoptions.get((progname, target), ('', ''))
# for :option:`-foo=bar` search for -foo option directive
if not docname and '=' in target:
target2 = target[:target.find('=')]
docname, labelid = self.progoptions.get((progname, target2), ('', ''))
if not docname:
commands = []
while ws_re.search(target):

View File

@ -112,9 +112,12 @@ class TocTreeCollector(EnvironmentCollector):
# Skip if no name set
if not sig_node.get('_toc_name', ''):
continue
# Skip if explicitly disabled
if sig_node.parent.get('nocontentsentry'):
continue
# Skip entries with no ID (e.g. with :noindex: set)
ids = sig_node['ids']
if not ids or sig_node.parent.get('noindexentry'):
if not ids:
continue
anchorname = _make_anchor_name(ids, numentries)

View File

@ -308,7 +308,7 @@ def html_visit_math(self: HTMLTranslator, node: nodes.math) -> None:
raise nodes.SkipNode from exc
if self.builder.config.imgmath_embed:
image_format = self.builder.config.imgmath_image_format.lower()
img_src = render_maths_to_base64(image_format, outfn)
img_src = render_maths_to_base64(image_format, imgpath)
else:
# Move generated image on tempdir to build dir
if imgpath is not None:
@ -350,7 +350,7 @@ def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None
self.body.append('</span>')
if self.builder.config.imgmath_embed:
image_format = self.builder.config.imgmath_image_format.lower()
img_src = render_maths_to_base64(image_format, outfn)
img_src = render_maths_to_base64(image_format, imgpath)
else:
# Move generated image on tempdir to build dir
if imgpath is not None:

View File

@ -1,9 +1,8 @@
"""Locale utilities."""
import gettext
import locale
from collections import defaultdict
from gettext import NullTranslations
from gettext import NullTranslations, translation
from os import path
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
@ -17,7 +16,7 @@ class _TranslationProxy:
"""
__slots__ = ('_func', '_args')
def __new__(cls, func: Callable, *args: str) -> "_TranslationProxy":
def __new__(cls, func: Callable[..., str], *args: str) -> '_TranslationProxy':
if not args:
# not called with "function" and "arguments", but a plain string
return str(func) # type: ignore[return-value]
@ -26,7 +25,7 @@ class _TranslationProxy:
def __getnewargs__(self) -> Tuple[str]:
return (self._func,) + self._args # type: ignore
def __init__(self, func: Callable, *args: str) -> None:
def __init__(self, func: Callable[..., str], *args: str) -> None:
self._func = func
self._args = args
@ -39,13 +38,13 @@ class _TranslationProxy:
def __getattr__(self, name: str) -> Any:
return getattr(self.__str__(), name)
def __getstate__(self) -> Tuple[Callable, Tuple[str, ...]]:
def __getstate__(self) -> Tuple[Callable[..., str], Tuple[str, ...]]:
return self._func, self._args
def __setstate__(self, tup: Tuple[Callable, Tuple[str]]) -> None:
def __setstate__(self, tup: Tuple[Callable[..., str], Tuple[str]]) -> None:
self._func, self._args = tup
def __copy__(self) -> "_TranslationProxy":
def __copy__(self) -> '_TranslationProxy':
return _TranslationProxy(self._func, *self._args)
def __repr__(self) -> str:
@ -91,11 +90,15 @@ class _TranslationProxy:
return self.__str__()[index]
translators: Dict[Tuple[str, str], NullTranslations] = defaultdict(NullTranslations)
translators: Dict[Tuple[str, str], NullTranslations] = {}
def init(locale_dirs: List[Optional[str]], language: Optional[str],
catalog: str = 'sphinx', namespace: str = 'general') -> Tuple[NullTranslations, bool]:
def init(
locale_dirs: List[Optional[str]],
language: Optional[str],
catalog: str = 'sphinx',
namespace: str = 'general',
) -> Tuple[NullTranslations, bool]:
"""Look for message catalogs in `locale_dirs` and *ensure* that there is at
least a NullTranslations catalog set in `translators`. If called multiple
times or if several ``.mo`` files are found, their contents are merged
@ -120,7 +123,7 @@ def init(locale_dirs: List[Optional[str]], language: Optional[str],
# loading
for dir_ in locale_dirs:
try:
trans = gettext.translation(catalog, localedir=dir_, languages=languages)
trans = translation(catalog, localedir=dir_, languages=languages)
if translator is None:
translator = trans
else:
@ -148,7 +151,7 @@ def setlocale(category: int, value: Union[str, Iterable[str], None] = None) -> N
* https://bugs.python.org/issue18378#msg215215
.. note:: Only for internal use. Please don't call this method from extensions.
This will be removed in future.
This will be removed in Sphinx 6.0.
"""
try:
locale.setlocale(category, value)
@ -156,7 +159,13 @@ def setlocale(category: int, value: Union[str, Iterable[str], None] = None) -> N
pass
def init_console(locale_dir: str, catalog: str) -> Tuple[NullTranslations, bool]:
_LOCALE_DIR = path.abspath(path.dirname(__file__))
def init_console(
locale_dir: str = _LOCALE_DIR,
catalog: str = 'sphinx',
) -> Tuple[NullTranslations, bool]:
"""Initialize locale for console.
.. versionadded:: 1.8
@ -172,7 +181,7 @@ def init_console(locale_dir: str, catalog: str) -> Tuple[NullTranslations, bool]
def get_translator(catalog: str = 'sphinx', namespace: str = 'general') -> NullTranslations:
return translators[(namespace, catalog)]
return translators.get((namespace, catalog), NullTranslations())
def is_translator_registered(catalog: str = 'sphinx', namespace: str = 'general') -> bool:

View File

@ -5,9 +5,8 @@ import tokenize
from collections import OrderedDict
from importlib import import_module
from inspect import Signature
from io import StringIO
from os import path
from typing import IO, Any, Dict, List, Optional, Tuple
from typing import Any, Dict, List, Optional, Tuple
from zipfile import ZipFile
from sphinx.errors import PycodeError
@ -76,7 +75,7 @@ class ModuleAnalyzer:
@classmethod
def for_string(cls, string: str, modname: str, srcname: str = '<string>'
) -> "ModuleAnalyzer":
return cls(StringIO(string), modname, srcname)
return cls(string, modname, srcname)
@classmethod
def for_file(cls, filename: str, modname: str) -> "ModuleAnalyzer":
@ -84,8 +83,9 @@ class ModuleAnalyzer:
return cls.cache['file', filename]
try:
with tokenize.open(filename) as f:
obj = cls(f, modname, filename)
cls.cache['file', filename] = obj
string = f.read()
obj = cls(string, modname, filename)
cls.cache['file', filename] = obj
except Exception as err:
if '.egg' + path.sep in filename:
obj = cls.cache['file', filename] = cls.for_egg(filename, modname)
@ -124,12 +124,12 @@ class ModuleAnalyzer:
cls.cache['module', modname] = obj
return obj
def __init__(self, source: IO, modname: str, srcname: str) -> None:
def __init__(self, source: str, modname: str, srcname: str) -> None:
self.modname = modname # name of the module
self.srcname = srcname # name of the source file
# cache the source code as well
self.code = source.read()
self.code = source
self._analyzed = False

View File

@ -463,7 +463,7 @@ class DefinitionFinder(TokenProcessor):
super().__init__(lines)
self.decorator: Optional[Token] = None
self.context: List[str] = []
self.indents: List = []
self.indents: List[Tuple[str, Optional[str], Optional[int]]] = []
self.definitions: Dict[str, Tuple[str, int, int]] = {}
def add_definition(self, name: str, entry: Tuple[str, int, int]) -> None:

View File

@ -204,6 +204,19 @@ Link to :option:`hg commit` and :option:`git commit -p`.
Foo bar.
Test repeated option directive.
.. option:: -mapi
My API.
.. option:: -mapi=secret
My secret API.
Reference the first option :option:`-mapi=secret`.
User markup
===========

View File

@ -1709,6 +1709,15 @@ def test_option_emphasise_placeholders_default(app, status, warning):
'<a class="headerlink" href="#cmdoption-perl-plugin.option" title="Permalink to this definition">¶</a></dt>') in content
@pytest.mark.sphinx('html', testroot='root')
def test_option_reference_with_value(app, status, warning):
app.build()
content = (app.outdir / 'objects.html').read_text()
assert ('<span class="pre">-mapi</span></span><span class="sig-prename descclassname">'
'</span><a class="headerlink" href="#cmdoption-git-commit-mapi"') in content
assert 'first option <a class="reference internal" href="#cmdoption-git-commit-mapi">' in content
@pytest.mark.sphinx('html', testroot='theming')
def test_theme_options(app, status, warning):
app.build()

View File

@ -1,6 +1,7 @@
"""Test math extensions."""
import re
import shutil
import subprocess
import warnings
@ -33,6 +34,7 @@ def test_imgmath_png(app, status, warning):
raise pytest.skip.Exception('dvipng command "dvipng" is not available')
content = (app.outdir / 'index.html').read_text(encoding='utf8')
shutil.rmtree(app.outdir)
html = (r'<div class="math">\s*<p>\s*<img src="_images/math/\w+.png"'
r'\s*alt="a\^2\+b\^2=c\^2"/>\s*</p>\s*</div>')
assert re.search(html, content, re.S)
@ -51,6 +53,7 @@ def test_imgmath_svg(app, status, warning):
raise pytest.skip.Exception('dvisvgm command "dvisvgm" is not available')
content = (app.outdir / 'index.html').read_text(encoding='utf8')
shutil.rmtree(app.outdir)
html = (r'<div class="math">\s*<p>\s*<img src="_images/math/\w+.svg"'
r'\s*alt="a\^2\+b\^2=c\^2"/>\s*</p>\s*</div>')
assert re.search(html, content, re.S)
@ -70,6 +73,7 @@ def test_imgmath_svg_embed(app, status, warning):
pytest.skip('dvisvgm command "dvisvgm" is not available')
content = (app.outdir / 'index.html').read_text(encoding='utf8')
shutil.rmtree(app.outdir)
html = r'<img src="data:image/svg\+xml;base64,[\w\+/=]+"'
assert re.search(html, content, re.DOTALL)
@ -81,6 +85,7 @@ def test_mathjax_options(app, status, warning):
app.builder.build_all()
content = (app.outdir / 'index.html').read_text(encoding='utf8')
shutil.rmtree(app.outdir)
assert ('<script async="async" integrity="sha384-0123456789" '
'src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js">'
'</script>' in content)
@ -92,6 +97,7 @@ def test_mathjax_align(app, status, warning):
app.builder.build_all()
content = (app.outdir / 'index.html').read_text(encoding='utf8')
shutil.rmtree(app.outdir)
html = (r'<div class="math notranslate nohighlight">\s*'
r'\\\[ \\begin\{align\}\\begin\{aligned\}S \&amp;= \\pi r\^2\\\\'
r'V \&amp;= \\frac\{4\}\{3\} \\pi r\^3\\end\{aligned\}\\end\{align\} \\\]</div>')