From 7caa1c7ec4b946c7295788f5277f515887978e62 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Wed, 17 Feb 2021 23:59:55 +0900 Subject: [PATCH 01/58] refactor: Remove meaningless type annotations --- sphinx/util/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 6ae5e6162..83b2561f2 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -47,8 +47,8 @@ if TYPE_CHECKING: logger = logging.getLogger(__name__) # Generally useful regular expressions. -ws_re = re.compile(r'\s+') # type: Pattern -url_re = re.compile(r'(?P.+)://.*') # type: Pattern +ws_re = re.compile(r'\s+') +url_re = re.compile(r'(?P.+)://.*') # High-level utility functions. From 7dacbd96c4fbae237f83682abc99b524f2cc31b3 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 17 Feb 2021 14:50:33 +0000 Subject: [PATCH 02/58] migrate html_add_permalinks=None and html_add_permalinks="" The docs list: Set it to None or the empty string to disable permalinks. --- sphinx/builders/html/__init__.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index c799b0176..fe5438e25 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -1221,15 +1221,26 @@ def validate_html_favicon(app: Sphinx, config: Config) -> None: config.html_favicon = None # type: ignore +UNSET = object() + + def migrate_html_add_permalinks(app: Sphinx, config: Config) -> None: """Migrate html_add_permalinks to html_permalinks*.""" - if config.html_add_permalinks: - if (isinstance(config.html_add_permalinks, bool) and - config.html_add_permalinks is False): - config.html_permalinks = False # type: ignore - else: - config.html_permalinks_icon = html.escape(config.html_add_permalinks) # type: ignore # NOQA + html_add_permalinks = config.html_add_permalinks + if html_add_permalinks is UNSET: + return + if ( + html_add_permalinks is None or + html_add_permalinks is False or + html_add_permalinks == "" + ): + config.html_permalinks = False # type: ignore[attr-defined] + return + + config.html_permalinks_icon = html.escape( # type: ignore[attr-defined] + config.html_add_permalinks + ) # for compatibility import sphinxcontrib.serializinghtml # NOQA @@ -1261,7 +1272,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('html_sidebars', {}, 'html') app.add_config_value('html_additional_pages', {}, 'html') app.add_config_value('html_domain_indices', True, 'html', [list]) - app.add_config_value('html_add_permalinks', None, 'html') + app.add_config_value('html_add_permalinks', UNSET, 'html') app.add_config_value('html_permalinks', True, 'html') app.add_config_value('html_permalinks_icon', '¶', 'html') app.add_config_value('html_use_index', True, 'html') From 95a872e7450d76bde759ac08de7322e692936da6 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Fri, 19 Feb 2021 14:20:13 +0100 Subject: [PATCH 03/58] C++, cpp_index_common_prefix remove longest Fixes sphinx-doc/sphinx#8911 --- CHANGES | 2 ++ sphinx/domains/cpp.py | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 9dfca4ee3..f9f267c3a 100644 --- a/CHANGES +++ b/CHANGES @@ -75,6 +75,8 @@ Bugs fixed change) with late TeXLive 2019 * #8253: LaTeX: Figures with no size defined get overscaled (compared to images with size explicitly set in pixels) (fixed for ``'pdflatex'/'lualatex'`` only) +* #8911: C++: remove the longest matching prefix in + :confval:`cpp_index_common_prefix` instead of the first that matches. Testing -------- diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 7ee023d1d..cf93681ae 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -7644,10 +7644,11 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value("cpp_debug_lookup", False, '') app.add_config_value("cpp_debug_show_tree", False, '') - def setDebugFlags(app): + def initStuff(app): Symbol.debug_lookup = app.config.cpp_debug_lookup Symbol.debug_show_tree = app.config.cpp_debug_show_tree - app.connect("builder-inited", setDebugFlags) + app.config.cpp_index_common_prefix.sort(reverse=True) + app.connect("builder-inited", initStuff) return { 'version': 'builtin', From ad3edd924cd0a76747a8f8c713f0f3559053b9da Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Tue, 23 Feb 2021 20:37:30 +0900 Subject: [PATCH 04/58] Fix #8915: html theme: The translation of sphinx_rtd_theme does not work Since sphinx_rtd_theme-0.5.0, it supports translations. But Sphinx core disallows to enable it because theming framework gives special treatment for the theme for a long time. This goes to load it via setuptools at first to enable the translations. Note: The special treatment for sphinx_rtd_theme (< 0.2.5) is not removed yet. But it will be removed in the future release. --- CHANGES | 2 ++ sphinx/theming.py | 16 +++++++--------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index 9dfca4ee3..798ddb0fc 100644 --- a/CHANGES +++ b/CHANGES @@ -30,6 +30,7 @@ Incompatible changes * html theme: Move a script tag for documentation_options.js in basic/layout.html to ``script_files`` variable * html theme: Move CSS tags in basic/layout.html to ``css_files`` variable +* #8915: html theme: Emit a warning for sphinx_rtd_theme-0.2.4 or older * #8508: LaTeX: uplatex becomes a default setting of latex_engine for Japanese documents * #5977: py domain: ``:var:``, ``:cvar:`` and ``:ivar:`` fields do not create @@ -69,6 +70,7 @@ Bugs fixed ---------- * #8380: html search: Paragraphs in search results are not identified as ``

`` +* #8915: html theme: The translation of sphinx_rtd_theme does not work * #8342: Emit a warning if a unknown domain is given for directive or role (ex. ``:unknown:doc:``) * #8711: LaTeX: backticks in code-blocks trigger latexpdf build warning (and font diff --git a/sphinx/theming.py b/sphinx/theming.py index e84f5ae49..bba47b344 100644 --- a/sphinx/theming.py +++ b/sphinx/theming.py @@ -178,8 +178,6 @@ class HTMLThemeFactory: """Try to load a theme having specifed name.""" if name == 'alabaster': self.load_alabaster_theme() - elif name == 'sphinx_rtd_theme': - self.load_sphinx_rtd_theme() else: self.load_external_theme(name) @@ -237,13 +235,13 @@ class HTMLThemeFactory: if name not in self.themes: self.load_extra_theme(name) + if name not in self.themes and name == 'sphinx_rtd_theme': + # sphinx_rtd_theme (< 0.2.5) # RemovedInSphinx60Warning + logger.warning(__('sphinx_rtd_theme (< 0.3.0) found. ' + 'It will not be available since Sphinx-6.0')) + self.load_sphinx_rtd_theme() + if name not in self.themes: - if name == 'sphinx_rtd_theme': - raise ThemeError(__('sphinx_rtd_theme is no longer a hard dependency ' - 'since version 1.4.0. Please install it manually.' - '(pip install sphinx_rtd_theme)')) - else: - raise ThemeError(__('no theme named %r found ' - '(missing theme.conf?)') % name) + raise ThemeError(__('no theme named %r found (missing theme.conf?)') % name) return Theme(name, self.themes[name], factory=self) From a39cf57717a684bcd7fc81833a73397593a412d7 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Wed, 24 Feb 2021 22:51:06 +0900 Subject: [PATCH 05/58] Close #8924: autodoc: Support `bound` argument for TypeVar --- CHANGES | 1 + sphinx/ext/autodoc/__init__.py | 2 ++ tests/roots/test-ext-autodoc/target/typevar.py | 3 +++ tests/test_ext_autodoc.py | 8 ++++++++ 4 files changed, 14 insertions(+) diff --git a/CHANGES b/CHANGES index 9dfca4ee3..27cef1e6e 100644 --- a/CHANGES +++ b/CHANGES @@ -55,6 +55,7 @@ Deprecated Features added -------------- +* #8924: autodoc: Support ``bound`` argument for TypeVar * #4826: py domain: Add ``:canonical:`` option to python directives to describe the location where the object is defined * #7784: i18n: The alt text for image is translated by default (without diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 2ac24ebff..8d6781a1a 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1812,6 +1812,8 @@ class TypeVarMixin(DataDocumenterMixinBase): attrs = [repr(self.object.__name__)] for constraint in self.object.__constraints__: attrs.append(stringify_typehint(constraint)) + if self.object.__bound__: + attrs.append(r"bound=\ " + restify(self.object.__bound__)) if self.object.__covariant__: attrs.append("covariant=True") if self.object.__contravariant__: diff --git a/tests/roots/test-ext-autodoc/target/typevar.py b/tests/roots/test-ext-autodoc/target/typevar.py index 864fea20c..c330e2d88 100644 --- a/tests/roots/test-ext-autodoc/target/typevar.py +++ b/tests/roots/test-ext-autodoc/target/typevar.py @@ -17,6 +17,9 @@ T5 = TypeVar("T5", contravariant=True) #: T6 T6 = NewType("T6", int) +#: T7 +T7 = TypeVar("T7", bound=int) + class Class: #: T1 diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index d657ead60..d9986e9ca 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -1993,6 +1993,14 @@ def test_autodoc_TypeVar(app): '', ' alias of :class:`int`', '', + '', + '.. py:data:: T7', + ' :module: target.typevar', + '', + ' T7', + '', + " alias of TypeVar('T7', bound=\\ :class:`int`)", + '', ] From 17337a3257927fbd3e8aeeabd73201054e39331b Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sun, 11 Oct 2020 13:57:04 +0200 Subject: [PATCH 06/58] C, properly error on keywords as function parameters --- CHANGES | 2 ++ sphinx/domains/c.py | 3 +++ tests/test_domain_c.py | 3 +++ 3 files changed, 8 insertions(+) diff --git a/CHANGES b/CHANGES index f9f267c3a..0e03a3f0a 100644 --- a/CHANGES +++ b/CHANGES @@ -77,6 +77,8 @@ Bugs fixed with size explicitly set in pixels) (fixed for ``'pdflatex'/'lualatex'`` only) * #8911: C++: remove the longest matching prefix in :confval:`cpp_index_common_prefix` instead of the first that matches. +* C, properly reject function declarations when a keyword is used + as parameter name. Testing -------- diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 061010d66..4909844cb 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -2703,6 +2703,9 @@ class DefinitionParser(BaseParser): declId = None elif named == 'single': if self.match(identifier_re): + if self.matched_text in _keywords: + self.fail("Expected identifier, " + "got keyword: %s" % self.matched_text) identifier = ASTIdentifier(self.matched_text) declId = ASTNestedName([identifier], rooted=False) else: diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py index 2cfcf74fa..999c99f4d 100644 --- a/tests/test_domain_c.py +++ b/tests/test_domain_c.py @@ -417,6 +417,9 @@ def test_function_definitions(): check('function', 'void f(int arr[const static volatile 42])', {1: 'f'}, output='void f(int arr[static volatile const 42])') + with pytest.raises(DefinitionError): + parse('function', 'void f(int for)') + def test_nested_name(): check('struct', '{key}.A', {1: "A"}) From 40f4db30e8943a1ea3bcb2c1726dbacbf6d419ac Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sun, 11 Oct 2020 14:05:07 +0200 Subject: [PATCH 07/58] C, remove dead code --- sphinx/domains/c.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 4909844cb..3693e536a 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -2693,15 +2693,9 @@ class DefinitionParser(BaseParser): def _parse_declarator_name_suffix( self, named: Union[bool, str], paramMode: str, typed: bool ) -> ASTDeclarator: + assert named in (True, False, 'single') # now we should parse the name, and then suffixes - if named == 'maybe': - pos = self.pos - try: - declId = self._parse_nested_name() - except DefinitionError: - self.pos = pos - declId = None - elif named == 'single': + if named == 'single': if self.match(identifier_re): if self.matched_text in _keywords: self.fail("Expected identifier, " @@ -2883,8 +2877,8 @@ class DefinitionParser(BaseParser): def _parse_type(self, named: Union[bool, str], outer: str = None) -> ASTType: """ - named=False|'maybe'|True: 'maybe' is e.g., for function objects which - doesn't need to name the arguments + named=False|'single'|True: 'single' is e.g., for function objects which + doesn't need to name the arguments, but otherwise is a single name """ if outer: # always named if outer not in ('type', 'member', 'function'): From 088cef98bcb34522c2ec88c2fb4fd74199e2f10d Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sun, 11 Oct 2020 15:17:32 +0200 Subject: [PATCH 08/58] C, remove more dead code --- sphinx/domains/c.py | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 3693e536a..79d4e145c 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -387,19 +387,6 @@ class ASTPostfixDec(ASTPostfixOp): signode.append(nodes.Text('--')) -class ASTPostfixMember(ASTPostfixOp): - def __init__(self, name): - self.name = name - - def _stringify(self, transform: StringifyTransform) -> str: - return '.' + transform(self.name) - - def describe_signature(self, signode: TextElement, mode: str, - env: "BuildEnvironment", symbol: "Symbol") -> None: - signode.append(nodes.Text('.')) - self.name.describe_signature(signode, 'noneIsName', env, symbol) - - class ASTPostfixMemberOfPointer(ASTPostfixOp): def __init__(self, name): self.name = name @@ -2256,7 +2243,7 @@ class DefinitionParser(BaseParser): # | postfix "[" expression "]" # | postfix "[" braced-init-list [opt] "]" # | postfix "(" expression-list [opt] ")" - # | postfix "." id-expression + # | postfix "." id-expression // taken care of in primary by nested name # | postfix "->" id-expression # | postfix "++" # | postfix "--" @@ -2274,17 +2261,6 @@ class DefinitionParser(BaseParser): self.fail("Expected ']' in end of postfix expression.") postFixes.append(ASTPostfixArray(expr)) continue - if self.skip_string('.'): - if self.skip_string('*'): - # don't steal the dot - self.pos -= 2 - elif self.skip_string('..'): - # don't steal the dot - self.pos -= 3 - else: - name = self._parse_nested_name() - postFixes.append(ASTPostfixMember(name)) - continue if self.skip_string('->'): if self.skip_string('*'): # don't steal the arrow From 71a656498b0c091adf22394a5a004bee759d059c Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sun, 11 Oct 2020 15:23:11 +0200 Subject: [PATCH 09/58] C, simplify tests --- tests/roots/test-domain-c/semicolon.rst | 10 -------- tests/test_domain_c.py | 31 +++++++++++++++++++++---- 2 files changed, 26 insertions(+), 15 deletions(-) delete mode 100644 tests/roots/test-domain-c/semicolon.rst diff --git a/tests/roots/test-domain-c/semicolon.rst b/tests/roots/test-domain-c/semicolon.rst deleted file mode 100644 index 14ba17756..000000000 --- a/tests/roots/test-domain-c/semicolon.rst +++ /dev/null @@ -1,10 +0,0 @@ -.. c:member:: int member; -.. c:var:: int var; -.. c:function:: void f(); -.. .. c:macro:: NO_SEMICOLON; -.. c:struct:: Struct; -.. c:union:: Union; -.. c:enum:: Enum; -.. c:enumerator:: Enumerator; -.. c:type:: Type; -.. c:type:: int TypeDef; diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py index 999c99f4d..5d2b2c505 100644 --- a/tests/test_domain_c.py +++ b/tests/test_domain_c.py @@ -526,8 +526,15 @@ def test_attributes(): # raise DefinitionError("") +def split_warnigns(warning): + ws = warning.getvalue().split("\n") + assert len(ws) >= 1 + assert ws[-1] == "" + return ws[:-1] + + def filter_warnings(warning, file): - lines = warning.getvalue().split("\n") + lines = split_warnigns(warning) res = [l for l in lines if "domain-c" in l and "{}.rst".format(file) in l and "WARNING: document isn't included in any toctree" not in l] print("Filtered warnings for file '{}':".format(file)) @@ -581,10 +588,22 @@ def test_build_domain_c_anon_dup_decl(app, status, warning): assert "WARNING: c:identifier reference target not found: @b" in ws[1] -@pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True}) -def test_build_domain_c_semicolon(app, status, warning): - app.builder.build_all() - ws = filter_warnings(warning, "semicolon") +@pytest.mark.sphinx(confoverrides={'nitpicky': True}) +def test_build_domain_c_semicolon(app, warning): + text = """ +.. c:member:: int member; +.. c:var:: int var; +.. c:function:: void f(); +.. .. c:macro:: NO_SEMICOLON; +.. c:struct:: Struct; +.. c:union:: Union; +.. c:enum:: Enum; +.. c:enumerator:: Enumerator; +.. c:type:: Type; +.. c:type:: int TypeDef; +""" + restructuredtext.parse(app, text) + ws = split_warnigns(warning) assert len(ws) == 0 @@ -659,6 +678,8 @@ def test_noindexentry(app): @pytest.mark.sphinx(testroot='domain-c-intersphinx', confoverrides={'nitpicky': True}) def test_intersphinx(tempdir, app, status, warning): + # a splitting of test_ids_vs_tags0 into the primary directives in a remote project, + # and then the references in the test project origSource = """\ .. c:member:: int _member .. c:var:: int _var From 0256daf11281650031c84fff3041aa5ca593fa93 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Wed, 24 Feb 2021 15:27:23 +0100 Subject: [PATCH 10/58] C, test namespace revamp --- tests/roots/test-domain-c/anon-dup-decl.rst | 2 ++ tests/roots/test-domain-c/function_param_target.rst | 2 ++ tests/roots/test-domain-c/index.rst | 2 ++ tests/test_domain_c.py | 4 ++-- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/roots/test-domain-c/anon-dup-decl.rst b/tests/roots/test-domain-c/anon-dup-decl.rst index 5f6c3bdfe..743ae2f6a 100644 --- a/tests/roots/test-domain-c/anon-dup-decl.rst +++ b/tests/roots/test-domain-c/anon-dup-decl.rst @@ -1,3 +1,5 @@ +.. c:namespace:: anon_dup_decl_ns + .. c:struct:: anon_dup_decl .. c:struct:: @a.A diff --git a/tests/roots/test-domain-c/function_param_target.rst b/tests/roots/test-domain-c/function_param_target.rst index 05de01445..d316d7bcd 100644 --- a/tests/roots/test-domain-c/function_param_target.rst +++ b/tests/roots/test-domain-c/function_param_target.rst @@ -1,3 +1,5 @@ +.. c:namespace:: function_param_target + .. c:function:: void f(int i) - :c:var:`i` diff --git a/tests/roots/test-domain-c/index.rst b/tests/roots/test-domain-c/index.rst index 7e2c18be9..4febd63ef 100644 --- a/tests/roots/test-domain-c/index.rst +++ b/tests/roots/test-domain-c/index.rst @@ -1,3 +1,5 @@ +.. c:namespace:: index + test-domain-c ============= diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py index 5d2b2c505..38e83a77e 100644 --- a/tests/test_domain_c.py +++ b/tests/test_domain_c.py @@ -615,8 +615,8 @@ def test_build_function_param_target(app, warning): assert len(ws) == 0 entries = extract_role_links(app, "function_param_target.html") assert entries == [ - ('c.f', 'i', 'i'), - ('c.f', 'f.i', 'f.i'), + ('c.function_param_target.f', 'i', 'i'), + ('c.function_param_target.f', 'f.i', 'f.i'), ] From 294870ba59b491ad8a854d58710a2c0b65b7efbb Mon Sep 17 00:00:00 2001 From: jfbu Date: Wed, 24 Feb 2021 12:53:20 +0100 Subject: [PATCH 11/58] LaTeX: let underfull calculation in wrapped code lines ignore last line Closes: #8925 --- sphinx/texinputs/sphinxlatexliterals.sty | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/sphinx/texinputs/sphinxlatexliterals.sty b/sphinx/texinputs/sphinxlatexliterals.sty index 4abb3472a..a2943b2dc 100644 --- a/sphinx/texinputs/sphinxlatexliterals.sty +++ b/sphinx/texinputs/sphinxlatexliterals.sty @@ -343,8 +343,21 @@ \fi\fi }% % auxiliary paragraph dissector to get max and min widths +% but minwidth must not take into account the last line \newbox\spx@scratchbox \def\spx@verb@getwidths {% + \unskip\unpenalty + \setbox\spx@scratchbox\lastbox + \ifvoid\spx@scratchbox + \else + \setbox\spx@scratchbox\hbox{\unhbox\spx@scratchbox}% + \ifdim\spx@verb@maxwidth<\wd\spx@scratchbox + \xdef\spx@verb@maxwidth{\number\wd\spx@scratchbox sp}% + \fi + \expandafter\spx@verb@getwidths@loop + \fi +}% +\def\spx@verb@getwidths@loop {% \unskip\unpenalty \setbox\spx@scratchbox\lastbox \ifvoid\spx@scratchbox @@ -356,7 +369,7 @@ \ifdim\spx@verb@minwidth>\wd\spx@scratchbox \xdef\spx@verb@minwidth{\number\wd\spx@scratchbox sp}% \fi - \expandafter\spx@verb@getwidths + \expandafter\spx@verb@getwidths@loop \fi }% % auxiliary macros to implement "cut long line even in middle of word" From 40b145c7dccd69704543f37f6a256d6cbfe0fe76 Mon Sep 17 00:00:00 2001 From: jfbu Date: Wed, 24 Feb 2021 12:53:51 +0100 Subject: [PATCH 12/58] Update CHANGES --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 0e03a3f0a..bd37f081f 100644 --- a/CHANGES +++ b/CHANGES @@ -79,6 +79,8 @@ Bugs fixed :confval:`cpp_index_common_prefix` instead of the first that matches. * C, properly reject function declarations when a keyword is used as parameter name. +* #8925: LaTeX: 3.5.0 ``verbatimmaxunderfull`` setting does not work as + expected Testing -------- From c9a1a9bb4f7a72831d214f2f6f48683601e56886 Mon Sep 17 00:00:00 2001 From: jfbu Date: Wed, 24 Feb 2021 19:01:34 +0100 Subject: [PATCH 13/58] Gather LaTeX items in CHANGES for 4.0.0 --- CHANGES | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index bd37f081f..d90e8981c 100644 --- a/CHANGES +++ b/CHANGES @@ -75,12 +75,12 @@ Bugs fixed change) with late TeXLive 2019 * #8253: LaTeX: Figures with no size defined get overscaled (compared to images with size explicitly set in pixels) (fixed for ``'pdflatex'/'lualatex'`` only) +* #8925: LaTeX: 3.5.0 ``verbatimmaxunderfull`` setting does not work as + expected * #8911: C++: remove the longest matching prefix in :confval:`cpp_index_common_prefix` instead of the first that matches. * C, properly reject function declarations when a keyword is used as parameter name. -* #8925: LaTeX: 3.5.0 ``verbatimmaxunderfull`` setting does not work as - expected Testing -------- From 4f072ee5c29faf7d6d636f37216e92e09011409c Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Wed, 24 Feb 2021 01:32:05 +0900 Subject: [PATCH 14/58] Fix #8917: autodoc: Raises a warning if function has wrong __globals__ value `sphinx.util.inspect:signature()` crashes with AttributeError when subject has wrong `__globals__` value. This ignores the error on building. --- CHANGES | 1 + sphinx/util/inspect.py | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index e4b06ae41..9ddab2294 100644 --- a/CHANGES +++ b/CHANGES @@ -69,6 +69,7 @@ Features added Bugs fixed ---------- +* #8917: autodoc: Raises a warning if function has wrong __globals__ value * #8380: html search: Paragraphs in search results are not identified as ``

`` * #8915: html theme: The translation of sphinx_rtd_theme does not work * #8342: Emit a warning if a unknown domain is given for directive or role (ex. diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 3cf1c6824..dbeb547b1 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -166,6 +166,15 @@ def getannotations(obj: Any) -> Mapping[str, Any]: return {} +def getglobals(obj: Any) -> Mapping[str, Any]: + """Get __globals__ from given *obj* safely.""" + __globals__ = safe_getattr(obj, '__globals__', None) + if isinstance(__globals__, Mapping): + return __globals__ + else: + return {} + + def getmro(obj: Any) -> Tuple["Type", ...]: """Get __mro__ from given *obj* safely.""" __mro__ = safe_getattr(obj, '__mro__', None) @@ -484,9 +493,9 @@ class DefaultValue: def _should_unwrap(subject: Callable) -> bool: """Check the function should be unwrapped on getting signature.""" - if (safe_getattr(subject, '__globals__', None) and - subject.__globals__.get('__name__') == 'contextlib' and # type: ignore - subject.__globals__.get('__file__') == contextlib.__file__): # type: ignore + __globals__ = getglobals(subject) + if (__globals__.get('__name__') == 'contextlib' and + __globals__.get('__file__') == contextlib.__file__): # contextmanger should be unwrapped return True From 255b4aee051e3318eb8fe42e606f321183736f8c Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 26 Feb 2021 01:51:01 +0900 Subject: [PATCH 15/58] Fix #8933: viewcode: Failed to create back-links on parallel build On parallel build mode, viewcode losts the back-links information on gathering results from each process. As a result, some back-links are missing in the generated viewcode pages. This fixes the merging back-links process for parallel builds. --- CHANGES | 1 + sphinx/ext/viewcode.py | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index e4b06ae41..6c3821417 100644 --- a/CHANGES +++ b/CHANGES @@ -83,6 +83,7 @@ Bugs fixed :confval:`cpp_index_common_prefix` instead of the first that matches. * C, properly reject function declarations when a keyword is used as parameter name. +* #8933: viewcode: Failed to create back-links on parallel build Testing -------- diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py index 6bedf2e1c..c98ff0c88 100644 --- a/sphinx/ext/viewcode.py +++ b/sphinx/ext/viewcode.py @@ -146,7 +146,14 @@ def env_merge_info(app: Sphinx, env: BuildEnvironment, docnames: Iterable[str], if not hasattr(env, '_viewcode_modules'): env._viewcode_modules = {} # type: ignore # now merge in the information from the subprocess - env._viewcode_modules.update(other._viewcode_modules) # type: ignore + for modname, entry in other._viewcode_modules.items(): # type: ignore + if modname not in env._viewcode_modules: # type: ignore + env._viewcode_modules[modname] = entry # type: ignore + else: + used = env._viewcode_modules[modname][2] # type: ignore + for fullname, docname in entry[2].items(): + if fullname not in used: + used[fullname] = docname def env_purge_doc(app: Sphinx, env: BuildEnvironment, docname: str) -> None: From 20884bb0c9f76834972b52e28f24df989a10eb68 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 13 Feb 2021 00:59:57 +0900 Subject: [PATCH 16/58] refactor: LaTeX: Use raw strings for LaTeX macros --- sphinx/writers/latex.py | 366 ++++++++++++++++++++-------------------- 1 file changed, 181 insertions(+), 185 deletions(-) diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 91585e402..93f97d028 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -164,15 +164,15 @@ class Table: return self.colspec elif self.colwidths and 'colwidths-given' in self.classes: total = sum(self.colwidths) - colspecs = ['\\X{%d}{%d}' % (width, total) for width in self.colwidths] + colspecs = [r'\X{%d}{%d}' % (width, total) for width in self.colwidths] return '{|%s|}' % '|'.join(colspecs) + CR elif self.has_problematic: - return '{|*{%d}{\\X{1}{%d}|}}' % (self.colcount, self.colcount) + CR + return r'{|*{%d}{\X{1}{%d}|}}' % (self.colcount, self.colcount) + CR elif self.get_table_type() == 'tabulary': # sphinx.sty sets T to be J by default. return '{|' + ('T|' * self.colcount) + '}' + CR elif self.has_oldproblematic: - return '{|*{%d}{\\X{1}{%d}|}}' % (self.colcount, self.colcount) + CR + return r'{|*{%d}{\X{1}{%d}|}}' % (self.colcount, self.colcount) + CR else: return '{|' + ('l|' * self.colcount) + '}' + CR @@ -253,19 +253,19 @@ def rstdim_to_latexdim(width_str: str, scale: int = 100) -> str: if scale == 100: float(amount) # validate amount is float if unit in ('', "px"): - res = "%s\\sphinxpxdimen" % amount + res = r"%s\sphinxpxdimen" % amount elif unit == 'pt': res = '%sbp' % amount # convert to 'bp' elif unit == "%": - res = "%.3f\\linewidth" % (float(amount) / 100.0) + res = r"%.3f\linewidth" % (float(amount) / 100.0) else: amount_float = float(amount) * scale / 100.0 if unit in ('', "px"): - res = "%.5f\\sphinxpxdimen" % amount_float + res = r"%.5f\sphinxpxdimen" % amount_float elif unit == 'pt': res = '%.5fbp' % amount_float elif unit == "%": - res = "%.5f\\linewidth" % (amount_float / 100.0) + res = r"%.5f\linewidth" % (amount_float / 100.0) else: res = "%.5f%s" % (amount_float, unit) return res @@ -373,9 +373,9 @@ class LaTeXTranslator(SphinxTranslator): if (self.config.language not in {None, 'en', 'ja'} and 'fncychap' not in self.config.latex_elements): # use Sonny style if any language specified (except English) - self.elements['fncychap'] = ('\\usepackage[Sonny]{fncychap}' + CR + - '\\ChNameVar{\\Large\\normalfont\\sffamily}' + CR + - '\\ChTitleVar{\\Large\\normalfont\\sffamily}') + self.elements['fncychap'] = (r'\usepackage[Sonny]{fncychap}' + CR + + r'\ChNameVar{\Large\normalfont\sffamily}' + CR + + r'\ChTitleVar{\Large\normalfont\sffamily}') self.babel = self.builder.babel if self.config.language and not self.babel.is_supported_language(): @@ -400,19 +400,19 @@ class LaTeXTranslator(SphinxTranslator): logger.warning(__('too large :maxdepth:, ignored.')) tocdepth = len(LATEXSECTIONNAMES) - 2 - self.elements['tocdepth'] = '\\setcounter{tocdepth}{%d}' % tocdepth + self.elements['tocdepth'] = r'\setcounter{tocdepth}{%d}' % tocdepth minsecnumdepth = max(minsecnumdepth, tocdepth) if self.config.numfig and (self.config.numfig_secnum_depth > 0): minsecnumdepth = max(minsecnumdepth, self.numfig_secnum_depth - 1) if minsecnumdepth > self.secnumdepth: - self.elements['secnumdepth'] = '\\setcounter{secnumdepth}{%d}' %\ + self.elements['secnumdepth'] = r'\setcounter{secnumdepth}{%d}' %\ minsecnumdepth contentsname = document.get('contentsname') if contentsname: - self.elements['contentsname'] = self.babel_renewcommand('\\contentsname', + self.elements['contentsname'] = self.babel_renewcommand(r'\contentsname', contentsname) if self.elements['maxlistdepth']: @@ -420,8 +420,7 @@ class LaTeXTranslator(SphinxTranslator): if sphinxpkgoptions: self.elements['sphinxpkgoptions'] = '[,%s]' % ','.join(sphinxpkgoptions) if self.elements['sphinxsetup']: - self.elements['sphinxsetup'] = ('\\sphinxsetup{%s}' % - self.elements['sphinxsetup']) + self.elements['sphinxsetup'] = (r'\sphinxsetup{%s}' % self.elements['sphinxsetup']) if self.elements['extraclassoptions']: self.elements['classoptions'] += ',' + \ self.elements['extraclassoptions'] @@ -466,8 +465,7 @@ class LaTeXTranslator(SphinxTranslator): def hypertarget(self, id: str, withdoc: bool = True, anchor: bool = True) -> str: if withdoc: id = self.curfilestack[-1] + ':' + id - return ('\\phantomsection' if anchor else '') + \ - '\\label{%s}' % self.idescape(id) + return (r'\phantomsection' if anchor else '') + r'\label{%s}' % self.idescape(id) def hypertarget_to(self, node: Element, anchor: bool = False) -> str: labels = ''.join(self.hypertarget(node_id, anchor=False) for node_id in node['ids']) @@ -477,48 +475,48 @@ class LaTeXTranslator(SphinxTranslator): return labels def hyperlink(self, id: str) -> str: - return '{\\hyperref[%s]{' % self.idescape(id) + return r'{\hyperref[%s]{' % self.idescape(id) def hyperpageref(self, id: str) -> str: - return '\\autopageref*{%s}' % self.idescape(id) + return r'\autopageref*{%s}' % self.idescape(id) def escape(self, s: str) -> str: return texescape.escape(s, self.config.latex_engine) def idescape(self, id: str) -> str: - return '\\detokenize{%s}' % str(id).translate(tex_replace_map).\ + return r'\detokenize{%s}' % str(id).translate(tex_replace_map).\ encode('ascii', 'backslashreplace').decode('ascii').\ replace('\\', '_') def babel_renewcommand(self, command: str, definition: str) -> str: if self.elements['multilingual']: - prefix = '\\addto\\captions%s{' % self.babel.get_language() + prefix = r'\addto\captions%s{' % self.babel.get_language() suffix = '}' else: # babel is disabled (mainly for Japanese environment) prefix = '' suffix = '' - return '%s\\renewcommand{%s}{%s}%s' % (prefix, command, definition, suffix) + CR + return r'%s\renewcommand{%s}{%s}%s' % (prefix, command, definition, suffix) + CR def generate_indices(self) -> str: def generate(content: List[Tuple[str, List[IndexEntry]]], collapsed: bool) -> None: - ret.append('\\begin{sphinxtheindex}' + CR) - ret.append('\\let\\bigletter\\sphinxstyleindexlettergroup' + CR) + ret.append(r'\begin{sphinxtheindex}' + CR) + ret.append(r'\let\bigletter\sphinxstyleindexlettergroup' + CR) for i, (letter, entries) in enumerate(content): if i > 0: - ret.append('\\indexspace' + CR) - ret.append('\\bigletter{%s}' % self.escape(letter) + CR) + ret.append(r'\indexspace' + CR) + ret.append(r'\bigletter{%s}' % self.escape(letter) + CR) for entry in entries: if not entry[3]: continue - ret.append('\\item\\relax\\sphinxstyleindexentry{%s}' % + ret.append(r'\item\relax\sphinxstyleindexentry{%s}' % self.encode(entry[0])) if entry[4]: # add "extra" info - ret.append('\\sphinxstyleindexextra{%s}' % self.encode(entry[4])) - ret.append('\\sphinxstyleindexpageref{%s:%s}' % + ret.append(r'\sphinxstyleindexextra{%s}' % self.encode(entry[4])) + ret.append(r'\sphinxstyleindexpageref{%s:%s}' % (entry[2], self.idescape(entry[3])) + CR) - ret.append('\\end{sphinxtheindex}' + CR) + ret.append(r'\end{sphinxtheindex}' + CR) ret = [] # latex_domain_indices can be False/True or a list of index names @@ -534,7 +532,7 @@ class LaTeXTranslator(SphinxTranslator): self.builder.docnames) if not content: continue - ret.append('\\renewcommand{\\indexname}{%s}' % indexcls.localname + CR) + ret.append(r'\renewcommand{\indexname}{%s}' % indexcls.localname + CR) generate(content, collapsed) return ''.join(ret) @@ -564,7 +562,7 @@ class LaTeXTranslator(SphinxTranslator): self.first_document = 0 elif self.first_document == 0: # ... and all others are the appendices - self.body.append(CR + '\\appendix' + CR) + self.body.append(CR + r'\appendix' + CR) self.first_document = -1 if 'docname' in node: self.body.append(self.hypertarget(':doc')) @@ -597,11 +595,11 @@ class LaTeXTranslator(SphinxTranslator): def visit_topic(self, node: Element) -> None: self.in_minipage = 1 - self.body.append(CR + '\\begin{sphinxShadowBox}' + CR) + self.body.append(CR + r'\begin{sphinxShadowBox}' + CR) def depart_topic(self, node: Element) -> None: self.in_minipage = 0 - self.body.append('\\end{sphinxShadowBox}' + CR) + self.body.append(r'\end{sphinxShadowBox}' + CR) visit_sidebar = visit_topic depart_sidebar = depart_topic @@ -613,20 +611,20 @@ class LaTeXTranslator(SphinxTranslator): def visit_productionlist(self, node: Element) -> None: self.body.append(BLANKLINE) - self.body.append('\\begin{productionlist}' + CR) + self.body.append(r'\begin{productionlist}' + CR) self.in_production_list = 1 def depart_productionlist(self, node: Element) -> None: - self.body.append('\\end{productionlist}' + BLANKLINE) + self.body.append(r'\end{productionlist}' + BLANKLINE) self.in_production_list = 0 def visit_production(self, node: Element) -> None: if node['tokenname']: tn = node['tokenname'] self.body.append(self.hypertarget('grammar-token-' + tn)) - self.body.append('\\production{%s}{' % self.encode(tn)) + self.body.append(r'\production{%s}{' % self.encode(tn)) else: - self.body.append('\\productioncont{') + self.body.append(r'\productioncont{') def depart_production(self, node: Element) -> None: self.body.append('}' + CR) @@ -681,7 +679,7 @@ class LaTeXTranslator(SphinxTranslator): logger.warning(__('encountered title node not in section, topic, table, ' 'admonition or sidebar'), location=node) - self.body.append('\\sphinxstyleothertitle{') + self.body.append(r'\sphinxstyleothertitle{') self.context.append('}' + CR) self.in_title = 1 @@ -694,7 +692,7 @@ class LaTeXTranslator(SphinxTranslator): def visit_subtitle(self, node: Element) -> None: if isinstance(node.parent, nodes.sidebar): - self.body.append('\\sphinxstylesidebarsubtitle{') + self.body.append(r'\sphinxstylesidebarsubtitle{') self.context.append('}' + CR) else: self.context.append('') @@ -705,18 +703,18 @@ class LaTeXTranslator(SphinxTranslator): def visit_desc(self, node: Element) -> None: if self.config.latex_show_urls == 'footnote': self.body.append(BLANKLINE) - self.body.append('\\begin{savenotes}\\begin{fulllineitems}' + CR) + self.body.append(r'\begin{savenotes}\begin{fulllineitems}' + CR) else: self.body.append(BLANKLINE) - self.body.append('\\begin{fulllineitems}' + CR) + self.body.append(r'\begin{fulllineitems}' + CR) if self.table: self.table.has_problematic = True def depart_desc(self, node: Element) -> None: if self.config.latex_show_urls == 'footnote': - self.body.append(CR + '\\end{fulllineitems}\\end{savenotes}' + BLANKLINE) + self.body.append(CR + r'\end{fulllineitems}\end{savenotes}' + BLANKLINE) else: - self.body.append(CR + '\\end{fulllineitems}' + BLANKLINE) + self.body.append(CR + r'\end{fulllineitems}' + BLANKLINE) def _visit_signature_line(self, node: Element) -> None: for child in node: @@ -739,14 +737,14 @@ class LaTeXTranslator(SphinxTranslator): self._visit_signature_line(node) else: self.body.append('%' + CR) - self.body.append('\\pysigstartmultiline' + CR) + self.body.append(r'\pysigstartmultiline' + CR) def depart_desc_signature(self, node: Element) -> None: if not node.get('is_multiline'): self._depart_signature_line(node) else: self.body.append('%' + CR) - self.body.append('\\pysigstopmultiline') + self.body.append(r'\pysigstopmultiline') def visit_desc_signature_line(self, node: Element) -> None: self._visit_signature_line(node) @@ -825,8 +823,8 @@ class LaTeXTranslator(SphinxTranslator): def visit_seealso(self, node: Element) -> None: self.body.append(BLANKLINE) - self.body.append('\\sphinxstrong{%s:}' % admonitionlabels['seealso'] + CR) - self.body.append('\\nopagebreak' + BLANKLINE) + self.body.append(r'\sphinxstrong{%s:}' % admonitionlabels['seealso'] + CR) + self.body.append(r'\nopagebreak' + BLANKLINE) def depart_seealso(self, node: Element) -> None: self.body.append(BLANKLINE) @@ -834,7 +832,7 @@ class LaTeXTranslator(SphinxTranslator): def visit_rubric(self, node: Element) -> None: if len(node) == 1 and node.astext() in ('Footnotes', _('Footnotes')): raise nodes.SkipNode - self.body.append('\\subsubsection*{') + self.body.append(r'\subsubsection*{') self.context.append('}' + CR) self.in_title = 1 @@ -846,23 +844,23 @@ class LaTeXTranslator(SphinxTranslator): self.in_footnote += 1 label = cast(nodes.label, node[0]) if 'auto' not in node: - self.body.append('\\sphinxstepexplicit ') + self.body.append(r'\sphinxstepexplicit ') if self.in_parsed_literal: - self.body.append('\\begin{footnote}[%s]' % label.astext()) + self.body.append(r'\begin{footnote}[%s]' % label.astext()) else: self.body.append('%' + CR) - self.body.append('\\begin{footnote}[%s]' % label.astext()) + self.body.append(r'\begin{footnote}[%s]' % label.astext()) if 'auto' not in node: - self.body.append('\\phantomsection' - '\\label{\\thesphinxscope.%s}%%' % label.astext() + CR) - self.body.append('\\sphinxAtStartFootnote' + CR) + self.body.append(r'\phantomsection' + r'\label{\thesphinxscope.%s}%%' % label.astext() + CR) + self.body.append(r'\sphinxAtStartFootnote' + CR) def depart_footnote(self, node: Element) -> None: if self.in_parsed_literal: - self.body.append('\\end{footnote}') + self.body.append(r'\end{footnote}') else: self.body.append('%' + CR) - self.body.append('\\end{footnote}') + self.body.append(r'\end{footnote}') self.in_footnote -= 1 def visit_label(self, node: Element) -> None: @@ -950,25 +948,24 @@ class LaTeXTranslator(SphinxTranslator): self.body.append('&') if cell.width == 1: # insert suitable strut for equalizing row heights in given multirow - self.body.append('\\sphinxtablestrut{%d}' % cell.cell_id) + self.body.append(r'\sphinxtablestrut{%d}' % cell.cell_id) else: # use \multicolumn for wide multirow cell - self.body.append('\\multicolumn{%d}{|l|}' - '{\\sphinxtablestrut{%d}}' % + self.body.append(r'\multicolumn{%d}{|l|}\sphinxtablestrut{%d}}' % (cell.width, cell.cell_id)) def depart_row(self, node: Element) -> None: - self.body.append('\\\\' + CR) + self.body.append(r'\\' + CR) cells = [self.table.cell(self.table.row, i) for i in range(self.table.colcount)] underlined = [cell.row + cell.height == self.table.row + 1 for cell in cells] if all(underlined): - self.body.append('\\hline') + self.body.append(r'\hline') else: i = 0 underlined.extend([False]) # sentinel while i < len(underlined): if underlined[i] is True: j = underlined[i:].index(False) - self.body.append('\\cline{%d-%d}' % (i + 1, i + j)) + self.body.append(r'\cline{%d-%d}' % (i + 1, i + j)) i += j i += 1 self.table.row += 1 @@ -982,22 +979,22 @@ class LaTeXTranslator(SphinxTranslator): if cell.width > 1: if self.config.latex_use_latex_multicolumn: if self.table.col == 0: - self.body.append('\\multicolumn{%d}{|l|}{%%' % cell.width + CR) + self.body.append(r'\multicolumn{%d}{|l|}{%%' % cell.width + CR) else: - self.body.append('\\multicolumn{%d}{l|}{%%' % cell.width + CR) + self.body.append(r'\multicolumn{%d}{l|}{%%' % cell.width + CR) context = '}%' + CR else: - self.body.append('\\sphinxstartmulticolumn{%d}%%' % cell.width + CR) - context = '\\sphinxstopmulticolumn' + CR + self.body.append(r'\sphinxstartmulticolumn{%d}%%' % cell.width + CR) + context = r'\sphinxstopmulticolumn' + CR if cell.height > 1: # \sphinxmultirow 2nd arg "cell_id" will serve as id for LaTeX macros as well - self.body.append('\\sphinxmultirow{%d}{%d}{%%' % (cell.height, cell.cell_id) + CR) + self.body.append(r'\sphinxmultirow{%d}{%d}{%%' % (cell.height, cell.cell_id) + CR) context = '}%' + CR + context if cell.width > 1 or cell.height > 1: - self.body.append('\\begin{varwidth}[t]{\\sphinxcolwidth{%d}{%d}}' + self.body.append(r'\begin{varwidth}[t]{\sphinxcolwidth{%d}{%d}}' % (cell.width, self.table.colcount) + CR) - context = ('\\par' + CR + '\\vskip-\\baselineskip' - '\\vbox{\\hbox{\\strut}}\\end{varwidth}%' + CR + context) + context = (r'\par' + CR + r'\vskip-\baselineskip' + r'\vbox{\hbox{\strut}}\end{varwidth}%' + CR + context) self.needs_linetrimming = 1 if len(node.traverse(nodes.paragraph)) >= 2: self.table.has_oldproblematic = True @@ -1005,7 +1002,7 @@ class LaTeXTranslator(SphinxTranslator): if len(node) == 1 and isinstance(node[0], nodes.paragraph) and node.astext() == '': pass else: - self.body.append('\\sphinxstyletheadfamily ') + self.body.append(r'\sphinxstyletheadfamily ') if self.needs_linetrimming: self.pushbody([]) self.context.append(context) @@ -1036,11 +1033,10 @@ class LaTeXTranslator(SphinxTranslator): if nextcell.width == 1: # insert suitable strut for equalizing row heights in multirow # they also serve to clear colour panels which would hide the text - self.body.append('\\sphinxtablestrut{%d}' % nextcell.cell_id) + self.body.append(r'\sphinxtablestrut{%d}' % nextcell.cell_id) else: # use \multicolumn for wide multirow cell - self.body.append('\\multicolumn{%d}{l|}' - '{\\sphinxtablestrut{%d}}' % + self.body.append(r'\multicolumn{%d}{l|}{\sphinxtablestrut{%d}}' % (nextcell.width, nextcell.cell_id)) def visit_acks(self, node: Element) -> None: @@ -1055,13 +1051,13 @@ class LaTeXTranslator(SphinxTranslator): def visit_bullet_list(self, node: Element) -> None: if not self.compact_list: - self.body.append('\\begin{itemize}' + CR) + self.body.append(r'\begin{itemize}' + CR) if self.table: self.table.has_problematic = True def depart_bullet_list(self, node: Element) -> None: if not self.compact_list: - self.body.append('\\end{itemize}' + CR) + self.body.append(r'\end{itemize}' + CR) def visit_enumerated_list(self, node: Element) -> None: def get_enumtype(node: Element) -> str: @@ -1086,16 +1082,16 @@ class LaTeXTranslator(SphinxTranslator): prefix = node.get('prefix', '') suffix = node.get('suffix', '.') - self.body.append('\\begin{enumerate}' + CR) - self.body.append('\\sphinxsetlistlabels{%s}{%s}{%s}{%s}{%s}%%' % + self.body.append(r'\begin{enumerate}' + CR) + self.body.append(r'\sphinxsetlistlabels{%s}{%s}{%s}{%s}{%s}%%' % (style, enum, enumnext, prefix, suffix) + CR) if 'start' in node: - self.body.append('\\setcounter{%s}{%d}' % (enum, node['start'] - 1) + CR) + self.body.append(r'\setcounter{%s}{%d}' % (enum, node['start'] - 1) + CR) if self.table: self.table.has_problematic = True def depart_enumerated_list(self, node: Element) -> None: - self.body.append('\\end{enumerate}' + CR) + self.body.append(r'\end{enumerate}' + CR) def visit_list_item(self, node: Element) -> None: # Append "{}" in case the next character is "[", which would break @@ -1106,12 +1102,12 @@ class LaTeXTranslator(SphinxTranslator): self.body.append(CR) def visit_definition_list(self, node: Element) -> None: - self.body.append('\\begin{description}' + CR) + self.body.append(r'\begin{description}' + CR) if self.table: self.table.has_problematic = True def depart_definition_list(self, node: Element) -> None: - self.body.append('\\end{description}' + CR) + self.body.append(r'\end{description}' + CR) def visit_definition_list_item(self, node: Element) -> None: pass @@ -1123,11 +1119,11 @@ class LaTeXTranslator(SphinxTranslator): self.in_term += 1 ctx = '' if node.get('ids'): - ctx = '\\phantomsection' + ctx = r'\phantomsection' for node_id in node['ids']: ctx += self.hypertarget(node_id, anchor=False) - ctx += '}] \\leavevmode' - self.body.append('\\item[{') + ctx += r'}] \leavevmode' + self.body.append(r'\item[{') self.context.append(ctx) def depart_term(self, node: Element) -> None: @@ -1147,12 +1143,12 @@ class LaTeXTranslator(SphinxTranslator): self.body.append(CR) def visit_field_list(self, node: Element) -> None: - self.body.append('\\begin{quote}\\begin{description}' + CR) + self.body.append(r'\begin{quote}\begin{description}' + CR) if self.table: self.table.has_problematic = True def depart_field_list(self, node: Element) -> None: - self.body.append('\\end{description}\\end{quote}' + CR) + self.body.append(r'\end{description}\end{quote}' + CR) def visit_field(self, node: Element) -> None: pass @@ -1172,7 +1168,7 @@ class LaTeXTranslator(SphinxTranslator): not isinstance(node.parent[index - 1], nodes.paragraph) and not isinstance(node.parent[index - 1], nodes.compound)): # insert blank line, if the paragraph follows a non-paragraph node in a compound - self.body.append('\\noindent' + CR) + self.body.append(r'\noindent' + CR) elif index == 1 and isinstance(node.parent, (nodes.footnote, footnotetext)): # don't insert blank line, if the paragraph is second child of a footnote # (first one is label node) @@ -1181,33 +1177,33 @@ class LaTeXTranslator(SphinxTranslator): # the \sphinxAtStartPar is to allow hyphenation of first word of # a paragraph in narrow contexts such as in a table cell # added as two items (cf. line trimming in depart_entry()) - self.body.extend([CR, '\\sphinxAtStartPar' + CR]) + self.body.extend([CR, r'\sphinxAtStartPar' + CR]) def depart_paragraph(self, node: Element) -> None: self.body.append(CR) def visit_centered(self, node: Element) -> None: - self.body.append(CR + '\\begin{center}') + self.body.append(CR + r'\begin{center}') if self.table: self.table.has_problematic = True def depart_centered(self, node: Element) -> None: - self.body.append(CR + '\\end{center}') + self.body.append(CR + r'\end{center}') def visit_hlist(self, node: Element) -> None: self.compact_list += 1 ncolumns = node['ncolumns'] if self.compact_list > 1: - self.body.append('\\setlength{\\multicolsep}{0pt}' + CR) - self.body.append('\\begin{multicols}{' + ncolumns + '}\\raggedright' + CR) - self.body.append('\\begin{itemize}\\setlength{\\itemsep}{0pt}' - '\\setlength{\\parskip}{0pt}' + CR) + self.body.append(r'\setlength{\multicolsep}{0pt}' + CR) + self.body.append(r'\begin{multicols}{' + ncolumns + '}\raggedright' + CR) + self.body.append(r'\begin{itemize}\setlength{\itemsep}{0pt}' + r'\setlength{\parskip}{0pt}' + CR) if self.table: self.table.has_problematic = True def depart_hlist(self, node: Element) -> None: self.compact_list -= 1 - self.body.append('\\end{itemize}\\raggedcolumns\\end{multicols}' + CR) + self.body.append(r'\end{itemize}\raggedcolumns\end{multicols}' + CR) def visit_hlistcol(self, node: Element) -> None: pass @@ -1217,7 +1213,7 @@ class LaTeXTranslator(SphinxTranslator): # some testing with long items showed that columns may be too uneven. # And in case only of short items, the automatic column breaks should # match the ones pre-computed by the hlist() directive. - # self.body.append('\\columnbreak\n') + # self.body.append(r'\columnbreak\n') pass def latex_image_length(self, width_str: str, scale: int = 100) -> str: @@ -1265,14 +1261,14 @@ class LaTeXTranslator(SphinxTranslator): align_prepost = { # By default latex aligns the top of an image. (1, 'top'): ('', ''), - (1, 'middle'): ('\\raisebox{-0.5\\height}{', '}'), - (1, 'bottom'): ('\\raisebox{-\\height}{', '}'), - (0, 'center'): ('{\\hspace*{\\fill}', '\\hspace*{\\fill}}'), + (1, 'middle'): (r'\raisebox{-0.5\height}{', '}'), + (1, 'bottom'): (r'\raisebox{-\height}{', '}'), + (0, 'center'): (r'{\hspace*{\fill}', r'\hspace*{\fill}}'), # These 2 don't exactly do the right thing. The image should # be floated alongside the paragraph. See # https://www.w3.org/TR/html4/struct/objects.html#adef-align-IMG - (0, 'left'): ('{', '\\hspace*{\\fill}}'), - (0, 'right'): ('{\\hspace*{\\fill}', '}'), + (0, 'left'): ('{', r'\hspace*{\fill}}'), + (0, 'right'): (r'{\hspace*{\fill}', '}'), } try: pre.append(align_prepost[is_inline, node['align']][0]) @@ -1280,10 +1276,10 @@ class LaTeXTranslator(SphinxTranslator): except KeyError: pass if self.in_parsed_literal: - pre.append('{\\sphinxunactivateextrasandspace ') + pre.append(r'{\sphinxunactivateextrasandspace ') post.append('}') if not is_inline and not has_hyperlink: - pre.append(CR + '\\noindent') + pre.append(CR + r'\noindent') post.append(CR) pre.reverse() if node['uri'] in self.builder.images: @@ -1304,10 +1300,10 @@ class LaTeXTranslator(SphinxTranslator): if self.in_title and base: # Lowercase tokens forcely because some fncychap themes capitalize # the options of \sphinxincludegraphics unexpectly (ex. WIDTH=...). - self.body.append('\\lowercase{\\sphinxincludegraphics%s}{{%s}%s}' % + self.body.append(r'\lowercase{\sphinxincludegraphics%s}{{%s}%s}' % (options, base, ext)) else: - self.body.append('\\sphinxincludegraphics%s{{%s}%s}' % + self.body.append(r'\sphinxincludegraphics%s{{%s}%s}' % (options, base, ext)) self.body.extend(post) @@ -1323,14 +1319,14 @@ class LaTeXTranslator(SphinxTranslator): if 'width' in node: length = self.latex_image_length(node['width']) if length: - self.body.append('\\begin{sphinxfigure-in-table}[%s]' % length + CR) - self.body.append('\\centering' + CR) + self.body.append(r'\begin{sphinxfigure-in-table}[%s]' % length + CR) + self.body.append(r'\centering' + CR) else: - self.body.append('\\begin{sphinxfigure-in-table}' + CR) - self.body.append('\\centering' + CR) + self.body.append(r'\begin{sphinxfigure-in-table}' + CR) + self.body.append(r'\centering' + CR) if any(isinstance(child, nodes.caption) for child in node): - self.body.append('\\capstart') - self.context.append('\\end{sphinxfigure-in-table}\\relax' + CR) + self.body.append(r'\capstart') + self.context.append(r'\end{sphinxfigure-in-table}\relax' + CR) elif node.get('align', '') in ('left', 'right'): length = None if 'width' in node: @@ -1339,19 +1335,19 @@ class LaTeXTranslator(SphinxTranslator): length = self.latex_image_length(node[0]['width']) self.body.append(BLANKLINE) # Insert a blank line to prevent infinite loop # https://github.com/sphinx-doc/sphinx/issues/7059 - self.body.append('\\begin{wrapfigure}{%s}{%s}' % + self.body.append(r'\begin{wrapfigure}{%s}{%s}' % ('r' if node['align'] == 'right' else 'l', length or '0pt') + CR) - self.body.append('\\centering') - self.context.append('\\end{wrapfigure}' + CR) + self.body.append(r'\centering') + self.context.append(r'\end{wrapfigure}' + CR) elif self.in_minipage: - self.body.append(CR + '\\begin{center}') - self.context.append('\\end{center}' + CR) + self.body.append(CR + r'\begin{center}') + self.context.append(r'\end{center}' + CR) else: - self.body.append(CR + '\\begin{figure}[%s]' % align + CR) - self.body.append('\\centering' + CR) + self.body.append(CR + r'\begin{figure}[%s]' % align + CR) + self.body.append(r'\centering' + CR) if any(isinstance(child, nodes.caption) for child in node): - self.body.append('\\capstart' + CR) - self.context.append('\\end{figure}' + CR) + self.body.append(r'\capstart' + CR) + self.context.append(r'\end{figure}' + CR) def depart_figure(self, node: Element) -> None: self.body.append(self.context.pop()) @@ -1359,13 +1355,13 @@ class LaTeXTranslator(SphinxTranslator): def visit_caption(self, node: Element) -> None: self.in_caption += 1 if isinstance(node.parent, captioned_literal_block): - self.body.append('\\sphinxSetupCaptionForVerbatim{') + self.body.append(r'\sphinxSetupCaptionForVerbatim{') elif self.in_minipage and isinstance(node.parent, nodes.figure): - self.body.append('\\captionof{figure}{') + self.body.append(r'\captionof{figure}{') elif self.table and node.parent.tagname == 'figure': - self.body.append('\\sphinxfigcaption{') + self.body.append(r'\sphinxfigcaption{') else: - self.body.append('\\caption{') + self.body.append(r'\caption{') def depart_caption(self, node: Element) -> None: self.body.append('}') @@ -1375,27 +1371,27 @@ class LaTeXTranslator(SphinxTranslator): self.in_caption -= 1 def visit_legend(self, node: Element) -> None: - self.body.append(CR + '\\begin{sphinxlegend}') + self.body.append(CR + r'\begin{sphinxlegend}') def depart_legend(self, node: Element) -> None: - self.body.append('\\end{sphinxlegend}' + CR) + self.body.append(r'\end{sphinxlegend}' + CR) def visit_admonition(self, node: Element) -> None: - self.body.append(CR + '\\begin{sphinxadmonition}{note}') + self.body.append(CR + r'\begin{sphinxadmonition}{note}') self.no_latex_floats += 1 def depart_admonition(self, node: Element) -> None: - self.body.append('\\end{sphinxadmonition}' + CR) + self.body.append(r'\end{sphinxadmonition}' + CR) self.no_latex_floats -= 1 def _visit_named_admonition(self, node: Element) -> None: label = admonitionlabels[node.tagname] - self.body.append(CR + '\\begin{sphinxadmonition}{%s}{%s:}' % + self.body.append(CR + r'\begin{sphinxadmonition}{%s}{%s:}' % (node.tagname, label)) self.no_latex_floats += 1 def _depart_named_admonition(self, node: Element) -> None: - self.body.append('\\end{sphinxadmonition}' + CR) + self.body.append(r'\end{sphinxadmonition}' + CR) self.no_latex_floats -= 1 visit_attention = _visit_named_admonition @@ -1473,11 +1469,11 @@ class LaTeXTranslator(SphinxTranslator): pass def visit_attribution(self, node: Element) -> None: - self.body.append(CR + '\\begin{flushright}' + CR) + self.body.append(CR + r'\begin{flushright}' + CR) self.body.append('---') def depart_attribution(self, node: Element) -> None: - self.body.append(CR + '\\end{flushright}' + CR) + self.body.append(CR + r'\end{flushright}' + CR) def visit_index(self, node: Element) -> None: def escape(value: str) -> str: @@ -1495,7 +1491,7 @@ class LaTeXTranslator(SphinxTranslator): if match: return match.expand(r'\\spxentry{\1}\\spxextra{\2}') else: - return '\\spxentry{%s}' % string + return r'\spxentry{%s}' % string if not node.get('inline', True): self.body.append(CR) @@ -1542,7 +1538,7 @@ class LaTeXTranslator(SphinxTranslator): except ValueError as err: logger.warning(str(err)) if not node.get('inline', True): - self.body.append('\\ignorespaces ') + self.body.append(r'\ignorespaces ') raise nodes.SkipNode def visit_raw(self, node: Element) -> None: @@ -1602,12 +1598,12 @@ class LaTeXTranslator(SphinxTranslator): else: if len(node) == 1 and uri == node[0]: if node.get('nolinkurl'): - self.body.append('\\sphinxnolinkurl{%s}' % self.encode_uri(uri)) + self.body.append(r'\sphinxnolinkurl{%s}' % self.encode_uri(uri)) else: - self.body.append('\\sphinxurl{%s}' % self.encode_uri(uri)) + self.body.append(r'\sphinxurl{%s}' % self.encode_uri(uri)) raise nodes.SkipNode else: - self.body.append('\\sphinxhref{%s}{' % self.encode_uri(uri)) + self.body.append(r'\sphinxhref{%s}{' % self.encode_uri(uri)) self.context.append('}') def depart_reference(self, node: Element) -> None: @@ -1621,16 +1617,16 @@ class LaTeXTranslator(SphinxTranslator): else: id = node.get('refuri', '')[1:].replace('#', ':') - title = self.escape(node.get('title', '%s')).replace('\\%s', '%s') - if '\\{name\\}' in title or '\\{number\\}' in title: + title = self.escape(node.get('title', '%s')).replace(r'\%s', '%s') + if r'\{name\}' in title or r'\{number\}' in title: # new style format (cf. "Fig.%{number}") - title = title.replace('\\{name\\}', '{name}').replace('\\{number\\}', '{number}') - text = escape_abbr(title).format(name='\\nameref{%s}' % self.idescape(id), - number='\\ref{%s}' % self.idescape(id)) + title = title.replace(r'\{name\}', '{name}').replace(r'\{number\}', '{number}') + text = escape_abbr(title).format(name=r'\nameref{%s}' % self.idescape(id), + number=r'\ref{%s}' % self.idescape(id)) else: # old style format (cf. "Fig.%{number}") - text = escape_abbr(title) % ('\\ref{%s}' % self.idescape(id)) - hyperref = '\\hyperref[%s]{%s}' % (self.idescape(id), text) + text = escape_abbr(title) % (r'\ref{%s}' % self.idescape(id)) + hyperref = r'\hyperref[%s]{%s}' % (self.idescape(id), text) self.body.append(hyperref) raise nodes.SkipNode @@ -1704,15 +1700,15 @@ class LaTeXTranslator(SphinxTranslator): # adjust max width of citation labels not to break the layout longest_label = longest_label[:MAX_CITATION_LABEL_LENGTH] - self.body.append(CR + '\\begin{sphinxthebibliography}{%s}' % + self.body.append(CR + r'\begin{sphinxthebibliography}{%s}' % self.encode(longest_label) + CR) def depart_thebibliography(self, node: Element) -> None: - self.body.append('\\end{sphinxthebibliography}' + CR) + self.body.append(r'\end{sphinxthebibliography}' + CR) def visit_citation(self, node: Element) -> None: label = cast(nodes.label, node[0]) - self.body.append('\\bibitem[%s]{%s:%s}' % (self.encode(label.astext()), + self.body.append(r'\bibitem[%s]{%s:%s}' % (self.encode(label.astext()), node['docname'], node['ids'][0])) def depart_citation(self, node: Element) -> None: @@ -1722,7 +1718,7 @@ class LaTeXTranslator(SphinxTranslator): if self.in_title: pass else: - self.body.append('\\sphinxcite{%s:%s}' % (node['docname'], node['refname'])) + self.body.append(r'\sphinxcite{%s:%s}' % (node['docname'], node['refname'])) raise nodes.SkipNode def depart_citation_reference(self, node: Element) -> None: @@ -1743,7 +1739,7 @@ class LaTeXTranslator(SphinxTranslator): raise nodes.SkipNode def visit_footnotemark(self, node: Element) -> None: - self.body.append('\\sphinxfootnotemark[') + self.body.append(r'\sphinxfootnotemark[') def depart_footnotemark(self, node: Element) -> None: self.body.append(']') @@ -1751,15 +1747,15 @@ class LaTeXTranslator(SphinxTranslator): def visit_footnotetext(self, node: Element) -> None: label = cast(nodes.label, node[0]) self.body.append('%' + CR) - self.body.append('\\begin{footnotetext}[%s]' - '\\phantomsection\\label{\\thesphinxscope.%s}%%' + self.body.append(r'\begin{footnotetext}[%s]' + r'\phantomsection\label{\thesphinxscope.%s}%%' % (label.astext(), label.astext()) + CR) - self.body.append('\\sphinxAtStartFootnote' + CR) + self.body.append(r'\sphinxAtStartFootnote' + CR) def depart_footnotetext(self, node: Element) -> None: # the \ignorespaces in particular for after table header use self.body.append('%' + CR) - self.body.append('\\end{footnotetext}\\ignorespaces ') + self.body.append(r'\end{footnotetext}\ignorespaces ') def visit_captioned_literal_block(self, node: Element) -> None: pass @@ -1771,13 +1767,13 @@ class LaTeXTranslator(SphinxTranslator): if node.rawsource != node.astext(): # most probably a parsed-literal block -- don't highlight self.in_parsed_literal += 1 - self.body.append('\\begin{sphinxalltt}' + CR) + self.body.append(r'\begin{sphinxalltt}' + CR) else: labels = self.hypertarget_to(node) if isinstance(node.parent, captioned_literal_block): labels += self.hypertarget_to(node.parent) if labels and not self.in_footnote: - self.body.append(CR + '\\def\\sphinxLiteralBlockLabel{' + labels + '}') + self.body.append(CR + r'\def\sphinxLiteralBlockLabel{' + labels + '}') lang = node.get('language', 'default') linenos = node.get('linenos', False) @@ -1790,57 +1786,57 @@ class LaTeXTranslator(SphinxTranslator): location=node, **highlight_args ) if self.in_footnote: - self.body.append(CR + '\\sphinxSetupCodeBlockInFootnote') - hlcode = hlcode.replace('\\begin{Verbatim}', - '\\begin{sphinxVerbatim}') + self.body.append(CR + r'\sphinxSetupCodeBlockInFootnote') + hlcode = hlcode.replace(r'\begin{Verbatim}', + r'\begin{sphinxVerbatim}') # if in table raise verbatim flag to avoid "tabulary" environment # and opt for sphinxVerbatimintable to handle caption & long lines elif self.table: self.table.has_problematic = True self.table.has_verbatim = True - hlcode = hlcode.replace('\\begin{Verbatim}', - '\\begin{sphinxVerbatimintable}') + hlcode = hlcode.replace(r'\begin{Verbatim}', + r'\begin{sphinxVerbatimintable}') else: - hlcode = hlcode.replace('\\begin{Verbatim}', - '\\begin{sphinxVerbatim}') + hlcode = hlcode.replace(r'\begin{Verbatim}', + r'\begin{sphinxVerbatim}') # get consistent trailer hlcode = hlcode.rstrip()[:-14] # strip \end{Verbatim} if self.table and not self.in_footnote: - hlcode += '\\end{sphinxVerbatimintable}' + hlcode += r'\end{sphinxVerbatimintable}' else: - hlcode += '\\end{sphinxVerbatim}' + hlcode += r'\end{sphinxVerbatim}' hllines = str(highlight_args.get('hl_lines', []))[1:-1] if hllines: - self.body.append(CR + '\\fvset{hllines={, %s,}}%%' % hllines) + self.body.append(CR + r'\fvset{hllines={, %s,}}%%' % hllines) self.body.append(CR + hlcode + CR) if hllines: - self.body.append('\\sphinxresetverbatimhllines' + CR) + self.body.append(r'\sphinxresetverbatimhllines' + CR) raise nodes.SkipNode def depart_literal_block(self, node: Element) -> None: - self.body.append(CR + '\\end{sphinxalltt}' + CR) + self.body.append(CR + r'\end{sphinxalltt}' + CR) self.in_parsed_literal -= 1 visit_doctest_block = visit_literal_block depart_doctest_block = depart_literal_block def visit_line(self, node: Element) -> None: - self.body.append('\\item[] ') + self.body.append(r'\item[] ') def depart_line(self, node: Element) -> None: self.body.append(CR) def visit_line_block(self, node: Element) -> None: if isinstance(node.parent, nodes.line_block): - self.body.append('\\item[]' + CR) - self.body.append('\\begin{DUlineblock}{\\DUlineblockindent}' + CR) + self.body.append(r'\item[]' + CR) + self.body.append(r'\begin{DUlineblock}{\DUlineblockindent}' + CR) else: - self.body.append(CR + '\\begin{DUlineblock}{0em}' + CR) + self.body.append(CR + r'\begin{DUlineblock}{0em}' + CR) if self.table: self.table.has_problematic = True def depart_line_block(self, node: Element) -> None: - self.body.append('\\end{DUlineblock}' + CR) + self.body.append(r'\end{DUlineblock}' + CR) def visit_block_quote(self, node: Element) -> None: # If the block quote contains a single object and that object @@ -1853,7 +1849,7 @@ class LaTeXTranslator(SphinxTranslator): isinstance(child, nodes.enumerated_list): done = 1 if not done: - self.body.append('\\begin{quote}' + CR) + self.body.append(r'\begin{quote}' + CR) if self.table: self.table.has_problematic = True @@ -1865,7 +1861,7 @@ class LaTeXTranslator(SphinxTranslator): isinstance(child, nodes.enumerated_list): done = 1 if not done: - self.body.append('\\end{quote}' + CR) + self.body.append(r'\end{quote}' + CR) # option node handling copied from docutils' latex writer @@ -1886,7 +1882,7 @@ class LaTeXTranslator(SphinxTranslator): pass def visit_option_group(self, node: Element) -> None: - self.body.append('\\item [') + self.body.append(r'\item [') # flag for first option self.context.append(0) @@ -1895,12 +1891,12 @@ class LaTeXTranslator(SphinxTranslator): self.body.append('] ') def visit_option_list(self, node: Element) -> None: - self.body.append('\\begin{optionlist}{3cm}' + CR) + self.body.append(r'\begin{optionlist}{3cm}' + CR) if self.table: self.table.has_problematic = True def depart_option_list(self, node: Element) -> None: - self.body.append('\\end{optionlist}' + CR) + self.body.append(r'\end{optionlist}' + CR) def visit_option_list_item(self, node: Element) -> None: pass @@ -1920,13 +1916,13 @@ class LaTeXTranslator(SphinxTranslator): pass def visit_superscript(self, node: Element) -> None: - self.body.append('$^{\\text{') + self.body.append(r'$^{\text{') def depart_superscript(self, node: Element) -> None: self.body.append('}}$') def visit_subscript(self, node: Element) -> None: - self.body.append('$_{\\text{') + self.body.append(r'$_{\text{') def depart_subscript(self, node: Element) -> None: self.body.append('}}$') @@ -1993,7 +1989,7 @@ class LaTeXTranslator(SphinxTranslator): if self.literal_whitespace: # Insert a blank before the newline, to avoid # ! LaTeX Error: There's no line here to end. - text = text.replace(CR, '~\\\\' + CR).replace(' ', '~') + text = text.replace(CR, r'~\\' + CR).replace(' ', '~') return text def encode_uri(self, text: str) -> str: @@ -2001,9 +1997,9 @@ class LaTeXTranslator(SphinxTranslator): # this must be checked against hyperref package exact dealings # mainly, %, #, {, } and \ need escaping via a \ escape # in \href, the tilde is allowed and must be represented literally - return self.encode(text).replace('\\textasciitilde{}', '~').\ - replace('\\sphinxhyphen{}', '-').\ - replace('\\textquotesingle{}', "'") + return self.encode(text).replace(r'\textasciitilde{}', '~').\ + replace(r'\sphinxhyphen{}', '-').\ + replace(r'\textquotesingle{}', "'") def visit_Text(self, node: Text) -> None: text = self.encode(node.astext()) From 3f62d2f294bb6e066325bc8f1a58f6a565c9b3c5 Mon Sep 17 00:00:00 2001 From: Joaquin Anton Date: Fri, 26 Feb 2021 14:26:09 +0100 Subject: [PATCH 17/58] Use explicit title for titlenode, when no title is provided Signed-off-by: Joaquin Anton --- sphinx/environment/collectors/title.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sphinx/environment/collectors/title.py b/sphinx/environment/collectors/title.py index 5225155e8..00832ae43 100644 --- a/sphinx/environment/collectors/title.py +++ b/sphinx/environment/collectors/title.py @@ -39,9 +39,10 @@ class TitleCollector(EnvironmentCollector): longtitlenode = titlenode # explicit title set with title directive; use this only for # the tag in HTML output - if 'title' in doctree: + explicit_title = nodes.Text(doctree['title']) if 'title' in doctree else None + if explicit_title: longtitlenode = nodes.title() - longtitlenode += nodes.Text(doctree['title']) + longtitlenode += explicit_title # look for first section title and use that as the title for node in doctree.traverse(nodes.section): visitor = SphinxContentsFilter(doctree) @@ -50,7 +51,7 @@ class TitleCollector(EnvironmentCollector): break else: # document has no title - titlenode += nodes.Text('<no title>') + titlenode += explicit_title or nodes.Text('<no title>') app.env.titles[app.env.docname] = titlenode app.env.longtitles[app.env.docname] = longtitlenode From 347ae4ff4733f42a439cd4efb9457e7d4e2a036b Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Sat, 27 Feb 2021 13:01:34 +0900 Subject: [PATCH 18/58] Fix #8938: imgconverter: Show the error of the command availability check imgconverter extension suppresses an OSError like "Cannot allocate memory" unexpectedly. So the error should be shown with the warning. --- CHANGES | 1 + sphinx/ext/imgconverter.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 428b6951f..50a9ce275 100644 --- a/CHANGES +++ b/CHANGES @@ -64,6 +64,7 @@ Features added * #2018: html: :confval:`html_favicon` and :confval:`html_logo` now accept URL for the image * #8070: html search: Support searching for 2characters word +* #8938: imgconverter: Show the error of the command availability check * #7830: Add debug logs for change detection of sources and templates * #8201: Emit a warning if toctree contains duplicated entries diff --git a/sphinx/ext/imgconverter.py b/sphinx/ext/imgconverter.py index b0d40b551..84fe6549c 100644 --- a/sphinx/ext/imgconverter.py +++ b/sphinx/ext/imgconverter.py @@ -37,10 +37,10 @@ class ImagemagickConverter(ImageConverter): logger.debug('Invoking %r ...', args) subprocess.run(args, stdout=PIPE, stderr=PIPE, check=True) return True - except OSError: + except OSError as exc: logger.warning(__('convert command %r cannot be run, ' - 'check the image_converter setting'), - self.config.image_converter) + 'check the image_converter setting: %s'), + self.config.image_converter, exc) return False except CalledProcessError as exc: logger.warning(__('convert exited with error:\n' From 7d3cc382fa2ab875ea168ee40ce34562a387bb40 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Sun, 15 Nov 2020 13:58:12 +0900 Subject: [PATCH 19/58] autodoc: an imported TypeVar is not resolved (refs: #8415) So far, a TypeVar is rendered without module name. As a result, it could not be resolved if it is imported from other modules. This prepends its module name and make it resolvable. This is available only in Python 3.7 or above. As a side effect, all of TypeVars are displayed with module name. It should be fixed in latter step (refs: #7119) --- CHANGES | 2 ++ sphinx/util/typing.py | 5 ++++- tests/test_util_inspect.py | 8 +++++++- tests/test_util_typing.py | 15 +++++++++++---- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index a908a0f42..6ad69d483 100644 --- a/CHANGES +++ b/CHANGES @@ -72,6 +72,8 @@ Bugs fixed ---------- * #8917: autodoc: Raises a warning if function has wrong __globals__ value +* #8415: autodoc: a TypeVar imported from other module is not resolved (in + Python 3.7 or above) * #8380: html search: Paragraphs in search results are not identified as ``<p>`` * #8915: html theme: The translation of sphinx_rtd_theme does not work * #8342: Emit a warning if a unknown domain is given for directive or role (ex. diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py index d2bd03efd..afd2f805a 100644 --- a/sphinx/util/typing.py +++ b/sphinx/util/typing.py @@ -263,7 +263,10 @@ def stringify(annotation: Any) -> str: else: return annotation elif isinstance(annotation, TypeVar): - return annotation.__name__ + if annotation.__module__ == 'typing': + return annotation.__name__ + else: + return '.'.join([annotation.__module__, annotation.__name__]) elif inspect.isNewType(annotation): # Could not get the module where it defiend return annotation.__name__ diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index bf2021063..7b86c6ade 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -142,7 +142,13 @@ def test_signature_annotations(): # TypeVars and generic types with TypeVars sig = inspect.signature(f2) - assert stringify_signature(sig) == '(x: List[T], y: List[T_co], z: T) -> List[T_contra]' + if sys.version_info < (3, 7): + assert stringify_signature(sig) == '(x: List[T], y: List[T_co], z: T) -> List[T_contra]' + else: + assert stringify_signature(sig) == ('(x: List[tests.typing_test_data.T],' + ' y: List[tests.typing_test_data.T_co],' + ' z: tests.typing_test_data.T' + ') -> List[tests.typing_test_data.T_contra]') # Union types sig = inspect.signature(f3) diff --git a/tests/test_util_typing.py b/tests/test_util_typing.py index 927db73fd..97732965e 100644 --- a/tests/test_util_typing.py +++ b/tests/test_util_typing.py @@ -194,10 +194,17 @@ def test_stringify_type_hints_typevars(): T_co = TypeVar('T_co', covariant=True) T_contra = TypeVar('T_contra', contravariant=True) - assert stringify(T) == "T" - assert stringify(T_co) == "T_co" - assert stringify(T_contra) == "T_contra" - assert stringify(List[T]) == "List[T]" + if sys.version_info < (3, 7): + assert stringify(T) == "T" + assert stringify(T_co) == "T_co" + assert stringify(T_contra) == "T_contra" + assert stringify(List[T]) == "List[T]" + else: + assert stringify(T) == "tests.test_util_typing.T" + assert stringify(T_co) == "tests.test_util_typing.T_co" + assert stringify(T_contra) == "tests.test_util_typing.T_contra" + assert stringify(List[T]) == "List[tests.test_util_typing.T]" + assert stringify(MyInt) == "MyInt" From 3059a999b2f3a2f4b8d69c7e473a81ce70f82f3c Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Wed, 25 Nov 2020 00:50:36 +0900 Subject: [PATCH 20/58] Close #8326: Rename master_doc to root_doc To describe the purpose more accurately, the `master_doc` is now renamed to `root_doc`. The old name is still available. But it is recommeneded to use new one from now on. --- CHANGES | 1 + doc/_static/conf.py.txt | 8 +++--- doc/conf.py | 2 +- doc/man/sphinx-apidoc.rst | 2 +- doc/man/sphinx-quickstart.rst | 4 +-- doc/templating.rst | 14 +++++++++- doc/usage/configuration.rst | 19 +++++++++---- doc/usage/restructuredtext/directives.rst | 6 ++--- sphinx/builders/__init__.py | 8 +++--- sphinx/builders/_epub_base.py | 14 +++++----- sphinx/builders/epub3.py | 2 +- sphinx/builders/html/__init__.py | 3 ++- sphinx/builders/latex/__init__.py | 2 +- sphinx/builders/manpage.py | 2 +- sphinx/builders/singlehtml.py | 16 +++++------ sphinx/builders/texinfo.py | 2 +- sphinx/cmd/quickstart.py | 27 ++++++++++++++++--- sphinx/config.py | 19 ++++++------- sphinx/environment/__init__.py | 4 +-- sphinx/environment/adapters/toctree.py | 2 +- sphinx/environment/collectors/toctree.py | 2 +- sphinx/setup_command.py | 2 +- sphinx/templates/htmlhelp/project.hhc | 2 +- sphinx/templates/htmlhelp/project.hhp | 4 +-- sphinx/templates/quickstart/conf.py_t | 6 ++--- .../{master_doc.rst_t => root_doc.rst_t} | 0 sphinx/themes/agogo/layout.html | 4 +-- sphinx/themes/basic/globaltoc.html | 2 +- sphinx/themes/basic/layout.html | 4 +-- sphinx/themes/basic/localtoc.html | 2 +- sphinx/themes/haiku/layout.html | 2 +- sphinx/themes/pyramid/layout.html | 2 +- tests/roots/test-ext-doctest-skipif/conf.py | 2 +- tests/roots/test-ext-doctest/conf.py | 2 +- tests/roots/test-latex-equations/conf.py | 2 +- .../roots/test-latex-includegraphics/conf.py | 2 -- tests/roots/test-linkcheck/conf.py | 2 +- tests/test_build.py | 12 ++++----- tests/test_build_latex.py | 8 +++--- tests/test_config.py | 14 +++++----- tests/test_environment.py | 4 +-- tests/test_quickstart.py | 2 +- tests/test_setup_command.py | 10 +++---- 43 files changed, 145 insertions(+), 104 deletions(-) rename sphinx/templates/quickstart/{master_doc.rst_t => root_doc.rst_t} (100%) diff --git a/CHANGES b/CHANGES index a908a0f42..72196a184 100644 --- a/CHANGES +++ b/CHANGES @@ -67,6 +67,7 @@ Features added * #8938: imgconverter: Show the error of the command availability check * #7830: Add debug logs for change detection of sources and templates * #8201: Emit a warning if toctree contains duplicated entries +* #8326: ``master_doc`` is now renamed to :confval:`root_doc` Bugs fixed ---------- diff --git a/doc/_static/conf.py.txt b/doc/_static/conf.py.txt index 5420e2717..844451fd8 100644 --- a/doc/_static/conf.py.txt +++ b/doc/_static/conf.py.txt @@ -43,7 +43,7 @@ source_suffix = '.rst' # source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +root_doc = 'index' # General information about the project. project = u'test' @@ -252,7 +252,7 @@ latex_elements = { # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'test.tex', u'test Documentation', + (root_doc, 'test.tex', u'test Documentation', u'test', 'manual'), ] @@ -283,7 +283,7 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'test', u'test Documentation', + (root_doc, 'test', u'test Documentation', [author], 1) ] @@ -298,7 +298,7 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'test', u'test Documentation', + (root_doc, 'test', u'test Documentation', author, 'test', 'One line description of project.', 'Miscellaneous'), ] diff --git a/doc/conf.py b/doc/conf.py index f38d03d6d..7cf74dc97 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -9,7 +9,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode', 'sphinx.ext.inheritance_diagram'] -master_doc = 'contents' +root_doc = 'contents' templates_path = ['_templates'] exclude_patterns = ['_build'] diff --git a/doc/man/sphinx-apidoc.rst b/doc/man/sphinx-apidoc.rst index 725d2f169..3ce5b4523 100644 --- a/doc/man/sphinx-apidoc.rst +++ b/doc/man/sphinx-apidoc.rst @@ -145,7 +145,7 @@ These options are used when :option:`--full` is specified: * ``module.rst_t`` * ``package.rst_t`` * ``toc.rst_t`` - * ``master_doc.rst_t`` + * ``root_doc.rst_t`` * ``conf.py_t`` * ``Makefile_t`` * ``Makefile.new_t`` diff --git a/doc/man/sphinx-quickstart.rst b/doc/man/sphinx-quickstart.rst index 520a420ce..16fc66560 100644 --- a/doc/man/sphinx-quickstart.rst +++ b/doc/man/sphinx-quickstart.rst @@ -72,7 +72,7 @@ Options .. option:: --master=MASTER - Master document name. (see :confval:`master_doc`). + Master document name. (see :confval:`root_doc`). .. rubric:: Extension Options @@ -149,7 +149,7 @@ Options sphinx project files generated by quickstart. Following Jinja2 template files are allowed: - * ``master_doc.rst_t`` + * ``root_doc.rst_t`` * ``conf.py_t`` * ``Makefile_t`` * ``Makefile.new_t`` diff --git a/doc/templating.rst b/doc/templating.rst index 97596e6a9..61b714bfa 100644 --- a/doc/templating.rst +++ b/doc/templating.rst @@ -325,7 +325,19 @@ in the future. .. data:: master_doc - The value of :confval:`master_doc`, for usage with :func:`pathto`. + Same as :data:`root_doc`. + + .. versionchanged:: 4.0 + + Renamed to ``root_doc``. + +.. data:: root_doc + + The value of :confval:`root_doc`, for usage with :func:`pathto`. + + .. versionchanged:: 4.0 + + Renamed from ``master_doc``. .. data:: pagename diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index de3416c47..23db18fed 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -183,11 +183,20 @@ General configuration .. confval:: master_doc - The document name of the "master" document, that is, the document that + Same as :confval:`root_doc`. + + .. versionchanged:: 4.0 + Renamed ``master_doc`` to ``master_doc``. + +.. confval:: root_doc + + The document name of the "root" document, that is, the document that contains the root :rst:dir:`toctree` directive. Default is ``'index'``. .. versionchanged:: 2.0 The default is changed to ``'index'`` from ``'contents'``. + .. versionchanged:: 4.0 + Renamed ``master_doc`` from ``master_doc``. .. confval:: exclude_patterns @@ -2023,8 +2032,8 @@ These options influence LaTeX output. *startdocname* String that specifies the :term:`document name` of the LaTeX file's master document. All documents referenced by the *startdoc* document in TOC trees - will be included in the LaTeX file. (If you want to use the default master - document for your LaTeX build, provide your :confval:`master_doc` here.) + will be included in the LaTeX file. (If you want to use the default root + document for your LaTeX build, provide your :confval:`root_doc` here.) *targetname* File name of the LaTeX file in the output directory. @@ -2293,7 +2302,7 @@ These options influence manual page output. String that specifies the :term:`document name` of the manual page's master document. All documents referenced by the *startdoc* document in TOC trees will be included in the manual file. (If you want to use the default - master document for your manual pages build, use your :confval:`master_doc` + root document for your manual pages build, use your :confval:`root_doc` here.) *name* @@ -2349,7 +2358,7 @@ These options influence Texinfo output. master document. All documents referenced by the *startdoc* document in TOC trees will be included in the Texinfo file. (If you want to use the default master document for your Texinfo build, provide your - :confval:`master_doc` here.) + :confval:`root_doc` here.) *targetname* File name (no extension) of the Texinfo file in the output directory. diff --git a/doc/usage/restructuredtext/directives.rst b/doc/usage/restructuredtext/directives.rst index 53625ac9e..995804e37 100644 --- a/doc/usage/restructuredtext/directives.rst +++ b/doc/usage/restructuredtext/directives.rst @@ -197,9 +197,9 @@ tables of contents. The ``toctree`` directive is the central element. <metadata>` to let a document be built, but notify Sphinx that it is not reachable via a toctree. - The "master document" (selected by :confval:`master_doc`) is the "root" of - the TOC tree hierarchy. It can be used as the documentation's main page, or - as a "full table of contents" if you don't give a ``maxdepth`` option. + The "root document" (selected by :confval:`root_doc`) is the "root" of the TOC + tree hierarchy. It can be used as the documentation's main page, or as a + "full table of contents" if you don't give a ``maxdepth`` option. .. versionchanged:: 0.3 Added "globbing" option. diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index 7e361652f..c4ac432e4 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -413,9 +413,9 @@ class Builder: else: self._read_serial(docnames) - if self.config.master_doc not in self.env.all_docs: - raise SphinxError('master file %s not found' % - self.env.doc2path(self.config.master_doc)) + if self.config.root_doc not in self.env.all_docs: + raise SphinxError('root file %s not found' % + self.env.doc2path(self.config.root_doc)) for retval in self.events.emit('env-updated', self.env): if retval is not None: @@ -517,7 +517,7 @@ class Builder: for tocdocname in self.env.files_to_rebuild.get(docname, set()): if tocdocname in self.env.found_docs: docnames.add(tocdocname) - docnames.add(self.config.master_doc) + docnames.add(self.config.root_doc) with progress_message(__('preparing documents')): self.prepare_writing(docnames) diff --git a/sphinx/builders/_epub_base.py b/sphinx/builders/_epub_base.py index 453474e09..3b19bb8d0 100644 --- a/sphinx/builders/_epub_base.py +++ b/sphinx/builders/_epub_base.py @@ -222,14 +222,14 @@ class EpubBuilder(StandaloneHTMLBuilder): appeared.add(node['refuri']) def get_toc(self) -> None: - """Get the total table of contents, containing the master_doc + """Get the total table of contents, containing the root_doc and pre and post files not managed by sphinx. """ - doctree = self.env.get_and_resolve_doctree(self.config.master_doc, + doctree = self.env.get_and_resolve_doctree(self.config.root_doc, self, prune_toctrees=False, includehidden=True) self.refnodes = self.get_refnodes(doctree, []) - master_dir = path.dirname(self.config.master_doc) + master_dir = path.dirname(self.config.root_doc) if master_dir: master_dir += '/' # XXX or os.sep? for item in self.refnodes: @@ -237,13 +237,13 @@ class EpubBuilder(StandaloneHTMLBuilder): self.toc_add_files(self.refnodes) def toc_add_files(self, refnodes: List[Dict[str, Any]]) -> None: - """Add the master_doc, pre and post files to a list of refnodes. + """Add the root_doc, pre and post files to a list of refnodes. """ refnodes.insert(0, { 'level': 1, - 'refuri': html.escape(self.config.master_doc + self.out_suffix), + 'refuri': html.escape(self.config.root_doc + self.out_suffix), 'text': ssp(html.escape( - self.env.titles[self.config.master_doc].astext())) + self.env.titles[self.config.root_doc].astext())) }) for file, text in reversed(self.config.epub_pre_files): refnodes.insert(0, { @@ -677,7 +677,7 @@ class EpubBuilder(StandaloneHTMLBuilder): logger.info(__('writing toc.ncx file...')) if self.config.epub_tocscope == 'default': - doctree = self.env.get_and_resolve_doctree(self.config.master_doc, + doctree = self.env.get_and_resolve_doctree(self.config.root_doc, self, prune_toctrees=False, includehidden=False) refnodes = self.get_refnodes(doctree, []) diff --git a/sphinx/builders/epub3.py b/sphinx/builders/epub3.py index 8a0dab303..623ee45a4 100644 --- a/sphinx/builders/epub3.py +++ b/sphinx/builders/epub3.py @@ -166,7 +166,7 @@ class Epub3Builder(_epub_base.EpubBuilder): if self.config.epub_tocscope == 'default': doctree = self.env.get_and_resolve_doctree( - self.config.master_doc, self, + self.config.root_doc, self, prune_toctrees=False, includehidden=False) refnodes = self.get_refnodes(doctree, []) self.toc_add_files(refnodes) diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index a68594a6d..0f55aa8bf 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -494,7 +494,8 @@ class StandaloneHTMLBuilder(Builder): 'version': self.config.version, 'last_updated': self.last_updated, 'copyright': self.config.copyright, - 'master_doc': self.config.master_doc, + 'master_doc': self.config.root_doc, + 'root_doc': self.config.root_doc, 'use_opensearch': self.config.html_use_opensearch, 'docstitle': self.config.html_title, 'shorttitle': self.config.html_short_title, diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index dc533b486..681d3cfe6 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -517,7 +517,7 @@ def default_latex_documents(config: Config) -> List[Tuple[str, str, str, str, st """ Better default latex_documents settings. """ project = texescape.escape(config.project, config.latex_engine) author = texescape.escape(config.author, config.latex_engine) - return [(config.master_doc, + return [(config.root_doc, make_filename_from_project(config.project) + '.tex', texescape.escape_abbr(project), texescape.escape_abbr(author), diff --git a/sphinx/builders/manpage.py b/sphinx/builders/manpage.py index e1e1b77f0..f994309e1 100644 --- a/sphinx/builders/manpage.py +++ b/sphinx/builders/manpage.py @@ -109,7 +109,7 @@ class ManualPageBuilder(Builder): def default_man_pages(config: Config) -> List[Tuple[str, str, str, List[str], int]]: """ Better default man_pages settings. """ filename = make_filename_from_project(config.project) - return [(config.master_doc, filename, '%s %s' % (config.project, config.release), + return [(config.root_doc, filename, '%s %s' % (config.project, config.release), [config.author], 1)] diff --git a/sphinx/builders/singlehtml.py b/sphinx/builders/singlehtml.py index 2e72887e3..1ef618dd0 100644 --- a/sphinx/builders/singlehtml.py +++ b/sphinx/builders/singlehtml.py @@ -42,7 +42,7 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): def get_target_uri(self, docname: str, typ: str = None) -> str: if docname in self.env.all_docs: # all references are on the same page... - return self.config.master_doc + self.out_suffix + \ + return self.config.root_doc + self.out_suffix + \ '#document-' + docname else: # chances are this is a html_additional_page @@ -54,7 +54,7 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): def fix_refuris(self, tree: Node) -> None: # fix refuris with double anchor - fname = self.config.master_doc + self.out_suffix + fname = self.config.root_doc + self.out_suffix for refnode in tree.traverse(nodes.reference): if 'refuri' not in refnode: continue @@ -75,7 +75,7 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): return self.render_partial(toctree)['fragment'] def assemble_doctree(self) -> nodes.document: - master = self.config.master_doc + master = self.config.root_doc tree = self.env.get_doctree(master) tree = inline_all_toctrees(self, set(), master, tree, darkgreen, [master]) tree['docname'] = master @@ -99,7 +99,7 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): alias = "%s/%s" % (docname, id) new_secnumbers[alias] = secnum - return {self.config.master_doc: new_secnumbers} + return {self.config.root_doc: new_secnumbers} def assemble_toc_fignumbers(self) -> Dict[str, Dict[str, Dict[str, Tuple[int, ...]]]]: # Assemble toc_fignumbers to resolve figure numbers on SingleHTML. @@ -120,11 +120,11 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): for id, fignum in fignums.items(): new_fignumbers[alias][id] = fignum - return {self.config.master_doc: new_fignumbers} + return {self.config.root_doc: new_fignumbers} def get_doc_context(self, docname: str, body: str, metatags: str) -> Dict: # no relation links... - toctree = TocTree(self.env).get_toctree_for(self.config.master_doc, self, False) + toctree = TocTree(self.env).get_toctree_for(self.config.root_doc, self, False) # if there is no toctree, toc is None if toctree: self.fix_refuris(toctree) @@ -160,8 +160,8 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): self.env.toc_fignumbers = self.assemble_toc_fignumbers() with progress_message(__('writing')): - self.write_doc_serialized(self.config.master_doc, doctree) - self.write_doc(self.config.master_doc, doctree) + self.write_doc_serialized(self.config.root_doc, doctree) + self.write_doc(self.config.root_doc, doctree) def finish(self) -> None: self.write_additional_files() diff --git a/sphinx/builders/texinfo.py b/sphinx/builders/texinfo.py index 1a56be0f9..bb2039e61 100644 --- a/sphinx/builders/texinfo.py +++ b/sphinx/builders/texinfo.py @@ -197,7 +197,7 @@ class TexinfoBuilder(Builder): def default_texinfo_documents(config: Config) -> List[Tuple[str, str, str, str, str, str, str]]: # NOQA """ Better default texinfo_documents settings. """ filename = make_filename_from_project(config.project) - return [(config.master_doc, filename, config.project, config.author, filename, + return [(config.root_doc, filename, config.project, config.author, filename, 'One line description of project', 'Miscellaneous')] diff --git a/sphinx/cmd/quickstart.py b/sphinx/cmd/quickstart.py index 4ef9335a4..a40a21073 100644 --- a/sphinx/cmd/quickstart.py +++ b/sphinx/cmd/quickstart.py @@ -162,10 +162,22 @@ class QuickstartRenderer(SphinxRenderer): self.templatedir = templatedir or '' super().__init__() + def _has_custom_template(self, template_name: str) -> bool: + """Check if custom template file exists. + + Note: Please don't use this function from extensions. + It will be removed in the future without deprecation period. + """ + template = path.join(self.templatedir, path.basename(template_name)) + if self.templatedir and path.exists(template): + return True + else: + return False + def render(self, template_name: str, context: Dict) -> str: - user_template = path.join(self.templatedir, path.basename(template_name)) - if self.templatedir and path.exists(user_template): - return self.render_from_file(user_template, context) + if self._has_custom_template(template_name): + custom_template = path.join(self.templatedir, path.basename(template_name)) + return self.render_from_file(custom_template, context) else: return super().render(template_name, context) @@ -318,6 +330,7 @@ def generate(d: Dict, overwrite: bool = True, silent: bool = False, templatedir: if 'mastertocmaxdepth' not in d: d['mastertocmaxdepth'] = 2 + d['root_doc'] = d['master'] d['now'] = time.asctime() d['project_underline'] = column_width(d['project']) * '=' d.setdefault('extensions', []) @@ -362,7 +375,13 @@ def generate(d: Dict, overwrite: bool = True, silent: bool = False, templatedir: write_file(path.join(srcdir, 'conf.py'), template.render_string(conf_text, d)) masterfile = path.join(srcdir, d['master'] + d['suffix']) - write_file(masterfile, template.render('quickstart/master_doc.rst_t', d)) + if template._has_custom_template('quickstart/master_doc.rst_t'): + msg = ('A custom template `master_doc.rst_t` found. It has been renamed to ' + '`root_doc.rst_t`. Please rename it on your project too.') + print(colorize('red', msg)) # RemovedInSphinx60Warning + write_file(masterfile, template.render('quickstart/master_doc.rst_t', d)) + else: + write_file(masterfile, template.render('quickstart/root_doc.rst_t', d)) if d.get('make_mode') is True: makefile_template = 'quickstart/Makefile.new_t' diff --git a/sphinx/config.py b/sphinx/config.py index 735a3e0b3..d7f9c4f8a 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -105,6 +105,7 @@ class Config: 'figure_language_filename': ('{root}.{language}{ext}', 'env', [str]), 'master_doc': ('index', 'env', []), + 'root_doc': (lambda config: config.master_doc, 'env', []), 'source_suffix': ({'.rst': 'restructuredtext'}, 'env', Any), 'source_encoding': ('utf-8-sig', 'env', []), 'exclude_patterns': ([], 'env', []), @@ -461,17 +462,17 @@ def check_primary_domain(app: "Sphinx", config: Config) -> None: config.primary_domain = None # type: ignore -def check_master_doc(app: "Sphinx", env: "BuildEnvironment", added: Set[str], - changed: Set[str], removed: Set[str]) -> Set[str]: - """Adjust master_doc to 'contents' to support an old project which does not have - no master_doc setting. +def check_root_doc(app: "Sphinx", env: "BuildEnvironment", added: Set[str], + changed: Set[str], removed: Set[str]) -> Set[str]: + """Adjust root_doc to 'contents' to support an old project which does not have + no root_doc setting. """ - if (app.config.master_doc == 'index' and + if (app.config.root_doc == 'index' and 'index' not in app.project.docnames and 'contents' in app.project.docnames): - logger.warning(__('Since v2.0, Sphinx uses "index" as master_doc by default. ' - 'Please add "master_doc = \'contents\'" to your conf.py.')) - app.config.master_doc = "contents" # type: ignore + logger.warning(__('Since v2.0, Sphinx uses "index" as root_doc by default. ' + 'Please add "root_doc = \'contents\'" to your conf.py.')) + app.config.root_doc = "contents" # type: ignore return changed @@ -483,7 +484,7 @@ def setup(app: "Sphinx") -> Dict[str, Any]: app.connect('config-inited', correct_copyright_year, priority=800) app.connect('config-inited', check_confval_types, priority=800) app.connect('config-inited', check_primary_domain, priority=800) - app.connect('env-get-outdated', check_master_doc) + app.connect('env-get-outdated', check_root_doc) return { 'version': 'builtin', diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 28599c977..60ede8188 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -600,7 +600,7 @@ class BuildEnvironment: traversed.add(subdocname) relations = {} - docnames = traverse_toctree(None, self.config.master_doc) + docnames = traverse_toctree(None, self.config.root_doc) prevdoc = None parent, docname = next(docnames) for nextparent, nextdoc in docnames: @@ -618,7 +618,7 @@ class BuildEnvironment: included = set().union(*self.included.values()) # type: ignore for docname in sorted(self.all_docs): if docname not in self.files_to_rebuild: - if docname == self.config.master_doc: + if docname == self.config.root_doc: # the master file is not included anywhere ;) continue if docname in included: diff --git a/sphinx/environment/adapters/toctree.py b/sphinx/environment/adapters/toctree.py index 2e33cf702..d12055b84 100644 --- a/sphinx/environment/adapters/toctree.py +++ b/sphinx/environment/adapters/toctree.py @@ -315,7 +315,7 @@ class TocTree: def get_toctree_for(self, docname: str, builder: "Builder", collapse: bool, **kwargs: Any) -> Element: """Return the global TOC nodetree.""" - doctree = self.env.get_doctree(self.env.config.master_doc) + doctree = self.env.get_doctree(self.env.config.root_doc) toctrees = [] # type: List[Element] if 'includehidden' not in kwargs: kwargs['includehidden'] = True diff --git a/sphinx/environment/collectors/toctree.py b/sphinx/environment/collectors/toctree.py index da0b3fe6c..aeb6ef3a6 100644 --- a/sphinx/environment/collectors/toctree.py +++ b/sphinx/environment/collectors/toctree.py @@ -281,7 +281,7 @@ class TocTreeCollector(EnvironmentCollector): _walk_doctree(docname, doctree, secnum) if env.config.numfig: - _walk_doc(env.config.master_doc, tuple()) + _walk_doc(env.config.root_doc, tuple()) for docname, fignums in env.toc_fignumbers.items(): if fignums != old_fignumbers.get(docname): rewrite_needed.append(docname) diff --git a/sphinx/setup_command.py b/sphinx/setup_command.py index dab19547e..a508c50a1 100644 --- a/sphinx/setup_command.py +++ b/sphinx/setup_command.py @@ -190,6 +190,6 @@ class BuildDoc(Command): if not self.link_index: continue - src = app.config.master_doc + app.builder.out_suffix # type: ignore + src = app.config.root_doc + app.builder.out_suffix # type: ignore dst = app.builder.get_outfilename('index') # type: ignore os.symlink(src, dst) diff --git a/sphinx/templates/htmlhelp/project.hhc b/sphinx/templates/htmlhelp/project.hhc index c1096e711..705cfeba7 100644 --- a/sphinx/templates/htmlhelp/project.hhc +++ b/sphinx/templates/htmlhelp/project.hhc @@ -18,7 +18,7 @@ </OBJECT> <UL> <LI> - {{ sitemap(short_title, master_doc)|indent(8) }} + {{ sitemap(short_title, root_doc)|indent(8) }} </LI> {%- for indexname, indexcls, content, collapse in domain_indices %} <LI> diff --git a/sphinx/templates/htmlhelp/project.hhp b/sphinx/templates/htmlhelp/project.hhp index b647b3713..17edc1f3d 100644 --- a/sphinx/templates/htmlhelp/project.hhp +++ b/sphinx/templates/htmlhelp/project.hhp @@ -4,7 +4,7 @@ Binary Index=No Compiled file={{ outname }}.chm Contents file={{ outname }}.hhc Default Window={{ outname }} -Default topic={{ master_doc }} +Default topic={{ root_doc }} Display compile progress=No Full text search stop list file={{ outname }}.stp Full-text search=Yes @@ -13,7 +13,7 @@ Language={{ "%#x"|format(lcid) }} Title={{ title }} [WINDOWS] -{{ outname }}="{{ title }}","{{ outname }}.hhc","{{ outname }}.hhk","{{ master_doc }}","{{ master_doc }}",,,,,0x63520,220,0x10384e,[0,0,1024,768],,,,,,,0 +{{ outname }}="{{ title }}","{{ outname }}.hhc","{{ outname }}.hhk","{{ root_doc }}","{{ root_doc }}",,,,,0x63520,220,0x10384e,[0,0,1024,768],,,,,,,0 [FILES] {%- for filename in files %} diff --git a/sphinx/templates/quickstart/conf.py_t b/sphinx/templates/quickstart/conf.py_t index 8a20fc4c8..0c5285f9e 100644 --- a/sphinx/templates/quickstart/conf.py_t +++ b/sphinx/templates/quickstart/conf.py_t @@ -64,9 +64,9 @@ templates_path = ['{{ dot }}templates'] source_suffix = {{ suffix | repr }} {% endif -%} -{% if master != 'index' -%} -# The master toctree document. -master_doc = {{ master | repr }} +{% if root_doc != 'index' -%} +# The root document. +root_doc = {{ root_doc | repr }} {% endif -%} {% if language -%} diff --git a/sphinx/templates/quickstart/master_doc.rst_t b/sphinx/templates/quickstart/root_doc.rst_t similarity index 100% rename from sphinx/templates/quickstart/master_doc.rst_t rename to sphinx/templates/quickstart/root_doc.rst_t diff --git a/sphinx/themes/agogo/layout.html b/sphinx/themes/agogo/layout.html index a8ae6304d..855ec8ccb 100644 --- a/sphinx/themes/agogo/layout.html +++ b/sphinx/themes/agogo/layout.html @@ -14,13 +14,13 @@ <div class="header-wrapper" role="banner"> <div class="header"> {%- if logo_url %} - <p class="logo"><a href="{{ pathto(master_doc)|e }}"> + <p class="logo"><a href="{{ pathto(root_doc)|e }}"> <img class="logo" src="{{ logo_url|e }}" alt="Logo"/> </a></p> {%- endif %} {%- block headertitle %} <div class="headertitle"><a - href="{{ pathto(master_doc)|e }}">{{ shorttitle|e }}</a></div> + href="{{ pathto(root_doc)|e }}">{{ shorttitle|e }}</a></div> {%- endblock %} <div class="rel" role="navigation" aria-label="related navigation"> {%- for rellink in rellinks|reverse %} diff --git a/sphinx/themes/basic/globaltoc.html b/sphinx/themes/basic/globaltoc.html index a47c32cc0..5ac0abbd4 100644 --- a/sphinx/themes/basic/globaltoc.html +++ b/sphinx/themes/basic/globaltoc.html @@ -7,5 +7,5 @@ :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} -<h3><a href="{{ pathto(master_doc)|e }}">{{ _('Table of Contents') }}</a></h3> +<h3><a href="{{ pathto(root_doc)|e }}">{{ _('Table of Contents') }}</a></h3> {{ toctree(includehidden=theme_globaltoc_includehidden, collapse=theme_globaltoc_collapse, maxdepth=theme_globaltoc_maxdepth) }} diff --git a/sphinx/themes/basic/layout.html b/sphinx/themes/basic/layout.html index e35d4e5fe..df0707220 100644 --- a/sphinx/themes/basic/layout.html +++ b/sphinx/themes/basic/layout.html @@ -35,7 +35,7 @@ {%- if not loop.first %}{{ reldelim2 }}{% endif %}</li> {%- endfor %} {%- block rootrellink %} - <li class="nav-item nav-item-0"><a href="{{ pathto(master_doc)|e }}">{{ shorttitle|e }}</a>{{ reldelim1 }}</li> + <li class="nav-item nav-item-0"><a href="{{ pathto(root_doc)|e }}">{{ shorttitle|e }}</a>{{ reldelim1 }}</li> {%- endblock %} {%- for parent in parents %} <li class="nav-item nav-item-{{ loop.index }}"><a href="{{ parent.link|e }}" {% if loop.last %}{{ accesskey("U") }}{% endif %}>{{ parent.title }}</a>{{ reldelim1 }}</li> @@ -52,7 +52,7 @@ <div class="sphinxsidebarwrapper"> {%- block sidebarlogo %} {%- if logo_url %} - <p class="logo"><a href="{{ pathto(master_doc)|e }}"> + <p class="logo"><a href="{{ pathto(root_doc)|e }}"> <img class="logo" src="{{ logo_url|e }}" alt="Logo"/> </a></p> {%- endif %} diff --git a/sphinx/themes/basic/localtoc.html b/sphinx/themes/basic/localtoc.html index c15ad2708..d761c2378 100644 --- a/sphinx/themes/basic/localtoc.html +++ b/sphinx/themes/basic/localtoc.html @@ -8,6 +8,6 @@ :license: BSD, see LICENSE for details. #} {%- if display_toc %} - <h3><a href="{{ pathto(master_doc)|e }}">{{ _('Table of Contents') }}</a></h3> + <h3><a href="{{ pathto(root_doc)|e }}">{{ _('Table of Contents') }}</a></h3> {{ toc }} {%- endif %} diff --git a/sphinx/themes/haiku/layout.html b/sphinx/themes/haiku/layout.html index bf76204b1..50ab798a9 100644 --- a/sphinx/themes/haiku/layout.html +++ b/sphinx/themes/haiku/layout.html @@ -21,7 +21,7 @@ «  <a href="{{ prev.link|e }}">{{ prev.title }}</a>   ::   {%- endif %} - <a class="uplink" href="{{ pathto(master_doc)|e }}">{{ _('Contents') }}</a> + <a class="uplink" href="{{ pathto(root_doc)|e }}">{{ _('Contents') }}</a> {%- if next %}   ::   <a href="{{ next.link|e }}">{{ next.title }}</a>  » diff --git a/sphinx/themes/pyramid/layout.html b/sphinx/themes/pyramid/layout.html index 097e97407..33d9bb7d1 100644 --- a/sphinx/themes/pyramid/layout.html +++ b/sphinx/themes/pyramid/layout.html @@ -12,7 +12,7 @@ {%- if logo %} <div class="header" role="banner"> <div class="logo"> - <a href="{{ pathto(master_doc)|e }}"> + <a href="{{ pathto(root_doc)|e }}"> <img class="logo" src="{{ pathto(logo, 1)|e }}" alt="Logo"/> </a> </div> diff --git a/tests/roots/test-ext-doctest-skipif/conf.py b/tests/roots/test-ext-doctest-skipif/conf.py index c863dbc01..6f5498256 100644 --- a/tests/roots/test-ext-doctest-skipif/conf.py +++ b/tests/roots/test-ext-doctest-skipif/conf.py @@ -1,7 +1,7 @@ extensions = ['sphinx.ext.doctest'] project = 'test project for the doctest :skipif: directive' -master_doc = 'skipif' +root_doc = 'skipif' source_suffix = '.txt' exclude_patterns = ['_build'] diff --git a/tests/roots/test-ext-doctest/conf.py b/tests/roots/test-ext-doctest/conf.py index fcf6a6cda..d0e8b107b 100644 --- a/tests/roots/test-ext-doctest/conf.py +++ b/tests/roots/test-ext-doctest/conf.py @@ -1,6 +1,6 @@ extensions = ['sphinx.ext.doctest'] project = 'test project for doctest' -master_doc = 'doctest' +root_doc = 'doctest' source_suffix = '.txt' exclude_patterns = ['_build'] diff --git a/tests/roots/test-latex-equations/conf.py b/tests/roots/test-latex-equations/conf.py index 0957543e6..d851892ba 100644 --- a/tests/roots/test-latex-equations/conf.py +++ b/tests/roots/test-latex-equations/conf.py @@ -1,2 +1,2 @@ -master_doc = 'equations' +root_doc = 'equations' extensions = ['sphinx.ext.imgmath'] diff --git a/tests/roots/test-latex-includegraphics/conf.py b/tests/roots/test-latex-includegraphics/conf.py index b53df86a2..65c19ab85 100644 --- a/tests/roots/test-latex-includegraphics/conf.py +++ b/tests/roots/test-latex-includegraphics/conf.py @@ -1,5 +1,3 @@ -master_doc = 'index' - exclude_patterns = ['_build'] latex_elements = { diff --git a/tests/roots/test-linkcheck/conf.py b/tests/roots/test-linkcheck/conf.py index ae8ef24b7..ac54f73b0 100644 --- a/tests/roots/test-linkcheck/conf.py +++ b/tests/roots/test-linkcheck/conf.py @@ -1,4 +1,4 @@ -master_doc = 'links' +root_doc = 'links' source_suffix = '.txt' exclude_patterns = ['_build'] linkcheck_anchors = True diff --git a/tests/test_build.py b/tests/test_build.py index 62de3ea5f..03643c91f 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -48,12 +48,12 @@ def nonascii_srcdir(request, rootdir, sphinx_test_tempdir): ======================= """)) - master_doc = srcdir / 'index.txt' - master_doc.write_text(master_doc.read_text() + dedent(""" - .. toctree:: + root_doc = srcdir / 'index.txt' + root_doc.write_text(root_doc.read_text() + dedent(""" + .. toctree:: - %(test_name)s/%(test_name)s - """ % {'test_name': test_name})) + %(test_name)s/%(test_name)s + """ % {'test_name': test_name})) return srcdir @@ -71,7 +71,7 @@ def test_build_all(requests_head, make_app, nonascii_srcdir, buildername): app.build() -def test_master_doc_not_found(tempdir, make_app): +def test_root_doc_not_found(tempdir, make_app): (tempdir / 'conf.py').write_text('') assert tempdir.listdir() == ['conf.py'] diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index 4c735952f..c8c94b44b 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -1037,7 +1037,7 @@ def test_toctree_maxdepth_howto(app, status, warning): @pytest.mark.sphinx( 'latex', testroot='toctree-maxdepth', - confoverrides={'master_doc': 'foo'}) + confoverrides={'root_doc': 'foo'}) def test_toctree_not_found(app, status, warning): app.builder.build_all() result = (app.outdir / 'python.tex').read_text() @@ -1051,7 +1051,7 @@ def test_toctree_not_found(app, status, warning): @pytest.mark.sphinx( 'latex', testroot='toctree-maxdepth', - confoverrides={'master_doc': 'bar'}) + confoverrides={'root_doc': 'bar'}) def test_toctree_without_maxdepth(app, status, warning): app.builder.build_all() result = (app.outdir / 'python.tex').read_text() @@ -1064,7 +1064,7 @@ def test_toctree_without_maxdepth(app, status, warning): @pytest.mark.sphinx( 'latex', testroot='toctree-maxdepth', - confoverrides={'master_doc': 'qux'}) + confoverrides={'root_doc': 'qux'}) def test_toctree_with_deeper_maxdepth(app, status, warning): app.builder.build_all() result = (app.outdir / 'python.tex').read_text() @@ -1532,7 +1532,7 @@ def test_latex_figure_in_admonition(app, status, warning): def test_default_latex_documents(): from sphinx.util import texescape texescape.init() - config = Config({'master_doc': 'index', + config = Config({'root_doc': 'index', 'project': 'STASI™ Documentation', 'author': "Wolfgang Schäuble & G'Beckstein."}) config.init_values() diff --git a/tests/test_config.py b/tests/test_config.py index 9a0b617d5..a48e7ce30 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -20,7 +20,7 @@ from sphinx.testing.path import path @pytest.mark.sphinx(testroot='config', confoverrides={ - 'master_doc': 'master', + 'root_doc': 'root', 'nonexisting_value': 'True', 'latex_elements.maketitle': 'blah blah blah', 'modindex_common_prefix': 'path1,path2'}) @@ -33,7 +33,7 @@ def test_core_config(app, status, warning): assert cfg.templates_path == ['_templates'] # overrides - assert cfg.master_doc == 'master' + assert cfg.root_doc == 'root' assert cfg.latex_elements['maketitle'] == 'blah blah blah' assert cfg.modindex_common_prefix == ['path1', 'path2'] @@ -78,11 +78,11 @@ def test_extension_values(): config = Config() # check standard settings - assert config.master_doc == 'index' + assert config.root_doc == 'index' # can't override it by add_config_value() with pytest.raises(ExtensionError) as excinfo: - config.add('master_doc', 'index', 'env', None) + config.add('root_doc', 'index', 'env', None) assert 'already present' in str(excinfo.value) # add a new config value @@ -201,13 +201,13 @@ def test_config_eol(logger, tempdir): assert logger.called is False -@pytest.mark.sphinx(confoverrides={'master_doc': 123, +@pytest.mark.sphinx(confoverrides={'root_doc': 123, 'language': 'foo', 'primary_domain': None}) def test_builtin_conf(app, status, warning): warnings = warning.getvalue() - assert 'master_doc' in warnings, ( - 'override on builtin "master_doc" should raise a type warning') + assert 'root_doc' in warnings, ( + 'override on builtin "root_doc" should raise a type warning') assert 'language' not in warnings, ( 'explicitly permitted override on builtin "language" should NOT raise ' 'a type warning') diff --git a/tests/test_environment.py b/tests/test_environment.py index bad06baa4..5d3035ac0 100644 --- a/tests/test_environment.py +++ b/tests/test_environment.py @@ -35,14 +35,14 @@ def test_config_status(make_app, app_params): assert "0 added, 0 changed, 0 removed" in app2._status.getvalue() # incremental build (config entry changed) - app3 = make_app(*args, confoverrides={'master_doc': 'indexx'}, **kwargs) + app3 = make_app(*args, confoverrides={'root_doc': 'indexx'}, **kwargs) fname = os.path.join(app3.srcdir, 'index.rst') assert os.path.isfile(fname) shutil.move(fname, fname[:-4] + 'x.rst') assert app3.env.config_status == CONFIG_CHANGED app3.build() shutil.move(fname[:-4] + 'x.rst', fname) - assert "[config changed ('master_doc')] 1 added" in app3._status.getvalue() + assert "[config changed ('root_doc')] 1 added" in app3._status.getvalue() # incremental build (extension changed) app4 = make_app(*args, confoverrides={'extensions': ['sphinx.ext.autodoc']}, **kwargs) diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index 02aafe335..94144ef22 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -163,7 +163,7 @@ def test_quickstart_all_answers(tempdir): ] assert ns['templates_path'] == ['.templates'] assert ns['source_suffix'] == '.txt' - assert ns['master_doc'] == 'contents' + assert ns['root_doc'] == 'contents' assert ns['project'] == 'STASI™' assert ns['copyright'] == '%s, Wolfgang Schäuble & G\'Beckstein' % \ time.strftime('%Y') diff --git a/tests/test_setup_command.py b/tests/test_setup_command.py index 52dad14fd..0561b2fc4 100644 --- a/tests/test_setup_command.py +++ b/tests/test_setup_command.py @@ -93,12 +93,12 @@ def nonascii_srcdir(request, setup_command): ========================== """)) - master_doc = srcdir / 'index.txt' - master_doc.write_bytes((master_doc.read_text() + dedent(""" - .. toctree:: + root_doc = srcdir / 'index.txt' + root_doc.write_bytes((root_doc.read_text() + dedent(""" + .. toctree:: - %(mb_name)s/%(mb_name)s - """ % locals())).encode()) + %(mb_name)s/%(mb_name)s + """ % locals())).encode()) @pytest.mark.usefixtures('nonascii_srcdir') From ae5986f9cdbac0a35110b010126d0d56446bd0de Mon Sep 17 00:00:00 2001 From: Thomas Grainger <tagrain@gmail.com> Date: Sun, 28 Feb 2021 12:00:18 +0000 Subject: [PATCH 21/58] Update sphinx/builders/html/__init__.py --- sphinx/builders/html/__init__.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index 760a17dc5..bd31e309b 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -1260,11 +1260,7 @@ def migrate_html_add_permalinks(app: Sphinx, config: Config) -> None: # RemovedInSphinx60Warning logger.warning(__('html_add_permalinks has been deprecated since v3.5.0. ' 'Please use html_permalinks and html_permalinks_icon instead.')) - if ( - html_add_permalinks is None or - html_add_permalinks is False or - html_add_permalinks == "" - ): + if not html_add_permalinks: config.html_permalinks = False # type: ignore[attr-defined] return From 878c11611275eca4d14a56efcf79a55ad242dc9a Mon Sep 17 00:00:00 2001 From: Thomas Grainger <tagrain@gmail.com> Date: Sun, 28 Feb 2021 14:29:07 +0000 Subject: [PATCH 22/58] Update sphinx/builders/html/__init__.py --- sphinx/builders/html/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index bd31e309b..01de0b94f 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -1265,7 +1265,7 @@ def migrate_html_add_permalinks(app: Sphinx, config: Config) -> None: return config.html_permalinks_icon = html.escape( # type: ignore[attr-defined] - config.html_add_permalinks + html_add_permalinks ) # for compatibility From 727e95f15cc2b054e44133cea5a2916051a9b066 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Sun, 28 Feb 2021 23:45:23 +0900 Subject: [PATCH 23/58] Update CHANGES for PR #8905 --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index cd2f05536..2c51be98f 100644 --- a/CHANGES +++ b/CHANGES @@ -75,6 +75,7 @@ Bugs fixed * #8917: autodoc: Raises a warning if function has wrong __globals__ value * #8415: autodoc: a TypeVar imported from other module is not resolved (in Python 3.7 or above) +* #8905: html: html_add_permalinks=None and html_add_permalinks="" are ignored * #8380: html search: Paragraphs in search results are not identified as ``<p>`` * #8915: html theme: The translation of sphinx_rtd_theme does not work * #8342: Emit a warning if a unknown domain is given for directive or role (ex. From 2c6c1469eedbc3f975e308cda47d746e5087938e Mon Sep 17 00:00:00 2001 From: Joaquin Anton <janton@nvidia.com> Date: Mon, 1 Mar 2021 10:32:31 +0100 Subject: [PATCH 24/58] Apply code review suggestions Signed-off-by: Joaquin Anton <janton@nvidia.com> --- sphinx/environment/collectors/title.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sphinx/environment/collectors/title.py b/sphinx/environment/collectors/title.py index 00832ae43..28e967427 100644 --- a/sphinx/environment/collectors/title.py +++ b/sphinx/environment/collectors/title.py @@ -39,10 +39,9 @@ class TitleCollector(EnvironmentCollector): longtitlenode = titlenode # explicit title set with title directive; use this only for # the <title> tag in HTML output - explicit_title = nodes.Text(doctree['title']) if 'title' in doctree else None - if explicit_title: + if 'title' in doctree: longtitlenode = nodes.title() - longtitlenode += explicit_title + longtitlenode += nodes.Text(doctree['title']) # look for first section title and use that as the title for node in doctree.traverse(nodes.section): visitor = SphinxContentsFilter(doctree) @@ -51,7 +50,7 @@ class TitleCollector(EnvironmentCollector): break else: # document has no title - titlenode += explicit_title or nodes.Text('<no title>') + titlenode += nodes.Text(doctree.get('title', '<no title>')) app.env.titles[app.env.docname] = titlenode app.env.longtitles[app.env.docname] = longtitlenode From 8144c52228ff1de89ceec2870e8916fd5929f1b1 Mon Sep 17 00:00:00 2001 From: Ask Hjorth Larsen <asklarsen@gmail.com> Date: Mon, 1 Mar 2021 17:50:13 +0100 Subject: [PATCH 25/58] Spanish translation: fix bad substitution templates --- sphinx/locale/es/LC_MESSAGES/sphinx.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/locale/es/LC_MESSAGES/sphinx.po b/sphinx/locale/es/LC_MESSAGES/sphinx.po index a01ae6c80..65ea746fe 100644 --- a/sphinx/locale/es/LC_MESSAGES/sphinx.po +++ b/sphinx/locale/es/LC_MESSAGES/sphinx.po @@ -3306,12 +3306,12 @@ msgstr "más de un objetivo destino encontrado para 'cualquier' referencia cruza #: sphinx/transforms/post_transforms/__init__.py:171 #, python-format msgid "%s:%s reference target not found: %%(target)s" -msgstr "%s:%s destino de referencia no encontrada: %% (destino)s" +msgstr "%s:%s destino de referencia no encontrada: %%(target)s" #: sphinx/transforms/post_transforms/__init__.py:174 #, python-format msgid "%r reference target not found: %%(target)s" -msgstr "%r destino de referencia no encontrada: %% (destino)s" +msgstr "%r destino de referencia no encontrada: %%(target)s" #: sphinx/transforms/post_transforms/images.py:86 #, python-format From 001c6f8a1cc882dfc1c1c041b068543bc788eaa8 Mon Sep 17 00:00:00 2001 From: Ask Hjorth Larsen <asklarsen@gmail.com> Date: Mon, 1 Mar 2021 18:37:58 +0100 Subject: [PATCH 26/58] update .mo file as well --- sphinx/locale/es/LC_MESSAGES/sphinx.mo | Bin 77490 -> 81233 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/sphinx/locale/es/LC_MESSAGES/sphinx.mo b/sphinx/locale/es/LC_MESSAGES/sphinx.mo index bd4be1841d6e848855c9dfc463bf2a7412a0ad05..fce55c7e00c03ed409c23dde679c06e78fb19a13 100644 GIT binary patch delta 15342 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 A6951J delta 11573 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 D%WJFo From 61c9c7fc58bf0c21207908bde6440cd315456818 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Tue, 2 Mar 2021 21:17:12 +0900 Subject: [PATCH 27/58] Deprecate SphinxComponentRegistry.get_source_input() The source_input system was deprecated at v2.0. So no client uses it longer now. Therefore this deprecate the getter interface and its usage. --- CHANGES | 2 ++ doc/extdev/deprecated.rst | 10 ++++++++++ sphinx/io.py | 29 +++++++---------------------- sphinx/registry.py | 5 +++++ 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/CHANGES b/CHANGES index 428b6951f..38dbc575b 100644 --- a/CHANGES +++ b/CHANGES @@ -48,6 +48,8 @@ Deprecated * ``sphinx.directives.patches.CSVTable`` * ``sphinx.directives.patches.ListTable`` * ``sphinx.directives.patches.RSTTable`` +* ``sphinx.registry.SphinxComponentRegistry.get_source_input()`` +* ``sphinx.registry.SphinxComponentRegistry.source_inputs`` * ``sphinx.transforms.FigureAligner`` * ``sphinx.util.pycompat.convert_with_2to3()`` * ``sphinx.util.pycompat.execfile_()`` diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index 828a73d05..10c1dfb22 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -47,6 +47,16 @@ The following is a list of deprecated interfaces. - 6.0 - ``docutils.parsers.rst.diretives.tables.RSTTable`` + * - ``sphinx.registry.SphinxComponentRegistry.get_source_input()`` + - 4.0 + - 6.0 + - N/A + + * - ``sphinx.registry.SphinxComponentRegistry.source_inputs`` + - 4.0 + - 6.0 + - N/A + * - ``sphinx.transforms.FigureAligner`` - 4.0 - 6.0 diff --git a/sphinx/io.py b/sphinx/io.py index 96f181184..f6ab0e1ef 100644 --- a/sphinx/io.py +++ b/sphinx/io.py @@ -178,27 +178,12 @@ def read_doc(app: "Sphinx", env: BuildEnvironment, filename: str) -> nodes.docum # CommonMarkParser. parser.settings_spec = RSTParser.settings_spec - input_class = app.registry.get_source_input(filetype) - if input_class: - # Sphinx-1.8 style - source = input_class(app, env, source=None, source_path=filename, # type: ignore - encoding=env.config.source_encoding) - pub = Publisher(reader=reader, - parser=parser, - writer=SphinxDummyWriter(), - source_class=SphinxDummySourceClass, # type: ignore - destination=NullOutput()) - pub.process_programmatic_settings(None, env.settings, None) - pub.set_source(source, filename) - else: - # Sphinx-2.0 style - pub = Publisher(reader=reader, - parser=parser, - writer=SphinxDummyWriter(), - source_class=SphinxFileInput, - destination=NullOutput()) - pub.process_programmatic_settings(None, env.settings, None) - pub.set_source(source_path=filename) - + pub = Publisher(reader=reader, + parser=parser, + writer=SphinxDummyWriter(), + source_class=SphinxFileInput, + destination=NullOutput()) + pub.process_programmatic_settings(None, env.settings, None) + pub.set_source(source_path=filename) pub.publish() return pub.document diff --git a/sphinx/registry.py b/sphinx/registry.py index 168583739..cd7a7f4a0 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -9,6 +9,7 @@ """ import traceback +import warnings from importlib import import_module from types import MethodType from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Tuple, Type, Union @@ -23,6 +24,7 @@ from pkg_resources import iter_entry_points from sphinx.builders import Builder from sphinx.config import Config +from sphinx.deprecation import RemovedInSphinx60Warning from sphinx.domains import Domain, Index, ObjType from sphinx.domains.std import GenericObject, Target from sphinx.environment import BuildEnvironment @@ -285,6 +287,9 @@ class SphinxComponentRegistry: return parser def get_source_input(self, filetype: str) -> "Type[Input]": + warnings.warn('SphinxComponentRegistry.get_source_input() is deprecated.', + RemovedInSphinx60Warning) + try: return self.source_inputs[filetype] except KeyError: From b923165d5ef289ba39bb964201d4a036eb5a5ff6 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Tue, 2 Mar 2021 22:27:46 +0900 Subject: [PATCH 28/58] Update CHANGES for PR #8937 --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 2c51be98f..812af757f 100644 --- a/CHANGES +++ b/CHANGES @@ -39,6 +39,7 @@ Incompatible changes ``None`` by default instead of ``'default'`` * #8769: LaTeX refactoring: split sphinx.sty into multiple files and rename some auxiliary files created in ``latex`` build output repertory +* #8937: Use explicit title instead of <no title> Deprecated ---------- From 4052cbc9b157abd26fae7c4d0aedd233a21b98d5 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen <Jakob@caput.dk> Date: Tue, 2 Mar 2021 20:22:22 +0100 Subject: [PATCH 29/58] C++, support spaceship operator Fixes sphinx-doc/sphinx#8942 --- CHANGES | 1 + sphinx/domains/cpp.py | 6 ++++-- tests/test_domain_cpp.py | 6 +++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 812af757f..4d22cfcaa 100644 --- a/CHANGES +++ b/CHANGES @@ -69,6 +69,7 @@ Features added * #7830: Add debug logs for change detection of sources and templates * #8201: Emit a warning if toctree contains duplicated entries * #8326: ``master_doc`` is now renamed to :confval:`root_doc` +* #8942: C++, add support for the C++20 spaceship operator, ``<=>``. Bugs fixed ---------- diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index cf93681ae..04b471b17 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -306,6 +306,7 @@ _operator_re = re.compile(r'''(?x) | \+\+ | -- | ->\*? | \, | (<<|>>)=? | && | \|\| + | <=> | [!<>=/*%+|&^~-]=? | (\b(and|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|xor|xor_eq)\b) ''') @@ -494,6 +495,7 @@ _id_operator_v2 = { '>': 'gt', '<=': 'le', '>=': 'ge', + '<=>': 'ss', '!': 'nt', 'not': 'nt', '&&': 'aa', 'and': 'aa', '||': 'oo', 'or': 'oo', @@ -528,7 +530,7 @@ _expression_bin_ops = [ ['^', 'xor'], ['&', 'bitand'], ['==', '!=', 'not_eq'], - ['<=', '>=', '<', '>'], + ['<=>', '<=', '>=', '<', '>'], ['<<', '>>'], ['+', '-'], ['*', '/', '%'], @@ -5309,7 +5311,7 @@ class DefinitionParser(BaseParser): # exclusive-or = and ^ # and = equality & # equality = relational ==, != - # relational = shift <, >, <=, >= + # relational = shift <, >, <=, >=, <=> # shift = additive <<, >> # additive = multiplicative +, - # multiplicative = pm *, /, % diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index da05c1261..4c7d63a26 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -289,13 +289,16 @@ def test_expressions(): exprCheck('5 == 42', 'eqL5EL42E') exprCheck('5 != 42', 'neL5EL42E') exprCheck('5 not_eq 42', 'neL5EL42E') - # ['<=', '>=', '<', '>'] + # ['<=', '>=', '<', '>', '<=>'] exprCheck('5 <= 42', 'leL5EL42E') exprCheck('A <= 42', 'le1AL42E') exprCheck('5 >= 42', 'geL5EL42E') exprCheck('5 < 42', 'ltL5EL42E') exprCheck('A < 42', 'lt1AL42E') exprCheck('5 > 42', 'gtL5EL42E') + exprCheck('A > 42', 'gt1AL42E') + exprCheck('5 <=> 42', 'ssL5EL42E') + exprCheck('A <=> 42', 'ss1AL42E') # ['<<', '>>'] exprCheck('5 << 42', 'lsL5EL42E') exprCheck('A << 42', 'ls1AL42E') @@ -661,6 +664,7 @@ def test_operators(): check('function', 'void operator>()', {1: "gt-operator", 2: "gtv"}) check('function', 'void operator<=()', {1: "lte-operator", 2: "lev"}) check('function', 'void operator>=()', {1: "gte-operator", 2: "gev"}) + check('function', 'void operator<=>()', {1: None, 2: "ssv"}) check('function', 'void operator!()', {1: "not-operator", 2: "ntv"}) check('function', 'void operator not()', {2: "ntv"}) check('function', 'void operator&&()', {1: "sand-operator", 2: "aav"}) From cf85d3afd1d26394df42b148d2b2a5c462f50947 Mon Sep 17 00:00:00 2001 From: Ask Hjorth Larsen <asklarsen@gmail.com> Date: Wed, 3 Mar 2021 19:52:49 +0100 Subject: [PATCH 30/58] format translatable strings in one go. This enables translation tools like msgfmt to verify that an incorrect translation cannot crash sphinx-build. This will fix current crashes for Hungarian and Greek (Spanish was fixed in #8941) --- sphinx/transforms/post_transforms/__init__.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/sphinx/transforms/post_transforms/__init__.py b/sphinx/transforms/post_transforms/__init__.py index 1c424050a..00a0f9c2d 100644 --- a/sphinx/transforms/post_transforms/__init__.py +++ b/sphinx/transforms/post_transforms/__init__.py @@ -168,14 +168,13 @@ class ReferencesResolver(SphinxPostTransform): if self.app.emit_firstresult('warn-missing-reference', domain, node): return elif domain and typ in domain.dangling_warnings: - msg = domain.dangling_warnings[typ] + msg = domain.dangling_warnings[typ] % {'target': target} elif node.get('refdomain', 'std') not in ('', 'std'): - msg = (__('%s:%s reference target not found: %%(target)s') % - (node['refdomain'], typ)) + msg = (__('%s:%s reference target not found: %s') % + (node['refdomain'], typ, target)) else: - msg = __('%r reference target not found: %%(target)s') % typ - logger.warning(msg % {'target': target}, - location=node, type='ref', subtype=typ) + msg = __('%r reference target not found: %s') % (typ, target) + logger.warning(msg, location=node, type='ref', subtype=typ) class OnlyNodeTransform(SphinxPostTransform): From 17599133f7fdd5ba185b99ba48faa617e01986fd Mon Sep 17 00:00:00 2001 From: Eric Larson <larson.eric.d@gmail.com> Date: Wed, 3 Mar 2021 16:28:33 -0500 Subject: [PATCH 31/58] BUG: Fix rebuild regression --- sphinx/builders/html/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index 01de0b94f..115f73538 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -1248,7 +1248,13 @@ def validate_html_favicon(app: Sphinx, config: Config) -> None: config.html_favicon = None # type: ignore -UNSET = object() +class _stable_repr_object(): + + def __repr__(self): + return '<object>' + + +UNSET = _stable_repr_object() def migrate_html_add_permalinks(app: Sphinx, config: Config) -> None: From bae8fb6909b6ae3b283fd48510b3aa7b3a9726ca Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Thu, 4 Mar 2021 22:08:41 +0900 Subject: [PATCH 32/58] i18n: Fix wrong substitution message catalog for el and hr (refs: #8943) --- CHANGES | 2 ++ sphinx/locale/el/LC_MESSAGES/sphinx.po | 4 ++-- sphinx/locale/hr/LC_MESSAGES/sphinx.po | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 1542e8834..fbe134bf5 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,8 @@ Features added Bugs fixed ---------- +* #8943: i18n: Crashed by broken translation messages in ES, EL and HR + Testing -------- diff --git a/sphinx/locale/el/LC_MESSAGES/sphinx.po b/sphinx/locale/el/LC_MESSAGES/sphinx.po index 03f5d01fe..6c0fc667b 100644 --- a/sphinx/locale/el/LC_MESSAGES/sphinx.po +++ b/sphinx/locale/el/LC_MESSAGES/sphinx.po @@ -3301,12 +3301,12 @@ msgstr "περισσότεροι από ένας στόχοι βρέθηκαν #: sphinx/transforms/post_transforms/__init__.py:171 #, python-format msgid "%s:%s reference target not found: %%(target)s" -msgstr "Ο %s:%s στόχος αναφοράς δεν βρέθηκε: %% (στόχος)" +msgstr "Ο %s:%s στόχος αναφοράς δεν βρέθηκε: %%(target)s" #: sphinx/transforms/post_transforms/__init__.py:174 #, python-format msgid "%r reference target not found: %%(target)s" -msgstr "ο στόχος αναφοράς %r δεν βρέθηκε: %%(στόχος)" +msgstr "ο στόχος αναφοράς %r δεν βρέθηκε: %%(target)s" #: sphinx/transforms/post_transforms/images.py:86 #, python-format diff --git a/sphinx/locale/hr/LC_MESSAGES/sphinx.po b/sphinx/locale/hr/LC_MESSAGES/sphinx.po index 20e336487..bd809f381 100644 --- a/sphinx/locale/hr/LC_MESSAGES/sphinx.po +++ b/sphinx/locale/hr/LC_MESSAGES/sphinx.po @@ -3305,7 +3305,7 @@ msgstr "%s:%s reference target nije pronađen: %%(target)s" #: sphinx/transforms/post_transforms/__init__.py:174 #, python-format msgid "%r reference target not found: %%(target)s" -msgstr "%r referenca target nije pronađena: %% (target)" +msgstr "%r referenca target nije pronađena: %%(target)s" #: sphinx/transforms/post_transforms/images.py:86 #, python-format From ec6ab7243da2f311e11ed3505171ec3001a1704b Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Thu, 4 Mar 2021 23:25:48 +0900 Subject: [PATCH 33/58] Fix #8952: Exceptions raised in a Directive cause parallel builds to hang Do shutdown explicitly when failure on the subprocess. --- CHANGES | 2 ++ sphinx/util/parallel.py | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 1542e8834..c794ec614 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,8 @@ Features added Bugs fixed ---------- +* #8952: Exceptions raised in a Directive cause parallel builds to hang + Testing -------- diff --git a/sphinx/util/parallel.py b/sphinx/util/parallel.py index ab27a5128..ed73ee4d6 100644 --- a/sphinx/util/parallel.py +++ b/sphinx/util/parallel.py @@ -103,8 +103,21 @@ class ParallelTasks: self._join_one() def join(self) -> None: - while self._pworking: - self._join_one() + try: + while self._pworking: + self._join_one() + except Exception: + # shutdown other child processes on failure + self.terminate() + raise + + def terminate(self) -> None: + for tid in list(self._precvs): + self._procs[tid].terminate() + self._result_funcs.pop(tid) + self._procs.pop(tid) + self._precvs.pop(tid) + self._pworking -= 1 def _join_one(self) -> None: for tid, pipe in self._precvs.items(): From f75e86d53ad3af9d9a4e597f280f74fbf094ca8d Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Fri, 5 Mar 2021 00:36:38 +0900 Subject: [PATCH 34/58] Fix #8936: LaTeX: A custom LaTeX builder fails with unknown node error At present, post transform components for LaTeX builder does not work for the custom LaTeX builder that inherits the original LaTeX builder. This allows them working with the custom LaTeX builders. --- CHANGES | 2 ++ sphinx/builders/latex/transforms.py | 18 +++++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index 1542e8834..c483e8234 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,8 @@ Features added Bugs fixed ---------- +* #8936: LaTeX: A custom LaTeX builder fails with unknown node error + Testing -------- diff --git a/sphinx/builders/latex/transforms.py b/sphinx/builders/latex/transforms.py index e16f925a1..0a74eded4 100644 --- a/sphinx/builders/latex/transforms.py +++ b/sphinx/builders/latex/transforms.py @@ -42,7 +42,7 @@ class SubstitutionDefinitionsRemover(SphinxPostTransform): # should be invoked after Substitutions process default_priority = Substitutions.default_priority + 1 - builders = ('latex',) + formats = ('latex',) def run(self, **kwargs: Any) -> None: for node in self.document.traverse(nodes.substitution_definition): @@ -57,7 +57,7 @@ class ShowUrlsTransform(SphinxPostTransform): .. note:: This transform is used for integrated doctree """ default_priority = 400 - builders = ('latex',) + formats = ('latex',) # references are expanded to footnotes (or not) expanded = False @@ -345,7 +345,7 @@ class LaTeXFootnoteTransform(SphinxPostTransform): """ default_priority = 600 - builders = ('latex',) + formats = ('latex',) def run(self, **kwargs: Any) -> None: footnotes = list(self.document.traverse(nodes.footnote)) @@ -497,7 +497,7 @@ class BibliographyTransform(SphinxPostTransform): ... """ default_priority = 750 - builders = ('latex',) + formats = ('latex',) def run(self, **kwargs: Any) -> None: citations = thebibliography() @@ -516,7 +516,7 @@ class CitationReferenceTransform(SphinxPostTransform): pending_xref nodes to citation_reference. """ default_priority = 5 # before ReferencesResolver - builders = ('latex',) + formats = ('latex',) def run(self, **kwargs: Any) -> None: domain = cast(CitationDomain, self.env.get_domain('citation')) @@ -536,7 +536,7 @@ class MathReferenceTransform(SphinxPostTransform): nodes to math_reference. """ default_priority = 5 # before ReferencesResolver - builders = ('latex',) + formats = ('latex',) def run(self, **kwargs: Any) -> None: equations = self.env.get_domain('math').data['objects'] @@ -551,7 +551,7 @@ class MathReferenceTransform(SphinxPostTransform): class LiteralBlockTransform(SphinxPostTransform): """Replace container nodes for literal_block by captioned_literal_block.""" default_priority = 400 - builders = ('latex',) + formats = ('latex',) def run(self, **kwargs: Any) -> None: matcher = NodeMatcher(nodes.container, literal_block=True) @@ -563,7 +563,7 @@ class LiteralBlockTransform(SphinxPostTransform): class DocumentTargetTransform(SphinxPostTransform): """Add :doc label to the first section of each document.""" default_priority = 400 - builders = ('latex',) + formats = ('latex',) def run(self, **kwargs: Any) -> None: for node in self.document.traverse(addnodes.start_of_file): @@ -599,7 +599,7 @@ class IndexInSectionTitleTransform(SphinxPostTransform): ... """ default_priority = 400 - builders = ('latex',) + formats = ('latex',) def run(self, **kwargs: Any) -> None: for node in self.document.traverse(nodes.title): From 72e231d0e6431f3709e74f0f9355a9798ebb0991 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen <Jakob@caput.dk> Date: Thu, 4 Mar 2021 21:28:18 +0100 Subject: [PATCH 35/58] C and C++, fix nested paramter lists --- CHANGES | 2 ++ sphinx/domains/c.py | 25 +++++++++++++++++-------- sphinx/domains/cpp.py | 24 ++++++++++++++++-------- tests/test_domain_c.py | 3 +++ tests/test_domain_cpp.py | 3 +++ 5 files changed, 41 insertions(+), 16 deletions(-) diff --git a/CHANGES b/CHANGES index 4d22cfcaa..a362d9037 100644 --- a/CHANGES +++ b/CHANGES @@ -93,6 +93,8 @@ Bugs fixed * C, properly reject function declarations when a keyword is used as parameter name. * #8933: viewcode: Failed to create back-links on parallel build +* #8960: C and C++, fix rendering of (member) function pointer types in + function parameter lists. Testing -------- diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 79d4e145c..0fecbad6d 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -669,15 +669,24 @@ class ASTParameters(ASTBase): def describe_signature(self, signode: TextElement, mode: str, env: "BuildEnvironment", symbol: "Symbol") -> None: verify_description_mode(mode) - paramlist = addnodes.desc_parameterlist() - for arg in self.args: - param = addnodes.desc_parameter('', '', noemph=True) - if mode == 'lastIsName': # i.e., outer-function params + # only use the desc_parameterlist for the outer list, not for inner lists + if mode == 'lastIsName': + paramlist = addnodes.desc_parameterlist() + for arg in self.args: + param = addnodes.desc_parameter('', '', noemph=True) arg.describe_signature(param, 'param', env, symbol=symbol) - else: - arg.describe_signature(param, 'markType', env, symbol=symbol) - paramlist += param - signode += paramlist + paramlist += param + signode += paramlist + else: + signode += nodes.Text('(', '(') + first = True + for arg in self.args: + if not first: + signode += nodes.Text(', ', ', ') + first = False + arg.describe_signature(signode, 'markType', env, symbol=symbol) + signode += nodes.Text(')', ')') + for attr in self.attrs: signode += nodes.Text(' ') attr.describe_signature(signode) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 04b471b17..3bd764de5 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -1967,15 +1967,23 @@ class ASTParametersQualifiers(ASTBase): def describe_signature(self, signode: TextElement, mode: str, env: "BuildEnvironment", symbol: "Symbol") -> None: verify_description_mode(mode) - paramlist = addnodes.desc_parameterlist() - for arg in self.args: - param = addnodes.desc_parameter('', '', noemph=True) - if mode == 'lastIsName': # i.e., outer-function params + # only use the desc_parameterlist for the outer list, not for inner lists + if mode == 'lastIsName': + paramlist = addnodes.desc_parameterlist() + for arg in self.args: + param = addnodes.desc_parameter('', '', noemph=True) arg.describe_signature(param, 'param', env, symbol=symbol) - else: - arg.describe_signature(param, 'markType', env, symbol=symbol) - paramlist += param - signode += paramlist + paramlist += param + signode += paramlist + else: + signode += nodes.Text('(', '(') + first = True + for arg in self.args: + if not first: + signode += nodes.Text(', ', ', ') + first = False + arg.describe_signature(signode, 'markType', env, symbol=symbol) + signode += nodes.Text(')', ')') def _add_anno(signode: TextElement, text: str) -> None: signode += nodes.Text(' ') diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py index 38e83a77e..ef4858786 100644 --- a/tests/test_domain_c.py +++ b/tests/test_domain_c.py @@ -420,6 +420,9 @@ def test_function_definitions(): with pytest.raises(DefinitionError): parse('function', 'void f(int for)') + # from #8960 + check('function', 'void f(void (*p)(int, double), int i)', {1: 'f'}) + def test_nested_name(): check('struct', '{key}.A', {1: "A"}) diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 4c7d63a26..edd5ea5bc 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -619,6 +619,9 @@ def test_function_definitions(): # from breathe#441 check('function', 'auto MakeThingy() -> Thingy*', {1: 'MakeThingy', 2: '10MakeThingyv'}) + # from #8960 + check('function', 'void f(void (*p)(int, double), int i)', {2: '1fPFvidEi'}) + def test_operators(): check('function', 'void operator new()', {1: "new-operator", 2: "nwv"}) From ff788495571c0d80a3a8b510e0e319b3d1e87cd6 Mon Sep 17 00:00:00 2001 From: igo95862 <igo95862@yandex.ru> Date: Fri, 5 Mar 2021 14:19:22 +0300 Subject: [PATCH 36/58] Fix py.typed file not being included in source archive --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 1114ca19f..7c2f852a8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -15,6 +15,7 @@ include sphinx-quickstart.py include sphinx-apidoc.py include tox.ini include sphinx/locale/.tx/config +include sphinx/py.typed recursive-include sphinx/templates * recursive-include sphinx/texinputs * From e1130972b2ad2e57db4ebe0a6fa83e1cb76aa6a3 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Sun, 15 Nov 2020 19:12:46 +0900 Subject: [PATCH 37/58] Add pending_xref_condition node To choose appropriate content for pending_xref node on resolving, this introduces a new custom node `pending_xref_condition`. It only has a condition for the filtering and contents of the reference. --- CHANGES | 2 ++ doc/extdev/nodes.rst | 1 + sphinx/addnodes.py | 48 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/CHANGES b/CHANGES index 4d22cfcaa..ddc433678 100644 --- a/CHANGES +++ b/CHANGES @@ -70,6 +70,8 @@ Features added * #8201: Emit a warning if toctree contains duplicated entries * #8326: ``master_doc`` is now renamed to :confval:`root_doc` * #8942: C++, add support for the C++20 spaceship operator, ``<=>``. +* #7199: A new node, ``sphinx.addnodes.pending_xref_condition`` has been added. + It can be used to choose appropriate content of the reference by conditions. Bugs fixed ---------- diff --git a/doc/extdev/nodes.rst b/doc/extdev/nodes.rst index e38393a78..3976de4c7 100644 --- a/doc/extdev/nodes.rst +++ b/doc/extdev/nodes.rst @@ -37,6 +37,7 @@ New inline nodes .. autoclass:: index .. autoclass:: pending_xref +.. autoclass:: pending_xref_condition .. autoclass:: literal_emphasis .. autoclass:: download_reference diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py index 9bcfaaabf..9ec4898c1 100644 --- a/sphinx/addnodes.py +++ b/sphinx/addnodes.py @@ -338,6 +338,54 @@ class pending_xref(nodes.Inline, nodes.Element): """ +class pending_xref_condition(nodes.Inline, nodes.TextElement): + """Node for cross-references that are used to choose appropriate + content of the reference by conditions on the resolving phase. + + When the :py:class:`pending_xref` node contains one or more + **pending_xref_condition** nodes, the cross-reference resolver + should choose the content of the reference using defined conditions + in ``condition`` attribute of each pending_xref_condition nodes:: + + <pending_xref refdomain="py" reftarget="io.StringIO ...> + <pending_xref_condition condition="resolved"> + <literal> + StringIO + <pending_xref_condition condition="*"> + <literal> + io.StringIO + + After the processing of cross-reference resolver, one of the content node + under pending_xref_condition node is chosen by its condition and to be + removed all of pending_xref_condition nodes:: + + # When resolved the cross-reference successfully + <reference> + <literal> + StringIO + + # When resolution is failed + <reference> + <literal> + io.StringIO + + .. note:: This node is only allowed to be placed under pending_xref node. + It is not allows to place it under other nodes. In addition, + pending_xref node must contain only pending_xref_condition + nodes if it contains one or more pending_xref_condition nodes. + + The pending_xref_condition node should have **condition** attribute. + Domains can be store their individual conditions into the attribute to + filter contents on resolving phase. As a reserved condition name, + ``condition="*"`` is used for the fallback of resolution failure. + Additionally, as a recommended condition name, ``condition="resolved"`` + is used for the representation of resolstion success in the intersphinx + module. + + .. versionadded:: 4.0 + """ + + class number_reference(nodes.reference): """Node for number references, similar to pending_xref.""" From d99132680d277db8409d17195948ee1d422dca66 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Sun, 15 Nov 2020 19:44:39 +0900 Subject: [PATCH 38/58] Filter pending_xref_condition node on failed resolution --- sphinx/transforms/post_transforms/__init__.py | 23 ++++++++++++++++--- sphinx/util/nodes.py | 10 ++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/sphinx/transforms/post_transforms/__init__.py b/sphinx/transforms/post_transforms/__init__.py index f8b01e81f..299b681ef 100644 --- a/sphinx/transforms/post_transforms/__init__.py +++ b/sphinx/transforms/post_transforms/__init__.py @@ -22,10 +22,14 @@ from sphinx.locale import __ from sphinx.transforms import SphinxTransform from sphinx.util import logging from sphinx.util.docutils import SphinxTranslator -from sphinx.util.nodes import process_only_nodes +from sphinx.util.nodes import find_pending_xref_condition, process_only_nodes logger = logging.getLogger(__name__) +if False: + # For type annotation + from docutils.nodes import Node + class SphinxPostTransform(SphinxTransform): """A base class of post-transforms. @@ -97,8 +101,21 @@ class ReferencesResolver(SphinxPostTransform): if newnode is None: self.warn_missing_reference(refdoc, typ, target, node, domain) except NoUri: - newnode = contnode - node.replace_self(newnode or contnode) + newnode = None + + if newnode: + newnodes = [newnode] # type: List[Node] + else: + newnodes = [contnode] + if newnode is None and isinstance(node[0], addnodes.pending_xref_condition): + matched = find_pending_xref_condition(node, "*") + if matched: + newnodes = matched.children + else: + logger.warning(__('Could not determine the fallback text for the ' + 'cross-reference. Might be a bug.'), location=node) + + node.replace_self(newnodes) def resolve_anyref(self, refdoc: str, node: pending_xref, contnode: Element) -> Element: """Resolve reference generated by the "any" role.""" diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index 944fd3ecb..22cc9d4c0 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -531,6 +531,16 @@ def make_id(env: "BuildEnvironment", document: nodes.document, return node_id +def find_pending_xref_condition(node: addnodes.pending_xref, condition: str) -> Element: + """Pick matched pending_xref_condition node up from the pending_xref.""" + for subnode in node: + if (isinstance(subnode, addnodes.pending_xref_condition) and + subnode.get('condition') == condition): + return subnode + else: + return None + + def make_refnode(builder: "Builder", fromdocname: str, todocname: str, targetid: str, child: Node, title: str = None) -> nodes.reference: """Shortcut to create a reference node.""" From 930a880294dc935a7320d64ecbe219c18e005536 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Sun, 15 Nov 2020 23:15:01 +0900 Subject: [PATCH 39/58] Fix #7199: py domain: Add a new confval: python_use_unqualified_type_names Add a new config variable: python_use_unqualified_type_names. If enabled, it goes to suppress the module name of the python reference if it can be resolved. --- CHANGES | 2 + doc/usage/configuration.rst | 11 ++++++ sphinx/domains/python.py | 37 ++++++++++++++++--- sphinx/util/nodes.py | 6 +-- .../conf.py | 1 + .../index.rst | 8 ++++ tests/test_domain_py.py | 19 ++++++++++ 7 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 tests/roots/test-domain-py-python_use_unqualified_type_names/conf.py create mode 100644 tests/roots/test-domain-py-python_use_unqualified_type_names/index.rst diff --git a/CHANGES b/CHANGES index ddc433678..3dc6bfc9d 100644 --- a/CHANGES +++ b/CHANGES @@ -60,6 +60,8 @@ Features added * #8924: autodoc: Support ``bound`` argument for TypeVar * #4826: py domain: Add ``:canonical:`` option to python directives to describe the location where the object is defined +* #7199: py domain: Add :confval:`python_use_unqualified_type_names` to suppress + the module name of the python reference if it can be resolved (experimental) * #7784: i18n: The alt text for image is translated by default (without :confval:`gettext_additional_targets` setting) * #2018: html: :confval:`html_favicon` and :confval:`html_logo` now accept URL diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index 23db18fed..989ce8737 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -2714,6 +2714,17 @@ Options for the C++ domain .. versionadded:: 1.5 +Options for the Python domain +----------------------------- + +.. confval:: python_use_unqualified_type_names + + If true, suppress the module name of the python reference if it can be + resolved. The default is ``False``. + + .. versionadded:: 4.0 + + .. note:: This configuration is still in experimental Example of configuration file ============================= diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 5e430a1d7..40a67f82c 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -22,7 +22,7 @@ from docutils.nodes import Element, Node from docutils.parsers.rst import directives from sphinx import addnodes -from sphinx.addnodes import desc_signature, pending_xref +from sphinx.addnodes import desc_signature, pending_xref, pending_xref_condition from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.deprecation import RemovedInSphinx50Warning @@ -37,7 +37,7 @@ from sphinx.util import logging from sphinx.util.docfields import Field, GroupedField, TypedField from sphinx.util.docutils import SphinxDirective from sphinx.util.inspect import signature_from_str -from sphinx.util.nodes import make_id, make_refnode +from sphinx.util.nodes import find_pending_xref_condition, make_id, make_refnode from sphinx.util.typing import TextlikeNode logger = logging.getLogger(__name__) @@ -92,7 +92,17 @@ def type_to_xref(text: str, env: BuildEnvironment = None) -> addnodes.pending_xr else: kwargs = {} - return pending_xref('', nodes.Text(text), + if env.config.python_use_unqualified_type_names: + # Note: It would be better to use qualname to describe the object to support support + # nested classes. But python domain can't access the real python object because this + # module should work not-dynamically. + shortname = text.split('.')[-1] + contnodes = [pending_xref_condition('', shortname, condition='resolved'), + pending_xref_condition('', text, condition='*')] # type: List[Node] + else: + contnodes = [nodes.Text(text)] + + return pending_xref('', *contnodes, refdomain='py', reftype=reftype, reftarget=text, **kwargs) @@ -1209,7 +1219,15 @@ class PythonDomain(Domain): if obj[2] == 'module': return self._make_module_refnode(builder, fromdocname, name, contnode) else: - return make_refnode(builder, fromdocname, obj[0], obj[1], contnode, name) + # determine the content of the reference by conditions + content = find_pending_xref_condition(node, 'resolved') + if content: + children = content.children + else: + # if not found, use contnode + children = [contnode] + + return make_refnode(builder, fromdocname, obj[0], obj[1], children, name) def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, target: str, node: pending_xref, contnode: Element @@ -1226,9 +1244,17 @@ class PythonDomain(Domain): self._make_module_refnode(builder, fromdocname, name, contnode))) else: + # determine the content of the reference by conditions + content = find_pending_xref_condition(node, 'resolved') + if content: + children = content.children + else: + # if not found, use contnode + children = [contnode] + results.append(('py:' + self.role_for_objtype(obj[2]), make_refnode(builder, fromdocname, obj[0], obj[1], - contnode, name))) + children, name))) return results def _make_module_refnode(self, builder: Builder, fromdocname: str, name: str, @@ -1295,6 +1321,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.setup_extension('sphinx.directives') app.add_domain(PythonDomain) + app.add_config_value('python_use_unqualified_type_names', False, 'env') app.connect('object-description-transform', filter_meta_fields) app.connect('missing-reference', builtin_resolver, priority=900) diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index 22cc9d4c0..c7619a836 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -10,7 +10,7 @@ import re import unicodedata -from typing import TYPE_CHECKING, Any, Callable, Iterable, List, Set, Tuple, Type, cast +from typing import TYPE_CHECKING, Any, Callable, Iterable, List, Set, Tuple, Type, Union, cast from docutils import nodes from docutils.nodes import Element, Node @@ -542,7 +542,7 @@ def find_pending_xref_condition(node: addnodes.pending_xref, condition: str) -> def make_refnode(builder: "Builder", fromdocname: str, todocname: str, targetid: str, - child: Node, title: str = None) -> nodes.reference: + child: Union[Node, List[Node]], title: str = None) -> nodes.reference: """Shortcut to create a reference node.""" node = nodes.reference('', '', internal=True) if fromdocname == todocname and targetid: @@ -555,7 +555,7 @@ def make_refnode(builder: "Builder", fromdocname: str, todocname: str, targetid: node['refuri'] = builder.get_relative_uri(fromdocname, todocname) if title: node['reftitle'] = title - node.append(child) + node += child return node diff --git a/tests/roots/test-domain-py-python_use_unqualified_type_names/conf.py b/tests/roots/test-domain-py-python_use_unqualified_type_names/conf.py new file mode 100644 index 000000000..c81bfe4c7 --- /dev/null +++ b/tests/roots/test-domain-py-python_use_unqualified_type_names/conf.py @@ -0,0 +1 @@ +python_use_unqualified_type_names = True diff --git a/tests/roots/test-domain-py-python_use_unqualified_type_names/index.rst b/tests/roots/test-domain-py-python_use_unqualified_type_names/index.rst new file mode 100644 index 000000000..599206d8c --- /dev/null +++ b/tests/roots/test-domain-py-python_use_unqualified_type_names/index.rst @@ -0,0 +1,8 @@ +domain-py-smart_reference +========================= + +.. py:class:: Name + :module: foo + + +.. py:function:: hello(name: foo.Name, age: foo.Age) diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index 448c2c954..03e865e84 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -999,6 +999,25 @@ def test_noindexentry(app): assert_node(doctree[2], addnodes.index, entries=[]) +@pytest.mark.sphinx('html', testroot='domain-py-python_use_unqualified_type_names') +def test_python_python_use_unqualified_type_names(app, status, warning): + app.build() + content = (app.outdir / 'index.html').read_text() + assert ('<span class="n"><a class="reference internal" href="#foo.Name" title="foo.Name">' + '<span class="pre">Name</span></a></span>' in content) + assert '<span class="n"><span class="pre">foo.Age</span></span>' in content + + +@pytest.mark.sphinx('html', testroot='domain-py-python_use_unqualified_type_names', + confoverrides={'python_use_unqualified_type_names': False}) +def test_python_python_use_unqualified_type_names_disabled(app, status, warning): + app.build() + content = (app.outdir / 'index.html').read_text() + assert ('<span class="n"><a class="reference internal" href="#foo.Name" title="foo.Name">' + '<span class="pre">foo.Name</span></a></span>' in content) + assert '<span class="n"><span class="pre">foo.Age</span></span>' in content + + @pytest.mark.sphinx('dummy', testroot='domain-py-xref-warning') def test_warn_missing_reference(app, status, warning): app.build() From 7f0b13af6e023382f8bfdaa0916c614a586ff05c Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Sat, 6 Mar 2021 14:43:13 +0900 Subject: [PATCH 40/58] intersphinx: Support pending_xref_condition --- sphinx/ext/intersphinx.py | 23 ++++++++++++++++++----- tests/test_ext_intersphinx.py | 8 ++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index 5569ad9de..a01bcc37a 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -33,10 +33,11 @@ from typing import IO, Any, Dict, List, Tuple from urllib.parse import urlsplit, urlunsplit from docutils import nodes -from docutils.nodes import Element, TextElement +from docutils.nodes import TextElement from docutils.utils import relative_path import sphinx +from sphinx.addnodes import pending_xref from sphinx.application import Sphinx from sphinx.builders.html import INVENTORY_FILENAME from sphinx.config import Config @@ -44,6 +45,7 @@ from sphinx.environment import BuildEnvironment from sphinx.locale import _, __ from sphinx.util import logging, requests from sphinx.util.inventory import InventoryFile +from sphinx.util.nodes import find_pending_xref_condition from sphinx.util.typing import Inventory logger = logging.getLogger(__name__) @@ -257,8 +259,8 @@ def load_mappings(app: Sphinx) -> None: inventories.main_inventory.setdefault(type, {}).update(objects) -def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: TextElement - ) -> nodes.reference: +def missing_reference(app: Sphinx, env: BuildEnvironment, node: pending_xref, + contnode: TextElement) -> nodes.reference: """Attempt to resolve a missing reference via intersphinx references.""" target = node['reftarget'] inventories = InventoryAdapter(env) @@ -284,6 +286,17 @@ def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnod if 'py:attribute' in objtypes: # Since Sphinx-2.1, properties are stored as py:method objtypes.append('py:method') + + # determine the contnode by pending_xref_condition + content = find_pending_xref_condition(node, 'resolved') + if content: + # resolved condition found. + contnodes = content.children + contnode = content.children[0] # type: ignore + else: + # not resolved. Use the given contnode + contnodes = [contnode] + to_try = [(inventories.main_inventory, target)] if domain: full_qualified_name = env.get_domain(domain).get_full_qualified_name(node) @@ -316,7 +329,7 @@ def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnod newnode = nodes.reference('', '', internal=False, refuri=uri, reftitle=reftitle) if node.get('refexplicit'): # use whatever title was given - newnode.append(contnode) + newnode.extend(contnodes) elif dispname == '-' or \ (domain == 'std' and node['reftype'] == 'keyword'): # use whatever title was given, but strip prefix @@ -325,7 +338,7 @@ def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnod newnode.append(contnode.__class__(title[len(in_set) + 1:], title[len(in_set) + 1:])) else: - newnode.append(contnode) + newnode.extend(contnodes) else: # else use the given display name (used for :ref:) newnode.append(contnode.__class__(dispname, dispname)) diff --git a/tests/test_ext_intersphinx.py b/tests/test_ext_intersphinx.py index a87677525..523ed2acc 100644 --- a/tests/test_ext_intersphinx.py +++ b/tests/test_ext_intersphinx.py @@ -196,6 +196,14 @@ def test_missing_reference_pydomain(tempdir, app, status, warning): rn = missing_reference(app, app.env, node, contnode) assert rn.astext() == 'Foo.bar' + # pending_xref_condition="resolved" + node = addnodes.pending_xref('', reftarget='Foo.bar', refdomain='py', reftype='attr') + node['py:module'] = 'module1' + node += addnodes.pending_xref_condition('', 'Foo.bar', condition='resolved') + node += addnodes.pending_xref_condition('', 'module1.Foo.bar', condition='*') + rn = missing_reference(app, app.env, node, nodes.Text('dummy-cont-node')) + assert rn.astext() == 'Foo.bar' + def test_missing_reference_stddomain(tempdir, app, status, warning): inv_file = tempdir / 'inventory' From 1ea11b1e489b50132b219ec2ee0fe570e49af638 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Thu, 28 Jan 2021 01:57:46 +0900 Subject: [PATCH 41/58] Fix #759: autodoc: Add sphinx.ext.autodoc.preserve_defaults extension Add a new extension `sphinx.ext.autodoc.preserve_defaults`. It preserves the default argument values of function signatures in source code and keep them not evaluated for readability. This is an experimental extension and it will be integrated into autodoc core in Sphinx-4.0. --- CHANGES | 3 + doc/usage/extensions/autodoc.rst | 10 +++ sphinx/ext/autodoc/__init__.py | 1 + sphinx/ext/autodoc/preserve_defaults.py | 88 +++++++++++++++++++ .../target/preserve_defaults.py | 19 ++++ tests/test_ext_autodoc_preserve_defaults.py | 45 ++++++++++ 6 files changed, 166 insertions(+) create mode 100644 sphinx/ext/autodoc/preserve_defaults.py create mode 100644 tests/roots/test-ext-autodoc/target/preserve_defaults.py create mode 100644 tests/test_ext_autodoc_preserve_defaults.py diff --git a/CHANGES b/CHANGES index a2ef4f73e..4198e57bb 100644 --- a/CHANGES +++ b/CHANGES @@ -192,6 +192,9 @@ Features added * #8775: autodoc: Support type union operator (PEP-604) in Python 3.10 or above * #8297: autodoc: Allow to extend :confval:`autodoc_default_options` via directive options +* #759: autodoc: Add a new configuration :confval:`autodoc_preserve_defaults` as + an experimental feature. It preserves the default argument values of + functions in source code and keep them not evaluated for readability. * #8619: html: kbd role generates customizable HTML tags for compound keys * #8634: html: Allow to change the order of JS/CSS via ``priority`` parameter for :meth:`Sphinx.add_js_file()` and :meth:`Sphinx.add_css_file()` diff --git a/doc/usage/extensions/autodoc.rst b/doc/usage/extensions/autodoc.rst index f69ac8c5c..ad58a1eb2 100644 --- a/doc/usage/extensions/autodoc.rst +++ b/doc/usage/extensions/autodoc.rst @@ -586,6 +586,16 @@ There are also config values that you can set: .. __: https://mypy.readthedocs.io/en/latest/kinds_of_types.html#type-aliases .. versionadded:: 3.3 +.. confval:: autodoc_preserve_defaults + + If True, the default argument values of functions will be not evaluated on + generating document. It preserves them as is in the source code. + + .. versionadded:: 4.0 + + Added as an experimental feature. This will be integrated into autodoc core + in the future. + .. confval:: autodoc_warningiserror This value controls the behavior of :option:`sphinx-build -W` during diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 8d6781a1a..0b5709301 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -2634,6 +2634,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.connect('config-inited', migrate_autodoc_member_order, priority=800) + app.setup_extension('sphinx.ext.autodoc.preserve_defaults') app.setup_extension('sphinx.ext.autodoc.type_comment') app.setup_extension('sphinx.ext.autodoc.typehints') diff --git a/sphinx/ext/autodoc/preserve_defaults.py b/sphinx/ext/autodoc/preserve_defaults.py new file mode 100644 index 000000000..3d859fe8e --- /dev/null +++ b/sphinx/ext/autodoc/preserve_defaults.py @@ -0,0 +1,88 @@ +""" + sphinx.ext.autodoc.preserve_defaults + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Preserve the default argument values of function signatures in source code + and keep them not evaluated for readability. + + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import ast +import inspect +from typing import Any, Dict + +from sphinx.application import Sphinx +from sphinx.locale import __ +from sphinx.pycode.ast import parse as ast_parse +from sphinx.pycode.ast import unparse as ast_unparse +from sphinx.util import logging + +logger = logging.getLogger(__name__) + + +class DefaultValue: + def __init__(self, name: str) -> None: + self.name = name + + def __repr__(self) -> str: + return self.name + + +def get_function_def(obj: Any) -> ast.FunctionDef: + """Get FunctionDef object from living object. + This tries to parse original code for living object and returns + AST node for given *obj*. + """ + try: + source = inspect.getsource(obj) + if source.startswith((' ', r'\t')): + # subject is placed inside class or block. To read its docstring, + # this adds if-block before the declaration. + module = ast_parse('if True:\n' + source) + return module.body[0].body[0] # type: ignore + else: + module = ast_parse(source) + return module.body[0] # type: ignore + except (OSError, TypeError): # failed to load source code + return None + + +def update_defvalue(app: Sphinx, obj: Any, bound_method: bool) -> None: + """Update defvalue info of *obj* using type_comments.""" + if not app.config.autodoc_preserve_defaults: + return + + try: + function = get_function_def(obj) + if function.args.defaults or function.args.kw_defaults: + sig = inspect.signature(obj) + defaults = list(function.args.defaults) + kw_defaults = list(function.args.kw_defaults) + parameters = list(sig.parameters.values()) + for i, param in enumerate(parameters): + if param.default is not param.empty: + if param.kind in (param.POSITIONAL_ONLY, param.POSITIONAL_OR_KEYWORD): + value = DefaultValue(ast_unparse(defaults.pop(0))) # type: ignore + parameters[i] = param.replace(default=value) + else: + value = DefaultValue(ast_unparse(kw_defaults.pop(0))) # type: ignore + parameters[i] = param.replace(default=value) + sig = sig.replace(parameters=parameters) + obj.__signature__ = sig + except (AttributeError, TypeError): + # failed to update signature (ex. built-in or extension types) + pass + except NotImplementedError as exc: # failed to ast.unparse() + logger.warning(__("Failed to parse a default argument value for %r: %s"), obj, exc) + + +def setup(app: Sphinx) -> Dict[str, Any]: + app.add_config_value('autodoc_preserve_defaults', False, True) + app.connect('autodoc-before-process-signature', update_defvalue) + + return { + 'version': '1.0', + 'parallel_read_safe': True + } diff --git a/tests/roots/test-ext-autodoc/target/preserve_defaults.py b/tests/roots/test-ext-autodoc/target/preserve_defaults.py new file mode 100644 index 000000000..c927fa035 --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/preserve_defaults.py @@ -0,0 +1,19 @@ +from datetime import datetime +from typing import Any + +CONSTANT = 'foo' +SENTINEL = object() + + +def foo(name: str = CONSTANT, + sentinal: Any = SENTINEL, + now: datetime = datetime.now()) -> None: + """docstring""" + + +class Class: + """docstring""" + + def meth(self, name: str = CONSTANT, sentinal: Any = SENTINEL, + now: datetime = datetime.now()) -> None: + """docstring""" diff --git a/tests/test_ext_autodoc_preserve_defaults.py b/tests/test_ext_autodoc_preserve_defaults.py new file mode 100644 index 000000000..f9833c291 --- /dev/null +++ b/tests/test_ext_autodoc_preserve_defaults.py @@ -0,0 +1,45 @@ +""" + test_ext_autodoc_preserve_defaults + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Test the autodoc extension. + + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import pytest + +from .test_ext_autodoc import do_autodoc + + +@pytest.mark.sphinx('html', testroot='ext-autodoc', + confoverrides={'autodoc_preserve_defaults': True}) +def test_preserve_defaults(app): + options = {"members": None} + actual = do_autodoc(app, 'module', 'target.preserve_defaults', options) + assert list(actual) == [ + '', + '.. py:module:: target.preserve_defaults', + '', + '', + '.. py:class:: Class()', + ' :module: target.preserve_defaults', + '', + ' docstring', + '', + '', + ' .. py:method:: Class.meth(name: str = CONSTANT, sentinal: Any = SENTINEL, ' + 'now: datetime.datetime = datetime.now()) -> None', + ' :module: target.preserve_defaults', + '', + ' docstring', + '', + '', + '.. py:function:: foo(name: str = CONSTANT, sentinal: Any = SENTINEL, now: ' + 'datetime.datetime = datetime.now()) -> None', + ' :module: target.preserve_defaults', + '', + ' docstring', + '', + ] From f37e679fd134feb9deeeb1e9d57536faa8156958 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Sat, 6 Mar 2021 18:57:18 +0900 Subject: [PATCH 42/58] doc: Update document for autodoc :members: option --- doc/usage/extensions/autodoc.rst | 34 +++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/doc/usage/extensions/autodoc.rst b/doc/usage/extensions/autodoc.rst index f69ac8c5c..b162683b7 100644 --- a/doc/usage/extensions/autodoc.rst +++ b/doc/usage/extensions/autodoc.rst @@ -89,33 +89,43 @@ inserting them into the page source under a suitable :rst:dir:`py:module`, Boil the noodle *time* minutes. - **Options and advanced usage** + .. rubric:: Options - * If you want to automatically document members, there's a ``members`` - option:: + .. rst:directive:option:: members + :type: no value or comma separated list + + If set, autodoc will generate document for the members of the target + module, class or exception. + + For example:: .. automodule:: noodle :members: - will document all module members (recursively), and :: + will document all module members (recursively), and :: .. autoclass:: Noodle :members: - will document all non-private member functions and properties (that is, - those whose name doesn't start with ``_``). + will document all class member methods and properties. - For modules, ``__all__`` will be respected when looking for members unless - you give the ``ignore-module-all`` flag option. Without - ``ignore-module-all``, the order of the members will also be the order in - ``__all__``. + By default, autodoc will not generate document for the members that are + private, not having docstrings, inherited from super class, or special + members. - You can also give an explicit list of members; only these will then be - documented:: + For modules, ``__all__`` will be respected when looking for members unless + you give the ``ignore-module-all`` flag option. Without + ``ignore-module-all``, the order of the members will also be the order in + ``__all__``. + + You can also give an explicit list of members; only these will then be + documented:: .. autoclass:: Noodle :members: eat, slurp + **Options and advanced usage** + * If you want to make the ``members`` option (or other options described below) the default, see :confval:`autodoc_default_options`. From 009fd4c396e949c7ff2568dc02855763df3a078a Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Sat, 6 Mar 2021 19:08:23 +0900 Subject: [PATCH 43/58] doc: Update document for autodoc :undoc-members: option --- doc/usage/extensions/autodoc.rst | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/doc/usage/extensions/autodoc.rst b/doc/usage/extensions/autodoc.rst index b162683b7..c7a553c12 100644 --- a/doc/usage/extensions/autodoc.rst +++ b/doc/usage/extensions/autodoc.rst @@ -124,6 +124,16 @@ inserting them into the page source under a suitable :rst:dir:`py:module`, .. autoclass:: Noodle :members: eat, slurp + .. rst:directive:option:: undoc-members + :type: no value + + If set, autodoc will also generate document for the members not having + docstrings:: + + .. automodule:: noodle + :members: + :undoc-members: + **Options and advanced usage** * If you want to make the ``members`` option (or other options described @@ -149,13 +159,6 @@ inserting them into the page source under a suitable :rst:dir:`py:module`, .. versionchanged:: 3.5 The default options can be overridden or extended temporarily. - * Members without docstrings will be left out, unless you give the - ``undoc-members`` flag option:: - - .. automodule:: noodle - :members: - :undoc-members: - * "Private" members (that is, those named like ``_private`` or ``__private``) will be included if the ``private-members`` flag option is given:: From 8411b9a536ec0006d8f0faae05947ad78bf4d2a9 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Sat, 6 Mar 2021 19:10:16 +0900 Subject: [PATCH 44/58] doc: Update document for autodoc :private-members: option --- doc/usage/extensions/autodoc.rst | 39 +++++++++++++++++--------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/doc/usage/extensions/autodoc.rst b/doc/usage/extensions/autodoc.rst index c7a553c12..0712b1ca0 100644 --- a/doc/usage/extensions/autodoc.rst +++ b/doc/usage/extensions/autodoc.rst @@ -134,6 +134,27 @@ inserting them into the page source under a suitable :rst:dir:`py:module`, :members: :undoc-members: + .. rst:directive:option:: private-members + :type: no value or comma separated list + + If set, autodoc will also generate document for the private members + (that is, those named like ``_private`` or ``__private``):: + + .. 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. + **Options and advanced usage** * If you want to make the ``members`` option (or other options described @@ -159,24 +180,6 @@ inserting them into the page source under a suitable :rst:dir:`py:module`, .. versionchanged:: 3.5 The default options can be overridden or extended temporarily. - * "Private" members (that is, those named like ``_private`` or ``__private``) - 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`. For example: From 697fdea61287eb5c5faf3608d5c72f417a91816c Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Sat, 6 Mar 2021 19:12:10 +0900 Subject: [PATCH 45/58] doc: Update document for autodoc :special-members: option --- doc/usage/extensions/autodoc.rst | 37 +++++++++++++++++++------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/doc/usage/extensions/autodoc.rst b/doc/usage/extensions/autodoc.rst index 0712b1ca0..59022cc80 100644 --- a/doc/usage/extensions/autodoc.rst +++ b/doc/usage/extensions/autodoc.rst @@ -155,6 +155,28 @@ inserting them into the page source under a suitable :rst:dir:`py:module`, .. versionchanged:: 3.2 The option can now take arguments. + .. rst:directive:option:: special-members + :type: no value or comma separated list + + If set, autodoc will also generate document for the special members + (that is, those named like ``__special__``):: + + .. autoclass:: my.Class + :members: + :special-members: + + It can also take an explicit list of member names to be documented as + arguments:: + + .. autoclass:: my.Class + :members: + :special-members: __init__, __name__ + + .. versionadded:: 1.1 + + .. versionchanged:: 1.2 + The option can now take arguments + **Options and advanced usage** * If you want to make the ``members`` option (or other options described @@ -219,21 +241,6 @@ inserting them into the page source under a suitable :rst:dir:`py:module`, .. versionadded:: 3.5 - * Python "special" members (that is, those named like ``__special__``) will - be included if the ``special-members`` flag option is given:: - - .. autoclass:: my.Class - :members: - :private-members: - :special-members: - - would document both "private" and "special" members of the class. - - .. versionadded:: 1.1 - - .. versionchanged:: 1.2 - The option can now take arguments, i.e. the special members to document. - * For classes and exceptions, members inherited from base classes will be left out when documenting all members, unless you give the ``inherited-members`` option, in addition to ``members``:: From a969e90f28e0ade11b4c38d9a6e6f3264d1bf971 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Sat, 6 Mar 2021 20:59:53 +0900 Subject: [PATCH 46/58] Bump to 3.5.2 final --- CHANGES | 19 ++----------------- sphinx/__init__.py | 4 ++-- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/CHANGES b/CHANGES index e45123154..9381744b8 100644 --- a/CHANGES +++ b/CHANGES @@ -1,17 +1,5 @@ -Release 3.5.2 (in development) -============================== - -Dependencies ------------- - -Incompatible changes --------------------- - -Deprecated ----------- - -Features added --------------- +Release 3.5.2 (released Mar 06, 2021) +===================================== Bugs fixed ---------- @@ -20,9 +8,6 @@ Bugs fixed * #8936: LaTeX: A custom LaTeX builder fails with unknown node error * #8952: Exceptions raised in a Directive cause parallel builds to hang -Testing --------- - Release 3.5.1 (released Feb 16, 2021) ===================================== diff --git a/sphinx/__init__.py b/sphinx/__init__.py index a5cf22064..d7e50097e 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -32,7 +32,7 @@ if 'PYTHONWARNINGS' not in os.environ: warnings.filterwarnings('ignore', "'U' mode is deprecated", DeprecationWarning, module='docutils.io') -__version__ = '3.5.2+' +__version__ = '3.5.2' __released__ = '3.5.2' # used when Sphinx builds its own docs #: Version info for better programmatic use. @@ -43,7 +43,7 @@ __released__ = '3.5.2' # used when Sphinx builds its own docs #: #: .. versionadded:: 1.2 #: Before version 1.2, check the string ``sphinx.__version__``. -version_info = (3, 5, 2, 'beta', 0) +version_info = (3, 5, 2, 'final', 0) package_dir = path.abspath(path.dirname(__file__)) From 4f8cb861e3b29186b38248fe81e4944fd987fcce Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Sat, 6 Mar 2021 21:03:43 +0900 Subject: [PATCH 47/58] Bump version --- CHANGES | 21 +++++++++++++++++++++ sphinx/__init__.py | 6 +++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 9381744b8..8fa86a98b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,24 @@ +Release 3.5.3 (in development) +============================== + +Dependencies +------------ + +Incompatible changes +-------------------- + +Deprecated +---------- + +Features added +-------------- + +Bugs fixed +---------- + +Testing +-------- + Release 3.5.2 (released Mar 06, 2021) ===================================== diff --git a/sphinx/__init__.py b/sphinx/__init__.py index d7e50097e..7f72a3d02 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -32,8 +32,8 @@ if 'PYTHONWARNINGS' not in os.environ: warnings.filterwarnings('ignore', "'U' mode is deprecated", DeprecationWarning, module='docutils.io') -__version__ = '3.5.2' -__released__ = '3.5.2' # used when Sphinx builds its own docs +__version__ = '3.5.3+' +__released__ = '3.5.3' # used when Sphinx builds its own docs #: Version info for better programmatic use. #: @@ -43,7 +43,7 @@ __released__ = '3.5.2' # used when Sphinx builds its own docs #: #: .. versionadded:: 1.2 #: Before version 1.2, check the string ``sphinx.__version__``. -version_info = (3, 5, 2, 'final', 0) +version_info = (3, 5, 3, 'beta', 0) package_dir = path.abspath(path.dirname(__file__)) From 0dcdbdc230edf6412445c2856df15b7037f333d3 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Sat, 6 Mar 2021 21:19:26 +0900 Subject: [PATCH 48/58] doc: Fix indentation --- doc/usage/extensions/autodoc.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/usage/extensions/autodoc.rst b/doc/usage/extensions/autodoc.rst index 2f9049dc0..034c86e11 100644 --- a/doc/usage/extensions/autodoc.rst +++ b/doc/usage/extensions/autodoc.rst @@ -172,10 +172,10 @@ inserting them into the page source under a suitable :rst:dir:`py:module`, :members: :special-members: __init__, __name__ - .. versionadded:: 1.1 + .. versionadded:: 1.1 - .. versionchanged:: 1.2 - The option can now take arguments + .. versionchanged:: 1.2 + The option can now take arguments **Options and advanced usage** From af98b9e14f8cd67bcf7894befa14fc6892898a70 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Sun, 7 Mar 2021 00:38:39 +0900 Subject: [PATCH 49/58] Fix wrong directive name in warning messages --- sphinx/directives/patches.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/directives/patches.py b/sphinx/directives/patches.py index 5baa0994c..3cfad5a3f 100644 --- a/sphinx/directives/patches.py +++ b/sphinx/directives/patches.py @@ -92,7 +92,7 @@ class CSVTable(tables.CSVTable): Only for docutils-0.13 or older version.""" def run(self) -> List[Node]: - warnings.warn('RSTTable is deprecated.', + warnings.warn('CSVTable is deprecated.', RemovedInSphinx60Warning) return super().run() @@ -110,7 +110,7 @@ class ListTable(tables.ListTable): Only for docutils-0.13 or older version.""" def run(self) -> List[Node]: - warnings.warn('RSTTable is deprecated.', + warnings.warn('ListTable is deprecated.', RemovedInSphinx60Warning) return super().run() From 2638e9aecc87460df82e3dd801d423e5aa779f05 Mon Sep 17 00:00:00 2001 From: Naveen M K <naveen@syrusdark.website> Date: Sun, 7 Mar 2021 00:13:40 +0530 Subject: [PATCH 50/58] Sphinx is available on Chocolatey --- doc/usage/installation.rst | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/doc/usage/installation.rst b/doc/usage/installation.rst index 46ef6a51e..ce26e961d 100644 --- a/doc/usage/installation.rst +++ b/doc/usage/installation.rst @@ -107,7 +107,29 @@ Anaconda Windows ------- -.. todo:: Could we start packaging this? +Sphinx can be install using `Chocolatey`__ or :ref:`installed manually <windows-other-method>`. + +__ https://chocolatey.org/ + +Chocolatey +~~~~~~~~~~ + +:: + + $ choco install sphinx + +You would need to `install Chocolatey +<https://chocolatey.org/install/>`_. +before running this. + +For more information, refer to the `chocolatey page`__. + +__ https://chocolatey.org/packages/sphinx/ + +.. _windows-other-method: + +Other Methods +~~~~~~~~~~~~~ Most Windows users do not have Python installed by default, so we begin with the installation of Python itself. To check if you already have Python From 1b425230e3430d427bd5b9a817f271c24b75ee7c Mon Sep 17 00:00:00 2001 From: Naveen M K <naveen@syrusdark.website> Date: Sun, 7 Mar 2021 00:21:41 +0530 Subject: [PATCH 51/58] lint --- doc/usage/installation.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/usage/installation.rst b/doc/usage/installation.rst index ce26e961d..69fed31ab 100644 --- a/doc/usage/installation.rst +++ b/doc/usage/installation.rst @@ -107,7 +107,8 @@ Anaconda Windows ------- -Sphinx can be install using `Chocolatey`__ or :ref:`installed manually <windows-other-method>`. +Sphinx can be install using `Chocolatey`__ or +:ref:`installed manually <windows-other-method>`. __ https://chocolatey.org/ @@ -119,7 +120,7 @@ Chocolatey $ choco install sphinx You would need to `install Chocolatey -<https://chocolatey.org/install/>`_. +<https://chocolatey.org/install/>`_ before running this. For more information, refer to the `chocolatey page`__. From fb4220d0a2b7fe143118767ff577face6c72bebc Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Sun, 7 Mar 2021 01:59:48 +0900 Subject: [PATCH 52/58] Close #8487: csv-table now considers abspath as relpath from srcdir To make directives' behavior consistent, the :file: option for csv-table directive now recognizes an absolute path as a relative path from source directory. --- CHANGES | 2 + doc/extdev/deprecated.rst | 5 --- sphinx/directives/patches.py | 37 +++++++++++++------ tests/roots/test-directive-csv-table/conf.py | 0 .../test-directive-csv-table/example.csv | 1 + .../subdir/example.csv | 1 + tests/test_directive_patch.py | 32 ++++++++++++++++ 7 files changed, 61 insertions(+), 17 deletions(-) create mode 100644 tests/roots/test-directive-csv-table/conf.py create mode 100644 tests/roots/test-directive-csv-table/example.csv create mode 100644 tests/roots/test-directive-csv-table/subdir/example.csv diff --git a/CHANGES b/CHANGES index c75929ca6..a183eba66 100644 --- a/CHANGES +++ b/CHANGES @@ -40,6 +40,8 @@ Incompatible changes * #8769: LaTeX refactoring: split sphinx.sty into multiple files and rename some auxiliary files created in ``latex`` build output repertory * #8937: Use explicit title instead of <no title> +* #8487: The :file: option for csv-table directive now recognizes an absolute + path as a relative path from source directory Deprecated ---------- diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index 10c1dfb22..96bc84ff3 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -32,11 +32,6 @@ The following is a list of deprecated interfaces. - TBD - ``logo_url`` - * - ``sphinx.directives.patches.CSVTable`` - - 4.0 - - 6.0 - - ``docutils.parsers.rst.diretives.tables.CSVTable`` - * - ``sphinx.directives.patches.ListTable`` - 4.0 - 6.0 diff --git a/sphinx/directives/patches.py b/sphinx/directives/patches.py index 3cfad5a3f..1c3cfd853 100644 --- a/sphinx/directives/patches.py +++ b/sphinx/directives/patches.py @@ -6,7 +6,9 @@ :license: BSD, see LICENSE for details. """ +import os import warnings +from os import path from typing import TYPE_CHECKING, Any, Dict, List, Tuple, cast from docutils import nodes @@ -18,13 +20,19 @@ from sphinx import addnodes from sphinx.deprecation import RemovedInSphinx60Warning from sphinx.directives import optional_int from sphinx.domains.math import MathDomain +from sphinx.locale import __ +from sphinx.util import logging from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import set_source_info +from sphinx.util.osutil import SEP, os_path, relpath if TYPE_CHECKING: from sphinx.application import Sphinx +logger = logging.getLogger(__name__) + + class Figure(images.Figure): """The figure directive which applies `:name:` option to the figure node instead of the image node. @@ -87,22 +95,26 @@ class RSTTable(tables.RSTTable): class CSVTable(tables.CSVTable): - """The csv-table directive which sets source and line information to its caption. - - Only for docutils-0.13 or older version.""" + """The csv-table directive which searches a CSV file from Sphinx project's source + directory when an absolute path is given via :file: option. + """ def run(self) -> List[Node]: - warnings.warn('CSVTable is deprecated.', - RemovedInSphinx60Warning) + if 'file' in self.options and self.options['file'].startswith((SEP, os.sep)): + env = self.state.document.settings.env + filename = self.options['file'] + if path.exists(filename): + logger.warning(__('":file:" option for csv-table directive now recognizes ' + 'an absolute path as a relative path from source directory. ' + 'Please update your document.'), + location=(env.docname, self.lineno)) + else: + abspath = path.join(env.srcdir, os_path(self.options['file'][1:])) + docdir = path.dirname(env.doc2path(env.docname)) + self.options['file'] = relpath(abspath, docdir) + return super().run() - def make_title(self) -> Tuple[nodes.title, List[system_message]]: - title, message = super().make_title() - if title: - set_source_info(self, title) - - return title, message - class ListTable(tables.ListTable): """The list-table directive which sets source and line information to its caption. @@ -224,6 +236,7 @@ class MathDirective(SphinxDirective): def setup(app: "Sphinx") -> Dict[str, Any]: directives.register_directive('figure', Figure) directives.register_directive('meta', Meta) + directives.register_directive('csv-table', CSVTable) directives.register_directive('code', Code) directives.register_directive('math', MathDirective) diff --git a/tests/roots/test-directive-csv-table/conf.py b/tests/roots/test-directive-csv-table/conf.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/roots/test-directive-csv-table/example.csv b/tests/roots/test-directive-csv-table/example.csv new file mode 100644 index 000000000..eb039aa9b --- /dev/null +++ b/tests/roots/test-directive-csv-table/example.csv @@ -0,0 +1 @@ +foo,bar,baz diff --git a/tests/roots/test-directive-csv-table/subdir/example.csv b/tests/roots/test-directive-csv-table/subdir/example.csv new file mode 100644 index 000000000..32fe56fad --- /dev/null +++ b/tests/roots/test-directive-csv-table/subdir/example.csv @@ -0,0 +1 @@ +FOO,BAR,BAZ diff --git a/tests/test_directive_patch.py b/tests/test_directive_patch.py index 7dc568b1d..b28e0f1e5 100644 --- a/tests/test_directive_patch.py +++ b/tests/test_directive_patch.py @@ -8,6 +8,7 @@ :license: BSD, see LICENSE for details. """ +import pytest from docutils import nodes from sphinx.testing import restructuredtext @@ -54,6 +55,37 @@ def test_code_directive(app): assert_node(doctree[0], language="python", linenos=True, highlight_args={'linenostart': 5}) +@pytest.mark.sphinx(testroot='directive-csv-table') +def test_csv_table_directive(app): + # relative path from current document + text = ('.. csv-table::\n' + ' :file: example.csv\n') + doctree = restructuredtext.parse(app, text, docname="subdir/index") + assert_node(doctree, + ([nodes.table, nodes.tgroup, (nodes.colspec, + nodes.colspec, + nodes.colspec, + [nodes.tbody, nodes.row])],)) + assert_node(doctree[0][0][3][0], + ([nodes.entry, nodes.paragraph, "FOO"], + [nodes.entry, nodes.paragraph, "BAR"], + [nodes.entry, nodes.paragraph, "BAZ"])) + + # absolute path from source directory + text = ('.. csv-table::\n' + ' :file: /example.csv\n') + doctree = restructuredtext.parse(app, text, docname="subdir/index") + assert_node(doctree, + ([nodes.table, nodes.tgroup, (nodes.colspec, + nodes.colspec, + nodes.colspec, + [nodes.tbody, nodes.row])],)) + assert_node(doctree[0][0][3][0], + ([nodes.entry, nodes.paragraph, "foo"], + [nodes.entry, nodes.paragraph, "bar"], + [nodes.entry, nodes.paragraph, "baz"])) + + def test_math_directive(app): # normal case text = '.. math:: E = mc^2' From 1ac05a2a832dfecfbf9431e919cb9f3b99ebf11f Mon Sep 17 00:00:00 2001 From: igo95862 <igo95862@yandex.ru> Date: Mon, 8 Mar 2021 12:18:25 +0300 Subject: [PATCH 53/58] doc: Create autodoc extension tutorial --- doc/development/tutorials/autodoc_ext.rst | 140 ++++++++++++++++++ .../tutorials/examples/autodoc_intenum.py | 52 +++++++ 2 files changed, 192 insertions(+) create mode 100644 doc/development/tutorials/autodoc_ext.rst create mode 100644 doc/development/tutorials/examples/autodoc_intenum.py diff --git a/doc/development/tutorials/autodoc_ext.rst b/doc/development/tutorials/autodoc_ext.rst new file mode 100644 index 000000000..eca0e5120 --- /dev/null +++ b/doc/development/tutorials/autodoc_ext.rst @@ -0,0 +1,140 @@ +Developing autodoc extension for IntEnum +======================================== + +The objective of this tutorial is to create an extension that adds +support for new type for autodoc. This autodoc extension will format +the ``IntEnum`` class from Python standard library. (module ``enum``) + +Overview +-------- + +We want the extension that will create auto-documentation for IntEnum. +``IntEnum`` is the integer enum class from standard library ``enum`` module. + +Currently this class has no special auto documentation behavior. + +We want to add following to autodoc: + +* A new ``autointenum`` directive that will document the ``IntEnum`` class. +* The generated documentation will have all the enum possible values + with names. +* The ``autointenum`` directive will have an option ``:hex:`` which will + cause the integers be printed in hexadecimal form. + + +Prerequisites +------------- + +We need the same setup as in :doc:`the previous extensions <todo>`. This time, +we will be putting out extension in a file called :file:`autodoc_intenum.py`. +The :file:`my_enums.py` will contain the sample enums we will document. + +Here is an example of the folder structure you might obtain: + +.. code-block:: text + + └── source +    ├── _ext + │   └── autodoc_intenum.py +    ├── conf.py +    ├── index.rst +    └── my_enums.py + + +Writing the extension +--------------------- + +Start with ``setup`` function for the extension. + +.. literalinclude:: examples/autodoc_intenum.py + :language: python + :linenos: + :pyobject: setup + + +The :meth:`~Sphinx.setup_extension` method will pull the autodoc extension +because our new extension depends on autodoc. :meth:`~Sphinx.add_autodocumenter` +is the method that registers our new auto documenter class. + +We want to import certain objects from the autodoc extension: + +.. literalinclude:: examples/autodoc_intenum.py + :language: python + :linenos: + :lines: 1-7 + + +There are several different documenter classes such as ``MethodDocumenter`` +or ``AttributeDocumenter`` available in the autodoc extension but +our new class is the subclass of ``ClassDocumenter`` which a +documenter class used by autodoc to document classes. + +This is the definition of our new the auto-documenter class: + +.. literalinclude:: examples/autodoc_intenum.py + :language: python + :linenos: + :pyobject: IntEnumDocumenter + + +Important attributes of the new class: + +**objtype** + This attribute determines the ``auto`` directive name. In + this case the auto directive will be ``autointenum``. + +**directivetype** + This attribute sets the generated directive name. In + this example the generated directive will be ``.. :py:class::``. + +**priority** + the larger the number the higher is the priority. We want our + documenter be higher priority than the parent. + +**option_spec** + option specifications. We copy the parent class options and + add a new option *hex*. + + +Overridden members: + +**can_document_member** + This member is important to override. It should + return *True* when the passed object can be documented by this class. + +**add_directive_header** + This method generates the directive header. We add + **:final:** directive option. Remember to call **super** or no directive + will be generated. + +**add_content** + This method generates the body of the class documentation. + After calling the super method we generate lines for enum description. + + +Using the extension +------------------- + +You can now use the new autodoc directive to document any ``IntEnum``. + +For example, you have the following ``IntEnum``: + +.. code-block:: python + :caption: my_enums.py + + class Colors(IntEnum): + """Colors enumerator""" + NONE = 0 + RED = 1 + GREEN = 2 + BLUE = 3 + + +This will be the documentation file with auto-documentation directive: + +.. code-block:: rst + :caption: index.rst + + .. autointenum:: my_enums.Colors + + diff --git a/doc/development/tutorials/examples/autodoc_intenum.py b/doc/development/tutorials/examples/autodoc_intenum.py new file mode 100644 index 000000000..7fb85d066 --- /dev/null +++ b/doc/development/tutorials/examples/autodoc_intenum.py @@ -0,0 +1,52 @@ +from enum import IntEnum +from typing import Any, Optional + +from docutils.statemachine import StringList + +from sphinx.application import Sphinx +from sphinx.ext.autodoc import ClassDocumenter, bool_option + + +class IntEnumDocumenter(ClassDocumenter): + objtype = 'intenum' + directivetype = 'class' + priority = 10 + ClassDocumenter.priority + option_spec = dict(ClassDocumenter.option_spec) + option_spec['hex'] = bool_option + + @classmethod + def can_document_member(cls, + member: Any, membername: str, + isattr: bool, parent: Any) -> bool: + return isinstance(member, IntEnum) + + def add_directive_header(self, sig: str) -> None: + super().add_directive_header(sig) + self.add_line(' :final:', self.get_sourcename()) + + def add_content(self, + more_content: Optional[StringList], + no_docstring: bool = False + ) -> None: + + super().add_content(more_content, no_docstring) + + source_name = self.get_sourcename() + enum_object: IntEnum = self.object + use_hex = self.options.hex + self.add_line('', source_name) + + for enum_value in enum_object: + the_value_name = enum_value.name + the_value_value = enum_value.value + if use_hex: + the_value_value = hex(the_value_value) + + self.add_line( + f"**{the_value_name}**: {the_value_value}", source_name) + self.add_line('', source_name) + + +def setup(app: Sphinx) -> None: + app.setup_extension('sphinx.ext.autodoc') # Require autodoc extension + app.add_autodocumenter(IntEnumDocumenter) From a56f69b916c1ae139eb1f38ee6d147b71b3d1d59 Mon Sep 17 00:00:00 2001 From: igo95862 <igo95862@yandex.ru> Date: Mon, 8 Mar 2021 12:21:56 +0300 Subject: [PATCH 54/58] doc: Added autodoc extension tutorial to tutorials index --- doc/development/tutorials/index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/development/tutorials/index.rst b/doc/development/tutorials/index.rst index be126b3ca..a7eee4899 100644 --- a/doc/development/tutorials/index.rst +++ b/doc/development/tutorials/index.rst @@ -13,3 +13,5 @@ Refer to the following tutorials to get started with extension development. helloworld todo recipe + autodoc_ext + From c57cb0b5ed02990b376ffc8b3ba58a95c4faba70 Mon Sep 17 00:00:00 2001 From: igo95862 <igo95862@yandex.ru> Date: Mon, 8 Mar 2021 12:23:02 +0300 Subject: [PATCH 55/58] doc: Link autodoc tutorial in add_autodocumenter docstring Uses :ref: link because :doc: does not work. --- sphinx/application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/application.py b/sphinx/application.py index b5cc44268..7812cecc9 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -1102,7 +1102,7 @@ class Sphinx: If *override* is True, the given *cls* is forcedly installed even if a documenter having the same name is already installed. - .. todo:: Add real docs for Documenter and subclassing + See :ref:`autodoc_ext_tutorial`. .. versionadded:: 0.6 .. versionchanged:: 2.2 From 7ee2000598716262cf802594165cd12c20f16d23 Mon Sep 17 00:00:00 2001 From: igo95862 <igo95862@yandex.ru> Date: Mon, 8 Mar 2021 12:45:43 +0300 Subject: [PATCH 56/58] doc: Added reflink to autodoc tutorial Used in add_autodocumenter docstring --- doc/development/tutorials/autodoc_ext.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/development/tutorials/autodoc_ext.rst b/doc/development/tutorials/autodoc_ext.rst index eca0e5120..d8905710c 100644 --- a/doc/development/tutorials/autodoc_ext.rst +++ b/doc/development/tutorials/autodoc_ext.rst @@ -1,3 +1,5 @@ +.. _autodoc_ext_tutorial: + Developing autodoc extension for IntEnum ======================================== From 54886fd256aeb87b7b7c8a1bcf65bad55c33d3eb Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Sat, 25 Apr 2020 12:24:36 +0900 Subject: [PATCH 57/58] Close #7549: autosummary: Enable autosummary_generate by default --- CHANGES | 1 + doc/conf.py | 1 + doc/usage/extensions/autosummary.rst | 18 ++++++++++++------ sphinx/ext/autosummary/__init__.py | 2 +- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index a183eba66..ad3c20988 100644 --- a/CHANGES +++ b/CHANGES @@ -62,6 +62,7 @@ Features added -------------- * #8924: autodoc: Support ``bound`` argument for TypeVar +* #7549: autosummary: Enable :confval:`autosummary_generate` by default * #4826: py domain: Add ``:canonical:`` option to python directives to describe the location where the object is defined * #7199: py domain: Add :confval:`python_use_unqualified_type_names` to suppress diff --git a/doc/conf.py b/doc/conf.py index 7cf74dc97..ddf68de05 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -79,6 +79,7 @@ latex_show_urls = 'footnote' latex_use_xindy = True autodoc_member_order = 'groupwise' +autosummary_generate = False todo_include_todos = True extlinks = {'duref': ('http://docutils.sourceforge.net/docs/ref/rst/' 'restructuredtext.html#%s', ''), diff --git a/doc/usage/extensions/autosummary.rst b/doc/usage/extensions/autosummary.rst index 03ea7548e..28f207de7 100644 --- a/doc/usage/extensions/autosummary.rst +++ b/doc/usage/extensions/autosummary.rst @@ -19,11 +19,13 @@ The :mod:`sphinx.ext.autosummary` extension does this in two parts: that contain links to the documented items, and short summary blurbs extracted from their docstrings. -2. Optionally, the convenience script :program:`sphinx-autogen` or the new - :confval:`autosummary_generate` config value can be used to generate short - "stub" files for the entries listed in the :rst:dir:`autosummary` directives. - These files by default contain only the corresponding - :mod:`sphinx.ext.autodoc` directive, but can be customized with templates. +2. A :rst:dir:`autosummary` directive also generates short "stub" files for the + entries listed in its content. These files by default contain only the + corresponding :mod:`sphinx.ext.autodoc` directive, but can be customized with + templates. + + The :program:`sphinx-autogen` script is also able to generate "stub" files + from command line. .. rst:directive:: autosummary @@ -161,7 +163,7 @@ also use these config values: .. confval:: autosummary_generate Boolean indicating whether to scan all found documents for autosummary - directives, and to generate stub pages for each. It is disabled by default. + directives, and to generate stub pages for each. It is enabled by default. Can also be a list of documents for which stub pages should be generated. @@ -173,6 +175,10 @@ also use these config values: Emits :event:`autodoc-skip-member` event as :mod:`~sphinx.ext.autodoc` does. + .. versionchanged:: 4.0 + + Enabled by default. + .. confval:: autosummary_generate_overwrite If true, autosummary overwrites existing files by generated stub pages. diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 7dbaaf686..1a0fd2409 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -772,7 +772,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.connect('builder-inited', process_generate_options) app.add_config_value('autosummary_context', {}, True) app.add_config_value('autosummary_filename_map', {}, 'html') - app.add_config_value('autosummary_generate', [], True, [bool]) + app.add_config_value('autosummary_generate', True, True, [bool]) app.add_config_value('autosummary_generate_overwrite', True, False) app.add_config_value('autosummary_mock_imports', lambda config: config.autodoc_mock_imports, 'env') From 6976c051ee1a356950ea0641fdebdfed51d10799 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA <i.tkomiya@gmail.com> Date: Wed, 10 Mar 2021 01:38:48 +0900 Subject: [PATCH 58/58] refactor: Use PEP-526 based variable annotation (sphinx.util) --- sphinx/util/__init__.py | 8 ++++---- sphinx/util/cfamily.py | 10 +++++----- sphinx/util/console.py | 8 ++++---- sphinx/util/docfields.py | 12 ++++++------ sphinx/util/docstrings.py | 2 +- sphinx/util/docutils.py | 8 ++++---- sphinx/util/inventory.py | 4 ++-- sphinx/util/jsdump.py | 6 +++--- sphinx/util/logging.py | 16 +++++++++------- sphinx/util/matching.py | 4 ++-- sphinx/util/nodes.py | 4 ++-- sphinx/util/osutil.py | 2 +- sphinx/util/parallel.py | 10 +++++----- sphinx/util/rst.py | 3 +-- sphinx/util/texescape.py | 10 +++++----- 15 files changed, 54 insertions(+), 53 deletions(-) diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 83b2561f2..cb8669eca 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -47,8 +47,8 @@ if TYPE_CHECKING: logger = logging.getLogger(__name__) # Generally useful regular expressions. -ws_re = re.compile(r'\s+') -url_re = re.compile(r'(?P<schema>.+)://.*') +ws_re: Pattern = re.compile(r'\s+') +url_re: Pattern = re.compile(r'(?P<schema>.+)://.*') # High-level utility functions. @@ -107,7 +107,7 @@ class FilenameUniqDict(dict): appear in. Used for images and downloadable files in the environment. """ def __init__(self) -> None: - self._existing = set() # type: Set[str] + self._existing: Set[str] = set() def add_file(self, docname: str, newfile: str) -> str: if newfile in self: @@ -379,7 +379,7 @@ def format_exception_cut_frames(x: int = 1) -> str: """Format an exception with traceback, but only the last x frames.""" typ, val, tb = sys.exc_info() # res = ['Traceback (most recent call last):\n'] - res = [] # type: List[str] + res: List[str] = [] tbres = traceback.format_tb(tb) res += tbres[-x:] res += traceback.format_exception_only(typ, val) diff --git a/sphinx/util/cfamily.py b/sphinx/util/cfamily.py index 31244852c..2be2e390f 100644 --- a/sphinx/util/cfamily.py +++ b/sphinx/util/cfamily.py @@ -98,7 +98,7 @@ class ASTBaseBase: return False return True - __hash__ = None # type: Callable[[], int] + __hash__: Callable[[], int] = None def clone(self) -> Any: return deepcopy(self) @@ -223,9 +223,9 @@ class BaseParser: self.pos = 0 self.end = len(self.definition) - self.last_match = None # type: Match - self._previous_state = (0, None) # type: Tuple[int, Match] - self.otherErrors = [] # type: List[DefinitionError] + self.last_match: Match = None + self._previous_state: Tuple[int, Match] = (0, None) + self.otherErrors: List[DefinitionError] = [] # in our tests the following is set to False to capture bad parsing self.allowFallbackExpressionParsing = True @@ -356,7 +356,7 @@ class BaseParser: # TODO: add handling of string literals and similar brackets = {'(': ')', '[': ']', '{': '}'} startPos = self.pos - symbols = [] # type: List[str] + symbols: List[str] = [] while not self.eof: if len(symbols) == 0 and self.current_char in end: break diff --git a/sphinx/util/console.py b/sphinx/util/console.py index 3ea5b9573..0c8008371 100644 --- a/sphinx/util/console.py +++ b/sphinx/util/console.py @@ -11,7 +11,7 @@ import os import re import sys -from typing import Dict +from typing import Dict, Pattern try: # check if colorama is installed to support color on Windows @@ -20,8 +20,8 @@ except ImportError: colorama = None -_ansi_re = re.compile('\x1b\\[(\\d\\d;){0,2}\\d\\dm') -codes = {} # type: Dict[str, str] +_ansi_re: Pattern = re.compile('\x1b\\[(\\d\\d;){0,2}\\d\\dm') +codes: Dict[str, str] = {} def terminal_safe(s: str) -> str: @@ -44,7 +44,7 @@ def get_terminal_width() -> int: return terminal_width -_tw = get_terminal_width() +_tw: int = get_terminal_width() def term_width_line(text: str) -> str: diff --git a/sphinx/util/docfields.py b/sphinx/util/docfields.py index 79abfe14b..43f15f507 100644 --- a/sphinx/util/docfields.py +++ b/sphinx/util/docfields.py @@ -27,7 +27,7 @@ def _is_single_paragraph(node: nodes.field_body) -> bool: if len(node) == 0: return False elif len(node) > 1: - for subnode in node[1:]: # type: nodes.Node + for subnode in node[1:]: # type: Node if not isinstance(subnode, nodes.system_message): return False if isinstance(node[0], nodes.paragraph): @@ -195,7 +195,7 @@ class TypedField(GroupedField): fieldname = nodes.field_name('', self.label) if len(items) == 1 and self.can_collapse: fieldarg, content = items[0] - bodynode = handle_item(fieldarg, content) # type: nodes.Node + bodynode: Node = handle_item(fieldarg, content) else: bodynode = self.list_type() for fieldarg, content in items: @@ -209,7 +209,7 @@ class DocFieldTransformer: Transforms field lists in "doc field" syntax into better-looking equivalents, using the field type definitions given on a domain. """ - typemap = None # type: Dict[str, Tuple[Field, bool]] + typemap: Dict[str, Tuple[Field, bool]] = None def __init__(self, directive: "ObjectDescription") -> None: self.directive = directive @@ -227,9 +227,9 @@ class DocFieldTransformer: """Transform a single field list *node*.""" typemap = self.typemap - entries = [] # type: List[Union[nodes.field, Tuple[Field, Any]]] - groupindices = {} # type: Dict[str, int] - types = {} # type: Dict[str, Dict] + entries: List[Union[nodes.field, Tuple[Field, Any]]] = [] + groupindices: Dict[str, int] = {} + types: Dict[str, Dict] = {} # step 1: traverse all fields and collect field types and content for field in cast(List[nodes.field], node): diff --git a/sphinx/util/docstrings.py b/sphinx/util/docstrings.py index ac778af87..46bb5b9b8 100644 --- a/sphinx/util/docstrings.py +++ b/sphinx/util/docstrings.py @@ -23,7 +23,7 @@ field_list_item_re = re.compile(Body.patterns['field_marker']) def extract_metadata(s: str) -> Dict[str, str]: """Extract metadata from docstring.""" in_other_element = False - metadata = {} # type: Dict[str, str] + metadata: Dict[str, str] = {} if not s: return metadata diff --git a/sphinx/util/docutils.py b/sphinx/util/docutils.py index a3e49e6d7..a4ecdc647 100644 --- a/sphinx/util/docutils.py +++ b/sphinx/util/docutils.py @@ -42,7 +42,7 @@ if TYPE_CHECKING: __version_info__ = tuple(LooseVersion(docutils.__version__).version) -additional_nodes = set() # type: Set[Type[nodes.Element]] +additional_nodes: Set[Type[Element]] = set() @contextmanager @@ -176,8 +176,8 @@ class sphinx_domains: """ def __init__(self, env: "BuildEnvironment") -> None: self.env = env - self.directive_func = None # type: Callable - self.roles_func = None # type: Callable + self.directive_func: Callable = None + self.roles_func: Callable = None def __enter__(self) -> None: self.enable() @@ -491,7 +491,7 @@ class SphinxTranslator(nodes.NodeVisitor): # cache a vanilla instance of nodes.document # Used in new_document() function -__document_cache__ = None # type: nodes.document +__document_cache__: nodes.document = None def new_document(source_path: str, settings: Any = None) -> nodes.document: diff --git a/sphinx/util/inventory.py b/sphinx/util/inventory.py index 40ac87997..f21c27323 100644 --- a/sphinx/util/inventory.py +++ b/sphinx/util/inventory.py @@ -93,7 +93,7 @@ class InventoryFile: @classmethod def load_v1(cls, stream: InventoryFileReader, uri: str, join: Callable) -> Inventory: - invdata = {} # type: Inventory + invdata: Inventory = {} projname = stream.readline().rstrip()[11:] version = stream.readline().rstrip()[11:] for line in stream.readlines(): @@ -111,7 +111,7 @@ class InventoryFile: @classmethod def load_v2(cls, stream: InventoryFileReader, uri: str, join: Callable) -> Inventory: - invdata = {} # type: Inventory + invdata: Inventory = {} projname = stream.readline().rstrip()[11:] version = stream.readline().rstrip()[11:] line = stream.readline() diff --git a/sphinx/util/jsdump.py b/sphinx/util/jsdump.py index 114fd7075..6d534cb3a 100644 --- a/sphinx/util/jsdump.py +++ b/sphinx/util/jsdump.py @@ -109,8 +109,8 @@ def loads(x: str) -> Any: nothing = object() i = 0 n = len(x) - stack = [] # type: List[Union[List, Dict]] - obj = nothing # type: Any + stack: List[Union[List, Dict]] = [] + obj: Any = nothing key = False keys = [] while i < n: @@ -160,7 +160,7 @@ def loads(x: str) -> Any: raise ValueError("multiple values") key = False else: - y = None # type: Any + y: Any = None m = _str_re.match(x, i) if m: y = decode_string(m.group()[1:-1]) diff --git a/sphinx/util/logging.py b/sphinx/util/logging.py index dd04b3f23..64b7d8fb4 100644 --- a/sphinx/util/logging.py +++ b/sphinx/util/logging.py @@ -28,7 +28,7 @@ if TYPE_CHECKING: NAMESPACE = 'sphinx' VERBOSE = 15 -LEVEL_NAMES = defaultdict(lambda: logging.WARNING) # type: Dict[str, int] +LEVEL_NAMES: Dict[str, int] = defaultdict(lambda: logging.WARNING) LEVEL_NAMES.update({ 'CRITICAL': logging.CRITICAL, 'SEVERE': logging.CRITICAL, @@ -39,7 +39,7 @@ LEVEL_NAMES.update({ 'DEBUG': logging.DEBUG, }) -VERBOSITY_MAP = defaultdict(lambda: 0) # type: Dict[int, int] +VERBOSITY_MAP: Dict[int, int] = defaultdict(lambda: 0) VERBOSITY_MAP.update({ 0: logging.INFO, 1: VERBOSE, @@ -91,7 +91,7 @@ def convert_serializable(records: List[logging.LogRecord]) -> None: class SphinxLogRecord(logging.LogRecord): """Log record class supporting location""" prefix = '' - location = None # type: Any + location: Any = None def getMessage(self) -> str: message = super().getMessage() @@ -163,6 +163,8 @@ class NewLineStreamHandler(logging.StreamHandler): class MemoryHandler(logging.handlers.BufferingHandler): """Handler buffering all logs.""" + buffer: List[logging.LogRecord] + def __init__(self) -> None: super().__init__(-1) @@ -174,7 +176,7 @@ class MemoryHandler(logging.handlers.BufferingHandler): try: for record in self.buffer: logger.handle(record) - self.buffer = [] # type: List[logging.LogRecord] + self.buffer = [] finally: self.release() @@ -328,7 +330,7 @@ def prefixed_warnings(prefix: str) -> Generator[None, None, None]: class LogCollector: def __init__(self) -> None: - self.logs = [] # type: List[logging.LogRecord] + self.logs: List[logging.LogRecord] = [] @contextmanager def collect(self) -> Generator[None, None, None]: @@ -449,7 +451,7 @@ class OnceFilter(logging.Filter): def __init__(self, name: str = '') -> None: super().__init__(name) - self.messages = {} # type: Dict[str, List] + self.messages: Dict[str, List] = {} def filter(self, record: logging.LogRecord) -> bool: once = getattr(record, 'once', '') @@ -470,7 +472,7 @@ class SphinxLogRecordTranslator(logging.Filter): * Make a instance of SphinxLogRecord * docname to path if location given """ - LogRecordClass = None # type: Type[logging.LogRecord] + LogRecordClass: Type[logging.LogRecord] = None def __init__(self, app: "Sphinx") -> None: self.app = app diff --git a/sphinx/util/matching.py b/sphinx/util/matching.py index 2ed804677..f32d682f1 100644 --- a/sphinx/util/matching.py +++ b/sphinx/util/matching.py @@ -21,7 +21,7 @@ def _translate_pattern(pat: str) -> str: match slashes. """ i, n = 0, len(pat) - res = '' # type: str + res = '' while i < n: c = pat[i] i += 1 @@ -86,7 +86,7 @@ class Matcher: DOTFILES = Matcher(['**/.*']) -_pat_cache = {} # type: Dict[str, Pattern] +_pat_cache: Dict[str, Pattern] = {} def patmatch(name: str, pat: str) -> Optional[Match[str]]: diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index c7619a836..61d688d8e 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -251,7 +251,7 @@ META_TYPE_NODES = ( def extract_messages(doctree: Element) -> Iterable[Tuple[Element, str]]: """Extract translatable messages from a document tree.""" - for node in doctree.traverse(is_translatable): # type: nodes.Element + for node in doctree.traverse(is_translatable): # type: Element if isinstance(node, addnodes.translatable): for msg in node.extract_original_messages(): yield node, msg @@ -363,7 +363,7 @@ indextypes = [ def process_index_entry(entry: str, targetid: str) -> List[Tuple[str, str, str, str, str]]: from sphinx.domains.python import pairindextypes - indexentries = [] # type: List[Tuple[str, str, str, str, str]] + indexentries: List[Tuple[str, str, str, str, str]] = [] entry = entry.strip() oentry = entry main = '' diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index 5ee2eccc8..c8ecce9e2 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -185,7 +185,7 @@ class FileAvoidWrite: """ def __init__(self, path: str) -> None: self._path = path - self._io = None # type: Optional[StringIO] + self._io: Optional[StringIO] = None def write(self, data: str) -> None: if not self._io: diff --git a/sphinx/util/parallel.py b/sphinx/util/parallel.py index ed73ee4d6..7ad7f81e7 100644 --- a/sphinx/util/parallel.py +++ b/sphinx/util/parallel.py @@ -60,15 +60,15 @@ class ParallelTasks: def __init__(self, nproc: int) -> None: self.nproc = nproc # (optional) function performed by each task on the result of main task - self._result_funcs = {} # type: Dict[int, Callable] + self._result_funcs: Dict[int, Callable] = {} # task arguments - self._args = {} # type: Dict[int, List[Any]] + self._args: Dict[int, List[Any]] = {} # list of subprocesses (both started and waiting) - self._procs = {} # type: Dict[int, multiprocessing.Process] + self._procs: Dict[int, multiprocessing.Process] = {} # list of receiving pipe connections of running subprocesses - self._precvs = {} # type: Dict[int, Any] + self._precvs: Dict[int, Any] = {} # list of receiving pipe connections of waiting subprocesses - self._precvsWaiting = {} # type: Dict[int, Any] + self._precvsWaiting: Dict[int, Any] = {} # number of working subprocesses self._pworking = 0 # task number of each subprocess diff --git a/sphinx/util/rst.py b/sphinx/util/rst.py index 79ede3432..82b3f6bda 100644 --- a/sphinx/util/rst.py +++ b/sphinx/util/rst.py @@ -30,8 +30,7 @@ symbols_re = re.compile(r'([!-\-/:-@\[-`{-~])') # symbols without dot(0x2e) SECTIONING_CHARS = ['=', '-', '~'] # width of characters -WIDECHARS = defaultdict(lambda: "WF") # type: Dict[str, str] - # WF: Wide + Full-width +WIDECHARS: Dict[str, str] = defaultdict(lambda: "WF") # WF: Wide + Full-width WIDECHARS["ja"] = "WFA" # In Japanese, Ambiguous characters also have double width diff --git a/sphinx/util/texescape.py b/sphinx/util/texescape.py index 7323d2c27..417a963a7 100644 --- a/sphinx/util/texescape.py +++ b/sphinx/util/texescape.py @@ -98,12 +98,12 @@ unicode_tex_replacements = [ # %, {, }, \, #, and ~ are the only ones which must be replaced by _ character # It would be simpler to define it entirely here rather than in init(). # Unicode replacements are superfluous, as idescape() uses backslashreplace -tex_replace_map = {} # type: Dict[int, str] +tex_replace_map: Dict[int, str] = {} -_tex_escape_map = {} # type: Dict[int, str] -_tex_escape_map_without_unicode = {} # type: Dict[int, str] -_tex_hlescape_map = {} # type: Dict[int, str] -_tex_hlescape_map_without_unicode = {} # type: Dict[int, str] +_tex_escape_map: Dict[int, str] = {} +_tex_escape_map_without_unicode: Dict[int, str] = {} +_tex_hlescape_map: Dict[int, str] = {} +_tex_hlescape_map_without_unicode: Dict[int, str] = {} def escape(s: str, latex_engine: str = None) -> str: