Merge branch '3.x' into custom-get_documenter

This commit is contained in:
Keewis
2020-08-02 14:46:35 +02:00
15 changed files with 203 additions and 64 deletions

10
CHANGES
View File

@@ -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
--------

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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:

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -25,3 +25,7 @@ class Class:
self.attr5: int #: attr5
self.attr6 = 0 # type: int
"""attr6"""
class Derived(Class):
pass

View File

@@ -16,7 +16,8 @@ class Foo:
pass
def __init__(self):
pass
#: docstring
self.value = 1
def bar(self):
pass

View File

@@ -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

View File

@@ -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

View File

@@ -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',

View File

@@ -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'

View File

@@ -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()