diff options
author | Adam Turner <9087854+aa-turner@users.noreply.github.com> | 2022-09-30 18:15:24 +0300 |
---|---|---|
committer | Adam Turner <9087854+aa-turner@users.noreply.github.com> | 2022-09-30 18:15:24 +0300 |
commit | 63dea6172aa3d5351a80085b9bebf02985c267e5 (patch) | |
tree | 658c42a4e346db41f464ebdd68990dc8d977fc97 | |
parent | bb62d2a2ee7d6d4b79b269c837ebf8d57aee0795 (diff) | |
parent | 29060711984f9ad6be210d9a237a5f5cd5bce5c9 (diff) |
Merge branch '5.x'
# Conflicts:
# sphinx/locale/__init__.py
-rw-r--r-- | .circleci/config.yml | 16 | ||||
-rw-r--r-- | .github/workflows/latex.yml | 24 | ||||
-rw-r--r-- | CHANGES | 10 | ||||
-rw-r--r-- | doc/latex.rst | 2 | ||||
-rw-r--r-- | doc/usage/configuration.rst | 5 | ||||
-rw-r--r-- | doc/usage/restructuredtext/domains.rst | 46 | ||||
-rw-r--r-- | pyproject.toml | 9 | ||||
-rw-r--r-- | sphinx/builders/linkcheck.py | 8 | ||||
-rw-r--r-- | sphinx/cmd/build.py | 4 | ||||
-rw-r--r-- | sphinx/cmd/quickstart.py | 2 | ||||
-rw-r--r-- | sphinx/config.py | 1 | ||||
-rw-r--r-- | sphinx/deprecation.py | 8 | ||||
-rw-r--r-- | sphinx/directives/__init__.py | 11 | ||||
-rw-r--r-- | sphinx/domains/c.py | 2 | ||||
-rw-r--r-- | sphinx/domains/cpp.py | 2 | ||||
-rw-r--r-- | sphinx/domains/javascript.py | 4 | ||||
-rw-r--r-- | sphinx/domains/python.py | 2 | ||||
-rw-r--r-- | sphinx/domains/rst.py | 1 | ||||
-rw-r--r-- | sphinx/domains/std.py | 8 | ||||
-rw-r--r-- | sphinx/environment/collectors/toctree.py | 5 | ||||
-rw-r--r-- | sphinx/ext/imgmath.py | 4 | ||||
-rw-r--r-- | sphinx/locale/__init__.py | 39 | ||||
-rw-r--r-- | sphinx/pycode/__init__.py | 14 | ||||
-rw-r--r-- | sphinx/pycode/parser.py | 2 | ||||
-rw-r--r-- | tests/roots/test-root/objects.txt | 13 | ||||
-rw-r--r-- | tests/test_build_html.py | 9 | ||||
-rw-r--r-- | tests/test_ext_math.py | 6 |
27 files changed, 184 insertions, 73 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 841260c69..000000000 --- a/.circleci/config.yml +++ /dev/null @@ -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 diff --git a/.github/workflows/latex.yml b/.github/workflows/latex.yml new file mode 100644 index 000000000..12e39f54f --- /dev/null +++ b/.github/workflows/latex.yml @@ -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 @@ -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) ===================================== diff --git a/doc/latex.rst b/doc/latex.rst index 7fa90fe89..ae8c256cb 100644 --- a/doc/latex.rst +++ b/doc/latex.rst @@ -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 diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index 1071c9377..c18c2d317 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -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, diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst index d5720877b..cd7091763 100644 --- a/doc/usage/restructuredtext/domains.rst +++ b/doc/usage/restructuredtext/domains.rst @@ -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). diff --git a/pyproject.toml b/pyproject.toml index dd11048a1..4a04ada4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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.*", diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index 71e391378..a036aeefb 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -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 diff --git a/sphinx/cmd/build.py b/sphinx/cmd/build.py index 6cadefe3b..cab282bd1 100644 --- a/sphinx/cmd/build.py +++ b/sphinx/cmd/build.py @@ -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 diff --git a/sphinx/cmd/quickstart.py b/sphinx/cmd/quickstart.py index f9e3546bc..62c551c14 100644 --- a/sphinx/cmd/quickstart.py +++ b/sphinx/cmd/quickstart.py @@ -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) diff --git a/sphinx/config.py b/sphinx/config.py index 45df6bb00..2906a3285 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -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', []), diff --git a/sphinx/deprecation.py b/sphinx/deprecation.py index b532b2b38..89fad5657 100644 --- a/sphinx/deprecation.py +++ b/sphinx/deprecation.py @@ -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) diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py index b6838a6fd..e59cb1295 100644 --- a/sphinx/directives/__init__.py +++ b/sphinx/directives/__init__.py @@ -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: diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 14c52e050..3aa951f1c 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -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: diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index b448449b7..b509b3489 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -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, } diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py index b78dfd30e..391cebf33 100644 --- a/sphinx/domains/javascript.py +++ b/sphinx/domains/javascript.py @@ -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]: diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index fe2d52eb1..72a15433d 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -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, } diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py index fc7f2e551..8f49fcaa0 100644 --- a/sphinx/domains/rst.py +++ b/sphinx/domains/rst.py @@ -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: diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index aa6045f66..f6d1c4dc4 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -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): diff --git a/sphinx/environment/collectors/toctree.py b/sphinx/environment/collectors/toctree.py index 628f4a444..74af40928 100644 --- a/sphinx/environment/collectors/toctree.py +++ b/sphinx/environment/collectors/toctree.py @@ -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) diff --git a/sphinx/ext/imgmath.py b/sphinx/ext/imgmath.py index 5bfca8d4d..ddef58187 100644 --- a/sphinx/ext/imgmath.py +++ b/sphinx/ext/imgmath.py @@ -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: diff --git a/sphinx/locale/__init__.py b/sphinx/locale/__init__.py index 7942079c8..61e3ae812 100644 --- a/sphinx/locale/__init__.py +++ b/sphinx/locale/__init__.py @@ -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: diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py index ac3f7673c..67b17d21b 100644 --- a/sphinx/pycode/__init__.py +++ b/sphinx/pycode/__init__.py @@ -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 diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py index 133748cb8..e537a7726 100644 --- a/sphinx/pycode/parser.py +++ b/sphinx/pycode/parser.py @@ -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: diff --git a/tests/roots/test-root/objects.txt b/tests/roots/test-root/objects.txt index a4a5c667c..fa9e475e5 100644 --- a/tests/roots/test-root/objects.txt +++ b/tests/roots/test-root/objects.txt @@ -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 =========== diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 6b18bac1c..9a31a4b8a 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -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() diff --git a/tests/test_ext_math.py b/tests/test_ext_math.py index 7b11ea3be..206c36ca9 100644 --- a/tests/test_ext_math.py +++ b/tests/test_ext_math.py @@ -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>') |