summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakeshi KOMIYA <i.tkomiya@gmail.com>2020-09-28 01:15:40 +0900
committerGitHub <noreply@github.com>2020-09-28 01:15:40 +0900
commitf6ae4dd4edd857f8a92c80ee32227aa2d0bcc9d4 (patch)
treea8dcca3e437a3c9a5f35eaa4fc0eb91b1042cdcb
parentf00e75278c5999f40b214d8934357fbf0e705417 (diff)
parent054dc5d5df73ac21f1af105aa84ba16797ebba1e (diff)
downloadsphinx-git-f6ae4dd4edd857f8a92c80ee32227aa2d0bcc9d4.tar.gz
Merge branch '3.x' into 8172_napoleon_redos
-rw-r--r--CHANGES14
-rw-r--r--EXAMPLES1
-rw-r--r--doc/extdev/deprecated.rst10
-rw-r--r--doc/usage/extensions/autodoc.rst2
-rw-r--r--doc/usage/restructuredtext/domains.rst12
-rw-r--r--sphinx/builders/html/__init__.py13
-rw-r--r--sphinx/builders/latex/__init__.py30
-rw-r--r--sphinx/domains/c.py70
-rw-r--r--sphinx/ext/autodoc/__init__.py5
-rw-r--r--sphinx/ext/inheritance_diagram.py5
-rw-r--r--sphinx/testing/util.py2
-rw-r--r--sphinx/util/fileutil.py18
-rw-r--r--sphinx/util/inspect.py5
-rw-r--r--tests/roots/test-ext-autodoc/target/cached_property.py7
-rw-r--r--tests/test_ext_autodoc.py20
-rw-r--r--tests/test_ext_autodoc_events.py3
16 files changed, 178 insertions, 39 deletions
diff --git a/CHANGES b/CHANGES
index a0dcc3998..3e155f33a 100644
--- a/CHANGES
+++ b/CHANGES
@@ -10,17 +10,31 @@ Incompatible changes
Deprecated
----------
+* ``sphinx.builders.latex.LaTeXBuilder.usepackages``
+* ``sphinx.builders.latex.LaTeXBuilder.usepackages_afger_hyperref``
+
Features added
--------------
+* #8100: html: Show a better error message for failures on copying
+ html_static_files
+* #8141: C: added a ``maxdepth`` option to :rst:dir:`c:alias` to insert
+ nested declarations.
+* #8081: LaTeX: Allow to add LaTeX package via ``app.add_latex_package()`` until
+ just before writing .tex file
+
Bugs fixed
----------
* #8085: i18n: Add support for having single text domain
* #8143: autodoc: AttributeError is raised when False value is passed to
autodoc_default_options
+* #8103: autodoc: functools.cached_property is not considered as a property
+* #8190: autodoc: parsing error is raised if some extension replaces docstring
+ by string not ending with blank lines
* #8192: napoleon: description is disappeared when it contains inline literals
* #8172: napoleon: Potential of regex denial of service in google style docs
+* #8169: LaTeX: pxjahyper loaded even when latex_engine is not platex
* #8093: The highlight warning has wrong location in some builders (LaTeX,
singlehtml and so on)
diff --git a/EXAMPLES b/EXAMPLES
index b87d8e73e..19f23172f 100644
--- a/EXAMPLES
+++ b/EXAMPLES
@@ -330,6 +330,7 @@ Documentation using a custom theme or integrated in a website
* `Lasso <http://lassoguide.com/>`__
* `Mako <http://docs.makotemplates.org/>`__
* `MirrorBrain <http://mirrorbrain.org/docs/>`__
+* `Mitiq <https://mitiq.readthedocs.io/>`__
* `MongoDB <https://docs.mongodb.com/>`__
* `Music21 <https://web.mit.edu/music21/doc/>`__
* `MyHDL <http://docs.myhdl.org/>`__
diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst
index 829bfc32b..d7ad21fff 100644
--- a/doc/extdev/deprecated.rst
+++ b/doc/extdev/deprecated.rst
@@ -26,6 +26,16 @@ The following is a list of deprecated interfaces.
- (will be) Removed
- Alternatives
+ * - ``sphinx.builders.latex.LaTeXBuilder.usepackages``
+ - 3.3
+ - 5.0
+ - N/A
+
+ * - ``sphinx.builders.latex.LaTeXBuilder.usepackages_afger_hyperref``
+ - 3.3
+ - 5.0
+ - N/A
+
* - ``sphinx.ext.autodoc.members_set_option()``
- 3.2
- 5.0
diff --git a/doc/usage/extensions/autodoc.rst b/doc/usage/extensions/autodoc.rst
index 71f49c240..802be3bd0 100644
--- a/doc/usage/extensions/autodoc.rst
+++ b/doc/usage/extensions/autodoc.rst
@@ -229,7 +229,7 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
.. versionchanged:: 3.0
- It takes an anchestor class name as an argument.
+ It takes an ancestor class name as an argument.
* It's possible to override the signature for explicitly documented callable
objects (functions, methods, classes) with the regular syntax that will
diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst
index 311b03d66..f3754ab7c 100644
--- a/doc/usage/restructuredtext/domains.rst
+++ b/doc/usage/restructuredtext/domains.rst
@@ -744,6 +744,18 @@ The following directive can be used for this purpose.
.. versionadded:: 3.2
+
+ .. rubric:: Options
+
+ .. rst:directive:option:: maxdepth: int
+
+ Insert nested declarations as well, up to the total depth given.
+ Use 0 for infinite depth and 1 for just the mentioned declaration.
+ Defaults to 1.
+
+ .. versionadded:: 3.3
+
+
.. c:namespace-pop::
diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py
index 923212a99..c30aa9cfd 100644
--- a/sphinx/builders/html/__init__.py
+++ b/sphinx/builders/html/__init__.py
@@ -751,18 +751,27 @@ class StandaloneHTMLBuilder(Builder):
copyfile(jsfile, path.join(self.outdir, '_static', '_stemmer.js'))
def copy_theme_static_files(self, context: Dict) -> None:
+ def onerror(filename: str, error: Exception) -> None:
+ logger.warning(__('Failed to copy a file in html_static_file: %s: %r'),
+ filename, error)
+
if self.theme:
for entry in self.theme.get_theme_dirs()[::-1]:
copy_asset(path.join(entry, 'static'),
path.join(self.outdir, '_static'),
- excluded=DOTFILES, context=context, renderer=self.templates)
+ excluded=DOTFILES, context=context,
+ renderer=self.templates, onerror=onerror)
def copy_html_static_files(self, context: Dict) -> None:
+ def onerror(filename: str, error: Exception) -> None:
+ logger.warning(__('Failed to copy a file in html_static_file: %s: %r'),
+ filename, error)
+
excluded = Matcher(self.config.exclude_patterns + ["**/.*"])
for entry in self.config.html_static_path:
copy_asset(path.join(self.confdir, entry),
path.join(self.outdir, '_static'),
- excluded, context=context, renderer=self.templates)
+ excluded, context=context, renderer=self.templates, onerror=onerror)
def copy_html_logo(self) -> None:
if self.config.html_logo:
diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py
index 88c471675..b58a44adf 100644
--- a/sphinx/builders/latex/__init__.py
+++ b/sphinx/builders/latex/__init__.py
@@ -24,7 +24,7 @@ from sphinx.builders.latex.constants import ADDITIONAL_SETTINGS, DEFAULT_SETTING
from sphinx.builders.latex.theming import Theme, ThemeFactory
from sphinx.builders.latex.util import ExtBabel
from sphinx.config import Config, ENUM
-from sphinx.deprecation import RemovedInSphinx40Warning
+from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning
from sphinx.environment.adapters.asset import ImageAdapter
from sphinx.errors import NoUri, SphinxError
from sphinx.locale import _, __
@@ -128,8 +128,6 @@ class LaTeXBuilder(Builder):
self.docnames = [] # type: Iterable[str]
self.document_data = [] # type: List[Tuple[str, str, str, str, str, bool]]
self.themes = ThemeFactory(self.app)
- self.usepackages = self.app.registry.latex_packages
- self.usepackages_after_hyperref = self.app.registry.latex_packages_after_hyperref
texescape.init()
self.init_context()
@@ -179,10 +177,6 @@ class LaTeXBuilder(Builder):
key = (self.config.latex_engine, self.config.language[:2])
self.context.update(ADDITIONAL_SETTINGS.get(key, {}))
- # Apply extension settings to context
- self.context['packages'] = self.usepackages
- self.context['packages_after_hyperref'] = self.usepackages_after_hyperref
-
# Apply user settings to context
self.context.update(self.config.latex_elements)
self.context['release'] = self.config.release
@@ -203,6 +197,13 @@ class LaTeXBuilder(Builder):
# Show the release label only if release value exists
self.context.setdefault('releasename', _('Release'))
+ def update_context(self) -> None:
+ """Update template variables for .tex file just before writing."""
+ # Apply extension settings to context
+ registry = self.app.registry
+ self.context['packages'] = registry.latex_packages
+ self.context['packages_after_hyperref'] = registry.latex_packages_after_hyperref
+
def init_babel(self) -> None:
self.babel = ExtBabel(self.config.language, not self.context['babel'])
if self.config.language and not self.babel.is_supported_language():
@@ -290,6 +291,7 @@ class LaTeXBuilder(Builder):
doctree['tocdepth'] = tocdepth
self.post_process_images(doctree)
self.update_doc_context(title, author, theme)
+ self.update_context()
with progress_message(__("writing")):
docsettings._author = author
@@ -448,6 +450,18 @@ class LaTeXBuilder(Builder):
filename = path.join(package_dir, 'templates', 'latex', 'sphinxmessages.sty_t')
copy_asset_file(filename, self.outdir, context=context, renderer=LaTeXRenderer())
+ @property
+ def usepackages(self) -> List[Tuple[str, str]]:
+ warnings.warn('LaTeXBuilder.usepackages is deprecated.',
+ RemovedInSphinx50Warning, stacklevel=2)
+ return self.app.registry.latex_packages
+
+ @property
+ def usepackages_after_hyperref(self) -> List[Tuple[str, str]]:
+ warnings.warn('LaTeXBuilder.usepackages_after_hyperref is deprecated.',
+ RemovedInSphinx50Warning, stacklevel=2)
+ return self.app.registry.latex_packages_after_hyperref
+
def patch_settings(settings: Any) -> Any:
"""Make settings object to show deprecation messages."""
@@ -505,7 +519,7 @@ def validate_latex_theme_options(app: Sphinx, config: Config) -> None:
def install_pakcages_for_ja(app: Sphinx) -> None:
"""Install packages for Japanese."""
- if app.config.language == 'ja':
+ if app.config.language == 'ja' and app.config.latex_engine in ('platex', 'uplatex'):
app.add_latex_package('pxjahyper', after_hyperref=True)
diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py
index c759dacbd..304c871ea 100644
--- a/sphinx/domains/c.py
+++ b/sphinx/domains/c.py
@@ -136,8 +136,8 @@ class ASTIdentifier(ASTBaseBase):
reftype='identifier',
reftarget=targetText, modname=None,
classname=None)
- # key = symbol.get_lookup_key()
- # pnode['c:parent_key'] = key
+ key = symbol.get_lookup_key()
+ pnode['c:parent_key'] = key
if self.is_anon():
pnode += nodes.strong(text="[anonymous]")
else:
@@ -1563,6 +1563,11 @@ class Symbol:
yield s
@property
+ def children(self) -> Iterator["Symbol"]:
+ for c in self._children:
+ yield c
+
+ @property
def children_recurse_anon(self) -> Iterator["Symbol"]:
for c in self._children:
yield c
@@ -3408,10 +3413,13 @@ class CNamespacePopObject(SphinxDirective):
class AliasNode(nodes.Element):
- def __init__(self, sig: str, env: "BuildEnvironment" = None,
+ def __init__(self, sig: str, maxdepth: int, document: Any, env: "BuildEnvironment" = None,
parentKey: LookupKey = None) -> None:
super().__init__()
self.sig = sig
+ self.maxdepth = maxdepth
+ assert maxdepth >= 0
+ self.document = document
if env is not None:
if 'c:parent_symbol' not in env.temp_data:
root = env.domaindata['c']['root_symbol']
@@ -3428,6 +3436,37 @@ class AliasNode(nodes.Element):
class AliasTransform(SphinxTransform):
default_priority = ReferencesResolver.default_priority - 1
+ def _render_symbol(self, s: Symbol, maxdepth: int, document: Any) -> List[Node]:
+ nodes = [] # type: List[Node]
+ options = dict() # type: ignore
+ signode = addnodes.desc_signature('', '')
+ nodes.append(signode)
+ s.declaration.describe_signature(signode, 'markName', self.env, options)
+ if maxdepth == 0:
+ recurse = True
+ elif maxdepth == 1:
+ recurse = False
+ else:
+ maxdepth -= 1
+ recurse = True
+ if recurse:
+ content = addnodes.desc_content()
+ desc = addnodes.desc()
+ content.append(desc)
+ desc.document = document
+ desc['domain'] = 'c'
+ # 'desctype' is a backwards compatible attribute
+ desc['objtype'] = desc['desctype'] = 'alias'
+ desc['noindex'] = True
+
+ for sChild in s.children:
+ childNodes = self._render_symbol(sChild, maxdepth, document)
+ desc.extend(childNodes)
+
+ if len(desc.children) != 0:
+ nodes.append(content)
+ return nodes
+
def apply(self, **kwargs: Any) -> None:
for node in self.document.traverse(AliasNode):
sig = node.sig
@@ -3468,17 +3507,16 @@ class AliasTransform(SphinxTransform):
logger.warning("Could not find C declaration for alias '%s'." % name,
location=node)
node.replace_self(signode)
- else:
- nodes = []
- options = dict() # type: ignore
- signode = addnodes.desc_signature(sig, '')
- nodes.append(signode)
- s.declaration.describe_signature(signode, 'markName', self.env, options)
- node.replace_self(nodes)
+ continue
+
+ nodes = self._render_symbol(s, maxdepth=node.maxdepth, document=node.document)
+ node.replace_self(nodes)
class CAliasObject(ObjectDescription):
- option_spec = {} # type: Dict
+ option_spec = {
+ 'maxdepth': directives.nonnegative_int
+ } # type: Dict
def run(self) -> List[Node]:
if ':' in self.name:
@@ -3494,16 +3532,10 @@ class CAliasObject(ObjectDescription):
node['noindex'] = True
self.names = [] # type: List[str]
+ maxdepth = self.options.get('maxdepth', 1)
signatures = self.get_signatures()
for i, sig in enumerate(signatures):
- node.append(AliasNode(sig, env=self.env))
-
- contentnode = addnodes.desc_content()
- node.append(contentnode)
- self.before_content()
- self.state.nested_parse(self.content, self.content_offset, contentnode)
- self.env.temp_data['object'] = None
- self.after_content()
+ node.append(AliasNode(sig, maxdepth, self.state.document, env=self.env))
return [node]
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py
index ed02c2c90..23fb43a4d 100644
--- a/sphinx/ext/autodoc/__init__.py
+++ b/sphinx/ext/autodoc/__init__.py
@@ -535,6 +535,11 @@ class Documenter:
self.env.app.emit('autodoc-process-docstring',
self.objtype, self.fullname, self.object,
self.options, docstringlines)
+
+ if docstringlines and docstringlines[-1] != '':
+ # append a blank line to the end of the docstring
+ docstringlines.append('')
+
yield from docstringlines
def get_sourcename(self) -> str:
diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py
index 7b2383fca..71a123b15 100644
--- a/sphinx/ext/inheritance_diagram.py
+++ b/sphinx/ext/inheritance_diagram.py
@@ -66,6 +66,10 @@ module_sig_re = re.compile(r'''^(?:([\w.]*)\.)? # module names
''', re.VERBOSE)
+py_builtins = [obj for obj in vars(builtins).values()
+ if inspect.isclass(obj)]
+
+
def try_import(objname: str) -> Any:
"""Import a object or module using *name* and *currentmodule*.
*name* should be a relative name from *currentmodule* or
@@ -178,7 +182,6 @@ class InheritanceGraph:
traverse to. Multiple names can be specified separated by comma.
"""
all_classes = {}
- py_builtins = vars(builtins).values()
def recurse(cls: Any) -> None:
if not show_builtins and cls in py_builtins:
diff --git a/sphinx/testing/util.py b/sphinx/testing/util.py
index 5ac334068..80ca84cb3 100644
--- a/sphinx/testing/util.py
+++ b/sphinx/testing/util.py
@@ -21,7 +21,6 @@ from docutils.nodes import Node
from docutils.parsers.rst import directives, roles
from sphinx import application, locale
-from sphinx.builders.latex import LaTeXBuilder
from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.pycode import ModuleAnalyzer
from sphinx.testing.path import path
@@ -141,7 +140,6 @@ class SphinxTestApp(application.Sphinx):
def cleanup(self, doctrees: bool = False) -> None:
ModuleAnalyzer.cache.clear()
- LaTeXBuilder.usepackages = []
locale.translators.clear()
sys.path[:] = self._saved_path
sys.modules.pop('autodoc_fodder', None)
diff --git a/sphinx/util/fileutil.py b/sphinx/util/fileutil.py
index d8e896d48..eec1ae463 100644
--- a/sphinx/util/fileutil.py
+++ b/sphinx/util/fileutil.py
@@ -10,7 +10,7 @@
import os
import posixpath
-from typing import Dict
+from typing import Callable, Dict
from docutils.utils import relative_path
@@ -56,7 +56,8 @@ def copy_asset_file(source: str, destination: str,
def copy_asset(source: str, destination: str, excluded: PathMatcher = lambda path: False,
- context: Dict = None, renderer: "BaseRenderer" = None) -> None:
+ context: Dict = None, renderer: "BaseRenderer" = None,
+ onerror: Callable[[str, Exception], None] = None) -> None:
"""Copy asset files to destination recursively.
On copying, it expands the template variables if context argument is given and
@@ -67,6 +68,7 @@ def copy_asset(source: str, destination: str, excluded: PathMatcher = lambda pat
:param excluded: The matcher to determine the given path should be copied or not
:param context: The template variables. If not given, template files are simply copied
:param renderer: The template engine. If not given, SphinxRenderer is used by default
+ :param onerror: The error handler.
"""
if not os.path.exists(source):
return
@@ -90,6 +92,12 @@ def copy_asset(source: str, destination: str, excluded: PathMatcher = lambda pat
for filename in files:
if not excluded(posixpath.join(reldir, filename)):
- copy_asset_file(posixpath.join(root, filename),
- posixpath.join(destination, reldir),
- context, renderer)
+ try:
+ copy_asset_file(posixpath.join(root, filename),
+ posixpath.join(destination, reldir),
+ context, renderer)
+ except Exception as exc:
+ if onerror:
+ onerror(posixpath.join(root, filename), exc)
+ else:
+ raise
diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py
index a5c64f882..37997e6b2 100644
--- a/sphinx/util/inspect.py
+++ b/sphinx/util/inspect.py
@@ -304,6 +304,11 @@ def iscoroutinefunction(obj: Any) -> bool:
def isproperty(obj: Any) -> bool:
"""Check if the object is property."""
+ if sys.version_info > (3, 8):
+ from functools import cached_property # cached_property is available since py3.8
+ if isinstance(obj, cached_property):
+ return True
+
return isinstance(obj, property)
diff --git a/tests/roots/test-ext-autodoc/target/cached_property.py b/tests/roots/test-ext-autodoc/target/cached_property.py
new file mode 100644
index 000000000..63ec09f8e
--- /dev/null
+++ b/tests/roots/test-ext-autodoc/target/cached_property.py
@@ -0,0 +1,7 @@
+from functools import cached_property
+
+
+class Foo:
+ @cached_property
+ def prop(self) -> int:
+ return 1
diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py
index 90a2ec95a..1ba64a0a7 100644
--- a/tests/test_ext_autodoc.py
+++ b/tests/test_ext_autodoc.py
@@ -881,6 +881,26 @@ def test_autodoc_descriptor(app):
]
+@pytest.mark.skipif(sys.version_info < (3, 8),
+ reason='cached_property is available since python3.8.')
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_autodoc_cached_property(app):
+ options = {"members": None,
+ "undoc-members": True}
+ actual = do_autodoc(app, 'class', 'target.cached_property.Foo', options)
+ assert list(actual) == [
+ '',
+ '.. py:class:: Foo()',
+ ' :module: target.cached_property',
+ '',
+ '',
+ ' .. py:method:: Foo.prop',
+ ' :module: target.cached_property',
+ ' :property:',
+ '',
+ ]
+
+
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_member_order(app):
# case member-order='bysource'
diff --git a/tests/test_ext_autodoc_events.py b/tests/test_ext_autodoc_events.py
index 4e8348abc..7ddc952ab 100644
--- a/tests/test_ext_autodoc_events.py
+++ b/tests/test_ext_autodoc_events.py
@@ -28,7 +28,8 @@ def test_process_docstring(app):
'.. py:function:: func()',
' :module: target.process_docstring',
'',
- ' my docstring'
+ ' my docstring',
+ '',
]