mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch '5.x'
# Conflicts: # sphinx/locale/__init__.py
This commit is contained in:
commit
63dea6172a
@ -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
24
.github/workflows/latex.yml
vendored
Normal 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
10
CHANGES
@ -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)
|
||||
=====================================
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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).
|
||||
|
||||
|
@ -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.*",
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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', []),
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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]:
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
===========
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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 \&= \\pi r\^2\\\\'
|
||||
r'V \&= \\frac\{4\}\{3\} \\pi r\^3\\end\{aligned\}\\end\{align\} \\\]</div>')
|
||||
|
Loading…
Reference in New Issue
Block a user