mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch '3.x'
This commit is contained in:
commit
a8927bcd3e
88
CHANGES
88
CHANGES
@ -42,7 +42,7 @@ Bugs fixed
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 3.2.0 (in development)
|
||||
Release 3.3.0 (in development)
|
||||
==============================
|
||||
|
||||
Dependencies
|
||||
@ -54,6 +54,44 @@ Incompatible changes
|
||||
Deprecated
|
||||
----------
|
||||
|
||||
Features added
|
||||
--------------
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 3.2.1 (in development)
|
||||
==============================
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
Incompatible changes
|
||||
--------------------
|
||||
|
||||
Deprecated
|
||||
----------
|
||||
|
||||
Features added
|
||||
--------------
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 3.2.0 (released Aug 08, 2020)
|
||||
=====================================
|
||||
|
||||
Deprecated
|
||||
----------
|
||||
|
||||
* ``sphinx.ext.autodoc.members_set_option()``
|
||||
* ``sphinx.ext.autodoc.merge_special_members_option()``
|
||||
* ``sphinx.writers.texinfo.TexinfoWriter.desc``
|
||||
* C, parsing of pre-v3 style type directives and roles, along with the options
|
||||
:confval:`c_allow_pre_v3` and :confval:`c_warn_on_allowed_pre_v3`.
|
||||
@ -62,14 +100,20 @@ Features added
|
||||
--------------
|
||||
|
||||
* #2076: autodoc: Allow overriding of exclude-members in skip-member function
|
||||
* #8034: autodoc: ``:private-member:`` can take an explicit list of member names
|
||||
to be documented
|
||||
* #2024: autosummary: Add :confval:`autosummary_filename_map` to avoid conflict
|
||||
of filenames between two object with different case
|
||||
* #8011: autosummary: Support instance attributes as a target of autosummary
|
||||
directive
|
||||
* #7849: html: Add :confval:`html_codeblock_linenos_style` to change the style
|
||||
of line numbers for code-blocks
|
||||
* #7853: C and C++, support parameterized GNU style attributes.
|
||||
* #7888: napoleon: Add aliases Warn and Raise.
|
||||
* #7690: napoleon: parse type strings and make them hyperlinks as possible. The
|
||||
conversion rule can be updated via :confval:`napoleon_type_aliases`
|
||||
* #8049: napoleon: Create a hyperlink for each the type of parameter when
|
||||
:confval:`napoleon_use_params` is False
|
||||
* C, added :rst:dir:`c:alias` directive for inserting copies
|
||||
of existing declarations.
|
||||
* #7745: html: inventory is broken if the docname contains a space
|
||||
@ -77,8 +121,11 @@ Features added
|
||||
* #7902: html theme: Add a new option :confval:`globaltoc_maxdepth` to control
|
||||
the behavior of globaltoc in sidebar
|
||||
* #7840: i18n: Optimize the dependencies check on bootstrap
|
||||
* #7768: i18n: :confval:`figure_language_filename` supports ``docpath`` token
|
||||
* #5208: linkcheck: Support checks for local links
|
||||
* #5090: setuptools: Link verbosity to distutils' -v and -q option
|
||||
* #6698: doctest: Add ``:trim-doctest-flags:`` and ``:no-trim-doctest-flags:``
|
||||
options to doctest, testcode and testoutput directives
|
||||
* #7052: add ``:noindexentry:`` to the Python, C, C++, and Javascript domains.
|
||||
Update the documentation to better reflect the relationship between this option
|
||||
and the ``:noindex:`` option.
|
||||
@ -89,6 +136,7 @@ Features added
|
||||
:confval:`c_warn_on_allowed_pre_v3`` to ``True``.
|
||||
The functionality is immediately deprecated.
|
||||
* #7999: C, add support for named variadic macro arguments.
|
||||
* #8071: Allow to suppress "self referenced toctrees" warning
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
@ -100,6 +148,12 @@ Bugs fixed
|
||||
* #904: autodoc: An instance attribute cause a crash of autofunction directive
|
||||
* #1362: autodoc: ``private-members`` option does not work for class attributes
|
||||
* #7983: autodoc: Generator type annotation is wrongly rendered in py36
|
||||
* #8030: autodoc: An uninitialized annotated instance variable is not documented
|
||||
when ``:inherited-members:`` option given
|
||||
* #8032: autodoc: A type hint for the instance variable defined at parent class
|
||||
is not shown in the document of the derived class
|
||||
* #8041: autodoc: An annotated instance variable on super class is not
|
||||
documented when derived class has other annotated instance variables
|
||||
* #7839: autosummary: cannot handle umlauts in function names
|
||||
* #7865: autosummary: Failed to extract summary line when abbreviations found
|
||||
* #7866: autosummary: Failed to extract correct summary line when docstring
|
||||
@ -108,6 +162,9 @@ Bugs fixed
|
||||
* #7940: apidoc: An extra newline is generated at the end of the rst file if a
|
||||
module has submodules
|
||||
* #4258: napoleon: decorated special methods are not shown
|
||||
* #7799: napoleon: parameters are not escaped for combined params in numpydoc
|
||||
* #7780: napoleon: multiple paramaters declaration in numpydoc was wrongly
|
||||
recognized when napoleon_use_params=True
|
||||
* #7715: LaTeX: ``numfig_secnum_depth > 1`` leads to wrong figure links
|
||||
* #7846: html theme: XML-invalid files were generated
|
||||
* #7894: gettext: Wrong source info is shown when using rst_epilog
|
||||
@ -125,39 +182,18 @@ Bugs fixed
|
||||
* #7993: texinfo: a warning not supporting desc_signature_line node is shown
|
||||
* #7869: :rst:role:`abbr` role without an explanation will show the explanation
|
||||
from the previous abbr role
|
||||
* #8048: graphviz: graphviz.css was copied on building non-HTML document
|
||||
* C and C++, removed ``noindex`` directive option as it did
|
||||
nothing.
|
||||
* #7619: Duplicated node IDs are generated if node has multiple IDs
|
||||
* #2050: Symbols sections are appeared twice in the index page
|
||||
* #8017: Fix circular import in sphinx.addnodes
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 3.1.3 (in development)
|
||||
==============================
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
Incompatible changes
|
||||
--------------------
|
||||
|
||||
Deprecated
|
||||
----------
|
||||
|
||||
Features added
|
||||
--------------
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
* #7986: CSS: make "highlight" selector more robust
|
||||
* #7944: C++, parse non-type template parameters starting with
|
||||
a dependent qualified name.
|
||||
* C, don't deepcopy the entire symbol table and make a mess every time an
|
||||
enumerator is handled.
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 3.1.2 (released Jul 05, 2020)
|
||||
=====================================
|
||||
|
||||
|
BIN
doc/_static/translation.png
vendored
BIN
doc/_static/translation.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 16 KiB |
2
doc/_static/translation.puml
vendored
2
doc/_static/translation.puml
vendored
@ -12,5 +12,5 @@ SphinxProject -r-> .rst
|
||||
.pot -r-> .po : Pootle
|
||||
.po -d-> .mo : msgfmt
|
||||
.mo -l-> TranslatedBuild
|
||||
.rst -d-> TranslatedBuild : "sphinx-buid -Dlanguage="
|
||||
.rst -d-> TranslatedBuild : "sphinx-build -Dlanguage="
|
||||
@enduml
|
||||
|
25
doc/_static/translation.svg
vendored
25
doc/_static/translation.svg
vendored
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 8.0 KiB |
@ -56,6 +56,16 @@ The following is a list of deprecated interfaces.
|
||||
- 6.0
|
||||
- ``docutils.utils.smartyquotes``
|
||||
|
||||
* - ``sphinx.ext.autodoc.members_set_option()``
|
||||
- 3.2
|
||||
- 5.0
|
||||
- N/A
|
||||
|
||||
* - ``sphinx.ext.autodoc.merge_special_members_option()``
|
||||
- 3.2
|
||||
- 5.0
|
||||
- ``sphinx.ext.autodoc.merge_members_option()``
|
||||
|
||||
* - ``sphinx.writers.texinfo.TexinfoWriter.desc``
|
||||
- 3.2
|
||||
- 5.0
|
||||
|
@ -20,7 +20,7 @@ Options
|
||||
|
||||
.. option:: -q, --quiet
|
||||
|
||||
Quiet mode that will skip interactive wizard to specify options.
|
||||
Quiet mode that skips the interactive wizard for specifying options.
|
||||
This option requires `-p`, `-a` and `-v` options.
|
||||
|
||||
.. option:: -h, --help, --version
|
||||
|
@ -7,7 +7,7 @@ Templating
|
||||
==========
|
||||
|
||||
Sphinx uses the `Jinja <http://jinja.pocoo.org>`_ templating engine for its HTML
|
||||
templates. Jinja is a text-based engine, and inspired by Django templates, so
|
||||
templates. Jinja is a text-based engine, inspired by Django templates, so
|
||||
anyone having used Django will already be familiar with it. It also has
|
||||
excellent documentation for those who need to make themselves familiar with it.
|
||||
|
||||
|
@ -6,8 +6,8 @@ Internationalization
|
||||
.. versionadded:: 1.1
|
||||
|
||||
Complementary to translations provided for Sphinx-generated messages such as
|
||||
navigation bars, Sphinx provides mechanisms facilitating *document* translations
|
||||
in itself. See the :ref:`intl-options` for details on configuration.
|
||||
navigation bars, Sphinx provides mechanisms facilitating the translation of
|
||||
*documents*. See the :ref:`intl-options` for details on configuration.
|
||||
|
||||
.. figure:: /_static/translation.*
|
||||
:width: 100%
|
||||
|
@ -823,6 +823,8 @@ documentation on :ref:`intl` for details.
|
||||
extension, e.g. ``dirname/filename``
|
||||
* ``{path}`` - the directory path component of the filename, with a trailing
|
||||
slash if non-empty, e.g. ``dirname/``
|
||||
* ``{docpath}`` - the directory path component for the current document, with
|
||||
a trailing slash if non-empty.
|
||||
* ``{basename}`` - the filename without the directory path or file extension
|
||||
components, e.g. ``filename``
|
||||
* ``{ext}`` - the file extension, e.g. ``.png``
|
||||
@ -836,6 +838,9 @@ documentation on :ref:`intl` for details.
|
||||
.. versionchanged:: 1.5
|
||||
Added ``{path}`` and ``{basename}`` tokens.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
Added ``{docpath}`` token.
|
||||
|
||||
|
||||
.. _math-options:
|
||||
|
||||
@ -915,7 +920,7 @@ that use Sphinx's HTMLWriter class.
|
||||
|
||||
.. confval:: html_short_title
|
||||
|
||||
A shorter "title" for the HTML docs. This is used in for links in the
|
||||
A shorter "title" for the HTML docs. This is used for links in the
|
||||
header and in the HTML Help docs. If not given, it defaults to the value of
|
||||
:confval:`html_title`.
|
||||
|
||||
|
@ -136,9 +136,22 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
|
||||
:undoc-members:
|
||||
|
||||
* "Private" members (that is, those named like ``_private`` or ``__private``)
|
||||
will be included if the ``private-members`` flag option is given.
|
||||
will be included if the ``private-members`` flag option is given::
|
||||
|
||||
.. automodule:: noodle
|
||||
:members:
|
||||
:private-members:
|
||||
|
||||
It can also take an explicit list of member names to be documented as
|
||||
arguments::
|
||||
|
||||
.. automodule:: noodle
|
||||
:members:
|
||||
:private-members: _spicy, _garlickly
|
||||
|
||||
.. versionadded:: 1.1
|
||||
.. versionchanged:: 3.2
|
||||
The option can now take arguments.
|
||||
|
||||
* autodoc considers a member private if its docstring contains
|
||||
``:meta private:`` in its :ref:`info-field-lists`.
|
||||
|
@ -67,7 +67,7 @@ a comma-separated list of group names.
|
||||
default set of flags is specified by the :confval:`doctest_default_flags`
|
||||
configuration variable.
|
||||
|
||||
This directive supports three options:
|
||||
This directive supports five options:
|
||||
|
||||
* ``hide``, a flag option, hides the doctest block in other builders. By
|
||||
default it is shown as a highlighted doctest block.
|
||||
@ -102,6 +102,11 @@ a comma-separated list of group names.
|
||||
|
||||
Supported PEP-440 operands and notations
|
||||
|
||||
* ``trim-doctest-flags`` and ``no-trim-doctest-flags``, a flag option,
|
||||
doctest flags (comments looking like ``# doctest: FLAG, ...``) at the
|
||||
ends of lines and ``<BLANKLINE>`` markers are removed (or not removed)
|
||||
individually. Default is ``trim-doctest-flags``.
|
||||
|
||||
Note that like with standard doctests, you have to use ``<BLANKLINE>`` to
|
||||
signal a blank line in the expected output. The ``<BLANKLINE>`` is removed
|
||||
when building presentation output (HTML, LaTeX etc.).
|
||||
@ -119,11 +124,16 @@ a comma-separated list of group names.
|
||||
|
||||
A code block for a code-output-style test.
|
||||
|
||||
This directive supports one option:
|
||||
This directive supports three options:
|
||||
|
||||
* ``hide``, a flag option, hides the code block in other builders. By
|
||||
default it is shown as a highlighted code block.
|
||||
|
||||
* ``trim-doctest-flags`` and ``no-trim-doctest-flags``, a flag option,
|
||||
doctest flags (comments looking like ``# doctest: FLAG, ...``) at the
|
||||
ends of lines and ``<BLANKLINE>`` markers are removed (or not removed)
|
||||
individually. Default is ``trim-doctest-flags``.
|
||||
|
||||
.. note::
|
||||
|
||||
Code in a ``testcode`` block is always executed all at once, no matter how
|
||||
@ -149,7 +159,7 @@ a comma-separated list of group names.
|
||||
The corresponding output, or the exception message, for the last
|
||||
:rst:dir:`testcode` block.
|
||||
|
||||
This directive supports two options:
|
||||
This directive supports four options:
|
||||
|
||||
* ``hide``, a flag option, hides the output block in other builders. By
|
||||
default it is shown as a literal block without highlighting.
|
||||
@ -157,6 +167,11 @@ a comma-separated list of group names.
|
||||
* ``options``, a string option, can be used to give doctest flags
|
||||
(comma-separated) just like in normal doctest blocks.
|
||||
|
||||
* ``trim-doctest-flags`` and ``no-trim-doctest-flags``, a flag option,
|
||||
doctest flags (comments looking like ``# doctest: FLAG, ...``) at the
|
||||
ends of lines and ``<BLANKLINE>`` markers are removed (or not removed)
|
||||
individually. Default is ``trim-doctest-flags``.
|
||||
|
||||
Example::
|
||||
|
||||
.. testcode::
|
||||
|
@ -114,9 +114,9 @@ tables of contents. The ``toctree`` directive is the central element.
|
||||
|
||||
**Additional options**
|
||||
|
||||
You can use ``caption`` option to provide a toctree caption and you can use
|
||||
``name`` option to provide implicit target name that can be referenced by
|
||||
using :rst:role:`ref`::
|
||||
You can use the ``caption`` option to provide a toctree caption and you can
|
||||
use the ``name`` option to provide an implicit target name that can be
|
||||
referenced by using :rst:role:`ref`::
|
||||
|
||||
.. toctree::
|
||||
:caption: Table of Contents
|
||||
@ -246,7 +246,7 @@ The special document names (and pages generated for them) are:
|
||||
|
||||
* every name beginning with ``_``
|
||||
|
||||
Though only few such names are currently used by Sphinx, you should not
|
||||
Though few such names are currently used by Sphinx, you should not
|
||||
create documents or document-containing directories with such names. (Using
|
||||
``_`` as a prefix for a custom template directory is fine.)
|
||||
|
||||
|
@ -348,7 +348,7 @@ Third Party Themes
|
||||
|
||||
There are many third-party themes available. Some of these are general use,
|
||||
while others are specific to an individual project. A section of third-party
|
||||
themes is listed below. Many more can be found on PyPI__, GitHub__ and
|
||||
themes is listed below. Many more can be found on PyPI__, GitHub__, GitLab__ and
|
||||
sphinx-themes.org__.
|
||||
|
||||
.. cssclass:: clear
|
||||
@ -367,4 +367,5 @@ sphinx-themes.org__.
|
||||
|
||||
.. __: https://pypi.org/search/?q=&o=&c=Framework+%3A%3A+Sphinx+%3A%3A+Theme
|
||||
.. __: https://github.com/search?utf8=%E2%9C%93&q=sphinx+theme&type=
|
||||
.. __: https://gitlab.com/explore?name=sphinx+theme
|
||||
.. __: https://sphinx-themes.org/
|
||||
|
@ -6250,23 +6250,18 @@ class DefinitionParser(BaseParser):
|
||||
|
||||
# ==========================================================================
|
||||
|
||||
def _parse_template_parameter_list(self) -> ASTTemplateParams:
|
||||
# only: '<' parameter-list '>'
|
||||
# we assume that 'template' has just been parsed
|
||||
templateParams = [] # type: List[ASTTemplateParam]
|
||||
self.skip_ws()
|
||||
if not self.skip_string("<"):
|
||||
self.fail("Expected '<' after 'template'")
|
||||
prevErrors = []
|
||||
while 1:
|
||||
self.skip_ws()
|
||||
if self.skip_word('template'):
|
||||
# declare a tenplate template parameter
|
||||
nestedParams = self._parse_template_parameter_list()
|
||||
else:
|
||||
nestedParams = None
|
||||
self.skip_ws()
|
||||
def _parse_template_paramter(self) -> ASTTemplateParam:
|
||||
if self.skip_word('template'):
|
||||
# declare a tenplate template parameter
|
||||
nestedParams = self._parse_template_parameter_list()
|
||||
else:
|
||||
nestedParams = None
|
||||
|
||||
pos = self.pos
|
||||
try:
|
||||
# Unconstrained type parameter or template type parameter
|
||||
key = None
|
||||
self.skip_ws()
|
||||
if self.skip_word_and_ws('typename'):
|
||||
key = 'typename'
|
||||
elif self.skip_word_and_ws('class'):
|
||||
@ -6274,52 +6269,79 @@ class DefinitionParser(BaseParser):
|
||||
elif nestedParams:
|
||||
self.fail("Expected 'typename' or 'class' after "
|
||||
"template template parameter list.")
|
||||
if key:
|
||||
# declare a type or template type parameter
|
||||
self.skip_ws()
|
||||
parameterPack = self.skip_string('...')
|
||||
self.skip_ws()
|
||||
if self.match(identifier_re):
|
||||
identifier = ASTIdentifier(self.matched_text)
|
||||
else:
|
||||
identifier = None
|
||||
self.skip_ws()
|
||||
if not parameterPack and self.skip_string('='):
|
||||
default = self._parse_type(named=False, outer=None)
|
||||
else:
|
||||
default = None
|
||||
data = ASTTemplateKeyParamPackIdDefault(key, identifier,
|
||||
parameterPack, default)
|
||||
if nestedParams:
|
||||
# template type
|
||||
templateParams.append(
|
||||
ASTTemplateParamTemplateType(nestedParams, data))
|
||||
else:
|
||||
# type
|
||||
templateParams.append(ASTTemplateParamType(data))
|
||||
else:
|
||||
# declare a non-type parameter, or constrained type parameter
|
||||
pos = self.pos
|
||||
try:
|
||||
param = self._parse_type_with_init('maybe', 'templateParam')
|
||||
templateParams.append(ASTTemplateParamNonType(param))
|
||||
except DefinitionError as e:
|
||||
msg = "If non-type template parameter or constrained template parameter"
|
||||
prevErrors.append((e, msg))
|
||||
self.pos = pos
|
||||
self.fail("Expected 'typename' or 'class' in tbe "
|
||||
"beginning of template type parameter.")
|
||||
self.skip_ws()
|
||||
parameterPack = self.skip_string('...')
|
||||
self.skip_ws()
|
||||
if self.match(identifier_re):
|
||||
identifier = ASTIdentifier(self.matched_text)
|
||||
else:
|
||||
identifier = None
|
||||
self.skip_ws()
|
||||
if not parameterPack and self.skip_string('='):
|
||||
default = self._parse_type(named=False, outer=None)
|
||||
else:
|
||||
default = None
|
||||
if self.current_char not in ',>':
|
||||
self.fail('Expected "," or ">" after (template) type parameter.')
|
||||
data = ASTTemplateKeyParamPackIdDefault(key, identifier,
|
||||
parameterPack, default)
|
||||
if nestedParams:
|
||||
return ASTTemplateParamTemplateType(nestedParams, data)
|
||||
else:
|
||||
return ASTTemplateParamType(data)
|
||||
except DefinitionError as eType:
|
||||
if nestedParams:
|
||||
raise
|
||||
try:
|
||||
# non-type parameter or constrained type parameter
|
||||
self.pos = pos
|
||||
param = self._parse_type_with_init('maybe', 'templateParam')
|
||||
return ASTTemplateParamNonType(param)
|
||||
except DefinitionError as eNonType:
|
||||
self.pos = pos
|
||||
header = "Error when parsing template parameter."
|
||||
errs = []
|
||||
errs.append(
|
||||
(eType, "If unconstrained type parameter or template type parameter"))
|
||||
errs.append(
|
||||
(eNonType, "If constrained type parameter or non-type parameter"))
|
||||
raise self._make_multi_error(errs, header)
|
||||
|
||||
def _parse_template_parameter_list(self) -> ASTTemplateParams:
|
||||
# only: '<' parameter-list '>'
|
||||
# we assume that 'template' has just been parsed
|
||||
templateParams = [] # type: List[ASTTemplateParam]
|
||||
self.skip_ws()
|
||||
if not self.skip_string("<"):
|
||||
self.fail("Expected '<' after 'template'")
|
||||
while 1:
|
||||
pos = self.pos
|
||||
err = None
|
||||
try:
|
||||
param = self._parse_template_paramter()
|
||||
templateParams.append(param)
|
||||
except DefinitionError as eParam:
|
||||
self.pos = pos
|
||||
err = eParam
|
||||
self.skip_ws()
|
||||
if self.skip_string('>'):
|
||||
return ASTTemplateParams(templateParams)
|
||||
elif self.skip_string(','):
|
||||
prevErrors = []
|
||||
continue
|
||||
else:
|
||||
header = "Error in template parameter list."
|
||||
errs = []
|
||||
if err:
|
||||
errs.append((err, "If parameter"))
|
||||
try:
|
||||
self.fail('Expected "=", ",", or ">".')
|
||||
self.fail('Expected "," or ">".')
|
||||
except DefinitionError as e:
|
||||
prevErrors.append((e, ""))
|
||||
raise self._make_multi_error(prevErrors, header)
|
||||
errs.append((e, "If no parameter"))
|
||||
print(errs)
|
||||
raise self._make_multi_error(errs, header)
|
||||
|
||||
def _parse_template_introduction(self) -> ASTTemplateIntroduction:
|
||||
pos = self.pos
|
||||
|
@ -584,7 +584,9 @@ class BuildEnvironment:
|
||||
|
||||
def traverse_toctree(parent: str, docname: str) -> Iterator[Tuple[str, str]]:
|
||||
if parent == docname:
|
||||
logger.warning(__('self referenced toctree found. Ignored.'), location=docname)
|
||||
logger.warning(__('self referenced toctree found. Ignored.'),
|
||||
location=docname, type='toc',
|
||||
subtype='circular')
|
||||
return
|
||||
|
||||
# traverse toctree by pre-order
|
||||
|
@ -18,7 +18,7 @@ from types import ModuleType
|
||||
from typing import (
|
||||
Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union
|
||||
)
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, get_type_hints
|
||||
|
||||
from docutils.statemachine import StringList
|
||||
|
||||
@ -60,13 +60,29 @@ py_ext_sig_re = re.compile(
|
||||
(?:\s* -> \s* (.*))? # return annotation
|
||||
)? $ # and nothing more
|
||||
''', re.VERBOSE)
|
||||
special_member_re = re.compile(r'^__\S+__$')
|
||||
|
||||
|
||||
def identity(x: Any) -> Any:
|
||||
return x
|
||||
|
||||
|
||||
ALL = object()
|
||||
class _All:
|
||||
"""A special value for :*-members: that matches to any member."""
|
||||
|
||||
def __contains__(self, item: Any) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
class _Empty:
|
||||
"""A special value for :exclude-members: that never matches to any member."""
|
||||
|
||||
def __contains__(self, item: Any) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
ALL = _All()
|
||||
EMPTY = _Empty()
|
||||
UNINITIALIZED_ATTR = object()
|
||||
INSTANCEATTR = object()
|
||||
SLOTSATTR = object()
|
||||
@ -81,11 +97,20 @@ def members_option(arg: Any) -> Union[object, List[str]]:
|
||||
|
||||
def members_set_option(arg: Any) -> Union[object, Set[str]]:
|
||||
"""Used to convert the :members: option to auto directives."""
|
||||
warnings.warn("members_set_option() is deprecated.",
|
||||
RemovedInSphinx50Warning, stacklevel=2)
|
||||
if arg is None:
|
||||
return ALL
|
||||
return {x.strip() for x in arg.split(',') if x.strip()}
|
||||
|
||||
|
||||
def exclude_members_option(arg: Any) -> Union[object, Set[str]]:
|
||||
"""Used to convert the :exclude-members: option."""
|
||||
if arg is None:
|
||||
return EMPTY
|
||||
return {x.strip() for x in arg.split(',') if x.strip()}
|
||||
|
||||
|
||||
def inherited_members_option(arg: Any) -> Union[object, Set[str]]:
|
||||
"""Used to convert the :members: option to auto directives."""
|
||||
if arg is None:
|
||||
@ -124,6 +149,8 @@ def bool_option(arg: Any) -> bool:
|
||||
|
||||
def merge_special_members_option(options: Dict) -> None:
|
||||
"""Merge :special-members: option to :members: option."""
|
||||
warnings.warn("merge_special_members_option() is deprecated.",
|
||||
RemovedInSphinx50Warning, stacklevel=2)
|
||||
if 'special-members' in options and options['special-members'] is not ALL:
|
||||
if options.get('members') is ALL:
|
||||
pass
|
||||
@ -135,6 +162,20 @@ def merge_special_members_option(options: Dict) -> None:
|
||||
options['members'] = options['special-members']
|
||||
|
||||
|
||||
def merge_members_option(options: Dict) -> None:
|
||||
"""Merge :*-members: option to the :members: option."""
|
||||
if options.get('members') is ALL:
|
||||
# merging is not needed when members: ALL
|
||||
return
|
||||
|
||||
members = options.setdefault('members', [])
|
||||
for key in {'private-members', 'special-members'}:
|
||||
if key in options and options[key] is not ALL:
|
||||
for member in options[key]:
|
||||
if member not in members:
|
||||
members.append(member)
|
||||
|
||||
|
||||
# Some useful event listener factories for autodoc-process-docstring.
|
||||
|
||||
def cut_lines(pre: int, post: int = 0, what: str = None) -> Callable:
|
||||
@ -574,6 +615,8 @@ class Documenter:
|
||||
return True
|
||||
elif name in cls.__dict__:
|
||||
return False
|
||||
elif name in self.get_attr(cls, '__annotations__', {}):
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
@ -624,35 +667,40 @@ class Documenter:
|
||||
if safe_getattr(member, '__sphinx_mock__', False):
|
||||
# mocked module or object
|
||||
pass
|
||||
elif (self.options.exclude_members not in (None, ALL) and
|
||||
membername in self.options.exclude_members):
|
||||
elif self.options.exclude_members and membername in self.options.exclude_members:
|
||||
# remove members given by exclude-members
|
||||
keep = False
|
||||
elif want_all and membername.startswith('__') and \
|
||||
membername.endswith('__') and len(membername) > 4:
|
||||
elif want_all and special_member_re.match(membername):
|
||||
# special __methods__
|
||||
if self.options.special_members is ALL:
|
||||
if self.options.special_members and membername in self.options.special_members:
|
||||
if membername == '__doc__':
|
||||
keep = False
|
||||
elif is_filtered_inherited_member(membername):
|
||||
keep = False
|
||||
else:
|
||||
keep = has_doc or self.options.undoc_members
|
||||
elif self.options.special_members:
|
||||
if membername in self.options.special_members:
|
||||
keep = has_doc or self.options.undoc_members
|
||||
else:
|
||||
keep = False
|
||||
elif (namespace, membername) in attr_docs:
|
||||
if want_all and isprivate:
|
||||
# ignore members whose name starts with _ by default
|
||||
keep = self.options.private_members
|
||||
if self.options.private_members is None:
|
||||
keep = False
|
||||
else:
|
||||
keep = membername in self.options.private_members
|
||||
else:
|
||||
# keep documented attributes
|
||||
keep = True
|
||||
isattr = True
|
||||
elif want_all and isprivate:
|
||||
# ignore members whose name starts with _ by default
|
||||
keep = self.options.private_members and \
|
||||
(has_doc or self.options.undoc_members)
|
||||
if has_doc or self.options.undoc_members:
|
||||
if self.options.private_members is None:
|
||||
keep = False
|
||||
elif is_filtered_inherited_member(membername):
|
||||
keep = False
|
||||
else:
|
||||
keep = membername in self.options.private_members
|
||||
else:
|
||||
keep = False
|
||||
else:
|
||||
if self.options.members is ALL and is_filtered_inherited_member(membername):
|
||||
keep = False
|
||||
@ -853,14 +901,14 @@ class ModuleDocumenter(Documenter):
|
||||
'noindex': bool_option, 'inherited-members': inherited_members_option,
|
||||
'show-inheritance': bool_option, 'synopsis': identity,
|
||||
'platform': identity, 'deprecated': bool_option,
|
||||
'member-order': member_order_option, 'exclude-members': members_set_option,
|
||||
'private-members': bool_option, 'special-members': members_option,
|
||||
'member-order': member_order_option, 'exclude-members': exclude_members_option,
|
||||
'private-members': members_option, 'special-members': members_option,
|
||||
'imported-members': bool_option, 'ignore-module-all': bool_option
|
||||
} # type: Dict[str, Callable]
|
||||
|
||||
def __init__(self, *args: Any) -> None:
|
||||
super().__init__(*args)
|
||||
merge_special_members_option(self.options)
|
||||
merge_members_option(self.options)
|
||||
self.__all__ = None
|
||||
|
||||
@classmethod
|
||||
@ -1264,8 +1312,8 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
'members': members_option, 'undoc-members': bool_option,
|
||||
'noindex': bool_option, 'inherited-members': inherited_members_option,
|
||||
'show-inheritance': bool_option, 'member-order': member_order_option,
|
||||
'exclude-members': members_set_option,
|
||||
'private-members': bool_option, 'special-members': members_option,
|
||||
'exclude-members': exclude_members_option,
|
||||
'private-members': members_option, 'special-members': members_option,
|
||||
} # type: Dict[str, Callable]
|
||||
|
||||
_signature_class = None # type: Any
|
||||
@ -1273,7 +1321,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
|
||||
def __init__(self, *args: Any) -> None:
|
||||
super().__init__(*args)
|
||||
merge_special_members_option(self.options)
|
||||
merge_members_option(self.options)
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
|
||||
@ -1539,8 +1587,12 @@ class DataDocumenter(ModuleLevelDocumenter):
|
||||
sourcename = self.get_sourcename()
|
||||
if not self.options.annotation:
|
||||
# obtain annotation for this data
|
||||
annotations = getattr(self.parent, '__annotations__', {})
|
||||
if annotations and self.objpath[-1] in annotations:
|
||||
try:
|
||||
annotations = get_type_hints(self.parent)
|
||||
except TypeError:
|
||||
annotations = {}
|
||||
|
||||
if self.objpath[-1] in annotations:
|
||||
objrepr = stringify_typehint(annotations.get(self.objpath[-1]))
|
||||
self.add_line(' :type: ' + objrepr, sourcename)
|
||||
else:
|
||||
@ -1905,8 +1957,12 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
|
||||
sourcename = self.get_sourcename()
|
||||
if not self.options.annotation:
|
||||
# obtain type annotation for this attribute
|
||||
annotations = getattr(self.parent, '__annotations__', {})
|
||||
if annotations and self.objpath[-1] in annotations:
|
||||
try:
|
||||
annotations = get_type_hints(self.parent)
|
||||
except TypeError:
|
||||
annotations = {}
|
||||
|
||||
if self.objpath[-1] in annotations:
|
||||
objrepr = stringify_typehint(annotations.get(self.objpath[-1]))
|
||||
self.add_line(' :type: ' + objrepr, sourcename)
|
||||
else:
|
||||
@ -2000,11 +2056,22 @@ class InstanceAttributeDocumenter(AttributeDocumenter):
|
||||
isattr and
|
||||
member is INSTANCEATTR)
|
||||
|
||||
def import_parent(self) -> Any:
|
||||
try:
|
||||
parent = importlib.import_module(self.modname)
|
||||
for name in self.objpath[:-1]:
|
||||
parent = self.get_attr(parent, name)
|
||||
|
||||
return parent
|
||||
except (ImportError, AttributeError):
|
||||
return None
|
||||
|
||||
def import_object(self, raiseerror: bool = False) -> bool:
|
||||
"""Never import anything."""
|
||||
# disguise as an attribute
|
||||
self.objtype = 'attribute'
|
||||
self.object = INSTANCEATTR
|
||||
self.parent = self.import_parent()
|
||||
self._datadescriptor = False
|
||||
return True
|
||||
|
||||
|
@ -17,6 +17,10 @@ from sphinx.pycode import ModuleAnalyzer
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.inspect import isclass, isenumclass, safe_getattr
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Type # NOQA
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -158,6 +162,24 @@ class Attribute(NamedTuple):
|
||||
value: Any
|
||||
|
||||
|
||||
def _getmro(obj: Any) -> Tuple["Type", ...]:
|
||||
"""Get __mro__ from given *obj* safely."""
|
||||
__mro__ = safe_getattr(obj, '__mro__', None)
|
||||
if isinstance(__mro__, tuple):
|
||||
return __mro__
|
||||
else:
|
||||
return tuple()
|
||||
|
||||
|
||||
def _getannotations(obj: Any) -> Mapping[str, Any]:
|
||||
"""Get __annotations__ from given *obj* safely."""
|
||||
__annotations__ = safe_getattr(obj, '__annotations__', None)
|
||||
if isinstance(__annotations__, Mapping):
|
||||
return __annotations__
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
||||
def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
|
||||
analyzer: ModuleAnalyzer = None) -> Dict[str, Attribute]:
|
||||
"""Get members and attributes of target object."""
|
||||
@ -199,11 +221,11 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
|
||||
continue
|
||||
|
||||
# annotation only member (ex. attr: int)
|
||||
if hasattr(subject, '__annotations__') and isinstance(subject.__annotations__, Mapping):
|
||||
for name in subject.__annotations__:
|
||||
name = unmangle(subject, name)
|
||||
for i, cls in enumerate(_getmro(subject)):
|
||||
for name in _getannotations(cls):
|
||||
name = unmangle(cls, name)
|
||||
if name and name not in members:
|
||||
members[name] = Attribute(name, True, INSTANCEATTR)
|
||||
members[name] = Attribute(name, i == 0, INSTANCEATTR)
|
||||
|
||||
if analyzer:
|
||||
# append instance attributes (cf. self.attr1) if analyzer knows
|
||||
|
@ -75,7 +75,7 @@ from sphinx.application import Sphinx
|
||||
from sphinx.deprecation import RemovedInSphinx50Warning
|
||||
from sphinx.environment import BuildEnvironment
|
||||
from sphinx.environment.adapters.toctree import TocTree
|
||||
from sphinx.ext.autodoc import Documenter
|
||||
from sphinx.ext.autodoc import Documenter, INSTANCEATTR
|
||||
from sphinx.ext.autodoc.directive import DocumenterBridge, Options
|
||||
from sphinx.ext.autodoc.importer import import_module
|
||||
from sphinx.ext.autodoc.mock import mock
|
||||
@ -281,6 +281,29 @@ class Autosummary(SphinxDirective):
|
||||
|
||||
return nodes
|
||||
|
||||
def import_by_name(self, name: str, prefixes: List[str]) -> Tuple[str, Any, Any, str]:
|
||||
with mock(self.config.autosummary_mock_imports):
|
||||
try:
|
||||
return import_by_name(name, prefixes)
|
||||
except ImportError as exc:
|
||||
# check existence of instance attribute
|
||||
try:
|
||||
return import_ivar_by_name(name, prefixes)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
raise exc # re-raise ImportError if instance attribute not found
|
||||
|
||||
def create_documenter(self, app: Sphinx, obj: Any,
|
||||
parent: Any, full_name: str) -> "Documenter":
|
||||
"""Get an autodoc.Documenter class suitable for documenting the given
|
||||
object.
|
||||
|
||||
Wraps get_documenter and is meant as a hook for extensions.
|
||||
"""
|
||||
doccls = get_documenter(app, obj, parent)
|
||||
return doccls(self.bridge, full_name)
|
||||
|
||||
def get_items(self, names: List[str]) -> List[Tuple[str, str, str, str]]:
|
||||
"""Try to import the given names, and return a list of
|
||||
``[(name, signature, summary_string, real_name), ...]``.
|
||||
@ -298,8 +321,7 @@ class Autosummary(SphinxDirective):
|
||||
display_name = name.split('.')[-1]
|
||||
|
||||
try:
|
||||
with mock(self.config.autosummary_mock_imports):
|
||||
real_name, obj, parent, modname = import_by_name(name, prefixes=prefixes)
|
||||
real_name, obj, parent, modname = self.import_by_name(name, prefixes=prefixes)
|
||||
except ImportError:
|
||||
logger.warning(__('autosummary: failed to import %s'), name,
|
||||
location=self.get_source_info())
|
||||
@ -313,8 +335,7 @@ class Autosummary(SphinxDirective):
|
||||
full_name = modname + '::' + full_name[len(modname) + 1:]
|
||||
# NB. using full_name here is important, since Documenters
|
||||
# handle module prefixes slightly differently
|
||||
doccls = get_documenter(self.env.app, obj, parent)
|
||||
documenter = doccls(self.bridge, full_name)
|
||||
documenter = self.create_documenter(self.env.app, obj, parent, full_name)
|
||||
if not documenter.parse_name():
|
||||
logger.warning(__('failed to parse name %s'), real_name,
|
||||
location=self.get_source_info())
|
||||
@ -632,6 +653,23 @@ def _import_by_name(name: str) -> Tuple[Any, Any, str]:
|
||||
raise ImportError(*e.args) from e
|
||||
|
||||
|
||||
def import_ivar_by_name(name: str, prefixes: List[str] = [None]) -> Tuple[str, Any, Any, str]:
|
||||
"""Import an instance variable that has the given *name*, under one of the
|
||||
*prefixes*. The first name that succeeds is used.
|
||||
"""
|
||||
try:
|
||||
name, attr = name.rsplit(".", 1)
|
||||
real_name, obj, parent, modname = import_by_name(name, prefixes)
|
||||
qualname = real_name.replace(modname + ".", "")
|
||||
analyzer = ModuleAnalyzer.for_module(modname)
|
||||
if (qualname, attr) in analyzer.find_attr_docs():
|
||||
return real_name + "." + attr, INSTANCEATTR, obj, modname
|
||||
except (ImportError, ValueError):
|
||||
pass
|
||||
|
||||
raise ImportError
|
||||
|
||||
|
||||
# -- :autolink: (smart default role) -------------------------------------------
|
||||
|
||||
class AutoLink(SphinxRole):
|
||||
|
@ -41,7 +41,7 @@ from sphinx.builders import Builder
|
||||
from sphinx.config import Config
|
||||
from sphinx.deprecation import RemovedInSphinx50Warning
|
||||
from sphinx.ext.autodoc import Documenter
|
||||
from sphinx.ext.autosummary import import_by_name, get_documenter
|
||||
from sphinx.ext.autosummary import import_by_name, import_ivar_by_name, get_documenter
|
||||
from sphinx.locale import __
|
||||
from sphinx.pycode import ModuleAnalyzer, PycodeError
|
||||
from sphinx.registry import SphinxComponentRegistry
|
||||
@ -395,8 +395,13 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None,
|
||||
name, obj, parent, modname = import_by_name(entry.name)
|
||||
qualname = name.replace(modname + ".", "")
|
||||
except ImportError as e:
|
||||
logger.warning(__('[autosummary] failed to import %r: %s') % (entry.name, e))
|
||||
continue
|
||||
try:
|
||||
# try to importl as an instance attribute
|
||||
name, obj, parent, modname = import_ivar_by_name(entry.name)
|
||||
qualname = name.replace(modname + ".", "")
|
||||
except ImportError:
|
||||
logger.warning(__('[autosummary] failed to import %r: %s') % (entry.name, e))
|
||||
continue
|
||||
|
||||
context = {}
|
||||
if app:
|
||||
|
@ -82,7 +82,7 @@ class TestDirective(SphinxDirective):
|
||||
# convert <BLANKLINE>s to ordinary blank lines for presentation
|
||||
test = code
|
||||
code = blankline_re.sub('', code)
|
||||
if doctestopt_re.search(code):
|
||||
if doctestopt_re.search(code) and 'no-trim-doctest-flags' not in self.options:
|
||||
if not test:
|
||||
test = code
|
||||
code = doctestopt_re.sub('', code)
|
||||
@ -142,6 +142,10 @@ class TestDirective(SphinxDirective):
|
||||
line=self.lineno)
|
||||
if 'skipif' in self.options:
|
||||
node['skipif'] = self.options['skipif']
|
||||
if 'trim-doctest-flags' in self.options:
|
||||
node['trim_flags'] = True
|
||||
elif 'no-trim-doctest-flags' in self.options:
|
||||
node['trim_flags'] = False
|
||||
return [node]
|
||||
|
||||
|
||||
@ -156,26 +160,32 @@ class TestcleanupDirective(TestDirective):
|
||||
class DoctestDirective(TestDirective):
|
||||
option_spec = {
|
||||
'hide': directives.flag,
|
||||
'no-trim-doctest-flags': directives.flag,
|
||||
'options': directives.unchanged,
|
||||
'pyversion': directives.unchanged_required,
|
||||
'skipif': directives.unchanged_required,
|
||||
'trim-doctest-flags': directives.flag,
|
||||
}
|
||||
|
||||
|
||||
class TestcodeDirective(TestDirective):
|
||||
option_spec = {
|
||||
'hide': directives.flag,
|
||||
'no-trim-doctest-flags': directives.flag,
|
||||
'pyversion': directives.unchanged_required,
|
||||
'skipif': directives.unchanged_required,
|
||||
'trim-doctest-flags': directives.flag,
|
||||
}
|
||||
|
||||
|
||||
class TestoutputDirective(TestDirective):
|
||||
option_spec = {
|
||||
'hide': directives.flag,
|
||||
'no-trim-doctest-flags': directives.flag,
|
||||
'options': directives.unchanged,
|
||||
'pyversion': directives.unchanged_required,
|
||||
'skipif': directives.unchanged_required,
|
||||
'trim-doctest-flags': directives.flag,
|
||||
}
|
||||
|
||||
|
||||
|
@ -385,7 +385,7 @@ def man_visit_graphviz(self: ManualPageTranslator, node: graphviz) -> None:
|
||||
|
||||
|
||||
def on_build_finished(app: Sphinx, exc: Exception) -> None:
|
||||
if exc is None:
|
||||
if exc is None and app.builder.format == 'html':
|
||||
src = path.join(sphinx.package_dir, 'templates', 'graphviz', 'graphviz.css')
|
||||
dst = path.join(app.outdir, '_static')
|
||||
copy_asset(src, dst)
|
||||
|
@ -239,7 +239,7 @@ class Config:
|
||||
|
||||
napoleon_type_aliases : :obj:`dict` (Defaults to None)
|
||||
Add a mapping of strings to string, translating types in numpy
|
||||
style docstrings. Only works when ``napoleon_use_param = True``.
|
||||
style docstrings.
|
||||
|
||||
napoleon_custom_sections : :obj:`list` (Defaults to None)
|
||||
Add a list of custom sections to include, expanding the list of parsed sections.
|
||||
|
@ -22,6 +22,7 @@ from sphinx.ext.napoleon.iterators import modify_iter
|
||||
from sphinx.locale import _, __
|
||||
from sphinx.util import logging
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_directive_regex = re.compile(r'\.\. \S+::')
|
||||
@ -33,7 +34,7 @@ _xref_or_code_regex = re.compile(
|
||||
r'((?::(?:[a-zA-Z0-9]+[\-_+:.])*[a-zA-Z0-9]+:`.+?`)|'
|
||||
r'(?:``.+``))')
|
||||
_xref_regex = re.compile(
|
||||
r'(?::(?:[a-zA-Z0-9]+[\-_+:.])*[a-zA-Z0-9]+:`.+?`)'
|
||||
r'(?:(?::(?:[a-zA-Z0-9]+[\-_+:.])*[a-zA-Z0-9]+:)?`.+?`)'
|
||||
)
|
||||
_bullet_list_regex = re.compile(r'^(\*|\+|\-)(\s+\S|\s*$)')
|
||||
_enumerated_list_regex = re.compile(
|
||||
@ -41,10 +42,15 @@ _enumerated_list_regex = re.compile(
|
||||
r'(\d+|#|[ivxlcdm]+|[IVXLCDM]+|[a-zA-Z])'
|
||||
r'(?(paren)\)|\.)(\s+\S|\s*$)')
|
||||
_token_regex = re.compile(
|
||||
r"(\sor\s|\sof\s|:\s|,\s|[{]|[}]"
|
||||
r"(,\sor\s|\sor\s|\sof\s|:\s|\sto\s|,\sand\s|\sand\s|,\s"
|
||||
r"|[{]|[}]"
|
||||
r'|"(?:\\"|[^"])*"'
|
||||
r"|'(?:\\'|[^'])*')"
|
||||
)
|
||||
_default_regex = re.compile(
|
||||
r"^default[^_0-9A-Za-z].*$",
|
||||
)
|
||||
_SINGLETONS = ("None", "True", "False", "Ellipsis")
|
||||
|
||||
|
||||
class GoogleDocstring:
|
||||
@ -256,13 +262,16 @@ class GoogleDocstring:
|
||||
_descs = self.__class__(_descs, self._config).lines()
|
||||
return _name, _type, _descs
|
||||
|
||||
def _consume_fields(self, parse_type: bool = True, prefer_type: bool = False
|
||||
) -> List[Tuple[str, str, List[str]]]:
|
||||
def _consume_fields(self, parse_type: bool = True, prefer_type: bool = False,
|
||||
multiple: bool = False) -> List[Tuple[str, str, List[str]]]:
|
||||
self._consume_empty()
|
||||
fields = []
|
||||
while not self._is_section_break():
|
||||
_name, _type, _desc = self._consume_field(parse_type, prefer_type)
|
||||
if _name or _type or _desc:
|
||||
if multiple and _name:
|
||||
for name in _name.split(","):
|
||||
fields.append((name.strip(), _type, _desc))
|
||||
elif _name or _type or _desc:
|
||||
fields.append((_name, _type, _desc,))
|
||||
return fields
|
||||
|
||||
@ -671,10 +680,12 @@ class GoogleDocstring:
|
||||
return self._format_fields(_('Other Parameters'), self._consume_fields())
|
||||
|
||||
def _parse_parameters_section(self, section: str) -> List[str]:
|
||||
fields = self._consume_fields()
|
||||
if self._config.napoleon_use_param:
|
||||
# Allow to declare multiple parameters at once (ex: x, y: int)
|
||||
fields = self._consume_fields(multiple=True)
|
||||
return self._format_docutils_params(fields)
|
||||
else:
|
||||
fields = self._consume_fields()
|
||||
return self._format_fields(_('Parameters'), fields)
|
||||
|
||||
def _parse_raises_section(self, section: str) -> List[str]:
|
||||
@ -804,6 +815,9 @@ def _recombine_set_tokens(tokens: List[str]) -> List[str]:
|
||||
previous_token = token
|
||||
continue
|
||||
|
||||
if not token.strip():
|
||||
continue
|
||||
|
||||
if token in keywords:
|
||||
tokens.appendleft(token)
|
||||
if previous_token is not None:
|
||||
@ -842,8 +856,13 @@ def _recombine_set_tokens(tokens: List[str]) -> List[str]:
|
||||
|
||||
def _tokenize_type_spec(spec: str) -> List[str]:
|
||||
def postprocess(item):
|
||||
if item.startswith("default"):
|
||||
return [item[:7], item[7:]]
|
||||
if _default_regex.match(item):
|
||||
default = item[:7]
|
||||
# can't be separated by anything other than a single space
|
||||
# for now
|
||||
other = item[8:]
|
||||
|
||||
return [default, " ", other]
|
||||
else:
|
||||
return [item]
|
||||
|
||||
@ -857,10 +876,19 @@ def _tokenize_type_spec(spec: str) -> List[str]:
|
||||
|
||||
|
||||
def _token_type(token: str, location: str = None) -> str:
|
||||
def is_numeric(token):
|
||||
try:
|
||||
# use complex to make sure every numeric value is detected as literal
|
||||
complex(token)
|
||||
except ValueError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
if token.startswith(" ") or token.endswith(" "):
|
||||
type_ = "delimiter"
|
||||
elif (
|
||||
token.isnumeric() or
|
||||
is_numeric(token) or
|
||||
(token.startswith("{") and token.endswith("}")) or
|
||||
(token.startswith('"') and token.endswith('"')) or
|
||||
(token.startswith("'") and token.endswith("'"))
|
||||
@ -910,9 +938,12 @@ def _convert_numpy_type_spec(_type: str, location: str = None, translations: dic
|
||||
def convert_obj(obj, translations, default_translation):
|
||||
translation = translations.get(obj, obj)
|
||||
|
||||
# use :class: (the default) only if obj is not a standard singleton (None, True, False)
|
||||
if translation in ("None", "True", "False") and default_translation == ":class:`%s`":
|
||||
# use :class: (the default) only if obj is not a standard singleton
|
||||
if translation in _SINGLETONS and default_translation == ":class:`%s`":
|
||||
default_translation = ":obj:`%s`"
|
||||
elif translation == "..." and default_translation == ":class:`%s`":
|
||||
# allow referencing the builtin ...
|
||||
default_translation = ":obj:`%s <Ellipsis>`"
|
||||
|
||||
if _xref_regex.match(translation) is None:
|
||||
translation = default_translation % translation
|
||||
@ -926,16 +957,9 @@ def _convert_numpy_type_spec(_type: str, location: str = None, translations: dic
|
||||
for token in combined_tokens
|
||||
]
|
||||
|
||||
# don't use the object role if it's not necessary
|
||||
default_translation = (
|
||||
":class:`%s`"
|
||||
if not all(type_ == "obj" for _, type_ in types)
|
||||
else "%s"
|
||||
)
|
||||
|
||||
converters = {
|
||||
"literal": lambda x: "``%s``" % x,
|
||||
"obj": lambda x: convert_obj(x, translations, default_translation),
|
||||
"obj": lambda x: convert_obj(x, translations, ":class:`%s`"),
|
||||
"control": lambda x: "*%s*" % x,
|
||||
"delimiter": lambda x: x,
|
||||
"reference": lambda x: x,
|
||||
@ -1046,14 +1070,24 @@ class NumpyDocstring(GoogleDocstring):
|
||||
super().__init__(docstring, config, app, what, name, obj, options)
|
||||
|
||||
def _get_location(self) -> str:
|
||||
filepath = inspect.getfile(self._obj) if self._obj is not None else ""
|
||||
filepath = inspect.getfile(self._obj) if self._obj is not None else None
|
||||
name = self._name
|
||||
|
||||
if filepath is None and name is None:
|
||||
return None
|
||||
elif filepath is None:
|
||||
filepath = ""
|
||||
|
||||
return ":".join([filepath, "docstring of %s" % name])
|
||||
|
||||
def _escape_args_and_kwargs(self, name: str) -> str:
|
||||
func = super()._escape_args_and_kwargs
|
||||
|
||||
if ", " in name:
|
||||
return ", ".join(func(param) for param in name.split(", "))
|
||||
else:
|
||||
return func(name)
|
||||
|
||||
def _consume_field(self, parse_type: bool = True, prefer_type: bool = False
|
||||
) -> Tuple[str, str, List[str]]:
|
||||
line = next(self._line_iter)
|
||||
@ -1063,12 +1097,11 @@ class NumpyDocstring(GoogleDocstring):
|
||||
_name, _type = line, ''
|
||||
_name, _type = _name.strip(), _type.strip()
|
||||
_name = self._escape_args_and_kwargs(_name)
|
||||
if self._config.napoleon_use_param:
|
||||
_type = _convert_numpy_type_spec(
|
||||
_type,
|
||||
location=self._get_location(),
|
||||
translations=self._config.napoleon_type_aliases or {},
|
||||
)
|
||||
_type = _convert_numpy_type_spec(
|
||||
_type,
|
||||
location=self._get_location(),
|
||||
translations=self._config.napoleon_type_aliases or {},
|
||||
)
|
||||
|
||||
if prefer_type and not _type:
|
||||
_type, _name = _name, _type
|
||||
|
@ -693,7 +693,7 @@ pre {
|
||||
overflow-y: hidden; /* fixes display issues on Chrome browsers */
|
||||
}
|
||||
|
||||
pre, div[class|="highlight"] {
|
||||
pre, div[class*="highlight-"] {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
@ -704,7 +704,7 @@ span.pre {
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
div[class^="highlight-"] {
|
||||
div[class*="highlight-"] {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
|
@ -9,10 +9,10 @@
|
||||
"""
|
||||
|
||||
import sys
|
||||
from typing import Any, Dict, List, NamedTuple, Union
|
||||
from typing import Any, Dict, List, NamedTuple
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.nodes import Node
|
||||
from docutils.nodes import Node, TextElement
|
||||
from pygments.lexers import PythonConsoleLexer, guess_lexer
|
||||
|
||||
from sphinx import addnodes
|
||||
@ -94,9 +94,6 @@ class TrimDoctestFlagsTransform(SphinxTransform):
|
||||
default_priority = HighlightLanguageTransform.default_priority + 1
|
||||
|
||||
def apply(self, **kwargs: Any) -> None:
|
||||
if not self.config.trim_doctest_flags:
|
||||
return
|
||||
|
||||
for lbnode in self.document.traverse(nodes.literal_block): # type: nodes.literal_block
|
||||
if self.is_pyconsole(lbnode):
|
||||
self.strip_doctest_flags(lbnode)
|
||||
@ -104,8 +101,10 @@ class TrimDoctestFlagsTransform(SphinxTransform):
|
||||
for dbnode in self.document.traverse(nodes.doctest_block): # type: nodes.doctest_block
|
||||
self.strip_doctest_flags(dbnode)
|
||||
|
||||
@staticmethod
|
||||
def strip_doctest_flags(node: Union[nodes.literal_block, nodes.doctest_block]) -> None:
|
||||
def strip_doctest_flags(self, node: TextElement) -> None:
|
||||
if not node.get('trim_flags', self.config.trim_doctest_flags):
|
||||
return
|
||||
|
||||
source = node.rawsource
|
||||
source = doctest.blankline_re.sub('', source)
|
||||
source = doctest.doctestopt_re.sub('', source)
|
||||
|
@ -232,8 +232,12 @@ def get_image_filename_for_language(filename: str, env: "BuildEnvironment") -> s
|
||||
dirname = path.dirname(d['root'])
|
||||
if dirname and not dirname.endswith(path.sep):
|
||||
dirname += path.sep
|
||||
docpath = path.dirname(env.docname)
|
||||
if docpath and not docpath.endswith(path.sep):
|
||||
docpath += path.sep
|
||||
d['path'] = dirname
|
||||
d['basename'] = path.basename(d['root'])
|
||||
d['docpath'] = docpath
|
||||
d['language'] = env.config.language
|
||||
try:
|
||||
return filename_format.format(**d)
|
||||
|
@ -25,3 +25,7 @@ class Class:
|
||||
self.attr5: int #: attr5
|
||||
self.attr6 = 0 # type: int
|
||||
"""attr6"""
|
||||
|
||||
|
||||
class Derived(Class):
|
||||
attr7: int
|
||||
|
@ -16,7 +16,8 @@ class Foo:
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
#: docstring
|
||||
self.value = 1
|
||||
|
||||
def bar(self):
|
||||
pass
|
||||
|
@ -10,6 +10,7 @@
|
||||
autosummary_dummy_module
|
||||
autosummary_dummy_module.Foo
|
||||
autosummary_dummy_module.Foo.Bar
|
||||
autosummary_dummy_module.Foo.value
|
||||
autosummary_dummy_module.bar
|
||||
autosummary_dummy_module.qux
|
||||
autosummary_importfail
|
||||
|
@ -22,7 +22,19 @@ test-trim_doctest_flags
|
||||
>>> datetime.date.now() # doctest: +QUX
|
||||
datetime.date(2008, 1, 1)
|
||||
|
||||
.. doctest_block::
|
||||
.. doctest::
|
||||
|
||||
>>> datetime.date.now() # doctest: +QUUX
|
||||
datetime.date(2008, 1, 1)
|
||||
|
||||
.. doctest::
|
||||
:trim-doctest-flags:
|
||||
|
||||
>>> datetime.date.now() # doctest: +CORGE
|
||||
datetime.date(2008, 1, 1)
|
||||
|
||||
.. doctest::
|
||||
:no-trim-doctest-flags:
|
||||
|
||||
>>> datetime.date.now() # doctest: +GRAULT
|
||||
datetime.date(2008, 1, 1)
|
||||
|
@ -759,6 +759,7 @@ def test_templates():
|
||||
check('class', "template<typename T = Test> {key}A", {2: "I0E1A"})
|
||||
|
||||
check('class', "template<template<typename> typename T> {key}A", {2: "II0E0E1A"})
|
||||
check('class', "template<template<typename> class T> {key}A", {2: "II0E0E1A"})
|
||||
check('class', "template<template<typename> typename> {key}A", {2: "II0E0E1A"})
|
||||
check('class', "template<template<typename> typename ...T> {key}A", {2: "II0EDpE1A"})
|
||||
check('class', "template<template<typename> typename...> {key}A", {2: "II0EDpE1A"})
|
||||
@ -769,6 +770,16 @@ def test_templates():
|
||||
check('class', "template<int T = 42> {key}A", {2: "I_iE1A"})
|
||||
check('class', "template<int = 42> {key}A", {2: "I_iE1A"})
|
||||
|
||||
check('class', "template<typename A<B>::C> {key}A", {2: "I_N1AI1BE1CEE1A"})
|
||||
check('class', "template<typename A<B>::C = 42> {key}A", {2: "I_N1AI1BE1CEE1A"})
|
||||
# from #7944
|
||||
check('function', "template<typename T, "
|
||||
"typename std::enable_if<!has_overloaded_addressof<T>::value, bool>::type = false"
|
||||
"> constexpr T *static_addressof(T &ref)",
|
||||
{2: "I0_NSt9enable_ifIX!has_overloaded_addressof<T>::valueEbE4typeEE16static_addressofR1T",
|
||||
3: "I0_NSt9enable_ifIXntN24has_overloaded_addressofI1TE5valueEEbE4typeEE16static_addressofR1T",
|
||||
4: "I0_NSt9enable_ifIXntN24has_overloaded_addressofI1TE5valueEEbE4typeEE16static_addressofP1TR1T"})
|
||||
|
||||
check('class', "template<> {key}A<NS::B<>>", {2: "IE1AIN2NS1BIEEE"})
|
||||
|
||||
# from #2058
|
||||
|
@ -1176,44 +1176,44 @@ def test_slots(app):
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_enum_class(app):
|
||||
options = {"members": None}
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls', options)
|
||||
actual = do_autodoc(app, 'class', 'target.enums.EnumCls', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: EnumCls(value)',
|
||||
' :module: target.enum',
|
||||
' :module: target.enums',
|
||||
'',
|
||||
' this is enum class',
|
||||
'',
|
||||
'',
|
||||
' .. py:method:: EnumCls.say_goodbye()',
|
||||
' :module: target.enum',
|
||||
' :module: target.enums',
|
||||
' :classmethod:',
|
||||
'',
|
||||
' a classmethod says good-bye to you.',
|
||||
'',
|
||||
'',
|
||||
' .. py:method:: EnumCls.say_hello()',
|
||||
' :module: target.enum',
|
||||
' :module: target.enums',
|
||||
'',
|
||||
' a method says hello to you.',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: EnumCls.val1',
|
||||
' :module: target.enum',
|
||||
' :module: target.enums',
|
||||
' :value: 12',
|
||||
'',
|
||||
' doc for val1',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: EnumCls.val2',
|
||||
' :module: target.enum',
|
||||
' :module: target.enums',
|
||||
' :value: 23',
|
||||
'',
|
||||
' doc for val2',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: EnumCls.val3',
|
||||
' :module: target.enum',
|
||||
' :module: target.enums',
|
||||
' :value: 34',
|
||||
'',
|
||||
' doc for val3',
|
||||
@ -1221,11 +1221,11 @@ def test_enum_class(app):
|
||||
]
|
||||
|
||||
# checks for an attribute of EnumClass
|
||||
actual = do_autodoc(app, 'attribute', 'target.enum.EnumCls.val1')
|
||||
actual = do_autodoc(app, 'attribute', 'target.enums.EnumCls.val1')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:attribute:: EnumCls.val1',
|
||||
' :module: target.enum',
|
||||
' :module: target.enums',
|
||||
' :value: 12',
|
||||
'',
|
||||
' doc for val1',
|
||||
@ -1576,6 +1576,15 @@ def test_autodoc_typed_instance_variables(app):
|
||||
' This is descr4',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Derived()',
|
||||
' :module: target.typed_vars',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Derived.attr7',
|
||||
' :module: target.typed_vars',
|
||||
' :type: int',
|
||||
'',
|
||||
'',
|
||||
'.. py:data:: attr1',
|
||||
' :module: target.typed_vars',
|
||||
' :type: str',
|
||||
@ -1601,6 +1610,47 @@ def test_autodoc_typed_instance_variables(app):
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 6), reason='py36+ is available since python3.6.')
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_typed_inherited_instance_variables(app):
|
||||
options = {"members": None,
|
||||
"undoc-members": True,
|
||||
"inherited-members": True}
|
||||
actual = do_autodoc(app, 'class', 'target.typed_vars.Derived', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: Derived()',
|
||||
' :module: target.typed_vars',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Derived.attr1',
|
||||
' :module: target.typed_vars',
|
||||
' :type: int',
|
||||
' :value: 0',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Derived.attr2',
|
||||
' :module: target.typed_vars',
|
||||
' :type: int',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Derived.attr3',
|
||||
' :module: target.typed_vars',
|
||||
' :value: 0',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Derived.attr7',
|
||||
' :module: target.typed_vars',
|
||||
' :type: int',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Derived.descr4',
|
||||
' :module: target.typed_vars',
|
||||
' :type: int',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_GenericAlias(app):
|
||||
options = {"members": None,
|
||||
|
@ -645,7 +645,7 @@ def test_autodoc_typehints_description_for_invalid_node(app):
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_default_options(app):
|
||||
# no settings
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
|
||||
actual = do_autodoc(app, 'class', 'target.enums.EnumCls')
|
||||
assert ' .. py:attribute:: EnumCls.val1' not in actual
|
||||
assert ' .. py:attribute:: EnumCls.val4' not in actual
|
||||
actual = do_autodoc(app, 'class', 'target.CustomIter')
|
||||
@ -655,13 +655,13 @@ def test_autodoc_default_options(app):
|
||||
|
||||
# with :members:
|
||||
app.config.autodoc_default_options = {'members': None}
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
|
||||
actual = do_autodoc(app, 'class', 'target.enums.EnumCls')
|
||||
assert ' .. py:attribute:: EnumCls.val1' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val4' not in actual
|
||||
|
||||
# with :members: = True
|
||||
app.config.autodoc_default_options = {'members': True}
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
|
||||
actual = do_autodoc(app, 'class', 'target.enums.EnumCls')
|
||||
assert ' .. py:attribute:: EnumCls.val1' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val4' not in actual
|
||||
|
||||
@ -670,7 +670,7 @@ def test_autodoc_default_options(app):
|
||||
'members': None,
|
||||
'undoc-members': None,
|
||||
}
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
|
||||
actual = do_autodoc(app, 'class', 'target.enums.EnumCls')
|
||||
assert ' .. py:attribute:: EnumCls.val1' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val4' in actual
|
||||
|
||||
@ -696,7 +696,7 @@ def test_autodoc_default_options(app):
|
||||
'members': None,
|
||||
'exclude-members': None,
|
||||
}
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
|
||||
actual = do_autodoc(app, 'class', 'target.enums.EnumCls')
|
||||
assert ' .. py:attribute:: EnumCls.val1' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val4' not in actual
|
||||
app.config.autodoc_default_options = {
|
||||
@ -720,7 +720,7 @@ def test_autodoc_default_options(app):
|
||||
def test_autodoc_default_options_with_values(app):
|
||||
# with :members:
|
||||
app.config.autodoc_default_options = {'members': 'val1,val2'}
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
|
||||
actual = do_autodoc(app, 'class', 'target.enums.EnumCls')
|
||||
assert ' .. py:attribute:: EnumCls.val1' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val2' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val3' not in actual
|
||||
@ -765,7 +765,7 @@ def test_autodoc_default_options_with_values(app):
|
||||
'members': None,
|
||||
'exclude-members': 'val1'
|
||||
}
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
|
||||
actual = do_autodoc(app, 'class', 'target.enums.EnumCls')
|
||||
assert ' .. py:attribute:: EnumCls.val1' not in actual
|
||||
assert ' .. py:attribute:: EnumCls.val2' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val3' in actual
|
||||
|
@ -60,3 +60,24 @@ def test_private_field_and_private_members(app):
|
||||
' :meta private:',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_private_members(app):
|
||||
app.config.autoclass_content = 'class'
|
||||
options = {"members": None,
|
||||
"private-members": "_public_function"}
|
||||
actual = do_autodoc(app, 'module', 'target.private', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.private',
|
||||
'',
|
||||
'',
|
||||
'.. py:function:: _public_function(name)',
|
||||
' :module: target.private',
|
||||
'',
|
||||
' public_function is a docstring().',
|
||||
'',
|
||||
' :meta public:',
|
||||
'',
|
||||
]
|
||||
|
@ -293,15 +293,17 @@ def test_autosummary_generate(app, status, warning):
|
||||
nodes.row,
|
||||
nodes.row,
|
||||
nodes.row,
|
||||
nodes.row,
|
||||
nodes.row)])])
|
||||
assert_node(doctree[4][0], addnodes.toctree, caption="An autosummary")
|
||||
|
||||
assert len(doctree[3][0][0][2]) == 5
|
||||
assert len(doctree[3][0][0][2]) == 6
|
||||
assert doctree[3][0][0][2][0].astext() == 'autosummary_dummy_module\n\n'
|
||||
assert doctree[3][0][0][2][1].astext() == 'autosummary_dummy_module.Foo()\n\n'
|
||||
assert doctree[3][0][0][2][2].astext() == 'autosummary_dummy_module.Foo.Bar()\n\n'
|
||||
assert doctree[3][0][0][2][3].astext() == 'autosummary_dummy_module.bar(x[, y])\n\n'
|
||||
assert doctree[3][0][0][2][4].astext() == 'autosummary_dummy_module.qux\n\na module-level attribute'
|
||||
assert doctree[3][0][0][2][3].astext() == 'autosummary_dummy_module.Foo.value\n\ndocstring'
|
||||
assert doctree[3][0][0][2][4].astext() == 'autosummary_dummy_module.bar(x[, y])\n\n'
|
||||
assert doctree[3][0][0][2][5].astext() == 'autosummary_dummy_module.qux\n\na module-level attribute'
|
||||
|
||||
module = (app.srcdir / 'generated' / 'autosummary_dummy_module.rst').read_text()
|
||||
assert (' .. autosummary::\n'
|
||||
@ -333,6 +335,11 @@ def test_autosummary_generate(app, status, warning):
|
||||
'\n'
|
||||
'.. autoclass:: Foo.Bar\n' in FooBar)
|
||||
|
||||
Foo_value = (app.srcdir / 'generated' / 'autosummary_dummy_module.Foo.value.rst').read_text()
|
||||
assert ('.. currentmodule:: autosummary_dummy_module\n'
|
||||
'\n'
|
||||
'.. autoattribute:: Foo.value' in Foo_value)
|
||||
|
||||
qux = (app.srcdir / 'generated' / 'autosummary_dummy_module.qux.rst').read_text()
|
||||
assert ('.. currentmodule:: autosummary_dummy_module\n'
|
||||
'\n'
|
||||
|
@ -16,6 +16,8 @@ from inspect import cleandoc
|
||||
from textwrap import dedent
|
||||
from unittest import TestCase, mock
|
||||
|
||||
import pytest
|
||||
|
||||
from sphinx.ext.napoleon import Config
|
||||
from sphinx.ext.napoleon.docstring import GoogleDocstring, NumpyDocstring
|
||||
from sphinx.ext.napoleon.docstring import (
|
||||
@ -64,19 +66,19 @@ Sample namedtuple subclass
|
||||
|
||||
Quick description of attr1
|
||||
|
||||
:type: Arbitrary type
|
||||
:type: :class:`Arbitrary type`
|
||||
|
||||
.. attribute:: attr2
|
||||
|
||||
Quick description of attr2
|
||||
|
||||
:type: Another arbitrary type
|
||||
:type: :class:`Another arbitrary type`
|
||||
|
||||
.. attribute:: attr3
|
||||
|
||||
Adds a newline after the type
|
||||
|
||||
:type: Type
|
||||
:type: :class:`Type`
|
||||
"""
|
||||
|
||||
self.assertEqual(expected, actual)
|
||||
@ -1078,6 +1080,22 @@ Methods:
|
||||
options={'noindex': True}))
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_keywords_with_types(self):
|
||||
docstring = """\
|
||||
Do as you please
|
||||
|
||||
Keyword Args:
|
||||
gotham_is_yours (None): shall interfere.
|
||||
"""
|
||||
actual = str(GoogleDocstring(docstring))
|
||||
expected = """\
|
||||
Do as you please
|
||||
|
||||
:keyword gotham_is_yours: shall interfere.
|
||||
:kwtype gotham_is_yours: None
|
||||
"""
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
|
||||
class NumpyDocstringTest(BaseDocstringTest):
|
||||
docstrings = [(
|
||||
@ -1108,7 +1126,7 @@ class NumpyDocstringTest(BaseDocstringTest):
|
||||
"""
|
||||
Single line summary
|
||||
|
||||
:Parameters: **arg1** (*str*) -- Extended
|
||||
:Parameters: **arg1** (:class:`str`) -- Extended
|
||||
description of arg1
|
||||
"""
|
||||
), (
|
||||
@ -1136,14 +1154,14 @@ class NumpyDocstringTest(BaseDocstringTest):
|
||||
"""
|
||||
Single line summary
|
||||
|
||||
:Parameters: * **arg1** (*str*) -- Extended
|
||||
:Parameters: * **arg1** (:class:`str`) -- Extended
|
||||
description of arg1
|
||||
* **arg2** (*int*) -- Extended
|
||||
* **arg2** (:class:`int`) -- Extended
|
||||
description of arg2
|
||||
|
||||
:Keyword Arguments: * **kwarg1** (*str*) -- Extended
|
||||
:Keyword Arguments: * **kwarg1** (:class:`str`) -- Extended
|
||||
description of kwarg1
|
||||
* **kwarg2** (*int*) -- Extended
|
||||
* **kwarg2** (:class:`int`) -- Extended
|
||||
description of kwarg2
|
||||
"""
|
||||
), (
|
||||
@ -1194,7 +1212,7 @@ class NumpyDocstringTest(BaseDocstringTest):
|
||||
"""
|
||||
Single line summary
|
||||
|
||||
:Parameters: * **arg1** (*str*) -- Extended description of arg1
|
||||
:Parameters: * **arg1** (:class:`str`) -- Extended description of arg1
|
||||
* **\\*args** -- Variable length argument list.
|
||||
* **\\*\\*kwargs** -- Arbitrary keyword arguments.
|
||||
"""
|
||||
@ -1202,6 +1220,23 @@ class NumpyDocstringTest(BaseDocstringTest):
|
||||
"""
|
||||
Single line summary
|
||||
|
||||
Parameters
|
||||
----------
|
||||
arg1:str
|
||||
Extended description of arg1
|
||||
*args, **kwargs:
|
||||
Variable length argument list and arbitrary keyword arguments.
|
||||
""",
|
||||
"""
|
||||
Single line summary
|
||||
|
||||
:Parameters: * **arg1** (:class:`str`) -- Extended description of arg1
|
||||
* **\\*args, \\*\\*kwargs** -- Variable length argument list and arbitrary keyword arguments.
|
||||
"""
|
||||
), (
|
||||
"""
|
||||
Single line summary
|
||||
|
||||
Yield
|
||||
-----
|
||||
str
|
||||
@ -1302,6 +1337,32 @@ param1 : :class:`MyClass <name.space.MyClass>` instance
|
||||
expected = """\
|
||||
:param param1:
|
||||
:type param1: :class:`MyClass <name.space.MyClass>` instance
|
||||
"""
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_multiple_parameters(self):
|
||||
docstring = """\
|
||||
Parameters
|
||||
----------
|
||||
x1, x2 : array_like
|
||||
Input arrays, description of ``x1``, ``x2``.
|
||||
|
||||
"""
|
||||
|
||||
config = Config(napoleon_use_param=False)
|
||||
actual = str(NumpyDocstring(docstring, config))
|
||||
expected = """\
|
||||
:Parameters: **x1, x2** (:class:`array_like`) -- Input arrays, description of ``x1``, ``x2``.
|
||||
"""
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
config = Config(napoleon_use_param=True)
|
||||
actual = str(NumpyDocstring(dedent(docstring), config))
|
||||
expected = """\
|
||||
:param x1: Input arrays, description of ``x1``, ``x2``.
|
||||
:type x1: :class:`array_like`
|
||||
:param x2: Input arrays, description of ``x1``, ``x2``.
|
||||
:type x2: :class:`array_like`
|
||||
"""
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
@ -1316,7 +1377,7 @@ param1 : MyClass instance
|
||||
config = Config(napoleon_use_param=False)
|
||||
actual = str(NumpyDocstring(docstring, config))
|
||||
expected = """\
|
||||
:Parameters: **param1** (*MyClass instance*)
|
||||
:Parameters: **param1** (:class:`MyClass instance`)
|
||||
"""
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
@ -1324,7 +1385,7 @@ param1 : MyClass instance
|
||||
actual = str(NumpyDocstring(dedent(docstring), config))
|
||||
expected = """\
|
||||
:param param1:
|
||||
:type param1: MyClass instance
|
||||
:type param1: :class:`MyClass instance`
|
||||
"""
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
@ -1413,7 +1474,7 @@ arg_ : type
|
||||
|
||||
expected = """
|
||||
:ivar arg_: some description
|
||||
:vartype arg_: type
|
||||
:vartype arg_: :class:`type`
|
||||
"""
|
||||
|
||||
config = Config(napoleon_use_ivar=True)
|
||||
@ -1433,7 +1494,7 @@ arg_ : type
|
||||
|
||||
expected = """
|
||||
:ivar arg\\_: some description
|
||||
:vartype arg\\_: type
|
||||
:vartype arg\\_: :class:`type`
|
||||
"""
|
||||
|
||||
config = Config(napoleon_use_ivar=True)
|
||||
@ -1801,59 +1862,59 @@ definition_after_normal_text : int
|
||||
expected = """One line summary.
|
||||
|
||||
:param no_list:
|
||||
:type no_list: int
|
||||
:type no_list: :class:`int`
|
||||
:param one_bullet_empty:
|
||||
*
|
||||
:type one_bullet_empty: int
|
||||
:type one_bullet_empty: :class:`int`
|
||||
:param one_bullet_single_line:
|
||||
- first line
|
||||
:type one_bullet_single_line: int
|
||||
:type one_bullet_single_line: :class:`int`
|
||||
:param one_bullet_two_lines:
|
||||
+ first line
|
||||
continued
|
||||
:type one_bullet_two_lines: int
|
||||
:type one_bullet_two_lines: :class:`int`
|
||||
:param two_bullets_single_line:
|
||||
- first line
|
||||
- second line
|
||||
:type two_bullets_single_line: int
|
||||
:type two_bullets_single_line: :class:`int`
|
||||
:param two_bullets_two_lines:
|
||||
* first line
|
||||
continued
|
||||
* second line
|
||||
continued
|
||||
:type two_bullets_two_lines: int
|
||||
:type two_bullets_two_lines: :class:`int`
|
||||
:param one_enumeration_single_line:
|
||||
1. first line
|
||||
:type one_enumeration_single_line: int
|
||||
:type one_enumeration_single_line: :class:`int`
|
||||
:param one_enumeration_two_lines:
|
||||
1) first line
|
||||
continued
|
||||
:type one_enumeration_two_lines: int
|
||||
:type one_enumeration_two_lines: :class:`int`
|
||||
:param two_enumerations_one_line:
|
||||
(iii) first line
|
||||
(iv) second line
|
||||
:type two_enumerations_one_line: int
|
||||
:type two_enumerations_one_line: :class:`int`
|
||||
:param two_enumerations_two_lines:
|
||||
a. first line
|
||||
continued
|
||||
b. second line
|
||||
continued
|
||||
:type two_enumerations_two_lines: int
|
||||
:type two_enumerations_two_lines: :class:`int`
|
||||
:param one_definition_one_line:
|
||||
item 1
|
||||
first line
|
||||
:type one_definition_one_line: int
|
||||
:type one_definition_one_line: :class:`int`
|
||||
:param one_definition_two_lines:
|
||||
item 1
|
||||
first line
|
||||
continued
|
||||
:type one_definition_two_lines: int
|
||||
:type one_definition_two_lines: :class:`int`
|
||||
:param two_definitions_one_line:
|
||||
item 1
|
||||
first line
|
||||
item 2
|
||||
second line
|
||||
:type two_definitions_one_line: int
|
||||
:type two_definitions_one_line: :class:`int`
|
||||
:param two_definitions_two_lines:
|
||||
item 1
|
||||
first line
|
||||
@ -1861,14 +1922,14 @@ definition_after_normal_text : int
|
||||
item 2
|
||||
second line
|
||||
continued
|
||||
:type two_definitions_two_lines: int
|
||||
:type two_definitions_two_lines: :class:`int`
|
||||
:param one_definition_blank_line:
|
||||
item 1
|
||||
|
||||
first line
|
||||
|
||||
extra first line
|
||||
:type one_definition_blank_line: int
|
||||
:type one_definition_blank_line: :class:`int`
|
||||
:param two_definitions_blank_lines:
|
||||
item 1
|
||||
|
||||
@ -1881,12 +1942,12 @@ definition_after_normal_text : int
|
||||
second line
|
||||
|
||||
extra second line
|
||||
:type two_definitions_blank_lines: int
|
||||
:type two_definitions_blank_lines: :class:`int`
|
||||
:param definition_after_normal_text: text line
|
||||
|
||||
item 1
|
||||
first line
|
||||
:type definition_after_normal_text: int
|
||||
:type definition_after_normal_text: :class:`int`
|
||||
"""
|
||||
config = Config(napoleon_use_param=True)
|
||||
actual = str(NumpyDocstring(docstring, config))
|
||||
@ -1894,60 +1955,60 @@ definition_after_normal_text : int
|
||||
|
||||
expected = """One line summary.
|
||||
|
||||
:Parameters: * **no_list** (*int*)
|
||||
* **one_bullet_empty** (*int*) --
|
||||
:Parameters: * **no_list** (:class:`int`)
|
||||
* **one_bullet_empty** (:class:`int`) --
|
||||
|
||||
*
|
||||
* **one_bullet_single_line** (*int*) --
|
||||
* **one_bullet_single_line** (:class:`int`) --
|
||||
|
||||
- first line
|
||||
* **one_bullet_two_lines** (*int*) --
|
||||
* **one_bullet_two_lines** (:class:`int`) --
|
||||
|
||||
+ first line
|
||||
continued
|
||||
* **two_bullets_single_line** (*int*) --
|
||||
* **two_bullets_single_line** (:class:`int`) --
|
||||
|
||||
- first line
|
||||
- second line
|
||||
* **two_bullets_two_lines** (*int*) --
|
||||
* **two_bullets_two_lines** (:class:`int`) --
|
||||
|
||||
* first line
|
||||
continued
|
||||
* second line
|
||||
continued
|
||||
* **one_enumeration_single_line** (*int*) --
|
||||
* **one_enumeration_single_line** (:class:`int`) --
|
||||
|
||||
1. first line
|
||||
* **one_enumeration_two_lines** (*int*) --
|
||||
* **one_enumeration_two_lines** (:class:`int`) --
|
||||
|
||||
1) first line
|
||||
continued
|
||||
* **two_enumerations_one_line** (*int*) --
|
||||
* **two_enumerations_one_line** (:class:`int`) --
|
||||
|
||||
(iii) first line
|
||||
(iv) second line
|
||||
* **two_enumerations_two_lines** (*int*) --
|
||||
* **two_enumerations_two_lines** (:class:`int`) --
|
||||
|
||||
a. first line
|
||||
continued
|
||||
b. second line
|
||||
continued
|
||||
* **one_definition_one_line** (*int*) --
|
||||
* **one_definition_one_line** (:class:`int`) --
|
||||
|
||||
item 1
|
||||
first line
|
||||
* **one_definition_two_lines** (*int*) --
|
||||
* **one_definition_two_lines** (:class:`int`) --
|
||||
|
||||
item 1
|
||||
first line
|
||||
continued
|
||||
* **two_definitions_one_line** (*int*) --
|
||||
* **two_definitions_one_line** (:class:`int`) --
|
||||
|
||||
item 1
|
||||
first line
|
||||
item 2
|
||||
second line
|
||||
* **two_definitions_two_lines** (*int*) --
|
||||
* **two_definitions_two_lines** (:class:`int`) --
|
||||
|
||||
item 1
|
||||
first line
|
||||
@ -1955,14 +2016,14 @@ definition_after_normal_text : int
|
||||
item 2
|
||||
second line
|
||||
continued
|
||||
* **one_definition_blank_line** (*int*) --
|
||||
* **one_definition_blank_line** (:class:`int`) --
|
||||
|
||||
item 1
|
||||
|
||||
first line
|
||||
|
||||
extra first line
|
||||
* **two_definitions_blank_lines** (*int*) --
|
||||
* **two_definitions_blank_lines** (:class:`int`) --
|
||||
|
||||
item 1
|
||||
|
||||
@ -1975,7 +2036,7 @@ definition_after_normal_text : int
|
||||
second line
|
||||
|
||||
extra second line
|
||||
* **definition_after_normal_text** (*int*) -- text line
|
||||
* **definition_after_normal_text** (:class:`int`) -- text line
|
||||
|
||||
item 1
|
||||
first line
|
||||
@ -1987,6 +2048,8 @@ definition_after_normal_text : int
|
||||
def test_token_type(self):
|
||||
tokens = (
|
||||
("1", "literal"),
|
||||
("-4.6", "literal"),
|
||||
("2j", "literal"),
|
||||
("'string'", "literal"),
|
||||
('"another_string"', "literal"),
|
||||
("{1, 2}", "literal"),
|
||||
@ -2010,20 +2073,32 @@ definition_after_normal_text : int
|
||||
def test_tokenize_type_spec(self):
|
||||
specs = (
|
||||
"str",
|
||||
"defaultdict",
|
||||
"int, float, or complex",
|
||||
"int or float or None, optional",
|
||||
'{"F", "C", "N"}',
|
||||
"{'F', 'C', 'N'}, default: 'F'",
|
||||
"{'F', 'C', 'N or C'}, default 'F'",
|
||||
"str, default: 'F or C'",
|
||||
"int, default: None",
|
||||
"int, default None",
|
||||
"int, default :obj:`None`",
|
||||
'"ma{icious"',
|
||||
r"'with \'quotes\''",
|
||||
)
|
||||
|
||||
tokens = (
|
||||
["str"],
|
||||
["defaultdict"],
|
||||
["int", ", ", "float", ", or ", "complex"],
|
||||
["int", " or ", "float", " or ", "None", ", ", "optional"],
|
||||
["{", '"F"', ", ", '"C"', ", ", '"N"', "}"],
|
||||
["{", "'F'", ", ", "'C'", ", ", "'N'", "}", ", ", "default", ": ", "'F'"],
|
||||
["{", "'F'", ", ", "'C'", ", ", "'N or C'", "}", ", ", "default", " ", "'F'"],
|
||||
["str", ", ", "default", ": ", "'F or C'"],
|
||||
["int", ", ", "default", ": ", "None"],
|
||||
["int", ", " , "default", " ", "None"],
|
||||
["int", ", ", "default", " ", ":obj:`None`"],
|
||||
['"ma{icious"'],
|
||||
[r"'with \'quotes\''"],
|
||||
)
|
||||
@ -2037,12 +2112,14 @@ definition_after_normal_text : int
|
||||
["{", "1", ", ", "2", "}"],
|
||||
["{", '"F"', ", ", '"C"', ", ", '"N"', "}", ", ", "optional"],
|
||||
["{", "'F'", ", ", "'C'", ", ", "'N'", "}", ", ", "default", ": ", "None"],
|
||||
["{", "'F'", ", ", "'C'", ", ", "'N'", "}", ", ", "default", " ", "None"],
|
||||
)
|
||||
|
||||
combined_tokens = (
|
||||
["{1, 2}"],
|
||||
['{"F", "C", "N"}', ", ", "optional"],
|
||||
["{'F', 'C', 'N'}", ", ", "default", ": ", "None"],
|
||||
["{'F', 'C', 'N'}", ", ", "default", " ", "None"],
|
||||
)
|
||||
|
||||
for tokens_, expected in zip(tokens, combined_tokens):
|
||||
@ -2075,8 +2152,10 @@ definition_after_normal_text : int
|
||||
"optional",
|
||||
"str, optional",
|
||||
"int or float or None, default: None",
|
||||
"int, default None",
|
||||
'{"F", "C", "N"}',
|
||||
"{'F', 'C', 'N'}, default: 'N'",
|
||||
"{'F', 'C', 'N'}, default 'N'",
|
||||
"DataFrame, optional",
|
||||
)
|
||||
|
||||
@ -2085,8 +2164,10 @@ definition_after_normal_text : int
|
||||
"*optional*",
|
||||
":class:`str`, *optional*",
|
||||
":class:`int` or :class:`float` or :obj:`None`, *default*: :obj:`None`",
|
||||
":class:`int`, *default* :obj:`None`",
|
||||
'``{"F", "C", "N"}``',
|
||||
"``{'F', 'C', 'N'}``, *default*: ``'N'``",
|
||||
"``{'F', 'C', 'N'}``, *default* ``'N'``",
|
||||
":class:`pandas.DataFrame`, *optional*",
|
||||
)
|
||||
|
||||
@ -2100,7 +2181,7 @@ definition_after_normal_text : int
|
||||
----------
|
||||
param1 : DataFrame
|
||||
the data to work on
|
||||
param2 : int or float or None
|
||||
param2 : int or float or None, optional
|
||||
a parameter with different types
|
||||
param3 : dict-like, optional
|
||||
a optional mapping
|
||||
@ -2108,21 +2189,35 @@ definition_after_normal_text : int
|
||||
a optional parameter with different types
|
||||
param5 : {"F", "C", "N"}, optional
|
||||
a optional parameter with fixed values
|
||||
param6 : int, default None
|
||||
different default format
|
||||
param7 : mapping of hashable to str, optional
|
||||
a optional mapping
|
||||
param8 : ... or Ellipsis
|
||||
ellipsis
|
||||
""")
|
||||
expected = dedent("""\
|
||||
:param param1: the data to work on
|
||||
:type param1: DataFrame
|
||||
:type param1: :class:`DataFrame`
|
||||
:param param2: a parameter with different types
|
||||
:type param2: :class:`int` or :class:`float` or :obj:`None`
|
||||
:type param2: :class:`int` or :class:`float` or :obj:`None`, *optional*
|
||||
:param param3: a optional mapping
|
||||
:type param3: :term:`dict-like <mapping>`, *optional*
|
||||
:param param4: a optional parameter with different types
|
||||
:type param4: :class:`int` or :class:`float` or :obj:`None`, *optional*
|
||||
:param param5: a optional parameter with fixed values
|
||||
:type param5: ``{"F", "C", "N"}``, *optional*
|
||||
:param param6: different default format
|
||||
:type param6: :class:`int`, *default* :obj:`None`
|
||||
:param param7: a optional mapping
|
||||
:type param7: :term:`mapping` of :term:`hashable` to :class:`str`, *optional*
|
||||
:param param8: ellipsis
|
||||
:type param8: :obj:`... <Ellipsis>` or :obj:`Ellipsis`
|
||||
""")
|
||||
translations = {
|
||||
"dict-like": ":term:`dict-like <mapping>`",
|
||||
"mapping": ":term:`mapping`",
|
||||
"hashable": ":term:`hashable`",
|
||||
}
|
||||
config = Config(
|
||||
napoleon_use_param=True,
|
||||
@ -2132,21 +2227,6 @@ definition_after_normal_text : int
|
||||
actual = str(NumpyDocstring(docstring, config))
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_keywords_with_types(self):
|
||||
docstring = """\
|
||||
Do as you please
|
||||
|
||||
Keyword Args:
|
||||
gotham_is_yours (None): shall interfere.
|
||||
"""
|
||||
actual = str(GoogleDocstring(docstring))
|
||||
expected = """\
|
||||
Do as you please
|
||||
|
||||
:keyword gotham_is_yours: shall interfere.
|
||||
:kwtype gotham_is_yours: None
|
||||
"""
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
@contextmanager
|
||||
def warns(warning, match):
|
||||
@ -2182,3 +2262,17 @@ class TestNumpyDocstring:
|
||||
for token, error in zip(tokens, errors):
|
||||
with warns(warning, match=error):
|
||||
_token_type(token)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("name", "expected"),
|
||||
(
|
||||
("x, y, z", "x, y, z"),
|
||||
("*args, **kwargs", r"\*args, \*\*kwargs"),
|
||||
("*x, **y", r"\*x, \*\*y"),
|
||||
),
|
||||
)
|
||||
def test_escape_args_and_kwargs(self, name, expected):
|
||||
numpy_docstring = NumpyDocstring("")
|
||||
actual = numpy_docstring._escape_args_and_kwargs(name)
|
||||
|
||||
assert actual == expected
|
||||
|
@ -19,6 +19,23 @@ def test_trim_doctest_flags_html(app, status, warning):
|
||||
assert 'BAZ' not in result
|
||||
assert 'QUX' not in result
|
||||
assert 'QUUX' not in result
|
||||
assert 'CORGE' not in result
|
||||
assert 'GRAULT' in result
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='trim_doctest_flags',
|
||||
confoverrides={'trim_doctest_flags': False})
|
||||
def test_trim_doctest_flags_disabled(app, status, warning):
|
||||
app.build()
|
||||
|
||||
result = (app.outdir / 'index.html').read_text()
|
||||
assert 'FOO' in result
|
||||
assert 'BAR' in result
|
||||
assert 'BAZ' in result
|
||||
assert 'QUX' in result
|
||||
assert 'QUUX' not in result
|
||||
assert 'CORGE' not in result
|
||||
assert 'GRAULT' in result
|
||||
|
||||
|
||||
@pytest.mark.sphinx('latex', testroot='trim_doctest_flags')
|
||||
@ -31,3 +48,5 @@ def test_trim_doctest_flags_latex(app, status, warning):
|
||||
assert 'BAZ' not in result
|
||||
assert 'QUX' not in result
|
||||
assert 'QUUX' not in result
|
||||
assert 'CORGE' not in result
|
||||
assert 'GRAULT' in result
|
||||
|
@ -90,6 +90,8 @@ def test_format_date():
|
||||
|
||||
@pytest.mark.xfail(os.name != 'posix', reason="Path separators don't match on windows")
|
||||
def test_get_filename_for_language(app):
|
||||
app.env.temp_data['docname'] = 'index'
|
||||
|
||||
# language is None
|
||||
app.env.config.language = None
|
||||
assert app.env.config.language is None
|
||||
@ -145,6 +147,17 @@ def test_get_filename_for_language(app):
|
||||
with pytest.raises(SphinxError):
|
||||
i18n.get_image_filename_for_language('foo.png', app.env)
|
||||
|
||||
# docpath (for a document in the top of source directory)
|
||||
app.env.config.language = 'en'
|
||||
app.env.config.figure_language_filename = '/{docpath}{language}/{basename}{ext}'
|
||||
assert (i18n.get_image_filename_for_language('foo.png', app.env) ==
|
||||
'/en/foo.png')
|
||||
|
||||
# docpath (for a document in the sub directory)
|
||||
app.env.temp_data['docname'] = 'subdir/index'
|
||||
assert (i18n.get_image_filename_for_language('foo.png', app.env) ==
|
||||
'/subdir/en/foo.png')
|
||||
|
||||
|
||||
def test_CatalogRepository(tempdir):
|
||||
(tempdir / 'loc1' / 'xx' / 'LC_MESSAGES').makedirs()
|
||||
|
Loading…
Reference in New Issue
Block a user