mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch '3.x' into custom-get_documenter
This commit is contained in:
10
CHANGES
10
CHANGES
@@ -20,6 +20,8 @@ Features added
|
||||
* #2076: autodoc: Allow overriding of exclude-members in skip-member function
|
||||
* #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.
|
||||
@@ -33,6 +35,7 @@ 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
|
||||
* #7052: add ``:noindexentry:`` to the Python, C, C++, and Javascript domains.
|
||||
@@ -56,6 +59,10 @@ 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
|
||||
* #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
|
||||
@@ -86,6 +93,9 @@ Bugs fixed
|
||||
* #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
|
||||
* #7986: CSS: make "highlight" selector more robust
|
||||
* #7944: C++, parse non-type template parameters starting with
|
||||
a dependent qualified name.
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
@@ -821,6 +821,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``
|
||||
@@ -834,6 +836,9 @@ documentation on :ref:`intl` for details.
|
||||
.. versionchanged:: 1.5
|
||||
Added ``{path}`` and ``{basename}`` tokens.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
Added ``{docpath}`` token.
|
||||
|
||||
|
||||
.. _math-options:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -579,6 +579,8 @@ class Documenter:
|
||||
return True
|
||||
elif name in cls.__dict__:
|
||||
return False
|
||||
elif name in self.get_attr(cls, '__annotations__', {}):
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
@@ -2018,11 +2020,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
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ from sphinx.application import Sphinx
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning, 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
|
||||
@@ -285,6 +285,19 @@ 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 dispatch_get_documenter(self, obj, parent, real_name):
|
||||
app = self.env.app
|
||||
documenters = [
|
||||
@@ -321,8 +334,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())
|
||||
@@ -678,6 +690,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) -------------------------------------------
|
||||
|
||||
def autolink_role(typ: str, rawtext: str, etext: str, lineno: int, inliner: Inliner,
|
||||
|
||||
@@ -41,7 +41,7 @@ from sphinx.builders import Builder
|
||||
from sphinx.config import Config
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning, 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
|
||||
@@ -413,8 +413,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:
|
||||
_warn(__('[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:
|
||||
_warn(__('[autosummary] failed to import %r: %s') % (entry.name, e))
|
||||
continue
|
||||
|
||||
context = {}
|
||||
if app:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -306,8 +306,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):
|
||||
pass
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -760,6 +760,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"})
|
||||
@@ -770,6 +771,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
|
||||
|
||||
@@ -1576,6 +1576,20 @@ def test_autodoc_typed_instance_variables(app):
|
||||
' This is descr4',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Derived()',
|
||||
' :module: target.typed_vars',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Derived.attr2',
|
||||
' :module: target.typed_vars',
|
||||
' :type: int',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Derived.descr4',
|
||||
' :module: target.typed_vars',
|
||||
' :type: int',
|
||||
'',
|
||||
'',
|
||||
'.. py:data:: attr1',
|
||||
' :module: target.typed_vars',
|
||||
' :type: str',
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user