From 7c1b6730768ed81f2bb137ec5ef71ef30dd915eb Mon Sep 17 00:00:00 2001 From: Jesse Tan Date: Thu, 9 Jan 2020 11:05:05 +0100 Subject: [PATCH 01/72] Add LaTeX styling hook for :kbd: role --- doc/latex.rst | 2 ++ sphinx/texinputs/sphinx.sty | 1 + sphinx/writers/latex.py | 2 ++ tests/test_markup.py | 7 +++++++ 4 files changed, 12 insertions(+) diff --git a/doc/latex.rst b/doc/latex.rst index dd5fd16791b..81f70dc1e57 100644 --- a/doc/latex.rst +++ b/doc/latex.rst @@ -817,6 +817,8 @@ Macros multiple paragraphs in header cells of tables. .. versionadded:: 1.6.3 ``\sphinxstylecodecontinued`` and ``\sphinxstylecodecontinues``. + .. versionadded:: 2.4.0 + ``\sphinxkeyboard`` - ``\sphinxtableofcontents``: it is a wrapper (defined differently in :file:`sphinxhowto.cls` and in :file:`sphinxmanual.cls`) of standard ``\tableofcontents``. The macro diff --git a/sphinx/texinputs/sphinx.sty b/sphinx/texinputs/sphinx.sty index 3e67b5610d2..e27b44aa819 100644 --- a/sphinx/texinputs/sphinx.sty +++ b/sphinx/texinputs/sphinx.sty @@ -1836,6 +1836,7 @@ \protected\def\sphinxtitleref#1{\emph{#1}} \protected\def\sphinxmenuselection#1{\emph{#1}} \protected\def\sphinxguilabel#1{\emph{#1}} +\protected\def\sphinxkeyboard#1{\sphinxcode{#1}} \protected\def\sphinxaccelerator#1{\underline{#1}} \protected\def\sphinxcrossref#1{\emph{#1}} \protected\def\sphinxtermref#1{\emph{#1}} diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 55348145f4f..a370b51441f 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -1935,6 +1935,8 @@ def depart_citation_reference(self, node: Element) -> None: def visit_literal(self, node: Element) -> None: if self.in_title: self.body.append(r'\sphinxstyleliteralintitle{\sphinxupquote{') + elif 'kbd' in node['classes']: + self.body.append(r'\sphinxkeyboard{\sphinxupquote{') else: self.body.append(r'\sphinxcode{\sphinxupquote{') diff --git a/tests/test_markup.py b/tests/test_markup.py index edf4d737937..b6d99db90a0 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -230,6 +230,13 @@ def get(name): '

Foo

', r'\sphinxguilabel{Foo}', ), + ( + # kbd role + 'verify', + ':kbd:`space`', + '

space

', + '\\sphinxkeyboard{\\sphinxupquote{space}}', + ), ( # non-interpolation of dashes in option role 'verify_re', From fd94270f1ce9f5a10d2b16e751df7fbf9417019e Mon Sep 17 00:00:00 2001 From: Wes Turner Date: Fri, 7 Feb 2020 02:51:03 -0500 Subject: [PATCH 02/72] ENH: linkcheck: also write all links to output.json * TST: linkcheck: make tests more flexible * CLN: linkcheck: flake8, mypy * REF: linkcheck: docpath->filename, write_jsonline->write_linkstat * REF: linkcheck: remove redundant call to doc2path * TST: linkcheck: show JSON obj structure in test * REF: linkcheck: remove docname from JSON obj because it's redundant (use path2doc(filename) if necessary) * TST: linkcheck: don't test row[info] output (see comments for examples) --- sphinx/builders/linkcheck.py | 39 +++++++++++++++++------ tests/test_build_linkcheck.py | 59 ++++++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 10 deletions(-) diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index 1b0dd401106..9fe689ec936 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -8,6 +8,7 @@ :license: BSD, see LICENSE for details. """ +import json import queue import re import socket @@ -90,6 +91,8 @@ def init(self) -> None: socket.setdefaulttimeout(5.0) # create output file open(path.join(self.outdir, 'output.txt'), 'w').close() + # create JSON output file + open(path.join(self.outdir, 'output.json'), 'w').close() # create queues and worker threads self.wqueue = queue.Queue() # type: queue.Queue @@ -225,9 +228,16 @@ def check() -> Tuple[str, str, int]: def process_result(self, result: Tuple[str, str, int, str, str, int]) -> None: uri, docname, lineno, status, info, code = result + + filename = self.env.doc2path(docname, None) + linkstat = dict(filename=filename, lineno=lineno, + status=status, code=code, uri=uri, + info=info) if status == 'unchecked': + self.write_linkstat(linkstat) return if status == 'working' and info == 'old': + self.write_linkstat(linkstat) return if lineno: logger.info('(line %4d) ', lineno, nonl=True) @@ -236,18 +246,22 @@ def process_result(self, result: Tuple[str, str, int, str, str, int]) -> None: logger.info(darkgray('-ignored- ') + uri + ': ' + info) else: logger.info(darkgray('-ignored- ') + uri) + self.write_linkstat(linkstat) elif status == 'local': logger.info(darkgray('-local- ') + uri) - self.write_entry('local', docname, lineno, uri) + self.write_entry('local', docname, filename, lineno, uri) + self.write_linkstat(linkstat) elif status == 'working': logger.info(darkgreen('ok ') + uri + info) + self.write_linkstat(linkstat) elif status == 'broken': - self.write_entry('broken', docname, lineno, uri + ': ' + info) if self.app.quiet or self.app.warningiserror: logger.warning(__('broken link: %s (%s)'), uri, info, - location=(self.env.doc2path(docname), lineno)) + location=(filename, lineno)) else: logger.info(red('broken ') + uri + red(' - ' + info)) + self.write_entry('broken', docname, filename, lineno, uri + ': ' + info) + self.write_linkstat(linkstat) elif status == 'redirected': try: text, color = { @@ -259,9 +273,11 @@ def process_result(self, result: Tuple[str, str, int, str, str, int]) -> None: }[code] except KeyError: text, color = ('with unknown code', purple) - self.write_entry('redirected ' + text, docname, lineno, - uri + ' to ' + info) + linkstat['text'] = text logger.info(color('redirect ') + uri + color(' - ' + text + ' to ' + info)) + self.write_entry('redirected ' + text, docname, filename, + lineno, uri + ' to ' + info) + self.write_linkstat(linkstat) def get_target_uri(self, docname: str, typ: str = None) -> str: return '' @@ -301,10 +317,15 @@ def write_doc(self, docname: str, doctree: Node) -> None: if self.broken: self.app.statuscode = 1 - def write_entry(self, what: str, docname: str, line: int, uri: str) -> None: - with open(path.join(self.outdir, 'output.txt'), 'a', encoding='utf-8') as output: - output.write("%s:%s: [%s] %s\n" % (self.env.doc2path(docname, None), - line, what, uri)) + def write_entry(self, what: str, docname: str, filename: str, line: int, + uri: str) -> None: + with open(path.join(self.outdir, 'output.txt'), 'a') as output: + output.write("%s:%s: [%s] %s\n" % (filename, line, what, uri)) + + def write_linkstat(self, data: dict) -> None: + with open(path.join(self.outdir, 'output.json'), 'a') as output: + output.write(json.dumps(data)) + output.write('\n') def finish(self) -> None: for worker in self.workers: diff --git a/tests/test_build_linkcheck.py b/tests/test_build_linkcheck.py index 7a01fb590e7..5220b480e2a 100644 --- a/tests/test_build_linkcheck.py +++ b/tests/test_build_linkcheck.py @@ -8,6 +8,8 @@ :license: BSD, see LICENSE for details. """ +import json +import re from unittest import mock import pytest @@ -20,7 +22,7 @@ def test_defaults(app, status, warning): content = (app.outdir / 'output.txt').read_text() print(content) - # looking for '#top' and 'does-not-exist' not found should fail + # looking for '#top' and '#does-not-exist' not found should fail assert "Anchor 'top' not found" in content assert "Anchor 'does-not-exist' not found" in content # looking for non-existent URL should fail @@ -31,6 +33,61 @@ def test_defaults(app, status, warning): assert len(content.splitlines()) == 5 +@pytest.mark.sphinx('linkcheck', testroot='linkcheck', freshenv=True) +def test_defaults_json(app, status, warning): + app.builder.build_all() + + assert (app.outdir / 'output.json').exists() + content = (app.outdir / 'output.json').read_text() + print(content) + + rows = [json.loads(x) for x in content.splitlines()] + row = rows[0] + for attr in ["filename", "lineno", "status", "code", "uri", + "info"]: + assert attr in row + + assert len(content.splitlines()) == 8 + assert len(rows) == 8 + # the output order of the rows is not stable + # due to possible variance in network latency + rowsby = {row["uri"]:row for row in rows} + assert rowsby["https://www.google.com#!bar"] == { + 'filename': 'links.txt', + 'lineno': 10, + 'status': 'working', + 'code': 0, + 'uri': 'https://www.google.com#!bar', + 'info': '' + } + # looking for non-existent URL should fail + dnerow = rowsby['https://localhost:7777/doesnotexist'] + assert dnerow['filename'] == 'links.txt' + assert dnerow['lineno'] == 13 + assert dnerow['status'] == 'broken' + assert dnerow['code'] == 0 + assert dnerow['uri'] == 'https://localhost:7777/doesnotexist' + #assert dnerow['info'] == "HTTPSConnectionPool(host='localhost', port=7777): Max retries exceeded with url: /doesnotexist (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 111] Connection refused'))" + #assert "[Errno 111] Connection refused" in dnerow['info'] + #assert "[WinError 10061] No connection could be made because the target machine actively refused it'" in dnerow['info'] + assert rowsby['https://www.google.com/image2.png'] == { + 'filename': 'links.txt', + 'lineno': 16, + 'status': 'broken', + 'code': 0, + 'uri': 'https://www.google.com/image2.png', + 'info': '404 Client Error: Not Found for url: https://www.google.com/image2.png' + } + # looking for '#top' and '#does-not-exist' not found should fail + assert "Anchor 'top' not found" == \ + rowsby["https://www.google.com/#top"]["info"] + assert "Anchor 'does-not-exist' not found" == \ + rowsby["http://www.sphinx-doc.org/en/1.7/intro.html#does-not-exist"]["info"] + # images should fail + assert "Not Found for url: https://www.google.com/image.png" in \ + rowsby["https://www.google.com/image.png"]["info"] + + @pytest.mark.sphinx( 'linkcheck', testroot='linkcheck', freshenv=True, confoverrides={'linkcheck_anchors_ignore': ["^!", "^top$"], From d98b2f05a3056fe9798eb0b67233d2702d846d2f Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 23 Feb 2020 15:57:51 +0900 Subject: [PATCH 03/72] utils/bump_version: Remove empty sections from CHANGES automatically --- utils/bump_version.py | 5 ++++- utils/release-checklist | 4 ---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/utils/bump_version.py b/utils/bump_version.py index 6491a38e300..b829877b6a5 100755 --- a/utils/bump_version.py +++ b/utils/bump_version.py @@ -114,7 +114,7 @@ def finalize_release_date(self): f.truncate(0) f.write(heading + '\n') f.write('=' * len(heading) + '\n') - f.write(body) + f.write(self.filter_empty_sections(body)) def add_release(self, version_info): if version_info[-2:] in (('beta', 0), ('final', 0)): @@ -142,6 +142,9 @@ def add_release(self, version_info): f.write('\n') f.write(body) + def filter_empty_sections(self, body): + return re.sub('^\n.+\n-{3,}\n+(?=\n.+\n[-=]{3,}\n)', '', body, flags=re.M) + def parse_options(argv): parser = argparse.ArgumentParser() diff --git a/utils/release-checklist b/utils/release-checklist index 12cbe6381fb..171fe50fc9b 100644 --- a/utils/release-checklist +++ b/utils/release-checklist @@ -8,7 +8,6 @@ for stable releases * Run ``git fetch; git status`` and check nothing changed * ``python utils/bump_version.py X.Y.Z`` * Check diff by ``git diff`` -* Edit CHANGES if empty section exists * ``git commit -am 'Bump to X.Y.Z final'`` * ``make clean`` * ``python setup.py release bdist_wheel sdist`` @@ -36,7 +35,6 @@ for first beta releases * Run ``(cd sphinx/locale; tx push -s)`` * ``python utils/bump_version.py X.Y.0b1`` * Check diff by ``git diff`` -* Edit CHANGES if empty section exists * ``git commit -am 'Bump to X.Y.0 beta1'`` * ``make clean`` * ``python setup.py release bdist_wheel sdist`` @@ -67,7 +65,6 @@ for other beta releases * Run ``git fetch; git status`` and check nothing changed * ``python utils/bump_version.py X.Y.0bN`` * Check diff by ``git diff`` -* Edit CHANGES if empty section exists * ``git commit -am 'Bump to X.Y.0 betaN'`` * ``make clean`` * ``python setup.py release bdist_wheel sdist`` @@ -97,7 +94,6 @@ for major releases * Run ``git commit -am 'Update message catalogs'`` * ``python utils/bump_version.py X.Y.0`` * Check diff by ``git diff`` -* Edit CHANGES if empty section exists * ``git commit -am 'Bump to X.Y.0 final'`` * ``make clean`` * ``python setup.py release bdist_wheel sdist`` From 6eac7901c97c0bb5d1569f2e8d6c5d039ec9334f Mon Sep 17 00:00:00 2001 From: jfbu Date: Sun, 23 Feb 2020 08:53:37 +0100 Subject: [PATCH 04/72] LaTeX, fix \sphinxhref + \includegraphics problem with platex Upstream issue: https://github.com/latex3/latex2e/issues/286 Fixes #7197 --- CHANGES | 2 ++ sphinx/texinputs/sphinx.sty | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 73ef4405a98..ae8a74f3654 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,8 @@ Features added Bugs fixed ---------- +* #7197: LaTeX: platex cause error to build image directive with target url + Testing -------- diff --git a/sphinx/texinputs/sphinx.sty b/sphinx/texinputs/sphinx.sty index 3e67b5610d2..714d98e0507 100644 --- a/sphinx/texinputs/sphinx.sty +++ b/sphinx/texinputs/sphinx.sty @@ -1407,7 +1407,8 @@ % \sphinxcode). Sphinx uses \#, \%, \& ... always inside \sphinxhref. \protected\def\sphinxhref#1#2{{% \sphinxunactivateextrasandspace % never do \scantokens with active space! - \endlinechar\m@ne\everyeof{{#2}}% keep catcode regime for #2 +% for the \endlinechar business, https://github.com/latex3/latex2e/issues/286 + \endlinechar\m@ne\everyeof{{\endlinechar13 #2}}% keep catcode regime for #2 \scantokens{\href{#1}}% normalise it for #1 during \href expansion }} % Same for \url. And also \nolinkurl for coherence. From 5330172d54b0dd32629eaa033450d5a6658ac8c1 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 29 Feb 2020 12:24:34 +0900 Subject: [PATCH 05/72] Fix #7223: Sphinx builds has been slower since 2.4.0 --- CHANGES | 1 + sphinx/util/docutils.py | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index ae8a74f3654..f2caf578d32 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,7 @@ Bugs fixed ---------- * #7197: LaTeX: platex cause error to build image directive with target url +* #7223: Sphinx builds has been slower since 2.4.0 Testing -------- diff --git a/sphinx/util/docutils.py b/sphinx/util/docutils.py index 23f2c888b7c..3fbd8a42085 100644 --- a/sphinx/util/docutils.py +++ b/sphinx/util/docutils.py @@ -479,8 +479,8 @@ def dispatch_visit(self, node): for node_class in node.__class__.__mro__: method = getattr(self, 'visit_%s' % (node_class.__name__), None) if method: - logger.debug('SphinxTranslator.dispatch_visit calling %s for %s' % - (method.__name__, node)) + logger.debug('SphinxTranslator.dispatch_visit calling %s for %s', + method.__name__, node) return method(node) else: super().dispatch_visit(node) @@ -497,8 +497,8 @@ def dispatch_departure(self, node): for node_class in node.__class__.__mro__: method = getattr(self, 'depart_%s' % (node_class.__name__), None) if method: - logger.debug('SphinxTranslator.dispatch_departure calling %s for %s' % - (method.__name__, node)) + logger.debug('SphinxTranslator.dispatch_departure calling %s for %s', + method.__name__, node) return method(node) else: super().dispatch_departure(node) From 0132aa527fc1b9d53c986e633f5ed62ad0775453 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 29 Feb 2020 14:41:00 +0900 Subject: [PATCH 06/72] Close #7220: genindex: Show "main" index entries at first --- CHANGES | 1 + sphinx/environment/adapters/indexentries.py | 15 ++++++++++++--- tests/test_environment_indexentries.py | 15 +++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index e3e088910d1..e69b40ff886 100644 --- a/CHANGES +++ b/CHANGES @@ -58,6 +58,7 @@ Features added * #7032: html: :confval:`html_scaled_image_link` will be disabled for images having ``no-scaled-link`` class * #7144: Add CSS class indicating its domain for each desc node +* #7220: genindex: Show "main" index entries at first Bugs fixed ---------- diff --git a/sphinx/environment/adapters/indexentries.py b/sphinx/environment/adapters/indexentries.py index 1d135bd32bf..dbc94f846a8 100644 --- a/sphinx/environment/adapters/indexentries.py +++ b/sphinx/environment/adapters/indexentries.py @@ -7,7 +7,7 @@ :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ -import bisect + import re import unicodedata from itertools import groupby @@ -52,8 +52,7 @@ def add_entry(word: str, subword: str, main: str, link: bool = True, except NoUri: pass else: - # maintain links in sorted/deterministic order - bisect.insort(entry[0], (main, uri)) + entry[0].append((main, uri)) domain = cast(IndexDomain, self.env.get_domain('index')) for fn, entries in domain.entries.items(): @@ -89,6 +88,16 @@ def add_entry(word: str, subword: str, main: str, link: bool = True, except ValueError as err: logger.warning(str(err), location=fn) + # sort the index entries for same keyword. + def keyfunc0(entry: Tuple[str, str]) -> Tuple[bool, str]: + main, uri = entry + return (not main, uri) # show main entries at first + + for indexentry in new.values(): + indexentry[0] = sorted(indexentry[0], key=keyfunc0) + for subentry in indexentry[1].values(): + subentry[0] = sorted(subentry[0], key=keyfunc0) # type: ignore + # sort the index entries; put all symbols at the front, even those # following the letters in ASCII, this is where the chr(127) comes from def keyfunc(entry: Tuple[str, List]) -> Tuple[str, str]: diff --git a/tests/test_environment_indexentries.py b/tests/test_environment_indexentries.py index 685b365992c..97882d44b33 100644 --- a/tests/test_environment_indexentries.py +++ b/tests/test_environment_indexentries.py @@ -112,6 +112,21 @@ def test_create_seealso_index(app): assert index[2] == ('S', [('Sphinx', [[], [('see also documentation tool', [])], None])]) +@pytest.mark.sphinx('dummy', freshenv=True) +def test_create_main_index(app): + text = (".. index:: !docutils\n" + ".. index:: docutils\n" + ".. index:: pip; install\n" + ".. index:: !pip; install\n") + restructuredtext.parse(app, text) + index = IndexEntries(app.env).create_index(app.builder) + assert len(index) == 2 + assert index[0] == ('D', [('docutils', [[('main', '#index-0'), + ('', '#index-1')], [], None])]) + assert index[1] == ('P', [('pip', [[], [('install', [('main', '#index-3'), + ('', '#index-2')])], None])]) + + @pytest.mark.sphinx('dummy', freshenv=True) def test_create_index_with_name(app): text = (".. index:: single: docutils\n" From f965829cd049b986beafd561e3221784d98e199d Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 29 Feb 2020 23:20:34 +0900 Subject: [PATCH 07/72] refactor: Do not evaluate debug message on calling --- sphinx/registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/registry.py b/sphinx/registry.py index d7bffa2ced5..ea423298f57 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -347,7 +347,7 @@ def get_source_input(self, filetype: str) -> "Type[Input]": def add_translator(self, name: str, translator: "Type[nodes.NodeVisitor]", override: bool = False) -> None: - logger.debug('[app] Change of translator for the %s builder.' % name) + logger.debug('[app] Change of translator for the %s builder.', name) if name in self.translators and not override: raise ExtensionError(__('Translator for %r already exists') % name) self.translators[name] = translator From bdeb81639749ae56dfe0ea6821fd96a8bb4d0982 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 10 Feb 2020 10:48:47 +0900 Subject: [PATCH 08/72] js domain: Generate node_id for modules in the right way --- sphinx/domains/javascript.py | 49 +++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py index bf4e641eb12..15aad05cd67 100644 --- a/sphinx/domains/javascript.py +++ b/sphinx/domains/javascript.py @@ -28,12 +28,21 @@ from sphinx.util import logging from sphinx.util.docfields import Field, GroupedField, TypedField from sphinx.util.docutils import SphinxDirective -from sphinx.util.nodes import make_refnode +from sphinx.util.nodes import make_id, make_refnode logger = logging.getLogger(__name__) +def make_old_jsmod_id(modname: str) -> str: + """Generate old styled node_id for JS modules. + + .. note:: Old Styled node_id was used until Sphinx-3.0. + This will be removed in Sphinx-5.0. + """ + return 'module-' + modname + + class JSObject(ObjectDescription): """ Description of a JavaScript object. @@ -249,18 +258,24 @@ def run(self) -> List[Node]: if not noindex: domain = cast(JavaScriptDomain, self.env.get_domain('js')) - domain.note_module(mod_name) + node_id = make_id(self.env, self.state.document, 'module', mod_name) + domain.note_module(mod_name, node_id) # Make a duplicate entry in 'objects' to facilitate searching for # the module in JavaScriptDomain.find_obj() domain.note_object(mod_name, 'module', location=(self.env.docname, self.lineno)) - targetnode = nodes.target('', '', ids=['module-' + mod_name], - ismod=True) - self.state.document.note_explicit_target(targetnode) - ret.append(targetnode) + target = nodes.target('', '', ids=[node_id], ismod=True) + + # Assign old styled node_id not to break old hyperlinks (if possible) + # Note: Will be removed in Sphinx-5.0 (RemovedInSphinx50Warning) + old_node_id = make_old_jsmod_id(mod_name) + if old_node_id not in self.state.document.ids and old_node_id not in target['ids']: + target['ids'].append(old_node_id) + + self.state.document.note_explicit_target(target) + ret.append(target) indextext = _('%s (module)') % mod_name - inode = addnodes.index(entries=[('single', indextext, - 'module-' + mod_name, '', None)]) + inode = addnodes.index(entries=[('single', indextext, node_id, '', None)]) ret.append(inode) return ret @@ -316,7 +331,7 @@ class JavaScriptDomain(Domain): } initial_data = { 'objects': {}, # fullname -> docname, objtype - 'modules': {}, # modname -> docname + 'modules': {}, # modname -> docname, node_id } # type: Dict[str, Dict[str, Tuple[str, str]]] @property @@ -331,17 +346,17 @@ def note_object(self, fullname: str, objtype: str, location: Any = None) -> None self.objects[fullname] = (self.env.docname, objtype) @property - def modules(self) -> Dict[str, str]: - return self.data.setdefault('modules', {}) # modname -> docname + def modules(self) -> Dict[str, Tuple[str, str]]: + return self.data.setdefault('modules', {}) # modname -> docname, node_id - def note_module(self, modname: str) -> None: - self.modules[modname] = self.env.docname + def note_module(self, modname: str, node_id: str) -> None: + self.modules[modname] = (self.env.docname, node_id) def clear_doc(self, docname: str) -> None: for fullname, (pkg_docname, _l) in list(self.objects.items()): if pkg_docname == docname: del self.objects[fullname] - for modname, pkg_docname in list(self.modules.items()): + for modname, (pkg_docname, node_id) in list(self.modules.items()): if pkg_docname == docname: del self.modules[modname] @@ -350,9 +365,9 @@ def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: for fullname, (fn, objtype) in otherdata['objects'].items(): if fn in docnames: self.objects[fullname] = (fn, objtype) - for mod_name, pkg_docname in otherdata['modules'].items(): + for mod_name, (pkg_docname, node_id) in otherdata['modules'].items(): if pkg_docname in docnames: - self.modules[mod_name] = pkg_docname + self.modules[mod_name] = (pkg_docname, node_id) def find_obj(self, env: BuildEnvironment, mod_name: str, prefix: str, name: str, typ: str, searchorder: int = 0) -> Tuple[str, Tuple[str, str]]: @@ -421,7 +436,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: return { 'version': 'builtin', - 'env_version': 1, + 'env_version': 2, 'parallel_read_safe': True, 'parallel_write_safe': True, } From 9ba0cdf3bf2309e995260eb063813f55ddd425f8 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 23 Feb 2020 13:02:51 +0900 Subject: [PATCH 09/72] js domain: Show better duplication warning message --- sphinx/domains/javascript.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py index 15aad05cd67..b54a423ada4 100644 --- a/sphinx/domains/javascript.py +++ b/sphinx/domains/javascript.py @@ -341,8 +341,8 @@ def objects(self) -> Dict[str, Tuple[str, str]]: def note_object(self, fullname: str, objtype: str, location: Any = None) -> None: if fullname in self.objects: docname = self.objects[fullname][0] - logger.warning(__('duplicate object description of %s, other instance in %s'), - fullname, docname, location=location) + logger.warning(__('duplicate %s description of %s, other %s in %s'), + objtype, fullname, objtype, docname, location=location) self.objects[fullname] = (self.env.docname, objtype) @property From 8f647643fb32472e9e4b48d65d27c5994108009e Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 23 Feb 2020 15:39:06 +0900 Subject: [PATCH 10/72] js domain: Generate node_id for objects in the right way --- sphinx/domains/javascript.py | 65 +++++++++++++++++++++--------------- sphinx/util/nodes.py | 6 +++- tests/test_domain_js.py | 57 +++++++++++++++++-------------- 3 files changed, 76 insertions(+), 52 deletions(-) diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py index b54a423ada4..d7e44ee606b 100644 --- a/sphinx/domains/javascript.py +++ b/sphinx/domains/javascript.py @@ -43,6 +43,15 @@ def make_old_jsmod_id(modname: str) -> str: return 'module-' + modname +def make_old_jsobj_id(fullname: str) -> str: + """Generate old styled node_id for JS objects. + + .. note:: Old Styled node_id was used until Sphinx-3.0. + This will be removed in Sphinx-5.0. + """ + return fullname.replace('$', '_S_') + + class JSObject(ObjectDescription): """ Description of a JavaScript object. @@ -115,19 +124,23 @@ def add_target_and_index(self, name_obj: Tuple[str, str], sig: str, signode: desc_signature) -> None: mod_name = self.env.ref_context.get('js:module') fullname = (mod_name + '.' if mod_name else '') + name_obj[0] - if fullname not in self.state.document.ids: - signode['names'].append(fullname) - signode['ids'].append(fullname.replace('$', '_S_')) - self.state.document.note_explicit_target(signode) + node_id = make_id(self.env, self.state.document, '', fullname) + signode['ids'].append(node_id) - domain = cast(JavaScriptDomain, self.env.get_domain('js')) - domain.note_object(fullname, self.objtype, location=signode) + # Assign old styled node_id not to break old hyperlinks (if possible) + # Note: Will be removed in Sphinx-5.0 (RemovedInSphinx50Warning) + old_node_id = make_old_jsobj_id(fullname) + if old_node_id not in self.state.document.ids and old_node_id not in signode['ids']: + signode['ids'].append(old_node_id) + + self.state.document.note_explicit_target(signode) + + domain = cast(JavaScriptDomain, self.env.get_domain('js')) + domain.note_object(fullname, self.objtype, node_id, location=signode) indextext = self.get_index_text(mod_name, name_obj) if indextext: - self.indexnode['entries'].append(('single', indextext, - fullname.replace('$', '_S_'), - '', None)) + self.indexnode['entries'].append(('single', indextext, node_id, '', None)) def get_index_text(self, objectname: str, name_obj: Tuple[str, str]) -> str: name, obj = name_obj @@ -262,7 +275,8 @@ def run(self) -> List[Node]: domain.note_module(mod_name, node_id) # Make a duplicate entry in 'objects' to facilitate searching for # the module in JavaScriptDomain.find_obj() - domain.note_object(mod_name, 'module', location=(self.env.docname, self.lineno)) + domain.note_object(mod_name, 'module', node_id, + location=(self.env.docname, self.lineno)) target = nodes.target('', '', ids=[node_id], ismod=True) @@ -330,20 +344,21 @@ class JavaScriptDomain(Domain): 'mod': JSXRefRole(), } initial_data = { - 'objects': {}, # fullname -> docname, objtype + 'objects': {}, # fullname -> docname, node_id, objtype 'modules': {}, # modname -> docname, node_id } # type: Dict[str, Dict[str, Tuple[str, str]]] @property - def objects(self) -> Dict[str, Tuple[str, str]]: - return self.data.setdefault('objects', {}) # fullname -> docname, objtype + def objects(self) -> Dict[str, Tuple[str, str, str]]: + return self.data.setdefault('objects', {}) # fullname -> docname, node_id, objtype - def note_object(self, fullname: str, objtype: str, location: Any = None) -> None: + def note_object(self, fullname: str, objtype: str, node_id: str, + location: Any = None) -> None: if fullname in self.objects: docname = self.objects[fullname][0] logger.warning(__('duplicate %s description of %s, other %s in %s'), objtype, fullname, objtype, docname, location=location) - self.objects[fullname] = (self.env.docname, objtype) + self.objects[fullname] = (self.env.docname, node_id, objtype) @property def modules(self) -> Dict[str, Tuple[str, str]]: @@ -353,7 +368,7 @@ def note_module(self, modname: str, node_id: str) -> None: self.modules[modname] = (self.env.docname, node_id) def clear_doc(self, docname: str) -> None: - for fullname, (pkg_docname, _l) in list(self.objects.items()): + for fullname, (pkg_docname, node_id, _l) in list(self.objects.items()): if pkg_docname == docname: del self.objects[fullname] for modname, (pkg_docname, node_id) in list(self.modules.items()): @@ -362,15 +377,15 @@ def clear_doc(self, docname: str) -> None: def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: # XXX check duplicates - for fullname, (fn, objtype) in otherdata['objects'].items(): + for fullname, (fn, node_id, objtype) in otherdata['objects'].items(): if fn in docnames: - self.objects[fullname] = (fn, objtype) + self.objects[fullname] = (fn, node_id, objtype) for mod_name, (pkg_docname, node_id) in otherdata['modules'].items(): if pkg_docname in docnames: self.modules[mod_name] = (pkg_docname, node_id) def find_obj(self, env: BuildEnvironment, mod_name: str, prefix: str, name: str, - typ: str, searchorder: int = 0) -> Tuple[str, Tuple[str, str]]: + typ: str, searchorder: int = 0) -> Tuple[str, Tuple[str, str, str]]: if name[-2:] == '()': name = name[:-2] @@ -402,8 +417,7 @@ def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder name, obj = self.find_obj(env, mod_name, prefix, target, typ, searchorder) if not obj: return None - return make_refnode(builder, fromdocname, obj[0], - name.replace('$', '_S_'), contnode, name) + return make_refnode(builder, fromdocname, obj[0], obj[1], contnode, name) def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, target: str, node: pending_xref, contnode: Element @@ -413,13 +427,12 @@ def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Bui name, obj = self.find_obj(env, mod_name, prefix, target, None, 1) if not obj: return [] - return [('js:' + self.role_for_objtype(obj[1]), - make_refnode(builder, fromdocname, obj[0], - name.replace('$', '_S_'), contnode, name))] + return [('js:' + self.role_for_objtype(obj[2]), + make_refnode(builder, fromdocname, obj[0], obj[1], contnode, name))] def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]: - for refname, (docname, type) in list(self.objects.items()): - yield refname, refname, type, docname, refname.replace('$', '_S_'), 1 + for refname, (docname, node_id, typ) in list(self.objects.items()): + yield refname, refname, typ, docname, node_id, 1 def get_full_qualified_name(self, node: Element) -> str: modname = node.get('js:module') diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index 7c7300c6094..474a704a16b 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -443,7 +443,7 @@ def make_id(env: "BuildEnvironment", document: nodes.document, if prefix: idformat = prefix + "-%s" else: - idformat = document.settings.id_prefix + "%s" + idformat = (document.settings.id_prefix or "id") + "%s" # try to generate node_id by *term* if prefix and term: @@ -451,6 +451,10 @@ def make_id(env: "BuildEnvironment", document: nodes.document, if node_id == prefix: # *term* is not good to generate a node_id. node_id = None + elif term: + node_id = nodes.make_id(term) + if node_id == '': + node_id = None # fallback to None while node_id is None or node_id in document.ids: node_id = idformat % env.new_serialno(prefix) diff --git a/tests/test_domain_js.py b/tests/test_domain_js.py index 6726c7bbe52..2c2e2b7cc8e 100644 --- a/tests/test_domain_js.py +++ b/tests/test_domain_js.py @@ -91,22 +91,22 @@ def test_domain_js_objects(app, status, warning): assert 'module_b.submodule' in modules assert 'module_b.submodule' in objects - assert objects['module_a.submodule.ModTopLevel'] == ('module', 'class') - assert objects['module_a.submodule.ModTopLevel.mod_child_1'] == ('module', 'method') - assert objects['module_a.submodule.ModTopLevel.mod_child_2'] == ('module', 'method') - assert objects['module_b.submodule.ModTopLevel'] == ('module', 'class') - - assert objects['TopLevel'] == ('roles', 'class') - assert objects['top_level'] == ('roles', 'function') - assert objects['NestedParentA'] == ('roles', 'class') - assert objects['NestedParentA.child_1'] == ('roles', 'function') - assert objects['NestedParentA.any_child'] == ('roles', 'function') - assert objects['NestedParentA.NestedChildA'] == ('roles', 'class') - assert objects['NestedParentA.NestedChildA.subchild_1'] == ('roles', 'function') - assert objects['NestedParentA.NestedChildA.subchild_2'] == ('roles', 'function') - assert objects['NestedParentA.child_2'] == ('roles', 'function') - assert objects['NestedParentB'] == ('roles', 'class') - assert objects['NestedParentB.child_1'] == ('roles', 'function') + assert objects['module_a.submodule.ModTopLevel'][2] == 'class' + assert objects['module_a.submodule.ModTopLevel.mod_child_1'][2] == 'method' + assert objects['module_a.submodule.ModTopLevel.mod_child_2'][2] == 'method' + assert objects['module_b.submodule.ModTopLevel'][2] == 'class' + + assert objects['TopLevel'][2] == 'class' + assert objects['top_level'][2] == 'function' + assert objects['NestedParentA'][2] == 'class' + assert objects['NestedParentA.child_1'][2] == 'function' + assert objects['NestedParentA.any_child'][2] == 'function' + assert objects['NestedParentA.NestedChildA'][2] == 'class' + assert objects['NestedParentA.NestedChildA.subchild_1'][2] == 'function' + assert objects['NestedParentA.NestedChildA.subchild_2'][2] == 'function' + assert objects['NestedParentA.child_2'][2] == 'function' + assert objects['NestedParentB'][2] == 'class' + assert objects['NestedParentB.child_1'][2] == 'function' @pytest.mark.sphinx('dummy', testroot='domain-js') @@ -120,21 +120,28 @@ def find_obj(mod_name, prefix, obj_name, obj_type, searchmode=0): assert (find_obj(None, None, 'NONEXISTANT', 'class') == (None, None)) assert (find_obj(None, None, 'NestedParentA', 'class') == - ('NestedParentA', ('roles', 'class'))) + ('NestedParentA', ('roles', 'nestedparenta', 'class'))) assert (find_obj(None, None, 'NestedParentA.NestedChildA', 'class') == - ('NestedParentA.NestedChildA', ('roles', 'class'))) + ('NestedParentA.NestedChildA', + ('roles', 'nestedparenta-nestedchilda', 'class'))) assert (find_obj(None, 'NestedParentA', 'NestedChildA', 'class') == - ('NestedParentA.NestedChildA', ('roles', 'class'))) + ('NestedParentA.NestedChildA', + ('roles', 'nestedparenta-nestedchilda', 'class'))) assert (find_obj(None, None, 'NestedParentA.NestedChildA.subchild_1', 'func') == - ('NestedParentA.NestedChildA.subchild_1', ('roles', 'function'))) + ('NestedParentA.NestedChildA.subchild_1', + ('roles', 'nestedparenta-nestedchilda-subchild-1', 'function'))) assert (find_obj(None, 'NestedParentA', 'NestedChildA.subchild_1', 'func') == - ('NestedParentA.NestedChildA.subchild_1', ('roles', 'function'))) + ('NestedParentA.NestedChildA.subchild_1', + ('roles', 'nestedparenta-nestedchilda-subchild-1', 'function'))) assert (find_obj(None, 'NestedParentA.NestedChildA', 'subchild_1', 'func') == - ('NestedParentA.NestedChildA.subchild_1', ('roles', 'function'))) + ('NestedParentA.NestedChildA.subchild_1', + ('roles', 'nestedparenta-nestedchilda-subchild-1', 'function'))) assert (find_obj('module_a.submodule', 'ModTopLevel', 'mod_child_2', 'meth') == - ('module_a.submodule.ModTopLevel.mod_child_2', ('module', 'method'))) + ('module_a.submodule.ModTopLevel.mod_child_2', + ('module', 'module-a-submodule-modtoplevel-mod-child-2', 'method'))) assert (find_obj('module_b.submodule', 'ModTopLevel', 'module_a.submodule', 'mod') == - ('module_a.submodule', ('module', 'module'))) + ('module_a.submodule', + ('module', 'module-module-a-submodule', 'module'))) def test_get_full_qualified_name(): @@ -198,7 +205,7 @@ def test_js_class(app): [desc_parameterlist, ()])], [desc_content, ()])])) assert_node(doctree[0], addnodes.index, - entries=[("single", "Application() (class)", "Application", "", None)]) + entries=[("single", "Application() (class)", "application", "", None)]) assert_node(doctree[1], addnodes.desc, domain="js", objtype="class", noindex=False) From dd8088f5aae7919e0977a47ced1e34a75f2c7ae1 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 23 Feb 2020 20:02:05 +0900 Subject: [PATCH 11/72] Update CHANGES --- CHANGES | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index e1c590adbef..10495da188b 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,10 @@ Incompatible changes * Due to the scoping changes for :rst:dir:`productionlist` some uses of :rst:role:`token` must be modified to include the scope which was previously ignored. +* #6903: js domain: Internal data structure has changed. Both objects and + modules have node_id for cross reference +* #7210: js domain: Non intended behavior is removed such as ``parseInt_`` links + to ``.. js:function:: parseInt`` Deprecated ---------- From 0ef797ff65576d8c0e08120b60be3a2b961b8f81 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 1 Mar 2020 02:10:46 +0900 Subject: [PATCH 12/72] refactor: js domain: Change make_old_*_id() to methods --- sphinx/domains/javascript.py | 38 +++++++++++++++++------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py index d7e44ee606b..d510d790342 100644 --- a/sphinx/domains/javascript.py +++ b/sphinx/domains/javascript.py @@ -34,24 +34,6 @@ logger = logging.getLogger(__name__) -def make_old_jsmod_id(modname: str) -> str: - """Generate old styled node_id for JS modules. - - .. note:: Old Styled node_id was used until Sphinx-3.0. - This will be removed in Sphinx-5.0. - """ - return 'module-' + modname - - -def make_old_jsobj_id(fullname: str) -> str: - """Generate old styled node_id for JS objects. - - .. note:: Old Styled node_id was used until Sphinx-3.0. - This will be removed in Sphinx-5.0. - """ - return fullname.replace('$', '_S_') - - class JSObject(ObjectDescription): """ Description of a JavaScript object. @@ -129,7 +111,7 @@ def add_target_and_index(self, name_obj: Tuple[str, str], sig: str, # Assign old styled node_id not to break old hyperlinks (if possible) # Note: Will be removed in Sphinx-5.0 (RemovedInSphinx50Warning) - old_node_id = make_old_jsobj_id(fullname) + old_node_id = self.make_old_id(fullname) if old_node_id not in self.state.document.ids and old_node_id not in signode['ids']: signode['ids'].append(old_node_id) @@ -211,6 +193,14 @@ def after_content(self) -> None: self.env.ref_context['js:object'] = (objects[-1] if len(objects) > 0 else None) + def make_old_id(self, fullname: str) -> str: + """Generate old styled node_id for JS objects. + + .. note:: Old Styled node_id was used until Sphinx-3.0. + This will be removed in Sphinx-5.0. + """ + return fullname.replace('$', '_S_') + class JSCallable(JSObject): """Description of a JavaScript function, method or constructor.""" @@ -282,7 +272,7 @@ def run(self) -> List[Node]: # Assign old styled node_id not to break old hyperlinks (if possible) # Note: Will be removed in Sphinx-5.0 (RemovedInSphinx50Warning) - old_node_id = make_old_jsmod_id(mod_name) + old_node_id = self.make_old_id(mod_name) if old_node_id not in self.state.document.ids and old_node_id not in target['ids']: target['ids'].append(old_node_id) @@ -293,6 +283,14 @@ def run(self) -> List[Node]: ret.append(inode) return ret + def make_old_id(self, modname: str) -> str: + """Generate old styled node_id for JS modules. + + .. note:: Old Styled node_id was used until Sphinx-3.0. + This will be removed in Sphinx-5.0. + """ + return 'module-' + modname + class JSXRefRole(XRefRole): def process_link(self, env: BuildEnvironment, refnode: Element, From ff4352e7941f64905a3ff76aa058f9ba404165e3 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 29 Feb 2020 18:22:34 +0900 Subject: [PATCH 13/72] rst domain: Generate node_id for objects in the right way --- CHANGES | 4 ++ sphinx/domains/rst.py | 103 +++++++++++++++++++++++++-------------- tests/test_domain_rst.py | 8 +-- 3 files changed, 74 insertions(+), 41 deletions(-) diff --git a/CHANGES b/CHANGES index 9409e16aa3f..bb7659a2ed0 100644 --- a/CHANGES +++ b/CHANGES @@ -27,6 +27,10 @@ Incompatible changes modules have node_id for cross reference * #7210: js domain: Non intended behavior is removed such as ``parseInt_`` links to ``.. js:function:: parseInt`` +* #6903: rst domain: Internal data structure has changed. Now objects have + node_id for cross reference +* #7229: rst domain: Non intended behavior is removed such as ``numref_`` links + to ``.. rst:role:: numref`` Deprecated ---------- diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py index c0117d89feb..e25b319368f 100644 --- a/sphinx/domains/rst.py +++ b/sphinx/domains/rst.py @@ -25,7 +25,7 @@ from sphinx.locale import _, __ from sphinx.roles import XRefRole from sphinx.util import logging -from sphinx.util.nodes import make_refnode +from sphinx.util.nodes import make_id, make_refnode logger = logging.getLogger(__name__) @@ -39,23 +39,35 @@ class ReSTMarkup(ObjectDescription): """ def add_target_and_index(self, name: str, sig: str, signode: desc_signature) -> None: - targetname = self.objtype + '-' + name - if targetname not in self.state.document.ids: - signode['names'].append(targetname) - signode['ids'].append(targetname) - self.state.document.note_explicit_target(signode) + node_id = make_id(self.env, self.state.document, self.objtype, name) + signode['ids'].append(node_id) - domain = cast(ReSTDomain, self.env.get_domain('rst')) - domain.note_object(self.objtype, name, location=signode) + # Assign old styled node_id not to break old hyperlinks (if possible) + # Note: Will be removed in Sphinx-5.0 (RemovedInSphinx50Warning) + old_node_id = self.make_old_id(name) + if old_node_id not in self.state.document.ids and old_node_id not in signode['ids']: + signode['ids'].append(old_node_id) + + self.state.document.note_explicit_target(signode) + + domain = cast(ReSTDomain, self.env.get_domain('rst')) + domain.note_object(self.objtype, name, node_id, location=signode) indextext = self.get_index_text(self.objtype, name) if indextext: - self.indexnode['entries'].append(('single', indextext, - targetname, '', None)) + self.indexnode['entries'].append(('single', indextext, node_id, '', None)) def get_index_text(self, objectname: str, name: str) -> str: return '' + def make_old_id(self, name: str) -> str: + """Generate old styled node_id for reST markups. + + .. note:: Old Styled node_id was used until Sphinx-3.0. + This will be removed in Sphinx-5.0. + """ + return self.objtype + '-' + name + def parse_directive(d: str) -> Tuple[str, str]: """Parse a directive signature. @@ -127,26 +139,37 @@ def handle_signature(self, sig: str, signode: desc_signature) -> str: return name def add_target_and_index(self, name: str, sig: str, signode: desc_signature) -> None: + domain = cast(ReSTDomain, self.env.get_domain('rst')) + directive_name = self.current_directive - targetname = '-'.join([self.objtype, self.current_directive, name]) - if targetname not in self.state.document.ids: - signode['names'].append(targetname) - signode['ids'].append(targetname) - self.state.document.note_explicit_target(signode) + if directive_name: + prefix = '-'.join([self.objtype, directive_name]) + objname = ':'.join([directive_name, name]) + else: + prefix = self.objtype + objname = name + + node_id = make_id(self.env, self.state.document, prefix, name) + signode['ids'].append(node_id) + + # Assign old styled node_id not to break old hyperlinks (if possible) + # Note: Will be removed in Sphinx-5.0 (RemovedInSphinx50Warning) + old_node_id = self.make_old_id(name) + if old_node_id not in self.state.document.ids and old_node_id not in signode['ids']: + signode['ids'].append(old_node_id) - objname = ':'.join(filter(None, [directive_name, name])) - domain = cast(ReSTDomain, self.env.get_domain('rst')) - domain.note_object(self.objtype, objname, location=signode) + self.state.document.note_explicit_target(signode) + domain.note_object(self.objtype, objname, node_id, location=signode) if directive_name: key = name[0].upper() pair = [_('%s (directive)') % directive_name, _(':%s: (directive option)') % name] - self.indexnode['entries'].append(('pair', '; '.join(pair), targetname, '', key)) + self.indexnode['entries'].append(('pair', '; '.join(pair), node_id, '', key)) else: key = name[0].upper() text = _(':%s: (directive option)') % name - self.indexnode['entries'].append(('single', text, targetname, '', key)) + self.indexnode['entries'].append(('single', text, node_id, '', key)) @property def current_directive(self) -> str: @@ -156,6 +179,14 @@ def current_directive(self) -> str: else: return '' + def make_old_id(self, name: str) -> str: + """Generate old styled node_id for directive options. + + .. note:: Old Styled node_id was used until Sphinx-3.0. + This will be removed in Sphinx-5.0. + """ + return '-'.join([self.objtype, self.current_directive, name]) + class ReSTRole(ReSTMarkup): """ @@ -193,37 +224,36 @@ class ReSTDomain(Domain): } # type: Dict[str, Dict[Tuple[str, str], str]] @property - def objects(self) -> Dict[Tuple[str, str], str]: - return self.data.setdefault('objects', {}) # (objtype, fullname) -> docname + def objects(self) -> Dict[Tuple[str, str], Tuple[str, str]]: + return self.data.setdefault('objects', {}) # (objtype, fullname) -> (docname, node_id) - def note_object(self, objtype: str, name: str, location: Any = None) -> None: + def note_object(self, objtype: str, name: str, node_id: str, location: Any = None) -> None: if (objtype, name) in self.objects: - docname = self.objects[objtype, name] + docname, node_id = self.objects[objtype, name] logger.warning(__('duplicate description of %s %s, other instance in %s') % (objtype, name, docname), location=location) - self.objects[objtype, name] = self.env.docname + self.objects[objtype, name] = (self.env.docname, node_id) def clear_doc(self, docname: str) -> None: - for (typ, name), doc in list(self.objects.items()): + for (typ, name), (doc, node_id) in list(self.objects.items()): if doc == docname: del self.objects[typ, name] def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: # XXX check duplicates - for (typ, name), doc in otherdata['objects'].items(): + for (typ, name), (doc, node_id) in otherdata['objects'].items(): if doc in docnames: - self.objects[typ, name] = doc + self.objects[typ, name] = (doc, node_id) def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, node: pending_xref, contnode: Element ) -> Element: objtypes = self.objtypes_for_role(typ) for objtype in objtypes: - todocname = self.objects.get((objtype, target)) + todocname, node_id = self.objects.get((objtype, target), (None, None)) if todocname: - return make_refnode(builder, fromdocname, todocname, - objtype + '-' + target, + return make_refnode(builder, fromdocname, todocname, node_id, contnode, target + ' ' + objtype) return None @@ -232,17 +262,16 @@ def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Bui ) -> List[Tuple[str, Element]]: results = [] # type: List[Tuple[str, Element]] for objtype in self.object_types: - todocname = self.objects.get((objtype, target)) + todocname, node_id = self.objects.get((objtype, target), (None, None)) if todocname: results.append(('rst:' + self.role_for_objtype(objtype), - make_refnode(builder, fromdocname, todocname, - objtype + '-' + target, + make_refnode(builder, fromdocname, todocname, node_id, contnode, target + ' ' + objtype))) return results def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]: - for (typ, name), docname in self.data['objects'].items(): - yield name, name, typ, docname, typ + '-' + name, 1 + for (typ, name), (docname, node_id) in self.data['objects'].items(): + yield name, name, typ, docname, node_id, 1 def setup(app: Sphinx) -> Dict[str, Any]: @@ -250,7 +279,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: return { 'version': 'builtin', - 'env_version': 1, + 'env_version': 2, 'parallel_read_safe': True, 'parallel_write_safe': True, } diff --git a/tests/test_domain_rst.py b/tests/test_domain_rst.py index 50d9d154f6c..86fe7ef3f2e 100644 --- a/tests/test_domain_rst.py +++ b/tests/test_domain_rst.py @@ -76,7 +76,7 @@ def test_rst_directive_option(app): [desc_content, ()])])) assert_node(doctree[0], entries=[("single", ":foo: (directive option)", - "directive:option--foo", "", "F")]) + "directive-option-foo", "", "F")]) assert_node(doctree[1], addnodes.desc, desctype="directive:option", domain="rst", objtype="directive:option", noindex=False) @@ -90,7 +90,7 @@ def test_rst_directive_option_with_argument(app): [desc_content, ()])])) assert_node(doctree[0], entries=[("single", ":foo: (directive option)", - "directive:option--foo", "", "F")]) + "directive-option-foo", "", "F")]) assert_node(doctree[1], addnodes.desc, desctype="directive:option", domain="rst", objtype="directive:option", noindex=False) @@ -105,7 +105,7 @@ def test_rst_directive_option_type(app): [desc_content, ()])])) assert_node(doctree[0], entries=[("single", ":foo: (directive option)", - "directive:option--foo", "", "F")]) + "directive-option-foo", "", "F")]) assert_node(doctree[1], addnodes.desc, desctype="directive:option", domain="rst", objtype="directive:option", noindex=False) @@ -121,7 +121,7 @@ def test_rst_directive_and_directive_option(app): desc)])])) assert_node(doctree[1][1][0], entries=[("pair", "foo (directive); :bar: (directive option)", - "directive:option-foo-bar", "", "B")]) + "directive-option-foo-bar", "", "B")]) assert_node(doctree[1][1][1], ([desc_signature, desc_name, ":bar:"], [desc_content, ()])) assert_node(doctree[1][1][1], addnodes.desc, desctype="directive:option", From 3378cae82f1567f67dc82c8c4715a64604e8a355 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 1 Mar 2020 17:43:55 +0900 Subject: [PATCH 14/72] Update CHANGES for PR #7103 --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index bb7659a2ed0..b2235619ab2 100644 --- a/CHANGES +++ b/CHANGES @@ -68,6 +68,7 @@ Features added * #7144: Add CSS class indicating its domain for each desc node * #7211: latex: Use babel for Chinese document when using XeLaTeX * #7220: genindex: Show "main" index entries at first +* #7103: linkcheck: writes all links to ``output.json`` Bugs fixed ---------- From 95b98d97d5c53031337b1ef5540ee1f3f718534b Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 1 Mar 2020 17:44:10 +0900 Subject: [PATCH 15/72] test: clean up --- tests/test_build_linkcheck.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_build_linkcheck.py b/tests/test_build_linkcheck.py index 5220b480e2a..54bde6b6858 100644 --- a/tests/test_build_linkcheck.py +++ b/tests/test_build_linkcheck.py @@ -67,9 +67,6 @@ def test_defaults_json(app, status, warning): assert dnerow['status'] == 'broken' assert dnerow['code'] == 0 assert dnerow['uri'] == 'https://localhost:7777/doesnotexist' - #assert dnerow['info'] == "HTTPSConnectionPool(host='localhost', port=7777): Max retries exceeded with url: /doesnotexist (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 111] Connection refused'))" - #assert "[Errno 111] Connection refused" in dnerow['info'] - #assert "[WinError 10061] No connection could be made because the target machine actively refused it'" in dnerow['info'] assert rowsby['https://www.google.com/image2.png'] == { 'filename': 'links.txt', 'lineno': 16, From 99dd7b3b42c8b7b797415a19834ce4125be602ca Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 1 Mar 2020 22:44:42 +0900 Subject: [PATCH 16/72] refactor: Sorting index entries --- sphinx/environment/adapters/indexentries.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/environment/adapters/indexentries.py b/sphinx/environment/adapters/indexentries.py index dbc94f846a8..5af213932ea 100644 --- a/sphinx/environment/adapters/indexentries.py +++ b/sphinx/environment/adapters/indexentries.py @@ -94,9 +94,9 @@ def keyfunc0(entry: Tuple[str, str]) -> Tuple[bool, str]: return (not main, uri) # show main entries at first for indexentry in new.values(): - indexentry[0] = sorted(indexentry[0], key=keyfunc0) + indexentry[0].sort(key=keyfunc0) for subentry in indexentry[1].values(): - subentry[0] = sorted(subentry[0], key=keyfunc0) # type: ignore + subentry[0].sort(key=keyfunc0) # type: ignore # sort the index entries; put all symbols at the front, even those # following the letters in ASCII, this is where the chr(127) comes from From fbfaf41e8309580b51ee0bb78e6f46ebcaf30633 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 29 Feb 2020 17:21:48 +0900 Subject: [PATCH 17/72] std domain: Generate node_id for generic objects in the right way --- sphinx/domains/std.py | 25 ++++++++++++++++++++----- tests/test_build_epub.py | 10 +++++++--- tests/test_build_html.py | 2 +- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index abf3be71607..d7239f79d78 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -66,9 +66,17 @@ def handle_signature(self, sig: str, signode: desc_signature) -> str: return name def add_target_and_index(self, name: str, sig: str, signode: desc_signature) -> None: - targetname = '%s-%s' % (self.objtype, name) - signode['ids'].append(targetname) + node_id = make_id(self.env, self.state.document, self.objtype, name) + signode['ids'].append(node_id) + + # Assign old styled node_id not to break old hyperlinks (if possible) + # Note: Will be removed in Sphinx-5.0 (RemovedInSphinx50Warning) + old_node_id = self.make_old_id(name) + if old_node_id not in self.state.document.ids and old_node_id not in signode['ids']: + signode['ids'].append(old_node_id) + self.state.document.note_explicit_target(signode) + if self.indextemplate: colon = self.indextemplate.find(':') if colon != -1: @@ -77,11 +85,18 @@ def add_target_and_index(self, name: str, sig: str, signode: desc_signature) -> else: indextype = 'single' indexentry = self.indextemplate % (name,) - self.indexnode['entries'].append((indextype, indexentry, - targetname, '', None)) + self.indexnode['entries'].append((indextype, indexentry, node_id, '', None)) std = cast(StandardDomain, self.env.get_domain('std')) - std.note_object(self.objtype, name, targetname, location=signode) + std.note_object(self.objtype, name, node_id, location=signode) + + def make_old_id(self, name: str) -> str: + """Generate old styled node_id for generic objects. + + .. note:: Old Styled node_id was used until Sphinx-3.0. + This will be removed in Sphinx-5.0. + """ + return self.objtype + '-' + name class EnvVar(GenericObject): diff --git a/tests/test_build_epub.py b/tests/test_build_epub.py index a5780b04f9b..a61c3cbbb60 100644 --- a/tests/test_build_epub.py +++ b/tests/test_build_epub.py @@ -320,9 +320,13 @@ def test_epub_anchor_id(app): app.build() html = (app.outdir / 'index.xhtml').read_text() - assert '

blah blah blah

' in html - assert '

blah blah blah

' in html - assert 'see ' in html + assert ('

' + '' + 'blah blah blah

' in html) + assert ('' + '' + '

blah blah blah

' in html) + assert 'see
' in html @pytest.mark.sphinx('epub', testroot='html_assets') diff --git a/tests/test_build_html.py b/tests/test_build_html.py index c53d0b97d9b..4c2618af9a9 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -218,7 +218,7 @@ def test_html4_output(app, status, warning): "[@class='rfc reference external']/strong", 'RFC 1'), (".//a[@href='https://tools.ietf.org/html/rfc1.html']" "[@class='rfc reference external']/strong", 'Request for Comments #1'), - (".//a[@href='objects.html#envvar-HOME']" + (".//a[@href='objects.html#envvar-home']" "[@class='reference internal']/code/span[@class='pre']", 'HOME'), (".//a[@href='#with']" "[@class='reference internal']/code/span[@class='pre']", '^with$'), From dfb2d6407ecb720ee1fc07f5e37ef1f920972e69 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 29 Feb 2020 17:23:55 +0900 Subject: [PATCH 18/72] std domain: Generate node_id for targets in the right way --- sphinx/domains/std.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index d7239f79d78..dcf72efe8f0 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -139,9 +139,16 @@ class Target(SphinxDirective): def run(self) -> List[Node]: # normalize whitespace in fullname like XRefRole does fullname = ws_re.sub(' ', self.arguments[0].strip()) - targetname = '%s-%s' % (self.name, fullname) - node = nodes.target('', '', ids=[targetname]) + node_id = make_id(self.env, self.state.document, self.name, fullname) + node = nodes.target('', '', ids=[node_id]) self.set_source_info(node) + + # Assign old styled node_id not to break old hyperlinks (if possible) + # Note: Will be removed in Sphinx-5.0 (RemovedInSphinx50Warning) + old_node_id = self.make_old_id(fullname) + if old_node_id not in self.state.document.ids and old_node_id not in node['ids']: + node['ids'].append(old_node_id) + self.state.document.note_explicit_target(node) ret = [node] # type: List[Node] if self.indextemplate: @@ -151,18 +158,25 @@ def run(self) -> List[Node]: if colon != -1: indextype = indexentry[:colon].strip() indexentry = indexentry[colon + 1:].strip() - inode = addnodes.index(entries=[(indextype, indexentry, - targetname, '', None)]) + inode = addnodes.index(entries=[(indextype, indexentry, node_id, '', None)]) ret.insert(0, inode) name = self.name if ':' in self.name: _, name = self.name.split(':', 1) std = cast(StandardDomain, self.env.get_domain('std')) - std.note_object(name, fullname, targetname, location=node) + std.note_object(name, fullname, node_id, location=node) return ret + def make_old_id(self, name: str) -> str: + """Generate old styled node_id for targets. + + .. note:: Old Styled node_id was used until Sphinx-3.0. + This will be removed in Sphinx-5.0. + """ + return self.name + '-' + name + class Cmdoption(ObjectDescription): """ From 34057601f46d1fd72f768bba3ff21dc3331a4093 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Tue, 3 Mar 2020 00:07:19 +0900 Subject: [PATCH 19/72] Close #7165: autodoc: Support Annotated type (PEP-593) --- CHANGES | 1 + sphinx/util/typing.py | 5 ++++- .../roots/test-ext-autodoc/target/annotated.py | 6 ++++++ tests/test_autodoc.py | 18 ++++++++++++++++++ tests/test_util_typing.py | 8 ++++++++ 5 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 tests/roots/test-ext-autodoc/target/annotated.py diff --git a/CHANGES b/CHANGES index b2235619ab2..b4710da92b7 100644 --- a/CHANGES +++ b/CHANGES @@ -52,6 +52,7 @@ Features added not to document inherited members of the class and uppers * #6830: autodoc: consider a member private if docstring contains ``:meta private:`` in info-field-list +* #7165: autodoc: Support Annotated type (PEP-593) * #6558: glossary: emit a warning for duplicated glossary entry * #3106: domain: Register hyperlink target for index page automatically * #6558: std domain: emit a warning for duplicated generic objects diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py index ccceefed6c8..eb38d232c90 100644 --- a/sphinx/util/typing.py +++ b/sphinx/util/typing.py @@ -49,7 +49,8 @@ def stringify(annotation: Any) -> str: return repr(annotation) elif annotation is NoneType: # type: ignore return 'None' - elif getattr(annotation, '__module__', None) == 'builtins': + elif (getattr(annotation, '__module__', None) == 'builtins' and + hasattr(annotation, '__qualname__')): return annotation.__qualname__ elif annotation is Ellipsis: return '...' @@ -88,6 +89,8 @@ def _stringify_py37(annotation: Any) -> str: args = ', '.join(stringify(a) for a in annotation.__args__[:-1]) returns = stringify(annotation.__args__[-1]) return '%s[[%s], %s]' % (qualname, args, returns) + elif str(annotation).startswith('typing.Annotated'): # for py39+ + return stringify(annotation.__args__[0]) elif annotation._special: return qualname else: diff --git a/tests/roots/test-ext-autodoc/target/annotated.py b/tests/roots/test-ext-autodoc/target/annotated.py new file mode 100644 index 00000000000..42718825668 --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/annotated.py @@ -0,0 +1,6 @@ +from typing import Annotated + + +def hello(name: Annotated[str, "attribute"]) -> None: + """docstring""" + pass diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index d81df824563..b001de80477 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -1524,6 +1524,24 @@ def test_autodoc_typed_instance_variables(app): ] +@pytest.mark.skipif(sys.version_info < (3, 9), reason='py39+ is required.') +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_Annotated(app): + options = {"members": None} + actual = do_autodoc(app, 'module', 'target.annotated', options) + assert list(actual) == [ + '', + '.. py:module:: target.annotated', + '', + '', + '.. py:function:: hello(name: str) -> None', + ' :module: target.annotated', + '', + ' docstring', + ' ' + ] + + @pytest.mark.sphinx('html', testroot='pycode-egg') def test_autodoc_for_egged_code(app): options = {"members": None, diff --git a/tests/test_util_typing.py b/tests/test_util_typing.py index 9a225f0f1f1..f6fd35fb037 100644 --- a/tests/test_util_typing.py +++ b/tests/test_util_typing.py @@ -12,6 +12,8 @@ from numbers import Integral from typing import Any, Dict, List, TypeVar, Union, Callable, Tuple, Optional +import pytest + from sphinx.util.typing import stringify @@ -42,6 +44,12 @@ def test_stringify_type_hints_containers(): assert stringify(List[Dict[str, Tuple]]) == "List[Dict[str, Tuple]]" +@pytest.mark.skipif(sys.version_info < (3, 9), reason='python 3.9+ is required.') +def test_stringify_Annotated(): + from typing import Annotated + assert stringify(Annotated[str, "foo", "bar"]) == "str" + + def test_stringify_type_hints_string(): assert stringify("int") == "int" assert stringify("str") == "str" From 728c6cb5eeafc6448d76ea5174b5b565ef9935c7 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Wed, 4 Mar 2020 00:24:08 +0900 Subject: [PATCH 20/72] refactor: Update type annotation for python domain --- sphinx/domains/python.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index c9c8d570455..a9edd85b4d6 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -1034,7 +1034,8 @@ def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: self.modules[modname] = data def find_obj(self, env: BuildEnvironment, modname: str, classname: str, - name: str, type: str, searchmode: int = 0) -> List[Tuple[str, Any]]: + name: str, type: str, searchmode: int = 0 + ) -> List[Tuple[str, Tuple[str, str]]]: """Find a Python object for "name", perhaps using the given module and/or classname. Returns a list of (name, object entry) tuples. """ @@ -1045,7 +1046,7 @@ def find_obj(self, env: BuildEnvironment, modname: str, classname: str, if not name: return [] - matches = [] # type: List[Tuple[str, Any]] + matches = [] # type: List[Tuple[str, Tuple[str, str]]] newname = None if searchmode == 1: From 933897c40bc73b4d97652dd01637cdb15175a9c8 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Tue, 3 Mar 2020 23:09:13 +0100 Subject: [PATCH 21/72] tox: passenv: add TERM tox is stupid to not include TERM by default (https://github.com/tox-dev/tox/issues/1441). This makes `tox -e docs` use proper progress indicators. It splits `passenv` into a list to make diffs easier in the future. Output from `tox -e docs` before: ``` Running Sphinx v4.0.0+/4633ab906 loading pickled environment... failed failed: build environment version not current building [mo]: targets for 0 po files that are out of date building [html]: targets for 77 source files that are out of date updating environment: [new config] 77 added, 0 changed, 0 removed reading sources... [ 1%] authors reading sources... [ 2%] changes reading sources... [ 3%] code_of_conduct reading sources... [ 5%] contents reading sources... [ 6%] develop reading sources... [ 7%] development/tutorials/examples/README reading sources... [ 9%] development/tutorials/helloworld reading sources... [ 10%] development/tutorials/index reading sources... [ 11%] development/tutorials/recipe reading sources... [ 12%] development/tutorials/todo reading sources... [ 14%] devguide reading sources... [ 15%] examples reading sources... [ 16%] extdev/appapi reading sources... [ 18%] extdev/builderapi reading sources... [ 19%] extdev/collectorapi reading sources... [ 20%] extdev/deprecated reading sources... [ 22%] extdev/domainapi reading sources... [ 23%] extdev/envapi reading sources... [ 24%] extdev/i18n reading sources... [ 25%] extdev/index reading sources... [ 27%] extdev/logging reading sources... [ 28%] extdev/markupapi reading sources... [ 29%] extdev/nodes reading sources... [ 31%] extdev/parserapi reading sources... [ 32%] extdev/projectapi reading sources... [ 33%] extdev/utils reading sources... [ 35%] faq reading sources... [ 36%] glossary reading sources... [ 37%] intro reading sources... [ 38%] latex reading sources... [ 40%] man/index reading sources... [ 41%] man/sphinx-apidoc reading sources... [ 42%] man/sphinx-autogen reading sources... [ 44%] man/sphinx-build reading sources... [ 45%] man/sphinx-quickstart reading sources... [ 46%] templating reading sources... [ 48%] theming reading sources... [ 49%] usage/advanced/intl reading sources... [ 50%] usage/advanced/setuptools reading sources... [ 51%] usage/advanced/websupport/api /tmp/tox/home/daniel/Vcs/sphinx/docs/lib/python3.8/site-packages/sphinxcontrib/websupport/__init__.py:18: RemovedInSphinx40Warning: sphinx.util.pycompat.htmlescape is deprecated. Check CHANGES for Sphinx API modifications. from sphinxcontrib.websupport.core import WebSupport # NOQA reading sources... [ 53%] usage/advanced/websupport/index reading sources... [ 54%] usage/advanced/websupport/quickstart reading sources... [ 55%] usage/advanced/websupport/searchadapters reading sources... [ 57%] usage/advanced/websupport/storagebackends reading sources... [ 58%] usage/builders/index reading sources... [ 59%] usage/configuration reading sources... [ 61%] usage/extensions/autodoc reading sources... [ 62%] usage/extensions/autosectionlabel reading sources... [ 63%] usage/extensions/autosummary reading sources... [ 64%] usage/extensions/coverage reading sources... [ 66%] usage/extensions/doctest reading sources... [ 67%] usage/extensions/duration reading sources... [ 68%] usage/extensions/example_google reading sources... [ 70%] usage/extensions/example_numpy reading sources... [ 71%] usage/extensions/extlinks reading sources... [ 72%] usage/extensions/githubpages reading sources... [ 74%] usage/extensions/graphviz reading sources... [ 75%] usage/extensions/ifconfig reading sources... [ 76%] usage/extensions/imgconverter reading sources... [ 77%] usage/extensions/index reading sources... [ 79%] usage/extensions/inheritance reading sources... [ 80%] usage/extensions/intersphinx reading sources... [ 81%] usage/extensions/linkcode reading sources... [ 83%] usage/extensions/math reading sources... [ 84%] usage/extensions/napoleon reading sources... [ 85%] usage/extensions/todo reading sources... [ 87%] usage/extensions/viewcode reading sources... [ 88%] usage/installation reading sources... [ 89%] usage/markdown reading sources... [ 90%] usage/quickstart reading sources... [ 92%] usage/restructuredtext/basics reading sources... [ 93%] usage/restructuredtext/directives reading sources... [ 94%] usage/restructuredtext/domains reading sources... [ 96%] usage/restructuredtext/field-lists reading sources... [ 97%] usage/restructuredtext/index reading sources... [ 98%] usage/restructuredtext/roles reading sources... [100%] usage/theming looking for now-outdated files... none found pickling environment... done checking consistency... done preparing documents... done writing output... [ 1%] authors writing output... [ 2%] changes writing output... [ 3%] code_of_conduct writing output... [ 5%] contents writing output... [ 6%] develop writing output... [ 7%] development/tutorials/examples/README writing output... [ 9%] development/tutorials/helloworld writing output... [ 10%] development/tutorials/index writing output... [ 11%] development/tutorials/recipe writing output... [ 12%] development/tutorials/todo writing output... [ 14%] devguide writing output... [ 15%] examples writing output... [ 16%] extdev/appapi writing output... [ 18%] extdev/builderapi writing output... [ 19%] extdev/collectorapi writing output... [ 20%] extdev/deprecated writing output... [ 22%] extdev/domainapi writing output... [ 23%] extdev/envapi writing output... [ 24%] extdev/i18n writing output... [ 25%] extdev/index writing output... [ 27%] extdev/logging writing output... [ 28%] extdev/markupapi writing output... [ 29%] extdev/nodes writing output... [ 31%] extdev/parserapi writing output... [ 32%] extdev/projectapi writing output... [ 33%] extdev/utils writing output... [ 35%] faq writing output... [ 36%] glossary writing output... [ 37%] intro writing output... [ 38%] latex writing output... [ 40%] man/index writing output... [ 41%] man/sphinx-apidoc writing output... [ 42%] man/sphinx-autogen writing output... [ 44%] man/sphinx-build writing output... [ 45%] man/sphinx-quickstart writing output... [ 46%] templating writing output... [ 48%] theming writing output... [ 49%] usage/advanced/intl writing output... [ 50%] usage/advanced/setuptools writing output... [ 51%] usage/advanced/websupport/api writing output... [ 53%] usage/advanced/websupport/index writing output... [ 54%] usage/advanced/websupport/quickstart writing output... [ 55%] usage/advanced/websupport/searchadapters writing output... [ 57%] usage/advanced/websupport/storagebackends writing output... [ 58%] usage/builders/index writing output... [ 59%] usage/configuration writing output... [ 61%] usage/extensions/autodoc writing output... [ 62%] usage/extensions/autosectionlabel writing output... [ 63%] usage/extensions/autosummary writing output... [ 64%] usage/extensions/coverage writing output... [ 66%] usage/extensions/doctest writing output... [ 67%] usage/extensions/duration writing output... [ 68%] usage/extensions/example_google writing output... [ 70%] usage/extensions/example_numpy writing output... [ 71%] usage/extensions/extlinks writing output... [ 72%] usage/extensions/githubpages writing output... [ 74%] usage/extensions/graphviz writing output... [ 75%] usage/extensions/ifconfig writing output... [ 76%] usage/extensions/imgconverter writing output... [ 77%] usage/extensions/index writing output... [ 79%] usage/extensions/inheritance writing output... [ 80%] usage/extensions/intersphinx writing output... [ 81%] usage/extensions/linkcode writing output... [ 83%] usage/extensions/math writing output... [ 84%] usage/extensions/napoleon writing output... [ 85%] usage/extensions/todo writing output... [ 87%] usage/extensions/viewcode writing output... [ 88%] usage/installation writing output... [ 89%] usage/markdown writing output... [ 90%] usage/quickstart writing output... [ 92%] usage/restructuredtext/basics writing output... [ 93%] usage/restructuredtext/directives writing output... [ 94%] usage/restructuredtext/domains writing output... [ 96%] usage/restructuredtext/field-lists writing output... [ 97%] usage/restructuredtext/index writing output... [ 98%] usage/restructuredtext/roles writing output... [100%] usage/theming generating indices... genindex py-modindexdone highlighting module code... [ 2%] docutils.parsers.rst highlighting module code... [ 4%] logging highlighting module code... [ 7%] sphinx.addnodes highlighting module code... [ 9%] sphinx.application highlighting module code... [ 11%] sphinx.builders highlighting module code... [ 14%] sphinx.builders.changes highlighting module code... [ 16%] sphinx.builders.dirhtml highlighting module code... [ 19%] sphinx.builders.dummy highlighting module code... [ 21%] sphinx.builders.epub3 highlighting module code... [ 23%] sphinx.builders.gettext highlighting module code... [ 26%] sphinx.builders.html highlighting module code... [ 28%] sphinx.builders.latex highlighting module code... [ 30%] sphinx.builders.linkcheck highlighting module code... [ 33%] sphinx.builders.manpage highlighting module code... [ 35%] sphinx.builders.singlehtml highlighting module code... [ 38%] sphinx.builders.texinfo highlighting module code... [ 40%] sphinx.builders.text highlighting module code... [ 42%] sphinx.builders.xml highlighting module code... [ 45%] sphinx.config highlighting module code... [ 47%] sphinx.domains highlighting module code... [ 50%] sphinx.environment highlighting module code... [ 52%] sphinx.environment.collectors highlighting module code... [ 54%] sphinx.errors highlighting module code... [ 57%] sphinx.events highlighting module code... [ 59%] sphinx.ext.autodoc highlighting module code... [ 61%] sphinx.ext.coverage highlighting module code... [ 64%] sphinx.locale highlighting module code... [ 66%] sphinx.parsers highlighting module code... [ 69%] sphinx.project highlighting module code... [ 71%] sphinx.transforms highlighting module code... [ 73%] sphinx.transforms.post_transforms highlighting module code... [ 76%] sphinx.transforms.post_transforms.images highlighting module code... [ 78%] sphinx.util.docutils highlighting module code... [ 80%] sphinx.util.logging highlighting module code... [ 83%] sphinxcontrib.applehelp highlighting module code... [ 85%] sphinxcontrib.devhelp highlighting module code... [ 88%] sphinxcontrib.htmlhelp highlighting module code... [ 90%] sphinxcontrib.qthelp highlighting module code... [ 92%] sphinxcontrib.serializinghtml highlighting module code... [ 95%] sphinxcontrib.websupport.core highlighting module code... [ 97%] sphinxcontrib.websupport.search highlighting module code... [100%] sphinxcontrib.websupport.storage writing additional pages... index search opensearchdone copying images... [ 7%] _static/translation.png copying images... [ 15%] _static/more.png copying images... [ 23%] _static/themes/alabaster.png copying images... [ 30%] _static/themes/classic.png copying images... [ 38%] _static/themes/sphinxdoc.png copying images... [ 46%] _static/themes/scrolls.png copying images... [ 53%] _static/themes/agogo.png copying images... [ 61%] _static/themes/traditional.png copying images... [ 69%] _static/themes/nature.png copying images... [ 76%] _static/themes/haiku.png copying images... [ 84%] _static/themes/pyramid.png copying images... [ 92%] _static/themes/bizstyle.png copying images... [100%] _static/themes/sphinx_rtd_theme.png copying downloadable files... [ 50%] usage/extensions/example_google.py copying downloadable files... [100%] usage/extensions/example_numpy.py copying static files... ... done copying extra files... done dumping search index in English (code: en)... done dumping object inventory... done build succeeded. ``` After: ``` Running Sphinx v2.4.3 loading pickled environment... done building [mo]: targets for 0 po files that are out of date building [html]: targets for 0 source files that are out of date updating environment: 0 added, 5 changed, 0 removed /tmp/tox/home/daniel/Vcs/sphinx/docs/lib/python3.8/site-packages/sphinxcontrib/websupport/__init__.py:18: RemovedInSphinx40Warning: sphinx.util.pycompat.htmlescape is deprecated. Check CHANGES for Sphinx API modifications. from sphinxcontrib.websupport.core import WebSupport # NOQA reading sources... [100%] usage/builders/index looking for now-outdated files... none found pickling environment... done checking consistency... done preparing documents... done writing output... [100%] usage/builders/index generating indices... genindex py-modindexdone highlighting module code... [100%] sphinxcontrib.websupport.storage writing additional pages... index search opensearchdone copying downloadable files... [100%] usage/extensions/example_numpy.py copying static files... ... done copying extra files... done dumping search index in English (code: en)... done dumping object inventory... done build succeeded. The HTML pages are in build/sphinx/html. ``` --- tox.ini | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 217cd1febd1..fea21e1a919 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,14 @@ envlist = docs,flake8,mypy,coverage,py{35,36,37,38,39},du{12,13,14,15} [testenv] usedevelop = True passenv = - https_proxy http_proxy no_proxy PERL PERL5LIB PYTEST_ADDOPTS EPUBCHECK_PATH + https_proxy + http_proxy + no_proxy + PERL + PERL5LIB + PYTEST_ADDOPTS + EPUBCHECK_PATH + TERM description = py{35,36,37,38,39}: Run unit tests against {envname}. du{12,13,14}: Run unit tests with the given version of docutils. From c5d42f31846a6bb1a03d6582b447ee34e9569eb3 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 4 Mar 2020 16:16:06 +0100 Subject: [PATCH 22/72] Remove unnecessary/missing "websupport" extra on CircleCI --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6ca62abb730..04c31934003 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,5 +7,5 @@ jobs: steps: - checkout - run: /python3.6/bin/pip install -U pip setuptools - - run: /python3.6/bin/pip install -U .[test,websupport] + - run: /python3.6/bin/pip install -U .[test] - run: make test PYTHON=/python3.6/bin/python From 58b39918fd0793f27cce5c0bd27f3e59e4f6aac8 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Thu, 5 Mar 2020 01:13:43 +0100 Subject: [PATCH 23/72] Use a string with warning in `handle_signature` Previously it would pass in the exception object itself, which then might crash filters that are using `record.msg.startswith` etc. The warning was triggered for me with `sig = foo: int = -1`, where it appears to not handle negative numbers (have not investigated). --- sphinx/domains/python.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index a9edd85b4d6..3e6036bc94b 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -334,7 +334,7 @@ def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str] # it supports to represent optional arguments (ex. "func(foo [, bar])") _pseudo_parse_arglist(signode, arglist) except NotImplementedError as exc: - logger.warning(exc) + logger.warning("could not parse arglist (%r): %s", arglist, exc) _pseudo_parse_arglist(signode, arglist) else: if self.needs_arglist(): From 73e99ab88b361d9a85e8b5669a51398c1e46bd06 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 5 Mar 2020 20:40:35 +0900 Subject: [PATCH 24/72] Update CHANGES and docs for PR #7064 --- CHANGES | 4 ++-- doc/usage/restructuredtext/field-lists.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index b4710da92b7..2224b953993 100644 --- a/CHANGES +++ b/CHANGES @@ -70,6 +70,8 @@ Features added * #7211: latex: Use babel for Chinese document when using XeLaTeX * #7220: genindex: Show "main" index entries at first * #7103: linkcheck: writes all links to ``output.json`` +* #7025: html search: full text search can be disabled for individual document + using ``:nosearch:`` file-wide metadata Bugs fixed ---------- @@ -194,8 +196,6 @@ Features added * #6966: graphviz: Support ``:class:`` option * #6696: html: ``:scale:`` option of image/figure directive not working for SVG images (imagesize-1.2.0 or above is required) -* #7025: html search: full text search can be disabled for individual document - using ``:nosearch:`` file-wide metadata * #6994: imgconverter: Support illustrator file (.ai) to .png conversion * autodoc: Support Positional-Only Argument separator (PEP-570 compliant) * autodoc: Support type annotations for variables diff --git a/doc/usage/restructuredtext/field-lists.rst b/doc/usage/restructuredtext/field-lists.rst index 0d1a476284d..28b3cfe1bbb 100644 --- a/doc/usage/restructuredtext/field-lists.rst +++ b/doc/usage/restructuredtext/field-lists.rst @@ -59,4 +59,4 @@ At the moment, these metadata fields are recognized: .. note:: object search is still available even if `nosearch` option is set. - .. versionadded:: 2.4 + .. versionadded:: 3.0 From 523b653cd9663d4b13ba75b72ab911c93ab5fee8 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 5 Mar 2020 20:51:43 +0900 Subject: [PATCH 25/72] Fix typo --- sphinx/domains/python.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 3a49d674515..ac6ee48399b 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -397,7 +397,7 @@ class PyModulelevel(PyObject): """ def run(self) -> List[Node]: - warnings.warn('PyClassmember is deprecated.', + warnings.warn('PyModulelevel is deprecated.', RemovedInSphinx40Warning) return super().run() From 3e7b18e971755bf022f97943585685b3ce7309f8 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 5 Mar 2020 22:28:35 +0900 Subject: [PATCH 26/72] Unpin coverage package coverage-5.0 expects to set "parallel = True" on config file. refs: https://github.com/nedbat/coveragepy/issues/716#issuecomment-429491441 --- setup.cfg | 1 + tox.ini | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index fdd2fe327f5..62d50f719b6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -61,6 +61,7 @@ markers = [coverage:run] branch = True +parallel = True source = sphinx [coverage:report] diff --git a/tox.ini b/tox.ini index 217cd1febd1..b98c2e584f8 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,6 @@ description = py{35,36,37,38,39}: Run unit tests against {envname}. du{12,13,14}: Run unit tests with the given version of docutils. deps = - coverage < 5.0 # refs: https://github.com/sphinx-doc/sphinx/pull/6924 git+https://github.com/html5lib/html5lib-python ; python_version >= "3.9" # refs: https://github.com/html5lib/html5lib-python/issues/419 du12: docutils==0.12 du13: docutils==0.13.1 From 34460157e7731ae7e214efd5cd2405a2f4f72e61 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 5 Mar 2020 22:57:56 +0900 Subject: [PATCH 27/72] Fix #6895: py domain: Do not emit nitpicky warnings for built-in types --- CHANGES | 1 + sphinx/domains/python.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/CHANGES b/CHANGES index b2235619ab2..4a5f7259690 100644 --- a/CHANGES +++ b/CHANGES @@ -56,6 +56,7 @@ Features added * #3106: domain: Register hyperlink target for index page automatically * #6558: std domain: emit a warning for duplicated generic objects * #6830: py domain: Add new event: :event:`object-description-transform` +* #6895: py domain: Do not emit nitpicky warnings for built-in types * py domain: Support lambda functions in function signature * Support priority of event handlers. For more detail, see :py:meth:`.Sphinx.connect()` diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index c9c8d570455..90d28cc2dfa 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -8,7 +8,10 @@ :license: BSD, see LICENSE for details. """ +import builtins +import inspect import re +import typing import warnings from inspect import Parameter from typing import Any, Dict, Iterable, Iterator, List, Tuple @@ -1173,11 +1176,37 @@ def get_full_qualified_name(self, node: Element) -> str: return '.'.join(filter(None, [modname, clsname, target])) +def builtin_resolver(app: Sphinx, env: BuildEnvironment, + node: pending_xref, contnode: Element) -> Element: + """Do not emit nitpicky warnings for built-in types.""" + def istyping(s: str) -> bool: + if s.startswith('typing.'): + s = s.split('.', 1)[1] + + return s in typing.__all__ # type: ignore + + if node.get('refdomain') != 'py': + return None + elif node.get('reftype') == 'obj' and node.get('reftarget') == 'None': + return contnode + elif node.get('reftype') in ('class', 'exc'): + reftarget = node.get('reftarget') + if inspect.isclass(getattr(builtins, reftarget, None)): + # built-in class + return contnode + elif istyping(reftarget): + # typing class + return contnode + + return None + + def setup(app: Sphinx) -> Dict[str, Any]: app.setup_extension('sphinx.directives') app.add_domain(PythonDomain) app.connect('object-description-transform', filter_meta_fields) + app.connect('missing-reference', builtin_resolver, priority=900) return { 'version': 'builtin', From 6e974cd75d8c51981a01bf1ac952b18dd73119f3 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 24 Feb 2020 12:21:38 +0900 Subject: [PATCH 28/72] Add a procedure to update docker image --- utils/bump_docker.sh | 16 ++++++++++++++++ utils/release-checklist | 2 ++ 2 files changed, 18 insertions(+) create mode 100755 utils/bump_docker.sh diff --git a/utils/bump_docker.sh b/utils/bump_docker.sh new file mode 100755 index 00000000000..436aaef10aa --- /dev/null +++ b/utils/bump_docker.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +set -ex + +if [ -z "$1" ]; then + echo "bump_docker.sh [VERSION]" + exit +fi + +cd docker +git checkout . +sed -i "" -e "s/==[0-9.]\{1,\}/==$1/" base/Dockerfile +sed -i "" -e "s/==[0-9.]\{1,\}/==$1/" latexpdf/Dockerfile +git commit -am "Bump to $1" +git tag $1 +git push origin master --tags diff --git a/utils/release-checklist b/utils/release-checklist index 94773e458b1..5a60e59c84c 100644 --- a/utils/release-checklist +++ b/utils/release-checklist @@ -14,6 +14,7 @@ for stable releases * ``twine check dist/Sphinx-*`` * ``twine upload dist/Sphinx-* --sign --identity [your GPG key]`` * open https://pypi.org/project/Sphinx/ and check there are no obvious errors +* ``sh utils/bump_docker.sh X.Y.Z`` * ``git tag vX.Y.Z`` * ``python utils/bump_version.py --in-develop X.Y.Zb0`` (ex. 1.5.3b0) * Check diff by ``git diff`` @@ -97,6 +98,7 @@ for major releases * ``twine check dist/Sphinx-*`` * ``twine upload dist/Sphinx-* --sign --identity [your GPG key]`` * open https://pypi.org/project/Sphinx/ and check there are no obvious errors +* ``sh utils/bump_docker.sh X.Y.Z`` * ``git tag vX.Y.0`` * ``python utils/bump_version.py --in-develop X.Y.1b0`` (ex. 1.6.1b0) * Check diff by ``git diff`` From a4dfb27e089dfe7ee9562efb04764812ccde12f1 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 24 Feb 2020 12:22:39 +0900 Subject: [PATCH 29/72] gitignore: Add docker/ --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d5dc0b0308e..b72664183d2 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ distribute-* env/ build/ dist/ +docker/ Sphinx.egg-info/ doc/_build/ doc/locale/ From 72ad5f2a98c60d2b1cd856b6b9057ac5b5c85edd Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 5 Mar 2020 23:02:46 +0900 Subject: [PATCH 30/72] Bump to 2.4.4 final --- CHANGES | 19 ++----------------- sphinx/__init__.py | 4 ++-- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/CHANGES b/CHANGES index f2caf578d32..7021b731fff 100644 --- a/CHANGES +++ b/CHANGES @@ -1,17 +1,5 @@ -Release 2.4.4 (in development) -============================== - -Dependencies ------------- - -Incompatible changes --------------------- - -Deprecated ----------- - -Features added --------------- +Release 2.4.4 (released Mar 05, 2020) +===================================== Bugs fixed ---------- @@ -19,9 +7,6 @@ Bugs fixed * #7197: LaTeX: platex cause error to build image directive with target url * #7223: Sphinx builds has been slower since 2.4.0 -Testing --------- - Release 2.4.3 (released Feb 22, 2020) ===================================== diff --git a/sphinx/__init__.py b/sphinx/__init__.py index a7cf04ccfa0..8363edd2abd 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -32,7 +32,7 @@ warnings.filterwarnings('ignore', "'U' mode is deprecated", DeprecationWarning, module='docutils.io') -__version__ = '2.4.4+' +__version__ = '2.4.4' __released__ = '2.4.4' # used when Sphinx builds its own docs #: Version info for better programmatic use. @@ -43,7 +43,7 @@ #: #: .. versionadded:: 1.2 #: Before version 1.2, check the string ``sphinx.__version__``. -version_info = (2, 4, 4, 'beta', 0) +version_info = (2, 4, 4, 'final', 0) package_dir = path.abspath(path.dirname(__file__)) From 4905ebea0f9cb3852fa3ab88cb0fdc06fd2ad0dc Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 5 Mar 2020 23:06:53 +0900 Subject: [PATCH 31/72] Bump version --- CHANGES | 21 +++++++++++++++++++++ sphinx/__init__.py | 6 +++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 7021b731fff..10a02767d64 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,24 @@ +Release 2.4.5 (in development) +============================== + +Dependencies +------------ + +Incompatible changes +-------------------- + +Deprecated +---------- + +Features added +-------------- + +Bugs fixed +---------- + +Testing +-------- + Release 2.4.4 (released Mar 05, 2020) ===================================== diff --git a/sphinx/__init__.py b/sphinx/__init__.py index 8363edd2abd..10e80616911 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -32,8 +32,8 @@ warnings.filterwarnings('ignore', "'U' mode is deprecated", DeprecationWarning, module='docutils.io') -__version__ = '2.4.4' -__released__ = '2.4.4' # used when Sphinx builds its own docs +__version__ = '2.4.5+' +__released__ = '2.4.5' # used when Sphinx builds its own docs #: Version info for better programmatic use. #: @@ -43,7 +43,7 @@ #: #: .. versionadded:: 1.2 #: Before version 1.2, check the string ``sphinx.__version__``. -version_info = (2, 4, 4, 'final', 0) +version_info = (2, 4, 5, 'beta', 0) package_dir = path.abspath(path.dirname(__file__)) From 6e83f0dcb3159da614db53e74b472cf94f34fb17 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 5 Mar 2020 23:46:58 +0900 Subject: [PATCH 32/72] doc: fix a versionchnaged to correct one --- doc/latex.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/latex.rst b/doc/latex.rst index 81f70dc1e57..59a785c1edc 100644 --- a/doc/latex.rst +++ b/doc/latex.rst @@ -817,7 +817,7 @@ Macros multiple paragraphs in header cells of tables. .. versionadded:: 1.6.3 ``\sphinxstylecodecontinued`` and ``\sphinxstylecodecontinues``. - .. versionadded:: 2.4.0 + .. versionadded:: 3.0 ``\sphinxkeyboard`` - ``\sphinxtableofcontents``: it is a wrapper (defined differently in :file:`sphinxhowto.cls` and in From 52ac6f11781464bcad24b21ec2a03557956481c6 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 5 Mar 2020 23:48:15 +0900 Subject: [PATCH 33/72] Update CHANGES for PR #7005 --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 74dc980f5a1..46fe00fa9dc 100644 --- a/CHANGES +++ b/CHANGES @@ -68,6 +68,7 @@ Features added ``no-scaled-link`` class * #7144: Add CSS class indicating its domain for each desc node * #7211: latex: Use babel for Chinese document when using XeLaTeX +* #7005: LaTeX: Add LaTeX styling macro for :rst:role:`kbd` role * #7220: genindex: Show "main" index entries at first * #7103: linkcheck: writes all links to ``output.json`` * #7025: html search: full text search can be disabled for individual document From 4d279fc5adb7cd42b90bda04488bee9ac48f2d82 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 29 Feb 2020 18:17:24 +0900 Subject: [PATCH 34/72] std domain: Generate node_id for cmdoptions in the right way --- sphinx/domains/std.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index dcf72efe8f0..cf331b10a29 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -214,16 +214,18 @@ def handle_signature(self, sig: str, signode: desc_signature) -> str: def add_target_and_index(self, firstname: str, sig: str, signode: desc_signature) -> None: currprogram = self.env.ref_context.get('std:program') for optname in signode.get('allnames', []): - targetname = optname.replace('/', '-') - if not targetname.startswith('-'): - targetname = '-arg-' + targetname + prefixes = ['cmdoption'] if currprogram: - targetname = '-' + currprogram + targetname - targetname = 'cmdoption' + targetname - signode['names'].append(targetname) + prefixes.append(currprogram) + if not optname.startswith(('-', '/')): + prefixes.append('arg') + prefix = '-'.join(prefixes) + node_id = make_id(self.env, self.state.document, prefix, optname) + signode['ids'].append(node_id) - domain = cast(StandardDomain, self.env.get_domain('std')) self.state.document.note_explicit_target(signode) + + domain = cast(StandardDomain, self.env.get_domain('std')) for optname in signode.get('allnames', []): domain.add_program_option(currprogram, optname, self.env.docname, signode['ids'][0]) From c0535d700e5b7f91a567e86ccc2b41faead8da16 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 29 Feb 2020 18:17:43 +0900 Subject: [PATCH 35/72] std domain: Generate node_id for productionlists in the right way --- sphinx/domains/std.py | 34 +++++++++++++++++++++------------- tests/test_build_html.py | 4 ++-- tests/test_domain_std.py | 34 +++++++++++++++++----------------- 3 files changed, 40 insertions(+), 32 deletions(-) diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index cf331b10a29..b2538f0345a 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -489,28 +489,36 @@ def run(self) -> List[Node]: subnode = addnodes.production(rule) subnode['tokenname'] = name.strip() if subnode['tokenname']: - # nodes.make_id converts '_' to '-', - # so we can use '_' to delimit group from name, - # and make sure we don't clash with other IDs. - idname = 'grammar-token-%s_%s' \ - % (nodes.make_id(productionGroup), nodes.make_id(name)) - if idname not in self.state.document.ids: - subnode['ids'].append(idname) - - idnameOld = nodes.make_id('grammar-token-' + name) - if idnameOld not in self.state.document.ids: - subnode['ids'].append(idnameOld) + prefix = 'grammar-token-%s' % productionGroup + node_id = make_id(self.env, self.state.document, prefix, name) + subnode['ids'].append(node_id) + + # Assign old styled node_id not to break old hyperlinks (if possible) + # Note: Will be removed in Sphinx-5.0 (RemovedInSphinx50Warning) + old_node_id = self.make_old_id(name) + if (old_node_id not in self.state.document.ids and + old_node_id not in subnode['ids']): + subnode['ids'].append(old_node_id) + self.state.document.note_implicit_target(subnode, subnode) + if len(productionGroup) != 0: objName = "%s:%s" % (productionGroup, name) else: objName = name - domain.note_object(objtype='token', name=objName, labelid=idname, - location=node) + domain.note_object('token', objName, node_id, location=node) subnode.extend(token_xrefs(tokens, productionGroup)) node.append(subnode) return [node] + def make_old_id(self, token: str) -> str: + """Generate old styled node_id for tokens. + + .. note:: Old Styled node_id was used until Sphinx-3.0. + This will be removed in Sphinx-5.0. + """ + return nodes.make_id('grammar-token-' + token) + class TokenXRefRole(XRefRole): def process_link(self, env: "BuildEnvironment", refnode: Element, has_explicit_title: bool, diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 4c2618af9a9..adb7462dfb7 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -222,7 +222,7 @@ def test_html4_output(app, status, warning): "[@class='reference internal']/code/span[@class='pre']", 'HOME'), (".//a[@href='#with']" "[@class='reference internal']/code/span[@class='pre']", '^with$'), - (".//a[@href='#grammar-token-_try-stmt']" + (".//a[@href='#grammar-token-try-stmt']" "[@class='reference internal']/code/span", '^statement$'), (".//a[@href='#some-label'][@class='reference internal']/span", '^here$'), (".//a[@href='#some-label'][@class='reference internal']/span", '^there$'), @@ -254,7 +254,7 @@ def test_html4_output(app, status, warning): (".//dl/dt[@id='term-boson']", 'boson'), # a production list (".//pre/strong", 'try_stmt'), - (".//pre/a[@href='#grammar-token-_try1-stmt']/code/span", 'try1_stmt'), + (".//pre/a[@href='#grammar-token-try1-stmt']/code/span", 'try1_stmt'), # tests for ``only`` directive (".//p", 'A global substitution.'), (".//p", 'In HTML.'), diff --git a/tests/test_domain_std.py b/tests/test_domain_std.py index 97320829869..1f0024efc14 100644 --- a/tests/test_domain_std.py +++ b/tests/test_domain_std.py @@ -352,23 +352,23 @@ def test_productionlist(app, status, warning): linkText = span.text.strip() cases.append((text, link, linkText)) assert cases == [ - ('A', 'Bare.html#grammar-token-_a', 'A'), - ('B', 'Bare.html#grammar-token-_b', 'B'), - ('P1:A', 'P1.html#grammar-token-p1_a', 'P1:A'), - ('P1:B', 'P1.html#grammar-token-p1_b', 'P1:B'), - ('P2:A', 'P1.html#grammar-token-p1_a', 'P1:A'), - ('P2:B', 'P2.html#grammar-token-p2_b', 'P2:B'), - ('Explicit title A, plain', 'Bare.html#grammar-token-_a', 'MyTitle'), - ('Explicit title A, colon', 'Bare.html#grammar-token-_a', 'My:Title'), - ('Explicit title P1:A, plain', 'P1.html#grammar-token-p1_a', 'MyTitle'), - ('Explicit title P1:A, colon', 'P1.html#grammar-token-p1_a', 'My:Title'), - ('Tilde A', 'Bare.html#grammar-token-_a', 'A'), - ('Tilde P1:A', 'P1.html#grammar-token-p1_a', 'A'), - ('Tilde explicit title P1:A', 'P1.html#grammar-token-p1_a', '~MyTitle'), - ('Tilde, explicit title P1:A', 'P1.html#grammar-token-p1_a', 'MyTitle'), - ('Dup', 'Dup2.html#grammar-token-_dup', 'Dup'), - ('FirstLine', 'firstLineRule.html#grammar-token-_firstline', 'FirstLine'), - ('SecondLine', 'firstLineRule.html#grammar-token-_secondline', 'SecondLine'), + ('A', 'Bare.html#grammar-token-a', 'A'), + ('B', 'Bare.html#grammar-token-b', 'B'), + ('P1:A', 'P1.html#grammar-token-p1-a', 'P1:A'), + ('P1:B', 'P1.html#grammar-token-p1-b', 'P1:B'), + ('P2:A', 'P1.html#grammar-token-p1-a', 'P1:A'), + ('P2:B', 'P2.html#grammar-token-p2-b', 'P2:B'), + ('Explicit title A, plain', 'Bare.html#grammar-token-a', 'MyTitle'), + ('Explicit title A, colon', 'Bare.html#grammar-token-a', 'My:Title'), + ('Explicit title P1:A, plain', 'P1.html#grammar-token-p1-a', 'MyTitle'), + ('Explicit title P1:A, colon', 'P1.html#grammar-token-p1-a', 'My:Title'), + ('Tilde A', 'Bare.html#grammar-token-a', 'A'), + ('Tilde P1:A', 'P1.html#grammar-token-p1-a', 'A'), + ('Tilde explicit title P1:A', 'P1.html#grammar-token-p1-a', '~MyTitle'), + ('Tilde, explicit title P1:A', 'P1.html#grammar-token-p1-a', 'MyTitle'), + ('Dup', 'Dup2.html#grammar-token-dup', 'Dup'), + ('FirstLine', 'firstLineRule.html#grammar-token-firstline', 'FirstLine'), + ('SecondLine', 'firstLineRule.html#grammar-token-secondline', 'SecondLine'), ] text = (app.outdir / 'LineContinuation.html').read_text() From 4ab4100cc57a1fb742f8849a75be3be758437fe8 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 21 Dec 2019 17:39:25 +0900 Subject: [PATCH 36/72] test: Add testcases for latex docclasses --- tests/test_build_latex.py | 50 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index bd4089fe7d0..88c79b5ede7 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -165,6 +165,56 @@ def test_latex_basic(app, status, warning): assert r'\renewcommand{\releasename}{}' in result +@pytest.mark.sphinx('latex', testroot='basic', + confoverrides={ + 'latex_documents': [('index', 'test.tex', 'title', 'author', 'manual')] + }) +def test_latex_basic_manual(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'test.tex').read_text(encoding='utf8') + print(result) + assert r'\def\sphinxdocclass{report}' in result + assert r'\documentclass[letterpaper,10pt,english]{sphinxmanual}' in result + + +@pytest.mark.sphinx('latex', testroot='basic', + confoverrides={ + 'latex_documents': [('index', 'test.tex', 'title', 'author', 'howto')] + }) +def test_latex_basic_howto(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'test.tex').read_text(encoding='utf8') + print(result) + assert r'\def\sphinxdocclass{article}' in result + assert r'\documentclass[letterpaper,10pt,english]{sphinxhowto}' in result + + +@pytest.mark.sphinx('latex', testroot='basic', + confoverrides={ + 'language': 'ja', + 'latex_documents': [('index', 'test.tex', 'title', 'author', 'manual')] + }) +def test_latex_basic_manual_ja(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'test.tex').read_text(encoding='utf8') + print(result) + assert r'\def\sphinxdocclass{jsbook}' in result + assert r'\documentclass[letterpaper,10pt,dvipdfmx]{sphinxmanual}' in result + + +@pytest.mark.sphinx('latex', testroot='basic', + confoverrides={ + 'language': 'ja', + 'latex_documents': [('index', 'test.tex', 'title', 'author', 'howto')] + }) +def test_latex_basic_howto_ja(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'test.tex').read_text(encoding='utf8') + print(result) + assert r'\def\sphinxdocclass{jreport}' in result + assert r'\documentclass[letterpaper,10pt,dvipdfmx]{sphinxhowto}' in result + + @pytest.mark.sphinx('latex', testroot='basic', confoverrides={'language': 'zh'}) def test_latex_additional_settings_for_language_code(app, status, warning): app.builder.build_all() From a13ec4f41cb151d51eb25011082a2668e443d498 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 21 Dec 2019 17:49:49 +0900 Subject: [PATCH 37/72] Support LaTeX Theming; a set of document class settings (refs: #6672) --- CHANGES | 2 + doc/extdev/deprecated.rst | 5 ++ sphinx/builders/latex/__init__.py | 18 +++++--- sphinx/builders/latex/theming.py | 76 +++++++++++++++++++++++++++++++ sphinx/writers/latex.py | 59 ++++++++++++++++++------ 5 files changed, 139 insertions(+), 21 deletions(-) create mode 100644 sphinx/builders/latex/theming.py diff --git a/CHANGES b/CHANGES index 46fe00fa9dc..2b56608455f 100644 --- a/CHANGES +++ b/CHANGES @@ -42,6 +42,7 @@ Deprecated * ``sphinx.testing.path.Path.text()`` * ``sphinx.testing.path.Path.bytes()`` * ``sphinx.util.inspect.getargspec()`` +* ``sphinx.writers.latex.LaTeXWriter.format_docclass()`` Features added -------------- @@ -68,6 +69,7 @@ Features added ``no-scaled-link`` class * #7144: Add CSS class indicating its domain for each desc node * #7211: latex: Use babel for Chinese document when using XeLaTeX +* #6672: LaTeX: Support LaTeX Theming (experimental) * #7005: LaTeX: Add LaTeX styling macro for :rst:role:`kbd` role * #7220: genindex: Show "main" index entries at first * #7103: linkcheck: writes all links to ``output.json`` diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index 484c76aba6e..e98652ed256 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -61,6 +61,11 @@ The following is a list of deprecated interfaces. - 5.0 - ``inspect.getargspec()`` + * - ``sphinx.writers.latex.LaTeXWriter.format_docclass()`` + - 3.0 + - 5.0 + - LaTeX Themes + * - ``decode`` argument of ``sphinx.pycode.ModuleAnalyzer()`` - 2.4 - 4.0 diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index 96e6d13f459..fbe1ff2bfd2 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -21,6 +21,7 @@ from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.builders.latex.constants import ADDITIONAL_SETTINGS, DEFAULT_SETTINGS +from sphinx.builders.latex.theming import Theme, ThemeFactory from sphinx.builders.latex.util import ExtBabel from sphinx.config import Config, ENUM from sphinx.deprecation import RemovedInSphinx40Warning @@ -126,6 +127,7 @@ def init(self) -> None: self.context = {} # type: Dict[str, Any] self.docnames = [] # type: Iterable[str] self.document_data = [] # type: List[Tuple[str, str, str, str, str, bool]] + self.themes = ThemeFactory(self.app) self.usepackages = self.app.registry.latex_packages texescape.init() @@ -227,7 +229,8 @@ def write(self, *ignored: Any) -> None: self.write_stylesheet() for entry in self.document_data: - docname, targetname, title, author, docclass = entry[:5] + docname, targetname, title, author, themename = entry[:5] + theme = self.themes.get(themename) toctree_only = False if len(entry) > 5: toctree_only = entry[5] @@ -243,21 +246,22 @@ def write(self, *ignored: Any) -> None: doctree = self.assemble_doctree( docname, toctree_only, - appendices=(self.config.latex_appendices if docclass != 'howto' else [])) - doctree['docclass'] = docclass + appendices=(self.config.latex_appendices if theme.name != 'howto' else [])) + doctree['docclass'] = theme.docclass doctree['contentsname'] = self.get_contentsname(docname) doctree['tocdepth'] = tocdepth self.post_process_images(doctree) - self.update_doc_context(title, author) + self.update_doc_context(title, author, theme) with progress_message(__("writing")): docsettings._author = author docsettings._title = title docsettings._contentsname = doctree['contentsname'] docsettings._docname = docname - docsettings._docclass = docclass + docsettings._docclass = theme.name doctree.settings = docsettings + docwriter.theme = theme docwriter.write(doctree, destination) def get_contentsname(self, indexfile: str) -> str: @@ -270,9 +274,11 @@ def get_contentsname(self, indexfile: str) -> str: return contentsname - def update_doc_context(self, title: str, author: str) -> None: + def update_doc_context(self, title: str, author: str, theme: Theme) -> None: self.context['title'] = title self.context['author'] = author + self.context['docclass'] = theme.docclass + self.context['wrapperclass'] = theme.wrapperclass def assemble_doctree(self, indexfile: str, toctree_only: bool, appendices: List[str]) -> nodes.document: # NOQA self.docnames = set([indexfile] + appendices) diff --git a/sphinx/builders/latex/theming.py b/sphinx/builders/latex/theming.py new file mode 100644 index 00000000000..c7c76d829b9 --- /dev/null +++ b/sphinx/builders/latex/theming.py @@ -0,0 +1,76 @@ +""" + sphinx.builders.latex.theming + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Theming support for LaTeX builder. + + :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from typing import Dict + +from sphinx.application import Sphinx +from sphinx.config import Config + + +class Theme: + """A set of LaTeX configurations.""" + + def __init__(self, name: str) -> None: + self.name = name + self.docclass = name + self.wrapperclass = name + self.toplevel_sectioning = 'chapter' + + +class BuiltInTheme(Theme): + """A built-in LaTeX theme.""" + + def __init__(self, name: str, config: Config) -> None: + # Note: Don't call supermethod here. + self.name = name + self.latex_docclass = config.latex_docclass # type: Dict[str, str] + + @property + def docclass(self) -> str: # type: ignore + if self.name == 'howto': + return self.latex_docclass.get('howto', 'article') + else: + return self.latex_docclass.get('manual', 'report') + + @property + def wrapperclass(self) -> str: # type: ignore + if self.name in ('manual', 'howto'): + return 'sphinx' + self.name + else: + return self.name + + @property + def toplevel_sectioning(self) -> str: # type: ignore + # we assume LaTeX class provides \chapter command except in case + # of non-Japanese 'howto' case + if self.name == 'howto' and not self.docclass.startswith('j'): + return 'section' + else: + return 'chapter' + + +class ThemeFactory: + """A factory class for LaTeX Themes.""" + + def __init__(self, app: Sphinx) -> None: + self.themes = {} # type: Dict[str, Theme] + self.load_builtin_themes(app.config) + + def load_builtin_themes(self, config: Config) -> None: + """Load built-in themes.""" + self.themes['manual'] = BuiltInTheme('manual', config) + self.themes['howto'] = BuiltInTheme('howto', config) + + def get(self, name: str) -> Theme: + """Get a theme for given *name*.""" + if name not in self.themes: + return Theme(name) + else: + return self.themes[name] diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 317b74fdbb5..cfce52f7fb5 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -23,7 +23,9 @@ from sphinx import addnodes from sphinx import highlighting -from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias +from sphinx.deprecation import ( + RemovedInSphinx40Warning, RemovedInSphinx50Warning, deprecated_alias +) from sphinx.domains import IndexEntry from sphinx.domains.std import StandardDomain from sphinx.errors import SphinxError @@ -43,6 +45,7 @@ if False: # For type annotation from sphinx.builders.latex import LaTeXBuilder + from sphinx.builders.latex.theming import Theme logger = logging.getLogger(__name__) @@ -93,9 +96,16 @@ class LaTeXWriter(writers.Writer): def __init__(self, builder: "LaTeXBuilder") -> None: super().__init__() self.builder = builder + self.theme = None # type: Theme def translate(self) -> None: - visitor = self.builder.create_translator(self.document, self.builder) + try: + visitor = self.builder.create_translator(self.document, self.builder, self.theme) + except TypeError: + warnings.warn('LaTeXTranslator now takes 3rd argument; "theme".', + RemovedInSphinx50Warning) + visitor = self.builder.create_translator(self.document, self.builder) + self.document.walkabout(visitor) self.output = cast(LaTeXTranslator, visitor).astext() @@ -281,9 +291,15 @@ class LaTeXTranslator(SphinxTranslator): # sphinx specific document classes docclasses = ('howto', 'manual') - def __init__(self, document: nodes.document, builder: "LaTeXBuilder") -> None: + def __init__(self, document: nodes.document, builder: "LaTeXBuilder", + theme: "Theme" = None) -> None: super().__init__(document, builder) self.body = [] # type: List[str] + self.theme = theme + + if theme is None: + warnings.warn('LaTeXTranslator now takes 3rd argument; "theme".', + RemovedInSphinx50Warning) # flags self.in_title = 0 @@ -306,21 +322,32 @@ def __init__(self, document: nodes.document, builder: "LaTeXBuilder") -> None: # sort out some elements self.elements = self.builder.context.copy() - # but some have other interface in config file - self.elements['wrapperclass'] = self.format_docclass(document.get('docclass')) - - # we assume LaTeX class provides \chapter command except in case - # of non-Japanese 'howto' case + # initial section names self.sectionnames = LATEXSECTIONNAMES[:] - if document.get('docclass') == 'howto': - docclass = self.config.latex_docclass.get('howto', 'article') - if docclass[0] == 'j': # Japanese class... - pass - else: + + if self.theme: + # new style: control sectioning via theme's setting + # + # .. note:: template variables(elements) are already assigned in builder + docclass = self.theme.docclass + if self.theme.toplevel_sectioning == 'section': self.sectionnames.remove('chapter') else: - docclass = self.config.latex_docclass.get('manual', 'report') - self.elements['docclass'] = docclass + # old style: sectioning control is hard-coded + # but some have other interface in config file + self.elements['wrapperclass'] = self.format_docclass(self.settings.docclass) + + # we assume LaTeX class provides \chapter command except in case + # of non-Japanese 'howto' case + if document.get('docclass') == 'howto': + docclass = self.config.latex_docclass.get('howto', 'article') + if docclass[0] == 'j': # Japanese class... + pass + else: + self.sectionnames.remove('chapter') + else: + docclass = self.config.latex_docclass.get('manual', 'report') + self.elements['docclass'] = docclass # determine top section level self.top_sectionlevel = 1 @@ -472,6 +499,8 @@ def popbody(self) -> List[str]: def format_docclass(self, docclass: str) -> str: """ prepends prefix to sphinx document classes """ + warnings.warn('LaTeXWriter.format_docclass() is deprecated.', + RemovedInSphinx50Warning, stacklevel=2) if docclass in self.docclasses: docclass = 'sphinx' + docclass return docclass From cdbefb600f74c35b31cea3fa1535bdf536dbf76b Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 28 Dec 2019 19:36:55 +0900 Subject: [PATCH 38/72] Support user defined LaTeX themes --- doc/usage/configuration.rst | 38 +++++++++++---- sphinx/builders/latex/__init__.py | 4 +- sphinx/builders/latex/theming.py | 48 +++++++++++++++++-- tests/roots/test-latex-theme/conf.py | 2 + tests/roots/test-latex-theme/index.rst | 2 + .../test-latex-theme/theme/custom/theme.conf | 4 ++ tests/test_build_latex.py | 12 ++++- 7 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 tests/roots/test-latex-theme/conf.py create mode 100644 tests/roots/test-latex-theme/index.rst create mode 100644 tests/roots/test-latex-theme/theme/custom/theme.conf diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index cfc5db065f8..e5ab9c7c033 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -1904,7 +1904,7 @@ These options influence LaTeX output. This value determines how to group the document tree into LaTeX source files. It must be a list of tuples ``(startdocname, targetname, title, author, - documentclass, toctree_only)``, where the items are: + theme, toctree_only)``, where the items are: *startdocname* String that specifies the :term:`document name` of the LaTeX file's master @@ -1926,13 +1926,8 @@ These options influence LaTeX output. applies. Use ``\\and`` to separate multiple authors, as in: ``'John \\and Sarah'`` (backslashes must be Python-escaped to reach LaTeX). - *documentclass* - Normally, one of ``'manual'`` or ``'howto'`` (provided by Sphinx and based - on ``'report'``, resp. ``'article'``; Japanese documents use ``'jsbook'``, - resp. ``'jreport'``.) "howto" (non-Japanese) documents will not get - appendices. Also they have a simpler title page. Other document classes - can be given. Independently of the document class, the "sphinx" package is - always loaded in order to define Sphinx's custom LaTeX commands. + *theme* + LaTeX theme. See :confval:`latex_theme`. *toctree_only* Must be ``True`` or ``False``. If true, the *startdoc* document itself is @@ -2087,6 +2082,33 @@ These options influence LaTeX output. This overrides the files which is provided from Sphinx such as ``sphinx.sty``. +.. confval:: latex_theme + + The "theme" that the LaTeX output should use. It is a collection of settings + for LaTeX output (ex. document class, top level sectioning unit and so on). + + As a built-in LaTeX themes, ``manual`` and ``howto`` are bundled. + + ``manual`` + A LaTeX theme for writing a manual. It imports the ``report`` document + class (Japanese documents use ``jsbook``). + + ``howto`` + A LaTeX theme for writing an article. It imports the ``article`` document + class (Japanese documents use ``jreport`` rather). :confval:`latex_appendices` + is available only for this theme. + + It defaults to ``'manual'``. + + .. versionadded:: 3.0 + +.. confval:: latex_theme_path + + A list of paths that contain custom LaTeX themes as subdirectories. Relative + paths are taken as relative to the configuration directory. + + .. versionadded:: 3.0 + .. _text-options: diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index fbe1ff2bfd2..354b20284b4 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -493,7 +493,7 @@ def default_latex_documents(config: Config) -> List[Tuple[str, str, str, str, st make_filename_from_project(config.project) + '.tex', texescape.escape_abbr(project), texescape.escape_abbr(author), - 'manual')] + config.latex_theme)] def setup(app: Sphinx) -> Dict[str, Any]: @@ -516,6 +516,8 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('latex_show_pagerefs', False, None) app.add_config_value('latex_elements', {}, None) app.add_config_value('latex_additional_files', [], None) + app.add_config_value('latex_theme', 'manual', None, [str]) + app.add_config_value('latex_theme_path', [], None, [list]) app.add_config_value('latex_docclass', default_latex_docclass, None) diff --git a/sphinx/builders/latex/theming.py b/sphinx/builders/latex/theming.py index c7c76d829b9..56f2735f076 100644 --- a/sphinx/builders/latex/theming.py +++ b/sphinx/builders/latex/theming.py @@ -8,10 +8,17 @@ :license: BSD, see LICENSE for details. """ +import configparser +from os import path from typing import Dict from sphinx.application import Sphinx from sphinx.config import Config +from sphinx.errors import ThemeError +from sphinx.locale import __ +from sphinx.util import logging + +logger = logging.getLogger(__name__) class Theme: @@ -56,11 +63,30 @@ def toplevel_sectioning(self) -> str: # type: ignore return 'chapter' +class UserTheme(Theme): + """A user defined LaTeX theme.""" + + def __init__(self, name: str, filename: str) -> None: + self.name = name + self.config = configparser.RawConfigParser() + self.config.read(path.join(filename)) + + try: + self.docclass = self.config.get('theme', 'docclass') + self.wrapperclass = self.config.get('theme', 'wrapperclass') + self.toplevel_sectioning = self.config.get('theme', 'toplevel_sectioning') + except configparser.NoSectionError: + raise ThemeError(__('%r doesn\'t have "theme" setting') % filename) + except configparser.NoOptionError as exc: + raise ThemeError(__('%r doesn\'t have "%s" setting') % (filename, exc.args[0])) + + class ThemeFactory: """A factory class for LaTeX Themes.""" def __init__(self, app: Sphinx) -> None: self.themes = {} # type: Dict[str, Theme] + self.theme_paths = [path.join(app.srcdir, p) for p in app.config.latex_theme_path] self.load_builtin_themes(app.config) def load_builtin_themes(self, config: Config) -> None: @@ -70,7 +96,23 @@ def load_builtin_themes(self, config: Config) -> None: def get(self, name: str) -> Theme: """Get a theme for given *name*.""" - if name not in self.themes: - return Theme(name) - else: + if name in self.themes: return self.themes[name] + else: + theme = self.find_user_theme(name) + if theme: + return theme + else: + return Theme(name) + + def find_user_theme(self, name: str) -> Theme: + """Find a theme named as *name* from latex_theme_path.""" + for theme_path in self.theme_paths: + config_path = path.join(theme_path, name, 'theme.conf') + if path.isfile(config_path): + try: + return UserTheme(name, config_path) + except ThemeError as exc: + logger.warning(exc) + + return None diff --git a/tests/roots/test-latex-theme/conf.py b/tests/roots/test-latex-theme/conf.py new file mode 100644 index 00000000000..196307ae6c8 --- /dev/null +++ b/tests/roots/test-latex-theme/conf.py @@ -0,0 +1,2 @@ +latex_theme = 'custom' +latex_theme_path = ['theme'] diff --git a/tests/roots/test-latex-theme/index.rst b/tests/roots/test-latex-theme/index.rst new file mode 100644 index 00000000000..f5b1d538097 --- /dev/null +++ b/tests/roots/test-latex-theme/index.rst @@ -0,0 +1,2 @@ +latex_theme +=========== diff --git a/tests/roots/test-latex-theme/theme/custom/theme.conf b/tests/roots/test-latex-theme/theme/custom/theme.conf new file mode 100644 index 00000000000..8961fac75ff --- /dev/null +++ b/tests/roots/test-latex-theme/theme/custom/theme.conf @@ -0,0 +1,4 @@ +[theme] +docclass = book +wrapperclass = sphinxbook +toplevel_sectioning = chapter diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index 88c79b5ede7..d55a3b8957e 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -20,7 +20,7 @@ from sphinx.builders.latex import default_latex_documents from sphinx.config import Config -from sphinx.errors import SphinxError +from sphinx.errors import SphinxError, ThemeError from sphinx.testing.util import strip_escseq from sphinx.util import docutils from sphinx.util.osutil import cd, ensuredir @@ -215,6 +215,15 @@ def test_latex_basic_howto_ja(app, status, warning): assert r'\documentclass[letterpaper,10pt,dvipdfmx]{sphinxhowto}' in result +@pytest.mark.sphinx('latex', testroot='latex-theme') +def test_latex_theme(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'python.tex').read_text(encoding='utf8') + print(result) + assert r'\def\sphinxdocclass{book}' in result + assert r'\documentclass[letterpaper,10pt,english]{sphinxbook}' in result + + @pytest.mark.sphinx('latex', testroot='basic', confoverrides={'language': 'zh'}) def test_latex_additional_settings_for_language_code(app, status, warning): app.builder.build_all() @@ -1465,6 +1474,7 @@ def test_default_latex_documents(): 'author': "Wolfgang Schäuble & G'Beckstein."}) config.init_values() config.add('latex_engine', None, True, None) + config.add('latex_theme', 'manual', True, None) expected = [('index', 'stasi.tex', 'STASI™ Documentation', r"Wolfgang Schäuble \& G\textquotesingle{}Beckstein.\@{}", 'manual')] assert default_latex_documents(config) == expected From 7c8b32a85d4e2a65ae13cc8d2b0ad2e0e209d02d Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 6 Mar 2020 22:42:06 +0900 Subject: [PATCH 39/72] doc: Remove a mention to todo ext. from env-merge-info --- doc/extdev/appapi.rst | 5 ----- 1 file changed, 5 deletions(-) diff --git a/doc/extdev/appapi.rst b/doc/extdev/appapi.rst index 76ad65a88f8..49081ee9e35 100644 --- a/doc/extdev/appapi.rst +++ b/doc/extdev/appapi.rst @@ -268,11 +268,6 @@ connect handlers to the events. Example: environment from the main process. *docnames* is a set of document names that have been read in the subprocess. - For a sample of how to deal with this event, look at the standard - ``sphinx.ext.todo`` extension. The implementation is often similar to that - of :event:`env-purge-doc`, only that information is not removed, but added to - the main environment from the other environment. - .. versionadded:: 1.3 .. event:: env-updated (app, env) From b3b0d3ad2c831879093c9b9a1700f8ee5a842c31 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 27 Dec 2019 00:14:24 +0900 Subject: [PATCH 40/72] refactor: latex: Move initialization of multilingual module to builder --- sphinx/builders/latex/__init__.py | 38 +++++++++++++++++++++++- sphinx/builders/latex/constants.py | 8 ++++++ sphinx/writers/latex.py | 46 +----------------------------- tests/test_build_latex.py | 2 +- 4 files changed, 47 insertions(+), 47 deletions(-) diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index 354b20284b4..4a7ec5e3139 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -20,7 +20,7 @@ from sphinx import package_dir, addnodes, highlighting from sphinx.application import Sphinx from sphinx.builders import Builder -from sphinx.builders.latex.constants import ADDITIONAL_SETTINGS, DEFAULT_SETTINGS +from sphinx.builders.latex.constants import ADDITIONAL_SETTINGS, DEFAULT_SETTINGS, SHORTHANDOFF from sphinx.builders.latex.theming import Theme, ThemeFactory from sphinx.builders.latex.util import ExtBabel from sphinx.config import Config, ENUM @@ -133,6 +133,7 @@ def init(self) -> None: self.init_context() self.init_babel() + self.init_multilingual() def get_outdated_docs(self) -> Union[str, List[str]]: return 'all documents' # for now @@ -208,6 +209,41 @@ def init_babel(self) -> None: logger.warning(__('no Babel option known for language %r'), self.config.language) + def init_multilingual(self) -> None: + if self.context['latex_engine'] == 'pdflatex': + if not self.babel.uses_cyrillic(): + if 'X2' in self.context['fontenc']: + self.context['substitutefont'] = '\\usepackage{substitutefont}' + self.context['textcyrillic'] = '\\usepackage[Xtwo]{sphinxcyrillic}' + elif 'T2A' in self.context['fontenc']: + self.context['substitutefont'] = '\\usepackage{substitutefont}' + self.context['textcyrillic'] = '\\usepackage[TtwoA]{sphinxcyrillic}' + if 'LGR' in self.context['fontenc']: + self.context['substitutefont'] = '\\usepackage{substitutefont}' + else: + self.context['textgreek'] = '' + + # 'babel' key is public and user setting must be obeyed + if self.context['babel']: + self.context['classoptions'] += ',' + self.babel.get_language() + # this branch is not taken for xelatex/lualatex if default settings + self.context['multilingual'] = self.context['babel'] + if self.config.language: + self.context['shorthandoff'] = SHORTHANDOFF + + # Times fonts don't work with Cyrillic languages + if self.babel.uses_cyrillic() and 'fontpkg' not in self.config.latex_elements: + self.context['fontpkg'] = '' + elif self.context['polyglossia']: + self.context['classoptions'] += ',' + self.babel.get_language() + options = self.babel.get_mainlanguage_options() + if options: + language = r'\setmainlanguage[%s]{%s}' % (options, self.babel.get_language()) + else: + language = r'\setmainlanguage{%s}' % self.babel.get_language() + + self.context['multilingual'] = '%s\n%s' % (self.context['polyglossia'], language) + def write_stylesheet(self) -> None: highlighter = highlighting.PygmentsBridge('latex', self.config.pygments_style) stylesheet = path.join(self.outdir, 'sphinxhighlight.sty') diff --git a/sphinx/builders/latex/constants.py b/sphinx/builders/latex/constants.py index 70fd0b1d195..9a89036bf31 100644 --- a/sphinx/builders/latex/constants.py +++ b/sphinx/builders/latex/constants.py @@ -192,3 +192,11 @@ 'fontpkg': XELATEX_GREEK_DEFAULT_FONTPKG, }, } # type: Dict[Any, Dict[str, Any]] + + +SHORTHANDOFF = r''' +\ifdefined\shorthandoff + \ifnum\catcode`\=\string=\active\shorthandoff{=}\fi + \ifnum\catcode`\"=\active\shorthandoff{"}\fi +\fi +''' diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index cfce52f7fb5..9390ba5de9a 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -50,13 +50,6 @@ logger = logging.getLogger(__name__) -SHORTHANDOFF = r''' -\ifdefined\shorthandoff - \ifnum\catcode`\=\string=\active\shorthandoff{=}\fi - \ifnum\catcode`\"=\active\shorthandoff{"}\fi -\fi -''' - MAX_CITATION_LABEL_LENGTH = 8 LATEXSECTIONNAMES = ["part", "chapter", "section", "subsection", "subsubsection", "paragraph", "subparagraph"] @@ -395,44 +388,6 @@ def __init__(self, document: nodes.document, builder: "LaTeXBuilder", logger.warning(__('no Babel option known for language %r'), self.config.language) - # set up multilingual module... - if self.elements['latex_engine'] == 'pdflatex': - if not self.babel.uses_cyrillic(): - if 'X2' in self.elements['fontenc']: - self.elements['substitutefont'] = '\\usepackage{substitutefont}' - self.elements['textcyrillic'] = ('\\usepackage[Xtwo]' - '{sphinxcyrillic}') - elif 'T2A' in self.elements['fontenc']: - self.elements['substitutefont'] = '\\usepackage{substitutefont}' - self.elements['textcyrillic'] = ('\\usepackage[TtwoA]' - '{sphinxcyrillic}') - if 'LGR' in self.elements['fontenc']: - self.elements['substitutefont'] = '\\usepackage{substitutefont}' - else: - self.elements['textgreek'] = '' - # 'babel' key is public and user setting must be obeyed - if self.elements['babel']: - self.elements['classoptions'] += ',' + self.babel.get_language() - # this branch is not taken for xelatex/lualatex if default settings - self.elements['multilingual'] = self.elements['babel'] - if self.config.language: - self.elements['shorthandoff'] = SHORTHANDOFF - - # Times fonts don't work with Cyrillic languages - if self.babel.uses_cyrillic() and 'fontpkg' not in self.config.latex_elements: - self.elements['fontpkg'] = '' - elif self.elements['polyglossia']: - self.elements['classoptions'] += ',' + self.babel.get_language() - options = self.babel.get_mainlanguage_options() - if options: - mainlanguage = r'\setmainlanguage[%s]{%s}' % (options, - self.babel.get_language()) - else: - mainlanguage = r'\setmainlanguage{%s}' % self.babel.get_language() - - self.elements['multilingual'] = '%s\n%s' % (self.elements['polyglossia'], - mainlanguage) - minsecnumdepth = self.secnumdepth # 2 from legacy sphinx manual/howto if self.document.get('tocdepth'): # reduce tocdepth if `part` or `chapter` is used for top_sectionlevel @@ -2177,6 +2132,7 @@ def generate_numfig_format(self, builder: "LaTeXBuilder") -> str: 'DEFAULT_SETTINGS': constants.DEFAULT_SETTINGS, 'LUALATEX_DEFAULT_FONTPKG': constants.LUALATEX_DEFAULT_FONTPKG, 'PDFLATEX_DEFAULT_FONTPKG': constants.PDFLATEX_DEFAULT_FONTPKG, + 'SHORTHANDOFF': constants.SHORTHANDOFF, 'XELATEX_DEFAULT_FONTPKG': constants.XELATEX_DEFAULT_FONTPKG, 'XELATEX_GREEK_DEFAULT_FONTPKG': constants.XELATEX_GREEK_DEFAULT_FONTPKG, 'ExtBabel': ExtBabel, diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index d55a3b8957e..61020b86102 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -99,7 +99,7 @@ def skip_if_stylefiles_notfound(testfunc): def test_build_latex_doc(app, status, warning, engine, docclass): app.config.latex_engine = engine app.config.latex_documents = [app.config.latex_documents[0][:4] + (docclass,)] - app.builder.init_context() + app.builder.init() LaTeXTranslator.ignore_missing_images = True app.builder.build_all() From e7c522dc365c7d4cae872e2d5cb0ee0793a114f6 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 16 Feb 2020 20:32:16 +0900 Subject: [PATCH 41/72] refactor: Update type annotation for cpp domain --- sphinx/domains/cpp.py | 161 ++++++++++++++++++++++++++---------------- 1 file changed, 100 insertions(+), 61 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 3a23ff15ade..a7ac237e442 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -11,11 +11,14 @@ import re import warnings from copy import deepcopy -from typing import Any, Callable, Dict, Iterator, List, Match, Pattern, Tuple, Type, Union +from typing import ( + Any, Callable, Dict, Iterator, List, Match, Pattern, Tuple, Type, TypeVar, Union +) from docutils import nodes, utils -from docutils.nodes import Element, Node, TextElement +from docutils.nodes import Element, Node, TextElement, system_message from docutils.parsers.rst import directives +from docutils.parsers.rst.states import Inliner from sphinx import addnodes from sphinx.addnodes import desc_signature, pending_xref @@ -39,6 +42,7 @@ logger = logging.getLogger(__name__) StringifyTransform = Callable[[Any], str] +T = TypeVar('T') """ Important note on ids @@ -652,7 +656,7 @@ class ASTCPPAttribute(ASTBase): def __init__(self, arg: str) -> None: self.arg = arg - def _stringify(self, transform): + def _stringify(self, transform: StringifyTransform) -> str: return "[[" + self.arg + "]]" def describe_signature(self, signode: desc_signature) -> None: @@ -733,12 +737,13 @@ def _stringify(self, transform: StringifyTransform) -> str: def get_id(self, version: int) -> str: return 'LDnE' - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: signode.append(nodes.Text('nullptr')) class ASTBooleanLiteral(ASTBase): - def __init__(self, value): + def __init__(self, value: bool) -> None: self.value = value def _stringify(self, transform: StringifyTransform) -> str: @@ -753,7 +758,8 @@ def get_id(self, version: int) -> str: else: return 'L0E' - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: signode.append(nodes.Text(str(self))) @@ -767,7 +773,8 @@ def _stringify(self, transform: StringifyTransform) -> str: def get_id(self, version: int) -> str: return "L%sE" % self.data - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: txt = str(self) signode.append(nodes.Text(txt, txt)) @@ -802,7 +809,8 @@ def _stringify(self, transform: StringifyTransform) -> str: def get_id(self, version: int) -> str: return self.type + str(self.value) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: txt = str(self) signode.append(nodes.Text(txt, txt)) @@ -818,7 +826,8 @@ def get_id(self, version: int) -> str: # note: the length is not really correct with escaping return "LA%d_KcE" % (len(self.data) - 2) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: txt = str(self) signode.append(nodes.Text(txt, txt)) @@ -830,7 +839,8 @@ def _stringify(self, transform: StringifyTransform) -> str: def get_id(self, version: int) -> str: return "fpT" - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: signode.append(nodes.Text("this")) @@ -844,7 +854,8 @@ def _stringify(self, transform: StringifyTransform) -> str: def get_id(self, version: int) -> str: return self.expr.get_id(version) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: signode.append(nodes.Text('(', '(')) self.expr.describe_signature(signode, mode, env, symbol) signode.append(nodes.Text(')', ')')) @@ -894,7 +905,8 @@ def get_id(self, version: int) -> str: res.append(self.rightExpr.get_id(version)) return ''.join(res) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: signode.append(nodes.Text('(')) if self.leftExpr: self.leftExpr.describe_signature(signode, mode, env, symbol) @@ -936,7 +948,8 @@ def get_id(self, version: int) -> str: res.append(self.exprs[-1].get_id(version)) return ''.join(res) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: self.exprs[0].describe_signature(signode, mode, env, symbol) for i in range(1, len(self.exprs)): signode.append(nodes.Text(' ')) @@ -970,7 +983,8 @@ def get_id(self, version: int) -> str: res.append(self.exprs[-1].get_id(version)) return ''.join(res) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: self.exprs[0].describe_signature(signode, mode, env, symbol) for i in range(1, len(self.exprs)): signode.append(nodes.Text(' ')) @@ -994,7 +1008,8 @@ def _stringify(self, transform: StringifyTransform) -> str: def get_id(self, version: int) -> str: return 'cv' + self.typ.get_id(version) + self.expr.get_id(version) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: signode.append(nodes.Text('(')) self.typ.describe_signature(signode, mode, env, symbol) signode.append(nodes.Text(')')) @@ -1012,7 +1027,8 @@ def _stringify(self, transform: StringifyTransform) -> str: def get_id(self, version: int) -> str: return _id_operator_unary_v2[self.op] + self.expr.get_id(version) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: signode.append(nodes.Text(self.op)) self.expr.describe_signature(signode, mode, env, symbol) @@ -1027,7 +1043,8 @@ def _stringify(self, transform: StringifyTransform) -> str: def get_id(self, version: int) -> str: return 'sZ' + self.identifier.get_id(version) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: signode.append(nodes.Text('sizeof...(')) self.identifier.describe_signature(signode, mode, env, symbol=symbol, prefix="", templateArgs="") @@ -1044,7 +1061,8 @@ def _stringify(self, transform: StringifyTransform) -> str: def get_id(self, version: int) -> str: return 'st' + self.typ.get_id(version) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: signode.append(nodes.Text('sizeof(')) self.typ.describe_signature(signode, mode, env, symbol) signode.append(nodes.Text(')')) @@ -1060,7 +1078,8 @@ def _stringify(self, transform: StringifyTransform) -> str: def get_id(self, version: int) -> str: return 'sz' + self.expr.get_id(version) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: signode.append(nodes.Text('sizeof ')) self.expr.describe_signature(signode, mode, env, symbol) @@ -1075,7 +1094,8 @@ def _stringify(self, transform: StringifyTransform) -> str: def get_id(self, version: int) -> str: return 'at' + self.typ.get_id(version) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: signode.append(nodes.Text('alignof(')) self.typ.describe_signature(signode, mode, env, symbol) signode.append(nodes.Text(')')) @@ -1091,7 +1111,8 @@ def _stringify(self, transform: StringifyTransform) -> str: def get_id(self, version: int) -> str: return 'nx' + self.expr.get_id(version) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: signode.append(nodes.Text('noexcept(')) self.expr.describe_signature(signode, mode, env, symbol) signode.append(nodes.Text(')')) @@ -1130,7 +1151,8 @@ def get_id(self, version: int) -> str: res.append('E') return ''.join(res) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: if self.rooted: signode.append(nodes.Text('::')) signode.append(nodes.Text('new ')) @@ -1166,7 +1188,8 @@ def get_id(self, version: int) -> str: id = "dl" return id + self.expr.get_id(version) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: if self.rooted: signode.append(nodes.Text('::')) signode.append(nodes.Text('delete ')) @@ -1196,7 +1219,8 @@ def get_id(self, version: int) -> str: self.typ.get_id(version) + self.expr.get_id(version)) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: signode.append(nodes.Text(self.cast)) signode.append(nodes.Text('<')) self.typ.describe_signature(signode, mode, env, symbol) @@ -1218,7 +1242,8 @@ def get_id(self, version: int) -> str: prefix = 'ti' if self.isType else 'te' return prefix + self.typeOrExpr.get_id(version) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: signode.append(nodes.Text('typeid')) signode.append(nodes.Text('(')) self.typeOrExpr.describe_signature(signode, mode, env, symbol) @@ -1239,7 +1264,8 @@ def get_id(self, idPrefix: str, version: int) -> str: res.append('E') return ''.join(res) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: self.lst.describe_signature(signode, mode, env, symbol) @@ -1253,7 +1279,8 @@ def _stringify(self, transform: StringifyTransform) -> str: def get_id(self, idPrefix: str, version: int) -> str: return 'ix' + idPrefix + self.expr.get_id(version) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: signode.append(nodes.Text('[')) self.expr.describe_signature(signode, mode, env, symbol) signode.append(nodes.Text(']')) @@ -1266,7 +1293,8 @@ def _stringify(self, transform: StringifyTransform) -> str: def get_id(self, idPrefix: str, version: int) -> str: return 'pp' + idPrefix - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: signode.append(nodes.Text('++')) @@ -1277,7 +1305,8 @@ def _stringify(self, transform: StringifyTransform) -> str: def get_id(self, idPrefix: str, version: int) -> str: return 'mm' + idPrefix - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: signode.append(nodes.Text('--')) @@ -1291,7 +1320,8 @@ def _stringify(self, transform: StringifyTransform) -> str: def get_id(self, idPrefix: str, version: int) -> str: return 'dt' + idPrefix + self.name.get_id(version) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: signode.append(nodes.Text('.')) self.name.describe_signature(signode, 'noneIsName', env, symbol) @@ -1306,7 +1336,8 @@ def _stringify(self, transform: StringifyTransform) -> str: def get_id(self, idPrefix: str, version: int) -> str: return 'pt' + idPrefix + self.name.get_id(version) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: signode.append(nodes.Text('->')) self.name.describe_signature(signode, 'noneIsName', env, symbol) @@ -1329,7 +1360,8 @@ def get_id(self, version: int) -> str: id = p.get_id(id, version) return id - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: self.prefix.describe_signature(signode, mode, env, symbol) for p in self.postFixes: p.describe_signature(signode, mode, env, symbol) @@ -1346,7 +1378,8 @@ def get_id(self, version: int) -> str: id = self.expr.get_id(version) return 'sp' + id - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: self.expr.describe_signature(signode, mode, env, symbol) signode += nodes.Text('...') @@ -1361,7 +1394,8 @@ def _stringify(self, transform: StringifyTransform) -> str: def get_id(self, version: int) -> str: return str(self.expr) - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: signode += nodes.Text(self.expr) @@ -1667,13 +1701,13 @@ def describe_signature(self, parentNode: desc_signature, mode: str, env: "BuildEnvironment", symbol: "Symbol", lineSpec: bool = None ) -> None: # 'lineSpec' is defaulted becuase of template template parameters - def makeLine(parentNode=parentNode): + def makeLine(parentNode: desc_signature = parentNode) -> addnodes.desc_signature_line: signode = addnodes.desc_signature_line() parentNode += signode signode.sphinx_cpp_tagname = 'templateParams' return signode if self.isNested: - lineNode = parentNode + lineNode = parentNode # type: Element else: lineNode = makeLine() lineNode += nodes.Text("template<") @@ -1822,7 +1856,7 @@ def describe_signature(self, signode: desc_signature, mode: str, class ASTOperator(ASTBase): - def is_anon(self): + def is_anon(self) -> bool: return False def is_operator(self) -> bool: @@ -2300,11 +2334,11 @@ def describe_signature(self, signode: desc_signature, mode: str, paramlist += param signode += paramlist - def _add_anno(signode, text): + def _add_anno(signode: desc_signature, text: str) -> None: signode += nodes.Text(' ') signode += addnodes.desc_annotation(text, text) - def _add_text(signode, text): + def _add_text(signode: desc_signature, text: str) -> None: signode += nodes.Text(' ' + text) if self.volatile: @@ -2376,7 +2410,7 @@ def _stringify(self, transform: StringifyTransform) -> str: return ' '.join(res) def describe_signature(self, modifiers: List[Node]) -> None: - def _add(modifiers, text): + def _add(modifiers: List[Node], text: str) -> None: if len(modifiers) > 0: modifiers.append(nodes.Text(' ')) modifiers.append(addnodes.desc_annotation(text, text)) @@ -2456,9 +2490,9 @@ def _stringify(self, transform: StringifyTransform) -> str: def describe_signature(self, signode: desc_signature, mode: str, env: "BuildEnvironment", symbol: "Symbol") -> None: _verify_description_mode(mode) - modifiers = [] # type: List[nodes.Node] + modifiers = [] # type: List[Node] - def _add(modifiers, text): + def _add(modifiers: List[Node], text: str) -> None: if len(modifiers) > 0: modifiers.append(nodes.Text(' ')) modifiers.append(addnodes.desc_annotation(text, text)) @@ -2503,7 +2537,8 @@ def get_id(self, version: int) -> str: else: return 'A_' - def describe_signature(self, signode, mode, env, symbol): + def describe_signature(self, signode: desc_signature, mode: str, + env: "BuildEnvironment", symbol: "Symbol") -> None: _verify_description_mode(mode) signode.append(nodes.Text("[")) if self.size: @@ -2595,7 +2630,7 @@ def describe_signature(self, signode: desc_signature, mode: str, if len(self.attrs) > 0 and (self.volatile or self.const): signode += nodes.Text(' ') - def _add_anno(signode, text): + def _add_anno(signode: desc_signature, text: str) -> None: signode += addnodes.desc_annotation(text, text) if self.volatile: _add_anno(signode, 'volatile') @@ -2795,7 +2830,7 @@ def describe_signature(self, signode: desc_signature, mode: str, self.className.describe_signature(signode, mode, env, symbol) signode += nodes.Text('::*') - def _add_anno(signode, text): + def _add_anno(signode: desc_signature, text: str) -> None: signode += addnodes.desc_annotation(text, text) if self.volatile: _add_anno(signode, 'volatile') @@ -3565,7 +3600,7 @@ class Symbol: debug_show_tree = False @staticmethod - def debug_print(*args): + def debug_print(*args: Any) -> None: print(Symbol.debug_indent_string * Symbol.debug_indent, end="") print(*args) @@ -3627,7 +3662,7 @@ def _fill_empty(self, declaration: ASTDeclaration, docname: str) -> None: # and symbol addition should be done as well self._add_template_and_function_params() - def _add_template_and_function_params(self): + def _add_template_and_function_params(self) -> None: if Symbol.debug_lookup: Symbol.debug_indent += 1 Symbol.debug_print("_add_template_and_function_params:") @@ -3663,7 +3698,7 @@ def _add_template_and_function_params(self): if Symbol.debug_lookup: Symbol.debug_indent -= 1 - def remove(self): + def remove(self) -> None: if self.parent is None: return assert self in self.parent._children @@ -3771,7 +3806,7 @@ def _find_named_symbols(self, identOrOp: Union[ASTIdentifier, ASTOperator], Symbol.debug_print("correctPrimaryTemplateAargs:", correctPrimaryTemplateArgs) Symbol.debug_print("searchInSiblings: ", searchInSiblings) - def isSpecialization(): + def isSpecialization() -> bool: # the names of the template parameters must be given exactly as args # and params that are packs must in the args be the name expanded if len(templateParams.params) != len(templateArgs.args): @@ -6509,7 +6544,7 @@ def parse_definition(self, parser: DefinitionParser) -> ASTDeclaration: def describe_signature(self, signode: desc_signature, ast: Any, options: Dict) -> None: ast.describe_signature(signode, 'lastIsName', self.env, options) - def run(self): + def run(self) -> List[Node]: env = self.state.document.settings.env # from ObjectDescription.run if 'cpp:parent_symbol' not in env.temp_data: root = env.domaindata['cpp']['root_symbol'] @@ -6617,7 +6652,7 @@ class CPPClassObject(CPPObject): object_type = 'class' @property - def display_object_type(self): + def display_object_type(self) -> str: # the distinction between class and struct is only cosmetic assert self.objtype in ('class', 'struct') return self.objtype @@ -6733,7 +6768,8 @@ def run(self) -> List[Node]: class AliasNode(nodes.Element): - def __init__(self, sig, env=None, parentKey=None): + def __init__(self, sig: str, env: "BuildEnvironment" = None, + parentKey: LookupKey = None) -> None: super().__init__() self.sig = sig if env is not None: @@ -6745,8 +6781,8 @@ def __init__(self, sig, env=None, parentKey=None): assert parentKey is not None self.parentKey = parentKey - def copy(self): - return self.__class__(self.sig, env=None, parentKey=self.parentKey) + def copy(self: T) -> T: + return self.__class__(self.sig, env=None, parentKey=self.parentKey) # type: ignore class AliasTransform(SphinxTransform): @@ -6755,7 +6791,7 @@ class AliasTransform(SphinxTransform): def apply(self, **kwargs: Any) -> None: for node in self.document.traverse(AliasNode): class Warner: - def warn(self, msg): + def warn(self, msg: Any) -> None: logger.warning(msg, location=node) warner = Warner() sig = node.sig @@ -6898,7 +6934,7 @@ def process_link(self, env: BuildEnvironment, refnode: Element, has_explicit_tit class CPPExprRole: - def __init__(self, asCode): + def __init__(self, asCode: bool) -> None: if asCode: # render the expression as inline code self.class_type = 'cpp-expr' @@ -6908,9 +6944,11 @@ def __init__(self, asCode): self.class_type = 'cpp-texpr' self.node_type = nodes.inline - def __call__(self, typ, rawtext, text, lineno, inliner, options={}, content=[]): + def __call__(self, typ: str, rawtext: str, text: str, lineno: int, + inliner: Inliner, options: Dict = {}, content: List[str] = [] + ) -> Tuple[List[Node], List[system_message]]: class Warner: - def warn(self, msg): + def warn(self, msg: str) -> None: inliner.reporter.warning(msg, line=lineno) text = utils.unescape(text).replace('\n', ' ') env = inliner.document.settings.env @@ -7056,7 +7094,7 @@ def _resolve_xref_inner(self, env: BuildEnvironment, fromdocname: str, builder: typ: str, target: str, node: pending_xref, contnode: Element, emitWarnings: bool = True) -> Tuple[Element, str]: class Warner: - def warn(self, msg): + def warn(self, msg: str) -> None: if emitWarnings: logger.warning(msg, location=node) warner = Warner() @@ -7067,7 +7105,8 @@ def warn(self, msg): try: ast, isShorthand = parser.parse_xref_object() except DefinitionError as e: - def findWarning(e): # as arg to stop flake8 from complaining + # as arg to stop flake8 from complaining + def findWarning(e: Exception) -> Tuple[str, Exception]: if typ != 'any' and typ != 'func': return target, e # hax on top of the paren hax to try to get correct errors @@ -7138,7 +7177,7 @@ def findWarning(e): # as arg to stop flake8 from complaining typ = 'class' declTyp = s.declaration.objectType - def checkType(): + def checkType() -> bool: if typ == 'any' or typ == 'identifier': return True if declTyp == 'templateParam': From 5ff3b9dc4d07b5c324680e17b2aeaa298e619a9a Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 31 Jan 2020 00:41:03 +0900 Subject: [PATCH 42/72] py domain: Generate node_id for modules in the right way --- CHANGES | 2 ++ sphinx/domains/python.py | 60 +++++++++++++++++++++++++-------------- tests/test_domain_py.py | 18 ++++++------ tests/test_environment.py | 2 +- 4 files changed, 51 insertions(+), 31 deletions(-) diff --git a/CHANGES b/CHANGES index 2b56608455f..027065f29a2 100644 --- a/CHANGES +++ b/CHANGES @@ -31,6 +31,8 @@ Incompatible changes node_id for cross reference * #7229: rst domain: Non intended behavior is removed such as ``numref_`` links to ``.. rst:role:: numref`` +* #6903: py domain: Internal data structure has changed. Now modules have + node_id for cross reference Deprecated ---------- diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 0ed3c6e6f05..d0b4a9b7fe3 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -32,7 +32,7 @@ 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_refnode +from sphinx.util.nodes import make_id, make_refnode from sphinx.util.typing import TextlikeNode if False: @@ -788,24 +788,43 @@ def run(self) -> List[Node]: ret = [] # type: List[Node] if not noindex: # note module to the domain + node_id = make_id(self.env, self.state.document, 'module', modname) + target = nodes.target('', '', ids=[node_id], ismod=True) + self.set_source_info(target) + + # Assign old styled node_id not to break old hyperlinks (if possible) + # Note: Will removed in Sphinx-5.0 (RemovedInSphinx50Warning) + old_node_id = self.make_old_id(modname) + if node_id != old_node_id and old_node_id not in self.state.document.ids: + target['ids'].append(old_node_id) + + self.state.document.note_explicit_target(target) + domain.note_module(modname, + node_id, self.options.get('synopsis', ''), self.options.get('platform', ''), 'deprecated' in self.options) - domain.note_object(modname, 'module', location=(self.env.docname, self.lineno)) + domain.note_object(modname, 'module', location=target) - targetnode = nodes.target('', '', ids=['module-' + modname], - ismod=True) - self.state.document.note_explicit_target(targetnode) # the platform and synopsis aren't printed; in fact, they are only # used in the modindex currently - ret.append(targetnode) + ret.append(target) indextext = _('%s (module)') % modname - inode = addnodes.index(entries=[('single', indextext, - 'module-' + modname, '', None)]) + inode = addnodes.index(entries=[('single', indextext, node_id, '', None)]) ret.append(inode) return ret + def make_old_id(self, name: str) -> str: + """Generate old styled node_id. + + Old styled node_id is incompatible with docutils' node_id. + It can contain dots and hyphens. + + .. note:: Old styled node_id was mainly used until Sphinx-3.0. + """ + return 'module-%s' % name + class PyCurrentModule(SphinxDirective): """ @@ -888,7 +907,7 @@ def generate(self, docnames: Iterable[str] = None # sort out collapsable modules prev_modname = '' num_toplevels = 0 - for modname, (docname, synopsis, platforms, deprecated) in modules: + for modname, (docname, node_id, synopsis, platforms, deprecated) in modules: if docnames and docname not in docnames: continue @@ -925,8 +944,7 @@ def generate(self, docnames: Iterable[str] = None qualifier = _('Deprecated') if deprecated else '' entries.append(IndexEntry(stripped + modname, subtype, docname, - 'module-' + stripped + modname, platforms, - qualifier, synopsis)) + node_id, platforms, qualifier, synopsis)) prev_modname = modname # apply heuristics when to collapse modindex at page load: @@ -1006,21 +1024,22 @@ def note_object(self, name: str, objtype: str, location: Any = None) -> None: self.objects[name] = (self.env.docname, objtype) @property - def modules(self) -> Dict[str, Tuple[str, str, str, bool]]: - return self.data.setdefault('modules', {}) # modname -> docname, synopsis, platform, deprecated # NOQA + def modules(self) -> Dict[str, Tuple[str, str, str, str, bool]]: + return self.data.setdefault('modules', {}) # modname -> docname, node_id, synopsis, platform, deprecated # NOQA - def note_module(self, name: str, synopsis: str, platform: str, deprecated: bool) -> None: + def note_module(self, name: str, node_id: str, synopsis: str, + platform: str, deprecated: bool) -> None: """Note a python module for cross reference. .. versionadded:: 2.1 """ - self.modules[name] = (self.env.docname, synopsis, platform, deprecated) + self.modules[name] = (self.env.docname, node_id, synopsis, platform, deprecated) def clear_doc(self, docname: str) -> None: for fullname, (fn, _l) in list(self.objects.items()): if fn == docname: del self.objects[fullname] - for modname, (fn, _x, _x, _y) in list(self.modules.items()): + for modname, (fn, _x, _x, _x, _y) in list(self.modules.items()): if fn == docname: del self.modules[modname] @@ -1146,7 +1165,7 @@ def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Bui def _make_module_refnode(self, builder: Builder, fromdocname: str, name: str, contnode: Node) -> Element: # get additional info for modules - docname, synopsis, platform, deprecated = self.modules[name] + docname, node_id, synopsis, platform, deprecated = self.modules[name] title = name if synopsis: title += ': ' + synopsis @@ -1154,12 +1173,11 @@ def _make_module_refnode(self, builder: Builder, fromdocname: str, name: str, title += _(' (deprecated)') if platform: title += ' (' + platform + ')' - return make_refnode(builder, fromdocname, docname, - 'module-' + name, contnode, title) + return make_refnode(builder, fromdocname, docname, node_id, contnode, title) def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]: for modname, info in self.modules.items(): - yield (modname, modname, 'module', info[0], 'module-' + modname, 0) + yield (modname, modname, 'module', info[0], info[1], 0) for refname, (docname, type) in self.objects.items(): if type != 'module': # modules are already handled yield (refname, refname, type, docname, refname, 1) @@ -1182,7 +1200,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: return { 'version': 'builtin', - 'env_version': 1, + 'env_version': 2, 'parallel_read_safe': True, 'parallel_write_safe': True, } diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index 178fe18cf8d..6d41a0ae776 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -595,10 +595,10 @@ def test_module_index(app): assert index.generate() == ( [('d', [IndexEntry('docutils', 0, 'index', 'module-docutils', '', '', '')]), ('s', [IndexEntry('sphinx', 1, 'index', 'module-sphinx', '', '', ''), - IndexEntry('sphinx.builders', 2, 'index', 'module-sphinx.builders', '', '', ''), # NOQA - IndexEntry('sphinx.builders.html', 2, 'index', 'module-sphinx.builders.html', '', '', ''), # NOQA - IndexEntry('sphinx.config', 2, 'index', 'module-sphinx.config', '', '', ''), - IndexEntry('sphinx_intl', 0, 'index', 'module-sphinx_intl', '', '', '')])], + IndexEntry('sphinx.builders', 2, 'index', 'module-sphinx-builders', '', '', ''), # NOQA + IndexEntry('sphinx.builders.html', 2, 'index', 'module-sphinx-builders-html', '', '', ''), # NOQA + IndexEntry('sphinx.config', 2, 'index', 'module-sphinx-config', '', '', ''), + IndexEntry('sphinx_intl', 0, 'index', 'module-sphinx-intl', '', '', '')])], False ) @@ -610,7 +610,7 @@ def test_module_index_submodule(app): index = PythonModuleIndex(app.env.get_domain('py')) assert index.generate() == ( [('s', [IndexEntry('sphinx', 1, '', '', '', '', ''), - IndexEntry('sphinx.config', 2, 'index', 'module-sphinx.config', '', '', '')])], + IndexEntry('sphinx.config', 2, 'index', 'module-sphinx-config', '', '', '')])], False ) @@ -639,12 +639,12 @@ def test_modindex_common_prefix(app): restructuredtext.parse(app, text) index = PythonModuleIndex(app.env.get_domain('py')) assert index.generate() == ( - [('b', [IndexEntry('sphinx.builders', 1, 'index', 'module-sphinx.builders', '', '', ''), # NOQA - IndexEntry('sphinx.builders.html', 2, 'index', 'module-sphinx.builders.html', '', '', '')]), # NOQA - ('c', [IndexEntry('sphinx.config', 0, 'index', 'module-sphinx.config', '', '', '')]), + [('b', [IndexEntry('sphinx.builders', 1, 'index', 'module-sphinx-builders', '', '', ''), # NOQA + IndexEntry('sphinx.builders.html', 2, 'index', 'module-sphinx-builders-html', '', '', '')]), # NOQA + ('c', [IndexEntry('sphinx.config', 0, 'index', 'module-sphinx-config', '', '', '')]), ('d', [IndexEntry('docutils', 0, 'index', 'module-docutils', '', '', '')]), ('s', [IndexEntry('sphinx', 0, 'index', 'module-sphinx', '', '', ''), - IndexEntry('sphinx_intl', 0, 'index', 'module-sphinx_intl', '', '', '')])], + IndexEntry('sphinx_intl', 0, 'index', 'module-sphinx-intl', '', '', '')])], True ) diff --git a/tests/test_environment.py b/tests/test_environment.py index 12fe86176f5..9324f36f014 100644 --- a/tests/test_environment.py +++ b/tests/test_environment.py @@ -99,7 +99,7 @@ def test_object_inventory(app): assert 'func_noindex' not in refs assert app.env.domaindata['py']['modules']['mod'] == \ - ('objects', 'Module synopsis.', 'UNIX', False) + ('objects', 'module-mod', 'Module synopsis.', 'UNIX', False) assert app.env.domains['py'].data is app.env.domaindata['py'] assert app.env.domains['c'].data is app.env.domaindata['c'] From 729efd28b043da163059e14f2122c1e88a11b64b Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 2 Mar 2020 00:45:50 +0900 Subject: [PATCH 43/72] py domain: Generate node_id for objects in the right way --- CHANGES | 6 ++- sphinx/domains/python.py | 61 +++++++++++++------------ tests/test_build_html.py | 18 ++++---- tests/test_domain_py.py | 95 ++++++++++++++++++++------------------- tests/test_environment.py | 2 +- tests/test_intl.py | 2 +- 6 files changed, 96 insertions(+), 88 deletions(-) diff --git a/CHANGES b/CHANGES index 027065f29a2..14f2f6c5f9a 100644 --- a/CHANGES +++ b/CHANGES @@ -31,8 +31,10 @@ Incompatible changes node_id for cross reference * #7229: rst domain: Non intended behavior is removed such as ``numref_`` links to ``.. rst:role:: numref`` -* #6903: py domain: Internal data structure has changed. Now modules have - node_id for cross reference +* #6903: py domain: Internal data structure has changed. Both objects and + modules have node_id for cross reference +* #6903: py domain: Non intended behavior is removed such as ``say_hello_`` + links to ``.. py:function:: say_hello()`` Deprecated ---------- diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index d0b4a9b7fe3..e627c70457f 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -358,19 +358,22 @@ def add_target_and_index(self, name_cls: Tuple[str, str], sig: str, signode: desc_signature) -> None: modname = self.options.get('module', self.env.ref_context.get('py:module')) fullname = (modname + '.' if modname else '') + name_cls[0] - # note target - if fullname not in self.state.document.ids: - signode['names'].append(fullname) + node_id = make_id(self.env, self.state.document, modname or '', name_cls[0]) + signode['ids'].append(node_id) + + # Assign old styled node_id(fullname) not to break old hyperlinks (if possible) + # Note: Will removed in Sphinx-5.0 (RemovedInSphinx50Warning) + if node_id != fullname and fullname not in self.state.document.ids: signode['ids'].append(fullname) - self.state.document.note_explicit_target(signode) - domain = cast(PythonDomain, self.env.get_domain('py')) - domain.note_object(fullname, self.objtype, location=signode) + self.state.document.note_explicit_target(signode) + + domain = cast(PythonDomain, self.env.get_domain('py')) + domain.note_object(fullname, self.objtype, node_id, location=signode) indextext = self.get_index_text(modname, name_cls) if indextext: - self.indexnode['entries'].append(('single', indextext, - fullname, '', None)) + self.indexnode['entries'].append(('single', indextext, node_id, '', None)) def before_content(self) -> None: """Handle object nesting before content @@ -805,7 +808,7 @@ def run(self) -> List[Node]: self.options.get('synopsis', ''), self.options.get('platform', ''), 'deprecated' in self.options) - domain.note_object(modname, 'module', location=target) + domain.note_object(modname, 'module', node_id, location=target) # the platform and synopsis aren't printed; in fact, they are only # used in the modindex currently @@ -1008,10 +1011,10 @@ class PythonDomain(Domain): ] @property - def objects(self) -> Dict[str, Tuple[str, str]]: - return self.data.setdefault('objects', {}) # fullname -> docname, objtype + def objects(self) -> Dict[str, Tuple[str, str, str]]: + return self.data.setdefault('objects', {}) # fullname -> docname, node_id, objtype - def note_object(self, name: str, objtype: str, location: Any = None) -> None: + def note_object(self, name: str, objtype: str, node_id: str, location: Any = None) -> None: """Note a python object for cross reference. .. versionadded:: 2.1 @@ -1021,7 +1024,7 @@ def note_object(self, name: str, objtype: str, location: Any = None) -> None: logger.warning(__('duplicate object description of %s, ' 'other instance in %s, use :noindex: for one of them'), name, docname, location=location) - self.objects[name] = (self.env.docname, objtype) + self.objects[name] = (self.env.docname, node_id, objtype) @property def modules(self) -> Dict[str, Tuple[str, str, str, str, bool]]: @@ -1036,7 +1039,7 @@ def note_module(self, name: str, node_id: str, synopsis: str, self.modules[name] = (self.env.docname, node_id, synopsis, platform, deprecated) def clear_doc(self, docname: str) -> None: - for fullname, (fn, _l) in list(self.objects.items()): + for fullname, (fn, _x, _x) in list(self.objects.items()): if fn == docname: del self.objects[fullname] for modname, (fn, _x, _x, _x, _y) in list(self.modules.items()): @@ -1045,16 +1048,16 @@ def clear_doc(self, docname: str) -> None: def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: # XXX check duplicates? - for fullname, (fn, objtype) in otherdata['objects'].items(): + for fullname, (fn, node_id, objtype) in otherdata['objects'].items(): if fn in docnames: - self.objects[fullname] = (fn, objtype) + self.objects[fullname] = (fn, node_id, objtype) for modname, data in otherdata['modules'].items(): if data[0] in docnames: self.modules[modname] = data def find_obj(self, env: BuildEnvironment, modname: str, classname: str, name: str, type: str, searchmode: int = 0 - ) -> List[Tuple[str, Tuple[str, str]]]: + ) -> List[Tuple[str, Tuple[str, str, str]]]: """Find a Python object for "name", perhaps using the given module and/or classname. Returns a list of (name, object entry) tuples. """ @@ -1065,7 +1068,7 @@ def find_obj(self, env: BuildEnvironment, modname: str, classname: str, if not name: return [] - matches = [] # type: List[Tuple[str, Tuple[str, str]]] + matches = [] # type: List[Tuple[str, Tuple[str, str, str]]] newname = None if searchmode == 1: @@ -1076,20 +1079,20 @@ def find_obj(self, env: BuildEnvironment, modname: str, classname: str, if objtypes is not None: if modname and classname: fullname = modname + '.' + classname + '.' + name - if fullname in self.objects and self.objects[fullname][1] in objtypes: + if fullname in self.objects and self.objects[fullname][2] in objtypes: newname = fullname if not newname: if modname and modname + '.' + name in self.objects and \ - self.objects[modname + '.' + name][1] in objtypes: + self.objects[modname + '.' + name][2] in objtypes: newname = modname + '.' + name - elif name in self.objects and self.objects[name][1] in objtypes: + elif name in self.objects and self.objects[name][2] in objtypes: newname = name else: # "fuzzy" searching mode searchname = '.' + name matches = [(oname, self.objects[oname]) for oname in self.objects if oname.endswith(searchname) and - self.objects[oname][1] in objtypes] + self.objects[oname][2] in objtypes] else: # NOTE: searching for exact match, object type is not considered if name in self.objects: @@ -1137,10 +1140,10 @@ def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder type='ref', subtype='python', location=node) name, obj = matches[0] - if obj[1] == 'module': + if obj[2] == 'module': return self._make_module_refnode(builder, fromdocname, name, contnode) else: - return make_refnode(builder, fromdocname, obj[0], name, contnode, name) + return make_refnode(builder, fromdocname, obj[0], obj[1], contnode, name) def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, target: str, node: pending_xref, contnode: Element @@ -1152,13 +1155,13 @@ def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Bui # always search in "refspecific" mode with the :any: role matches = self.find_obj(env, modname, clsname, target, None, 1) for name, obj in matches: - if obj[1] == 'module': + if obj[2] == 'module': results.append(('py:mod', self._make_module_refnode(builder, fromdocname, name, contnode))) else: - results.append(('py:' + self.role_for_objtype(obj[1]), - make_refnode(builder, fromdocname, obj[0], name, + results.append(('py:' + self.role_for_objtype(obj[2]), + make_refnode(builder, fromdocname, obj[0], obj[1], contnode, name))) return results @@ -1178,9 +1181,9 @@ def _make_module_refnode(self, builder: Builder, fromdocname: str, name: str, def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]: for modname, info in self.modules.items(): yield (modname, modname, 'module', info[0], info[1], 0) - for refname, (docname, type) in self.objects.items(): + for refname, (docname, node_id, type) in self.objects.items(): if type != 'module': # modules are already handled - yield (refname, refname, type, docname, refname, 1) + yield (refname, refname, type, docname, node_id, 1) def get_full_qualified_name(self, node: Element) -> str: modname = node.get('py:module') diff --git a/tests/test_build_html.py b/tests/test_build_html.py index adb7462dfb7..39cb3bf714f 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -176,8 +176,8 @@ def test_html4_output(app, status, warning): r'-| |-'), ], 'autodoc.html': [ - (".//dl[@class='py class']/dt[@id='autodoc_target.Class']", ''), - (".//dl[@class='py function']/dt[@id='autodoc_target.function']/em", r'\*\*kwds'), + (".//dl[@class='py class']/dt[@id='autodoc-target-class']", ''), + (".//dl[@class='py function']/dt[@id='autodoc-target-function']/em", r'\*\*kwds'), (".//dd/p", r'Return spam\.'), ], 'extapi.html': [ @@ -262,7 +262,7 @@ def test_html4_output(app, status, warning): (".//p", 'Always present'), # tests for ``any`` role (".//a[@href='#with']/span", 'headings'), - (".//a[@href='objects.html#func_without_body']/code/span", 'objects'), + (".//a[@href='objects.html#func-without-body']/code/span", 'objects'), # tests for numeric labels (".//a[@href='#id1'][@class='reference internal']/span", 'Testing various markup'), # tests for smartypants @@ -274,18 +274,18 @@ def test_html4_output(app, status, warning): (".//p", 'Il dit : « C’est “super” ! »'), ], 'objects.html': [ - (".//dt[@id='mod.Cls.meth1']", ''), - (".//dt[@id='errmod.Error']", ''), + (".//dt[@id='mod-cls-meth1']", ''), + (".//dt[@id='errmod-error']", ''), (".//dt/code", r'long\(parameter,\s* list\)'), (".//dt/code", 'another one'), - (".//a[@href='#mod.Cls'][@class='reference internal']", ''), + (".//a[@href='#mod-cls'][@class='reference internal']", ''), (".//dl[@class='std userdesc']", ''), (".//dt[@id='userdesc-myobj']", ''), (".//a[@href='#userdesc-myobj'][@class='reference internal']", ''), # docfields - (".//a[@class='reference internal'][@href='#TimeInt']/em", 'TimeInt'), - (".//a[@class='reference internal'][@href='#Time']", 'Time'), - (".//a[@class='reference internal'][@href='#errmod.Error']/strong", 'Error'), + (".//a[@class='reference internal'][@href='#timeint']/em", 'TimeInt'), + (".//a[@class='reference internal'][@href='#time']", 'Time'), + (".//a[@class='reference internal'][@href='#errmod-error']/strong", 'Error'), # C references (".//span[@class='pre']", 'CFunction()'), (".//a[@href='#c.Sphinx_DoSomething']", ''), diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index 6d41a0ae776..218ded5101c 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -145,24 +145,24 @@ def test_domain_py_objects(app, status, warning): assert 'module_b.submodule' in modules assert 'module_b.submodule' in objects - assert objects['module_a.submodule.ModTopLevel'] == ('module', 'class') - assert objects['module_a.submodule.ModTopLevel.mod_child_1'] == ('module', 'method') - assert objects['module_a.submodule.ModTopLevel.mod_child_2'] == ('module', 'method') + assert objects['module_a.submodule.ModTopLevel'][2] == 'class' + assert objects['module_a.submodule.ModTopLevel.mod_child_1'][2] == 'method' + assert objects['module_a.submodule.ModTopLevel.mod_child_2'][2] == 'method' assert 'ModTopLevel.ModNoModule' not in objects - assert objects['ModNoModule'] == ('module', 'class') - assert objects['module_b.submodule.ModTopLevel'] == ('module', 'class') - - assert objects['TopLevel'] == ('roles', 'class') - assert objects['top_level'] == ('roles', 'method') - assert objects['NestedParentA'] == ('roles', 'class') - assert objects['NestedParentA.child_1'] == ('roles', 'method') - assert objects['NestedParentA.any_child'] == ('roles', 'method') - assert objects['NestedParentA.NestedChildA'] == ('roles', 'class') - assert objects['NestedParentA.NestedChildA.subchild_1'] == ('roles', 'method') - assert objects['NestedParentA.NestedChildA.subchild_2'] == ('roles', 'method') - assert objects['NestedParentA.child_2'] == ('roles', 'method') - assert objects['NestedParentB'] == ('roles', 'class') - assert objects['NestedParentB.child_1'] == ('roles', 'method') + assert objects['ModNoModule'][2] == 'class' + assert objects['module_b.submodule.ModTopLevel'][2] == 'class' + + assert objects['TopLevel'][2] == 'class' + assert objects['top_level'][2] == 'method' + assert objects['NestedParentA'][2] == 'class' + assert objects['NestedParentA.child_1'][2] == 'method' + assert objects['NestedParentA.any_child'][2] == 'method' + assert objects['NestedParentA.NestedChildA'][2] == 'class' + assert objects['NestedParentA.NestedChildA.subchild_1'][2] == 'method' + assert objects['NestedParentA.NestedChildA.subchild_2'][2] == 'method' + assert objects['NestedParentA.child_2'][2] == 'method' + assert objects['NestedParentB'][2] == 'class' + assert objects['NestedParentB.child_1'][2] == 'method' @pytest.mark.sphinx('html', testroot='domain-py') @@ -170,11 +170,11 @@ def test_resolve_xref_for_properties(app, status, warning): app.builder.build_all() content = (app.outdir / 'module.html').read_text() - assert ('Link to ' '' 'prop attribute' in content) - assert ('Link to ' '' 'prop method' in content) @@ -191,17 +191,20 @@ def find_obj(modname, prefix, obj_name, obj_type, searchmode=0): assert (find_obj(None, None, 'NONEXISTANT', 'class') == []) assert (find_obj(None, None, 'NestedParentA', 'class') == - [('NestedParentA', ('roles', 'class'))]) + [('NestedParentA', ('roles', 'nestedparenta', 'class'))]) assert (find_obj(None, None, 'NestedParentA.NestedChildA', 'class') == - [('NestedParentA.NestedChildA', ('roles', 'class'))]) + [('NestedParentA.NestedChildA', ('roles', 'nestedparenta-nestedchilda', 'class'))]) assert (find_obj(None, 'NestedParentA', 'NestedChildA', 'class') == - [('NestedParentA.NestedChildA', ('roles', 'class'))]) + [('NestedParentA.NestedChildA', ('roles', 'nestedparenta-nestedchilda', 'class'))]) assert (find_obj(None, None, 'NestedParentA.NestedChildA.subchild_1', 'meth') == - [('NestedParentA.NestedChildA.subchild_1', ('roles', 'method'))]) + [('NestedParentA.NestedChildA.subchild_1', + ('roles', 'nestedparenta-nestedchilda-subchild-1', 'method'))]) assert (find_obj(None, 'NestedParentA', 'NestedChildA.subchild_1', 'meth') == - [('NestedParentA.NestedChildA.subchild_1', ('roles', 'method'))]) + [('NestedParentA.NestedChildA.subchild_1', + ('roles', 'nestedparenta-nestedchilda-subchild-1', 'method'))]) assert (find_obj(None, 'NestedParentA.NestedChildA', 'subchild_1', 'meth') == - [('NestedParentA.NestedChildA.subchild_1', ('roles', 'method'))]) + [('NestedParentA.NestedChildA.subchild_1', + ('roles', 'nestedparenta-nestedchilda-subchild-1', 'method'))]) def test_get_full_qualified_name(): @@ -402,7 +405,7 @@ def test_pydata(app): [desc, ([desc_signature, desc_name, "var"], [desc_content, ()])])) assert 'var' in domain.objects - assert domain.objects['var'] == ('index', 'data') + assert domain.objects['var'] == ('index', 'var', 'data') def test_pyfunction(app): @@ -421,9 +424,9 @@ def test_pyfunction(app): [desc_parameterlist, ()])], [desc_content, ()])])) assert 'func1' in domain.objects - assert domain.objects['func1'] == ('index', 'function') + assert domain.objects['func1'] == ('index', 'func1', 'function') assert 'func2' in domain.objects - assert domain.objects['func2'] == ('index', 'function') + assert domain.objects['func2'] == ('index', 'func2', 'function') def test_pymethod_options(app): @@ -460,61 +463,61 @@ def test_pymethod_options(app): # method assert_node(doctree[1][1][0], addnodes.index, - entries=[('single', 'meth1() (Class method)', 'Class.meth1', '', None)]) + entries=[('single', 'meth1() (Class method)', 'class-meth1', '', None)]) assert_node(doctree[1][1][1], ([desc_signature, ([desc_name, "meth1"], [desc_parameterlist, ()])], [desc_content, ()])) assert 'Class.meth1' in domain.objects - assert domain.objects['Class.meth1'] == ('index', 'method') + assert domain.objects['Class.meth1'] == ('index', 'class-meth1', 'method') # :classmethod: assert_node(doctree[1][1][2], addnodes.index, - entries=[('single', 'meth2() (Class class method)', 'Class.meth2', '', None)]) + entries=[('single', 'meth2() (Class class method)', 'class-meth2', '', None)]) assert_node(doctree[1][1][3], ([desc_signature, ([desc_annotation, "classmethod "], [desc_name, "meth2"], [desc_parameterlist, ()])], [desc_content, ()])) assert 'Class.meth2' in domain.objects - assert domain.objects['Class.meth2'] == ('index', 'method') + assert domain.objects['Class.meth2'] == ('index', 'class-meth2', 'method') # :staticmethod: assert_node(doctree[1][1][4], addnodes.index, - entries=[('single', 'meth3() (Class static method)', 'Class.meth3', '', None)]) + entries=[('single', 'meth3() (Class static method)', 'class-meth3', '', None)]) assert_node(doctree[1][1][5], ([desc_signature, ([desc_annotation, "static "], [desc_name, "meth3"], [desc_parameterlist, ()])], [desc_content, ()])) assert 'Class.meth3' in domain.objects - assert domain.objects['Class.meth3'] == ('index', 'method') + assert domain.objects['Class.meth3'] == ('index', 'class-meth3', 'method') # :async: assert_node(doctree[1][1][6], addnodes.index, - entries=[('single', 'meth4() (Class method)', 'Class.meth4', '', None)]) + entries=[('single', 'meth4() (Class method)', 'class-meth4', '', None)]) assert_node(doctree[1][1][7], ([desc_signature, ([desc_annotation, "async "], [desc_name, "meth4"], [desc_parameterlist, ()])], [desc_content, ()])) assert 'Class.meth4' in domain.objects - assert domain.objects['Class.meth4'] == ('index', 'method') + assert domain.objects['Class.meth4'] == ('index', 'class-meth4', 'method') # :property: assert_node(doctree[1][1][8], addnodes.index, - entries=[('single', 'meth5() (Class property)', 'Class.meth5', '', None)]) + entries=[('single', 'meth5() (Class property)', 'class-meth5', '', None)]) assert_node(doctree[1][1][9], ([desc_signature, ([desc_annotation, "property "], [desc_name, "meth5"])], [desc_content, ()])) assert 'Class.meth5' in domain.objects - assert domain.objects['Class.meth5'] == ('index', 'method') + assert domain.objects['Class.meth5'] == ('index', 'class-meth5', 'method') # :abstractmethod: assert_node(doctree[1][1][10], addnodes.index, - entries=[('single', 'meth6() (Class method)', 'Class.meth6', '', None)]) + entries=[('single', 'meth6() (Class method)', 'class-meth6', '', None)]) assert_node(doctree[1][1][11], ([desc_signature, ([desc_annotation, "abstract "], [desc_name, "meth6"], [desc_parameterlist, ()])], [desc_content, ()])) assert 'Class.meth6' in domain.objects - assert domain.objects['Class.meth6'] == ('index', 'method') + assert domain.objects['Class.meth6'] == ('index', 'class-meth6', 'method') def test_pyclassmethod(app): @@ -529,13 +532,13 @@ def test_pyclassmethod(app): [desc_content, (addnodes.index, desc)])])) assert_node(doctree[1][1][0], addnodes.index, - entries=[('single', 'meth() (Class class method)', 'Class.meth', '', None)]) + entries=[('single', 'meth() (Class class method)', 'class-meth', '', None)]) assert_node(doctree[1][1][1], ([desc_signature, ([desc_annotation, "classmethod "], [desc_name, "meth"], [desc_parameterlist, ()])], [desc_content, ()])) assert 'Class.meth' in domain.objects - assert domain.objects['Class.meth'] == ('index', 'method') + assert domain.objects['Class.meth'] == ('index', 'class-meth', 'method') def test_pystaticmethod(app): @@ -550,13 +553,13 @@ def test_pystaticmethod(app): [desc_content, (addnodes.index, desc)])])) assert_node(doctree[1][1][0], addnodes.index, - entries=[('single', 'meth() (Class static method)', 'Class.meth', '', None)]) + entries=[('single', 'meth() (Class static method)', 'class-meth', '', None)]) assert_node(doctree[1][1][1], ([desc_signature, ([desc_annotation, "static "], [desc_name, "meth"], [desc_parameterlist, ()])], [desc_content, ()])) assert 'Class.meth' in domain.objects - assert domain.objects['Class.meth'] == ('index', 'method') + assert domain.objects['Class.meth'] == ('index', 'class-meth', 'method') def test_pyattribute(app): @@ -573,13 +576,13 @@ def test_pyattribute(app): [desc_content, (addnodes.index, desc)])])) assert_node(doctree[1][1][0], addnodes.index, - entries=[('single', 'attr (Class attribute)', 'Class.attr', '', None)]) + entries=[('single', 'attr (Class attribute)', 'class-attr', '', None)]) assert_node(doctree[1][1][1], ([desc_signature, ([desc_name, "attr"], [desc_annotation, ": str"], [desc_annotation, " = ''"])], [desc_content, ()])) assert 'Class.attr' in domain.objects - assert domain.objects['Class.attr'] == ('index', 'attribute') + assert domain.objects['Class.attr'] == ('index', 'class-attr', 'attribute') @pytest.mark.sphinx(freshenv=True) diff --git a/tests/test_environment.py b/tests/test_environment.py index 9324f36f014..4b1f8e77ede 100644 --- a/tests/test_environment.py +++ b/tests/test_environment.py @@ -84,7 +84,7 @@ def test_object_inventory(app): refs = app.env.domaindata['py']['objects'] assert 'func_without_module' in refs - assert refs['func_without_module'] == ('objects', 'function') + assert refs['func_without_module'] == ('objects', 'func-without-module', 'function') assert 'func_without_module2' in refs assert 'mod.func_in_module' in refs assert 'mod.Cls' in refs diff --git a/tests/test_intl.py b/tests/test_intl.py index 0e7dd4f62c9..ee96490a459 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -870,7 +870,7 @@ def test_xml_refs_in_python_domain(app): assert_elem( para0[0], ['SEE THIS DECORATOR:', 'sensitive_variables()', '.'], - ['sensitive.sensitive_variables']) + ['sensitive-sensitive-variables']) @sphinx_intl From f13c546862edb2097a21550a7f0c2c2e492acb40 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 2 Mar 2020 01:16:40 +0900 Subject: [PATCH 44/72] doc: Update docs --- doc/extdev/appapi.rst | 3 +-- doc/templating.rst | 5 +---- doc/usage/extensions/autosummary.rst | 1 + 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/doc/extdev/appapi.rst b/doc/extdev/appapi.rst index 49081ee9e35..961558600af 100644 --- a/doc/extdev/appapi.rst +++ b/doc/extdev/appapi.rst @@ -45,7 +45,6 @@ package. .. automethod:: Sphinx.add_enumerable_node(node, figtype, title_getter=None, \*\*kwds) -.. method:: Sphinx.add_directive(name, func, content, arguments, \*\*options) .. automethod:: Sphinx.add_directive(name, directiveclass) .. automethod:: Sphinx.add_role(name, role) @@ -54,7 +53,6 @@ package. .. automethod:: Sphinx.add_domain(domain) -.. method:: Sphinx.add_directive_to_domain(domain, name, func, content, arguments, \*\*options) .. automethod:: Sphinx.add_directive_to_domain(domain, name, directiveclass) .. automethod:: Sphinx.add_role_to_domain(domain, name, role) @@ -107,6 +105,7 @@ Emitting events --------------- .. class:: Sphinx + :noindex: .. automethod:: emit(event, \*arguments) diff --git a/doc/templating.rst b/doc/templating.rst index fd0f6b637de..1f4bef9c83d 100644 --- a/doc/templating.rst +++ b/doc/templating.rst @@ -227,6 +227,7 @@ them to generate links or output multiply used elements. documents. .. function:: pathto(file, 1) + :noindex: Return the path to a *file* which is a filename relative to the root of the generated output. Use this to refer to static files. @@ -413,10 +414,6 @@ are in HTML form), these variables are also available: nonempty if the :confval:`html_copy_source` value is ``True``. This has empty value on creating automatically-generated files. -.. data:: title - - The page title. - .. data:: toc The local table of contents for the current page, rendered as HTML bullet diff --git a/doc/usage/extensions/autosummary.rst b/doc/usage/extensions/autosummary.rst index fedc0a3cfa1..cedc8a42f7c 100644 --- a/doc/usage/extensions/autosummary.rst +++ b/doc/usage/extensions/autosummary.rst @@ -273,6 +273,7 @@ Additionally, the following filters are available replaces the builtin Jinja `escape filter`_ that does html-escaping. .. function:: underline(s, line='=') + :noindex: Add a title underline to a piece of text. From d7cab2ebfa768c7b04fbbf131c15462e3a3147ec Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Wed, 4 Mar 2020 00:53:48 +0900 Subject: [PATCH 45/72] test: Add testcase for special case of PythonDomain.find_obj() --- tests/roots/test-domain-py/module.rst | 8 ++++++++ tests/test_domain_py.py | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/tests/roots/test-domain-py/module.rst b/tests/roots/test-domain-py/module.rst index c01032b2682..dce3fa5acce 100644 --- a/tests/roots/test-domain-py/module.rst +++ b/tests/roots/test-domain-py/module.rst @@ -51,3 +51,11 @@ module .. py:attribute:: attr2 :type: :doc:`index` + +.. py:module:: exceptions + +.. py:exception:: Exception + +.. py:module:: object + +.. py:function:: sum() diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index 218ded5101c..8d6d43b487b 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -206,6 +206,18 @@ def find_obj(modname, prefix, obj_name, obj_type, searchmode=0): [('NestedParentA.NestedChildA.subchild_1', ('roles', 'nestedparenta-nestedchilda-subchild-1', 'method'))]) + # special case: exceptions + assert (find_obj('exceptions', None, 'Exception', 'exc') == + [('exceptions.Exception', ('module', 'exceptions-exception', 'exception'))]) + assert (find_obj(None, None, 'Exception', 'exc') == + [('exceptions.Exception', ('module', 'exceptions-exception', 'exception'))]) + + # special case: object + assert (find_obj('object', None, 'sum', 'func') == + [('object.sum', ('module', 'object-sum', 'function'))]) + assert (find_obj(None, None, 'sum', 'func') == + [('object.sum', ('module', 'object-sum', 'function'))]) + def test_get_full_qualified_name(): env = Mock(domaindata={}) From 354259ce0b4baf264958a41a44c1269cfbf5fff4 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Fri, 16 Aug 2019 12:08:28 -0700 Subject: [PATCH 46/72] [2.4.x] Specify the target Python version in the mypy configuration Currently targets Python 3.5. This caught a bug. The subprocess.run() function only started taking the 'encoding' keyword argument starting with Python 3.6. (cherry picked from commit acb2eadc8e692be4c8212cf1d3697e10e298a384) Conflicts: setup.cfg --- setup.cfg | 1 + sphinx/__init__.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 62d50f719b6..6207a6c5365 100644 --- a/setup.cfg +++ b/setup.cfg @@ -40,6 +40,7 @@ paths = . [mypy] +python_version = 3.5 disallow_incomplete_defs = True show_column_numbers = True show_error_context = True diff --git a/sphinx/__init__.py b/sphinx/__init__.py index 10e80616911..2b23bc41283 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -56,8 +56,8 @@ __version__ = __version__[:-1] # remove '+' for PEP-440 version spec. try: ret = subprocess.run(['git', 'show', '-s', '--pretty=format:%h'], - stdout=PIPE, stderr=PIPE, encoding='ascii') + stdout=PIPE, stderr=PIPE) if ret.stdout: - __display_version__ += '/' + ret.stdout.strip() + __display_version__ += '/' + ret.stdout.decode('ascii').strip() except Exception: pass From fbd111cfd13aaa9fe0af95e20d3220df52557803 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 7 Mar 2020 03:42:04 +0100 Subject: [PATCH 47/72] doc/{Makefile,make.bat}: remove unused SPHINXPROJ Appears to have been forgotten in f101bb661. --- doc/Makefile | 1 - doc/make.bat | 1 - 2 files changed, 2 deletions(-) diff --git a/doc/Makefile b/doc/Makefile index d90ae08818b..84eafdbe110 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -5,7 +5,6 @@ PYTHON ?= python3 # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = $(PYTHON) ../sphinx/cmd/build.py -SPHINXPROJ = sphinx SOURCEDIR = . BUILDDIR = _build diff --git a/doc/make.bat b/doc/make.bat index 1e6dc991e25..4bc6ddb9afb 100644 --- a/doc/make.bat +++ b/doc/make.bat @@ -7,7 +7,6 @@ if "%SPHINXBUILD%" == "" ( ) set SOURCEDIR=. set BUILDDIR=_build -set SPHINXPROJ=sphinx-doc if "%1" == "" goto help From d517aa6c16e6361c3389ff9827a68164e21d5f00 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 7 Mar 2020 13:19:37 +0900 Subject: [PATCH 48/72] Close #7079: Make autodoc_typehints=description formal --- CHANGES | 2 ++ doc/usage/extensions/autodoc.rst | 25 ++++--------------------- sphinx/ext/autodoc/__init__.py | 6 ++++-- sphinx/ext/autodoc/typehints.py | 18 +----------------- tests/test_ext_autodoc_configs.py | 13 +++++-------- 5 files changed, 16 insertions(+), 48 deletions(-) diff --git a/CHANGES b/CHANGES index 15c4f376c72..fdac5368db5 100644 --- a/CHANGES +++ b/CHANGES @@ -58,6 +58,8 @@ Features added * #6830: autodoc: consider a member private if docstring contains ``:meta private:`` in info-field-list * #7165: autodoc: Support Annotated type (PEP-593) +* #7079: autodoc: :confval:`autodoc_typehints` accepts ``"description"`` + configuration. It shows typehints as object description * #6558: glossary: emit a warning for duplicated glossary entry * #3106: domain: Register hyperlink target for index page automatically * #6558: std domain: emit a warning for duplicated generic objects diff --git a/doc/usage/extensions/autodoc.rst b/doc/usage/extensions/autodoc.rst index ceef5f36a1d..9c3120eb5b7 100644 --- a/doc/usage/extensions/autodoc.rst +++ b/doc/usage/extensions/autodoc.rst @@ -466,9 +466,13 @@ There are also config values that you can set: following values: * ``'signature'`` -- Show typehints as its signature (default) + * ``'description'`` -- Show typehints as content of function or method * ``'none'`` -- Do not show typehints .. versionadded:: 2.1 + .. versionadded:: 3.0 + + New option ``'description'`` is added. .. confval:: autodoc_warningiserror @@ -596,24 +600,3 @@ member should be included in the documentation by using the following event: ``inherited_members``, ``undoc_members``, ``show_inheritance`` and ``noindex`` that are true if the flag option of same name was given to the auto directive - -Generating documents from type annotations ------------------------------------------- - -As an experimental feature, autodoc provides ``sphinx.ext.autodoc.typehints`` as -an additional extension. It extends autodoc itself to generate function document -from its type annotations. - -To enable the feature, please add ``sphinx.ext.autodoc.typehints`` to list of -extensions and set `'description'` to :confval:`autodoc_typehints`: - -.. code-block:: python - - extensions = ['sphinx.ext.autodoc', 'sphinx.ext.autodoc.typehints'] - - autodoc_typehints = 'description' - -.. versionadded:: 2.4 - - Added as an experimental feature. This will be integrated into autodoc core - in Sphinx-3.0. diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 7abd6c8797b..e08a3d2ef13 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1003,7 +1003,7 @@ def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: (inspect.isroutine(member) and isinstance(parent, ModuleDocumenter))) def format_args(self, **kwargs: Any) -> str: - if self.env.config.autodoc_typehints == 'none': + if self.env.config.autodoc_typehints in ('none', 'description'): kwargs.setdefault('show_annotation', False) if inspect.isbuiltin(self.object) or inspect.ismethoddescriptor(self.object): @@ -1625,7 +1625,8 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('autodoc_default_options', {}, True) app.add_config_value('autodoc_docstring_signature', True, True) app.add_config_value('autodoc_mock_imports', [], True) - app.add_config_value('autodoc_typehints', "signature", True, ENUM("signature", "none")) + app.add_config_value('autodoc_typehints', "signature", True, + ENUM("signature", "description", "none")) app.add_config_value('autodoc_warningiserror', True, True) app.add_config_value('autodoc_inherit_docstrings', True, True) app.add_event('autodoc-before-process-signature') @@ -1634,5 +1635,6 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_event('autodoc-skip-member') app.setup_extension('sphinx.ext.autodoc.type_comment') + app.setup_extension('sphinx.ext.autodoc.typehints') return {'version': sphinx.__display_version__, 'parallel_read_safe': True} diff --git a/sphinx/ext/autodoc/typehints.py b/sphinx/ext/autodoc/typehints.py index 64782dc1cde..0d5cc583076 100644 --- a/sphinx/ext/autodoc/typehints.py +++ b/sphinx/ext/autodoc/typehints.py @@ -18,21 +18,9 @@ from sphinx import addnodes from sphinx.application import Sphinx -from sphinx.config import Config, ENUM from sphinx.util import inspect, typing -def config_inited(app: Sphinx, config: Config) -> None: - if config.autodoc_typehints == 'description': - # HACK: override this to make autodoc suppressing typehints in signatures - config.autodoc_typehints = 'none' # type: ignore - - # preserve user settings - app._autodoc_typehints_description = True # type: ignore - else: - app._autodoc_typehints_description = False # type: ignore - - def record_typehints(app: Sphinx, objtype: str, name: str, obj: Any, options: Dict, args: str, retann: str) -> None: """Record type hints to env object.""" @@ -53,7 +41,7 @@ def record_typehints(app: Sphinx, objtype: str, name: str, obj: Any, def merge_typehints(app: Sphinx, domain: str, objtype: str, contentnode: Element) -> None: if domain != 'py': return - if app._autodoc_typehints_description is False: # type: ignore + if app.config.autodoc_typehints != 'description': return signature = cast(addnodes.desc_signature, contentnode.parent[0]) @@ -141,10 +129,6 @@ def modify_field_list(node: nodes.field_list, annotations: Dict[str, str]) -> No def setup(app: Sphinx) -> Dict[str, Any]: - app.setup_extension('sphinx.ext.autodoc') - app.config.values['autodoc_typehints'] = ('signature', True, - ENUM("signature", "description", "none")) - app.connect('config-inited', config_inited) app.connect('autodoc-process-signature', record_typehints) app.connect('object-description-transform', merge_typehints) diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py index b90772f6e1c..e250b21b389 100644 --- a/tests/test_ext_autodoc_configs.py +++ b/tests/test_ext_autodoc_configs.py @@ -462,10 +462,9 @@ def test_mocked_module_imports(app, warning): assert warning.getvalue() == '' -@pytest.mark.sphinx('html', testroot='ext-autodoc') +@pytest.mark.sphinx('html', testroot='ext-autodoc', + confoverrides={'autodoc_typehints': "signature"}) def test_autodoc_typehints_signature(app): - app.config.autodoc_typehints = "signature" - options = {"members": None, "undoc-members": True} actual = do_autodoc(app, 'module', 'target.typehints', options) @@ -513,10 +512,9 @@ def test_autodoc_typehints_signature(app): ] -@pytest.mark.sphinx('html', testroot='ext-autodoc') +@pytest.mark.sphinx('html', testroot='ext-autodoc', + confoverrides={'autodoc_typehints': "none"}) def test_autodoc_typehints_none(app): - app.config.autodoc_typehints = "none" - options = {"members": None, "undoc-members": True} actual = do_autodoc(app, 'module', 'target.typehints', options) @@ -564,8 +562,7 @@ def test_autodoc_typehints_none(app): @pytest.mark.sphinx('text', testroot='ext-autodoc', - confoverrides={'extensions': ['sphinx.ext.autodoc.typehints'], - 'autodoc_typehints': 'description'}) + confoverrides={'autodoc_typehints': "description"}) def test_autodoc_typehints_description(app): app.build() context = (app.outdir / 'index.txt').read_text() From 961b4d1545eb5174617d8c3251006cc19f420088 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 3 Feb 2018 13:44:17 +0900 Subject: [PATCH 49/72] Close #2815: autodoc: Support singledispatch functions --- CHANGES | 1 + sphinx/ext/autodoc/__init__.py | 60 ++++++++++++++++++- sphinx/ext/autosummary/generate.py | 2 + sphinx/util/inspect.py | 11 ++++ .../test-ext-autodoc/target/singledispatch.py | 19 ++++++ tests/test_autodoc.py | 19 ++++++ 6 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 tests/roots/test-ext-autodoc/target/singledispatch.py diff --git a/CHANGES b/CHANGES index 15c4f376c72..65a69f6b8c5 100644 --- a/CHANGES +++ b/CHANGES @@ -58,6 +58,7 @@ Features added * #6830: autodoc: consider a member private if docstring contains ``:meta private:`` in info-field-list * #7165: autodoc: Support Annotated type (PEP-593) +* #2815: autodoc: Support singledispatch functions * #6558: glossary: emit a warning for duplicated glossary entry * #3106: domain: Register hyperlink target for index page automatically * #6558: std domain: emit a warning for duplicated generic objects diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 7abd6c8797b..f6d581cfc4c 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -14,7 +14,8 @@ import re import warnings from types import ModuleType -from typing import Any, Callable, Dict, Iterator, List, Sequence, Set, Tuple, Union +from typing import Any, Callable, Dict, Iterator, List, Sequence, Set, Tuple, Type, Union +from unittest.mock import patch from docutils.statemachine import StringList @@ -1056,6 +1057,62 @@ def add_directive_header(self, sig: str) -> None: self.add_line(' :async:', sourcename) +class SingledispatchFunctionDocumenter(FunctionDocumenter): + """ + Specialized Documenter subclass for singledispatch'ed functions. + """ + objtype = 'singledispatch_function' + directivetype = 'function' + member_order = 30 + + # before FunctionDocumenter + priority = FunctionDocumenter.priority + 1 + + @classmethod + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: + return (super().can_document_member(member, membername, isattr, parent) and + inspect.is_singledispatch_function(member)) + + def add_directive_header(self, sig: str) -> None: + sourcename = self.get_sourcename() + + # intercept generated directive headers + # TODO: It is very hacky to use mock to intercept header generation + with patch.object(self, 'add_line') as add_line: + super().add_directive_header(sig) + + # output first line of header + self.add_line(*add_line.call_args_list[0][0]) + + # inserts signature of singledispatch'ed functions + for typ, func in self.object.registry.items(): + if typ is object: + pass # default implementation. skipped. + else: + self.annotate_to_first_argument(func, typ) + + documenter = FunctionDocumenter(self.directive, '') + documenter.object = func + self.add_line(' %s%s' % (self.format_name(), + documenter.format_signature()), + sourcename) + + # output remains of directive header + for call in add_line.call_args_list[1:]: + self.add_line(*call[0]) + + def annotate_to_first_argument(self, func: Callable, typ: Type) -> None: + """Annotate type hint to the first argument of function if needed.""" + sig = inspect.signature(func) + if len(sig.parameters) == 0: + return + + name = list(sig.parameters)[0] + if name not in func.__annotations__: + func.__annotations__[name] = typ + + class DecoratorDocumenter(FunctionDocumenter): """ Specialized Documenter subclass for decorator functions. @@ -1612,6 +1669,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_autodocumenter(DataDocumenter) app.add_autodocumenter(DataDeclarationDocumenter) app.add_autodocumenter(FunctionDocumenter) + app.add_autodocumenter(SingledispatchFunctionDocumenter) app.add_autodocumenter(DecoratorDocumenter) app.add_autodocumenter(MethodDocumenter) app.add_autodocumenter(AttributeDocumenter) diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index ef623e2bd8c..8bea3b4a337 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -72,12 +72,14 @@ def setup_documenters(app: Any) -> None: FunctionDocumenter, MethodDocumenter, AttributeDocumenter, InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter, SlotsAttributeDocumenter, DataDeclarationDocumenter, + SingledispatchFunctionDocumenter, ) documenters = [ ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter, FunctionDocumenter, MethodDocumenter, AttributeDocumenter, InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter, SlotsAttributeDocumenter, DataDeclarationDocumenter, + SingledispatchFunctionDocumenter, ] # type: List[Type[Documenter]] for documenter in documenters: app.registry.add_documenter(documenter.objtype, documenter) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 281ef4493be..58f922caea9 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -224,6 +224,17 @@ def isattributedescriptor(obj: Any) -> bool: return False +def is_singledispatch_function(obj: Any) -> bool: + """Check if the object is singledispatch function.""" + if (inspect.isfunction(obj) and + hasattr(obj, 'dispatch') and + hasattr(obj, 'register') and + obj.dispatch.__module__ == 'functools'): + return True + else: + return False + + def isfunction(obj: Any) -> bool: """Check if the object is function.""" return inspect.isfunction(unwrap(obj)) diff --git a/tests/roots/test-ext-autodoc/target/singledispatch.py b/tests/roots/test-ext-autodoc/target/singledispatch.py new file mode 100644 index 00000000000..c33d001b1c1 --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/singledispatch.py @@ -0,0 +1,19 @@ +from functools import singledispatch + + +@singledispatch +def func(arg, kwarg=None): + """A function for general use.""" + pass + + +@func.register(int) +def _func_int(arg, kwarg=None): + """A function for int.""" + pass + + +@func.register(str) +def _func_str(arg, kwarg=None): + """A function for str.""" + pass diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index b001de80477..dcbdc6d3adc 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -1563,3 +1563,22 @@ def test_autodoc_for_egged_code(app): ' :module: sample', '' ] + + +@pytest.mark.usefixtures('setup_test') +def test_singledispatch(): + options = {"members": None} + actual = do_autodoc(app, 'module', 'target.singledispatch', options) + assert list(actual) == [ + '', + '.. py:module:: target.singledispatch', + '', + '', + '.. py:function:: func(arg, kwarg=None)', + ' func(arg: int, kwarg=None)', + ' func(arg: str, kwarg=None)', + ' :module: target.singledispatch', + '', + ' A function for general use.', + ' ' + ] From 8f7cc26b208ae9e477a95c8daf5e690e49945ad5 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 1 Mar 2020 22:15:10 +0900 Subject: [PATCH 50/72] autodoc: Support singledispatch methods --- CHANGES | 2 +- sphinx/ext/autodoc/__init__.py | 61 +++++++++++++++++++ sphinx/util/inspect.py | 9 +++ .../target/singledispatchmethod.py | 20 ++++++ tests/test_autodoc.py | 27 ++++++++ 5 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 tests/roots/test-ext-autodoc/target/singledispatchmethod.py diff --git a/CHANGES b/CHANGES index 65a69f6b8c5..3263f9fb5cd 100644 --- a/CHANGES +++ b/CHANGES @@ -58,7 +58,7 @@ Features added * #6830: autodoc: consider a member private if docstring contains ``:meta private:`` in info-field-list * #7165: autodoc: Support Annotated type (PEP-593) -* #2815: autodoc: Support singledispatch functions +* #2815: autodoc: Support singledispatch functions and methods * #6558: glossary: emit a warning for duplicated glossary entry * #3106: domain: Register hyperlink target for index page automatically * #6558: std domain: emit a warning for duplicated generic objects diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index f6d581cfc4c..97995a410d7 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1457,6 +1457,66 @@ def document_members(self, all_members: bool = False) -> None: pass +class SingledispatchMethodDocumenter(MethodDocumenter): + """ + Specialized Documenter subclass for singledispatch'ed methods. + """ + objtype = 'singledispatch_method' + directivetype = 'method' + member_order = 50 + + # before MethodDocumenter + priority = MethodDocumenter.priority + 1 + + @classmethod + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: + if super().can_document_member(member, membername, isattr, parent) and parent.object: + meth = parent.object.__dict__.get(membername) + return inspect.is_singledispatch_method(meth) + else: + return False + + def add_directive_header(self, sig: str) -> None: + sourcename = self.get_sourcename() + + # intercept generated directive headers + # TODO: It is very hacky to use mock to intercept header generation + with patch.object(self, 'add_line') as add_line: + super().add_directive_header(sig) + + # output first line of header + self.add_line(*add_line.call_args_list[0][0]) + + # inserts signature of singledispatch'ed functions + meth = self.parent.__dict__.get(self.objpath[-1]) + for typ, func in meth.dispatcher.registry.items(): + if typ is object: + pass # default implementation. skipped. + else: + self.annotate_to_first_argument(func, typ) + + documenter = MethodDocumenter(self.directive, '') + documenter.object = func + self.add_line(' %s%s' % (self.format_name(), + documenter.format_signature()), + sourcename) + + # output remains of directive header + for call in add_line.call_args_list[1:]: + self.add_line(*call[0]) + + def annotate_to_first_argument(self, func: Callable, typ: Type) -> None: + """Annotate type hint to the first argument of function if needed.""" + sig = inspect.signature(func, bound_method=True) + if len(sig.parameters) == 0: + return + + name = list(sig.parameters)[0] + if name not in func.__annotations__: + func.__annotations__[name] = typ + + class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # type: ignore """ Specialized Documenter subclass for attributes. @@ -1672,6 +1732,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_autodocumenter(SingledispatchFunctionDocumenter) app.add_autodocumenter(DecoratorDocumenter) app.add_autodocumenter(MethodDocumenter) + app.add_autodocumenter(SingledispatchMethodDocumenter) app.add_autodocumenter(AttributeDocumenter) app.add_autodocumenter(PropertyDocumenter) app.add_autodocumenter(InstanceAttributeDocumenter) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 58f922caea9..d22df765618 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -235,6 +235,15 @@ def is_singledispatch_function(obj: Any) -> bool: return False +def is_singledispatch_method(obj: Any) -> bool: + """Check if the object is singledispatch method.""" + try: + from functools import singledispatchmethod # type: ignore + return isinstance(obj, singledispatchmethod) + except ImportError: # py35-37 + return False + + def isfunction(obj: Any) -> bool: """Check if the object is function.""" return inspect.isfunction(unwrap(obj)) diff --git a/tests/roots/test-ext-autodoc/target/singledispatchmethod.py b/tests/roots/test-ext-autodoc/target/singledispatchmethod.py new file mode 100644 index 00000000000..b5ccbb2f09d --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/singledispatchmethod.py @@ -0,0 +1,20 @@ +from functools import singledispatchmethod + + +class Foo: + """docstring""" + + @singledispatchmethod + def meth(self, arg, kwarg=None): + """A method for general use.""" + pass + + @meth.register(int) + def _meth_int(self, arg, kwarg=None): + """A method for int.""" + pass + + @meth.register(str) + def _meth_str(self, arg, kwarg=None): + """A method for str.""" + pass diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index dcbdc6d3adc..0510fff86be 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -1582,3 +1582,30 @@ def test_singledispatch(): ' A function for general use.', ' ' ] + + +@pytest.mark.skipif(sys.version_info < (3, 8), + reason='singledispatchmethod is available since python3.8') +@pytest.mark.usefixtures('setup_test') +def test_singledispatchmethod(): + options = {"members": None} + actual = do_autodoc(app, 'module', 'target.singledispatchmethod', options) + assert list(actual) == [ + '', + '.. py:module:: target.singledispatchmethod', + '', + '', + '.. py:class:: Foo', + ' :module: target.singledispatchmethod', + '', + ' docstring', + ' ', + ' ', + ' .. py:method:: Foo.meth(arg, kwarg=None)', + ' Foo.meth(arg: int, kwarg=None)', + ' Foo.meth(arg: str, kwarg=None)', + ' :module: target.singledispatchmethod', + ' ', + ' A method for general use.', + ' ' + ] From ea329866b0dabe67f40d0cf2ded22a5d5bd701f4 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 7 Mar 2020 18:01:16 +0900 Subject: [PATCH 51/72] Update CHANGES for #6903 --- CHANGES | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index 15c4f376c72..e21623cc413 100644 --- a/CHANGES +++ b/CHANGES @@ -23,16 +23,13 @@ Incompatible changes * Due to the scoping changes for :rst:dir:`productionlist` some uses of :rst:role:`token` must be modified to include the scope which was previously ignored. -* #6903: js domain: Internal data structure has changed. Both objects and - modules have node_id for cross reference +* #6903: Internal data structure of Python, reST and standard domains have + changed. The node_id is added to the index of objects and modules. Now they + contains a pair of docname and node_id for cross reference. * #7210: js domain: Non intended behavior is removed such as ``parseInt_`` links to ``.. js:function:: parseInt`` -* #6903: rst domain: Internal data structure has changed. Now objects have - node_id for cross reference * #7229: rst domain: Non intended behavior is removed such as ``numref_`` links to ``.. rst:role:: numref`` -* #6903: py domain: Internal data structure has changed. Both objects and - modules have node_id for cross reference * #6903: py domain: Non intended behavior is removed such as ``say_hello_`` links to ``.. py:function:: say_hello()`` From 8b5686d87c2b1729cd6f84810400ca7bdf800ebc Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 7 Mar 2020 18:49:14 +0900 Subject: [PATCH 52/72] Fix #7267: autodoc: errormsg for invalid directive options has wrong location --- CHANGES | 1 + sphinx/ext/autodoc/directive.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 15c4f376c72..a9f109061d9 100644 --- a/CHANGES +++ b/CHANGES @@ -91,6 +91,7 @@ Bugs fixed * C++, suppress warnings for directly dependent typenames in cross references generated automatically in signatures. * #5637: autodoc: Incorrect handling of nested class names on show-inheritance +* #7267: autodoc: error message for invalid directive options has wrong location * #5637: inheritance_diagram: Incorrect handling of nested class names * #7139: ``code-block:: guess`` does not work diff --git a/sphinx/ext/autodoc/directive.py b/sphinx/ext/autodoc/directive.py index b44bd75b39e..9302a954a9d 100644 --- a/sphinx/ext/autodoc/directive.py +++ b/sphinx/ext/autodoc/directive.py @@ -137,7 +137,7 @@ def run(self) -> List[Node]: except (KeyError, ValueError, TypeError) as exc: # an option is either unknown or has a wrong type logger.error('An option to %s is either unknown or has an invalid value: %s' % - (self.name, exc), location=(source, lineno)) + (self.name, exc), location=(self.env.docname, lineno)) return [] # generate the output From cfb9f8387b85480d7ac71bf1eece027323659cdb Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 7 Mar 2020 22:56:37 +0900 Subject: [PATCH 53/72] Fix #7266: Update deprecation messages for PyClassmember and PyModulelevel --- sphinx/domains/python.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index ac6ee48399b..be6bf34e3f3 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -397,8 +397,14 @@ class PyModulelevel(PyObject): """ def run(self) -> List[Node]: - warnings.warn('PyModulelevel is deprecated.', - RemovedInSphinx40Warning) + for cls in self.__class__.__mro__: + if cls.__name__ != 'DirectiveAdapter': + warnings.warn('PyModulelevel is deprecated. ' + 'Please check the implementation of %s' % cls, + RemovedInSphinx40Warning) + break + else: + warnings.warn('PyModulelevel is deprecated', RemovedInSphinx40Warning) return super().run() @@ -500,8 +506,14 @@ class PyClassmember(PyObject): """ def run(self) -> List[Node]: - warnings.warn('PyClassmember is deprecated.', - RemovedInSphinx40Warning) + for cls in self.__class__.__mro__: + if cls.__name__ != 'DirectiveAdapter': + warnings.warn('PyClassmember is deprecated. ' + 'Please check the implementation of %s' % cls, + RemovedInSphinx40Warning) + break + else: + warnings.warn('PyClassmember is deprecated', RemovedInSphinx40Warning) return super().run() From dd455fe3d379054a704c97b32c14a992306680b5 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 7 Mar 2020 23:05:26 +0900 Subject: [PATCH 54/72] test: Add testcases for decorators --- tests/test_domain_py.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index 218ded5101c..27819af6b4f 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -585,6 +585,36 @@ def test_pyattribute(app): assert domain.objects['Class.attr'] == ('index', 'class-attr', 'attribute') +def test_pydecorator_signature(app): + text = ".. py:decorator:: deco" + domain = app.env.get_domain('py') + doctree = restructuredtext.parse(app, text) + assert_node(doctree, (addnodes.index, + [desc, ([desc_signature, ([desc_addname, "@"], + [desc_name, "deco"])], + desc_content)])) + assert_node(doctree[1], addnodes.desc, desctype="function", + domain="py", objtype="function", noindex=False) + + assert 'deco' in domain.objects + assert domain.objects['deco'] == ('index', 'deco', 'function') + + +def test_pydecoratormethod_signature(app): + text = ".. py:decoratormethod:: deco" + domain = app.env.get_domain('py') + doctree = restructuredtext.parse(app, text) + assert_node(doctree, (addnodes.index, + [desc, ([desc_signature, ([desc_addname, "@"], + [desc_name, "deco"])], + desc_content)])) + assert_node(doctree[1], addnodes.desc, desctype="method", + domain="py", objtype="method", noindex=False) + + assert 'deco' in domain.objects + assert domain.objects['deco'] == ('index', 'deco', 'method') + + @pytest.mark.sphinx(freshenv=True) def test_module_index(app): text = (".. py:module:: docutils\n" From 3cadc825593718a150db95d20caa6a2218237735 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 7 Mar 2020 23:12:34 +0900 Subject: [PATCH 55/72] py domain: refactor PyDecoratorFunction and PyDecoratorMethod --- sphinx/domains/python.py | 52 +++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index e667f91dc7c..ea8d6bb707c 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -489,6 +489,23 @@ def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: return _('%s() (built-in function)') % name +class PyDecoratorFunction(PyFunction): + """Description of a decorator.""" + + def run(self) -> List[Node]: + # a decorator function is a function after all + self.name = 'py:function' + return super().run() + + def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]: + ret = super().handle_signature(sig, signode) + signode.insert(0, addnodes.desc_addname('@', '@')) + return ret + + def needs_arglist(self) -> bool: + return False + + class PyVariable(PyObject): """Description of a variable.""" @@ -700,6 +717,22 @@ def run(self) -> List[Node]: return super().run() +class PyDecoratorMethod(PyMethod): + """Description of a decoratormethod.""" + + def run(self) -> List[Node]: + self.name = 'py:method' + return super().run() + + def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]: + ret = super().handle_signature(sig, signode) + signode.insert(0, addnodes.desc_addname('@', '@')) + return ret + + def needs_arglist(self) -> bool: + return False + + class PyAttribute(PyObject): """Description of an attribute.""" @@ -750,25 +783,6 @@ def needs_arglist(self) -> bool: return False -class PyDecoratorFunction(PyDecoratorMixin, PyModulelevel): - """ - Directive to mark functions meant to be used as decorators. - """ - def run(self) -> List[Node]: - # a decorator function is a function after all - self.name = 'py:function' - return super().run() - - -class PyDecoratorMethod(PyDecoratorMixin, PyClassmember): - """ - Directive to mark methods meant to be used as decorators. - """ - def run(self) -> List[Node]: - self.name = 'py:method' - return super().run() - - class PyModule(SphinxDirective): """ Directive to mark description of a new module. From d49bec1c6706bff5a0a4adb7446e0e9a33b39813 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 7 Mar 2020 23:13:06 +0900 Subject: [PATCH 56/72] py domain: Deprecate PyDecoratorMixin --- CHANGES | 1 + doc/extdev/deprecated.rst | 5 +++++ sphinx/domains/python.py | 11 ++++++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 15c4f376c72..ef26337aba8 100644 --- a/CHANGES +++ b/CHANGES @@ -42,6 +42,7 @@ Deprecated * ``desc_signature['first']`` * ``sphinx.directives.DescDirective`` * ``sphinx.domains.std.StandardDomain.add_object()`` +* ``sphinx.domains.python.PyDecoratorMixin`` * ``sphinx.parsers.Parser.app`` * ``sphinx.testing.path.Path.text()`` * ``sphinx.testing.path.Path.bytes()`` diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index e98652ed256..cf914a7cddc 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -41,6 +41,11 @@ The following is a list of deprecated interfaces. - 5.0 - ``sphinx.domains.std.StandardDomain.note_object()`` + * - ``sphinx.domains.python.PyDecoratorMixin`` + - 3.0 + - 5.0 + - N/A + * - ``sphinx.parsers.Parser.app`` - 3.0 - 5.0 diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index ea8d6bb707c..eed7eaaa6a4 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -25,7 +25,7 @@ from sphinx.addnodes import pending_xref, desc_signature from sphinx.application import Sphinx from sphinx.builders import Builder -from sphinx.deprecation import RemovedInSphinx40Warning +from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType, Index, IndexEntry from sphinx.environment import BuildEnvironment @@ -775,6 +775,15 @@ class PyDecoratorMixin: Mixin for decorator directives. """ def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]: + for cls in self.__class__.__mro__: + if cls.__name__ != 'DirectiveAdapter': + warnings.warn('PyDecoratorMixin is deprecated. ' + 'Please check the implementation of %s' % cls, + RemovedInSphinx50Warning) + break + else: + warnings.warn('PyDecoratorMixin is deprecated', RemovedInSphinx50Warning) + ret = super().handle_signature(sig, signode) # type: ignore signode.insert(0, addnodes.desc_addname('@', '@')) return ret From 8e1934b6a131c239fb27f685714864c5d14d3625 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 7 Mar 2020 17:46:26 +0900 Subject: [PATCH 57/72] Close #7246: Drop special cross reference helper for exceptions, functions and methods --- CHANGES | 2 ++ sphinx/domains/python.py | 8 -------- tests/test_domain_py.py | 12 ------------ 3 files changed, 2 insertions(+), 20 deletions(-) diff --git a/CHANGES b/CHANGES index c5f26b37c54..268b6d835e4 100644 --- a/CHANGES +++ b/CHANGES @@ -32,6 +32,8 @@ Incompatible changes to ``.. rst:role:: numref`` * #6903: py domain: Non intended behavior is removed such as ``say_hello_`` links to ``.. py:function:: say_hello()`` +* #7246: py domain: Drop special cross reference helper for exceptions, + functions and methods Deprecated ---------- diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index e667f91dc7c..4fd7dab08f7 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -1110,14 +1110,6 @@ def find_obj(self, env: BuildEnvironment, modname: str, classname: str, elif modname and classname and \ modname + '.' + classname + '.' + name in self.objects: newname = modname + '.' + classname + '.' + name - # special case: builtin exceptions have module "exceptions" set - elif type == 'exc' and '.' not in name and \ - 'exceptions.' + name in self.objects: - newname = 'exceptions.' + name - # special case: object methods - elif type in ('func', 'meth') and '.' not in name and \ - 'object.' + name in self.objects: - newname = 'object.' + name if newname is not None: matches.append((newname, self.objects[newname])) return matches diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index 8d6d43b487b..218ded5101c 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -206,18 +206,6 @@ def find_obj(modname, prefix, obj_name, obj_type, searchmode=0): [('NestedParentA.NestedChildA.subchild_1', ('roles', 'nestedparenta-nestedchilda-subchild-1', 'method'))]) - # special case: exceptions - assert (find_obj('exceptions', None, 'Exception', 'exc') == - [('exceptions.Exception', ('module', 'exceptions-exception', 'exception'))]) - assert (find_obj(None, None, 'Exception', 'exc') == - [('exceptions.Exception', ('module', 'exceptions-exception', 'exception'))]) - - # special case: object - assert (find_obj('object', None, 'sum', 'func') == - [('object.sum', ('module', 'object-sum', 'function'))]) - assert (find_obj(None, None, 'sum', 'func') == - [('object.sum', ('module', 'object-sum', 'function'))]) - def test_get_full_qualified_name(): env = Mock(domaindata={}) From a717ffec78db1f92553b42f6524b8f6b031bfda5 Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 7 Mar 2020 18:19:52 +0100 Subject: [PATCH 58/72] Fix use of html_file_suffix instead of html_link_suffix in search results --- sphinx/builders/html/__init__.py | 1 + sphinx/themes/basic/static/documentation_options.js_t | 1 + sphinx/themes/basic/static/searchtools.js | 5 ++++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index cf8cd56ceba..aff75d96838 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -473,6 +473,7 @@ def prepare_writing(self, docnames: Set[str]) -> None: 'show_source': self.config.html_show_sourcelink, 'sourcelink_suffix': self.config.html_sourcelink_suffix, 'file_suffix': self.out_suffix, + 'link_suffix': self.link_suffix, 'script_files': self.script_files, 'language': self.config.language, 'css_files': self.css_files, diff --git a/sphinx/themes/basic/static/documentation_options.js_t b/sphinx/themes/basic/static/documentation_options.js_t index 059e8871cc5..8afaac2f8d9 100644 --- a/sphinx/themes/basic/static/documentation_options.js_t +++ b/sphinx/themes/basic/static/documentation_options.js_t @@ -5,6 +5,7 @@ var DOCUMENTATION_OPTIONS = { COLLAPSE_INDEX: false, BUILDER: '{{ builder }}', FILE_SUFFIX: '{{ file_suffix }}', + LINK_SUFFIX: '{{ link_suffix }}', HAS_SOURCE: {{ has_source|lower }}, SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}', NAVIGATION_WITH_KEYS: {{ 'true' if theme_navigation_with_keys|tobool else 'false'}} diff --git a/sphinx/themes/basic/static/searchtools.js b/sphinx/themes/basic/static/searchtools.js index d11b33a78f7..ab564996551 100644 --- a/sphinx/themes/basic/static/searchtools.js +++ b/sphinx/themes/basic/static/searchtools.js @@ -251,6 +251,7 @@ var Search = { var item = results.pop(); var listItem = $('
  • '); var requestUrl = ""; + var linkUrl = ""; if (DOCUMENTATION_OPTIONS.BUILDER === 'dirhtml') { // dirhtml builder var dirname = item[0] + '/'; @@ -260,13 +261,15 @@ var Search = { dirname = ''; } requestUrl = DOCUMENTATION_OPTIONS.URL_ROOT + dirname; + linkUrl = requestUrl; } else { // normal html builders requestUrl = DOCUMENTATION_OPTIONS.URL_ROOT + item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX; + linkUrl = item[0] + DOCUMENTATION_OPTIONS.LINK_SUFFIX; } listItem.append($('').attr('href', - requestUrl + + linkUrl + highlightstring + item[2]).html(item[1])); if (item[3]) { listItem.append($(' (' + item[3] + ')')); From 2b6f06b6228e35564ea789188ae4d47fad3f776e Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 8 Mar 2020 22:15:50 +0900 Subject: [PATCH 59/72] Update message catalog --- sphinx/locale/sphinx.pot | 1782 ++++++++++++++++++++------------------ 1 file changed, 934 insertions(+), 848 deletions(-) diff --git a/sphinx/locale/sphinx.pot b/sphinx/locale/sphinx.pot index c4f9a54fb09..da326058593 100644 --- a/sphinx/locale/sphinx.pot +++ b/sphinx/locale/sphinx.pot @@ -1,126 +1,136 @@ # Translations template for Sphinx. -# Copyright (C) 2019 ORGANIZATION +# Copyright (C) 2020 ORGANIZATION # This file is distributed under the same license as the Sphinx project. -# FIRST AUTHOR , 2019. +# FIRST AUTHOR , 2020. # #, fuzzy msgid "" msgstr "" -"Project-Id-Version: Sphinx 2.0.1\n" +"Project-Id-Version: Sphinx 3.0.0\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2019-03-29 01:05+0900\n" +"POT-Creation-Date: 2020-03-08 22:11+0900\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.6.0\n" +"Generated-By: Babel 2.8.0\n" -#: sphinx/application.py:153 +#: sphinx/application.py:159 #, python-format msgid "config directory doesn't contain a conf.py file (%s)" msgstr "" -#: sphinx/application.py:157 +#: sphinx/application.py:163 #, python-format msgid "Cannot find source directory (%s)" msgstr "" -#: sphinx/application.py:161 +#: sphinx/application.py:167 msgid "Source directory and destination directory cannot be identical" msgstr "" -#: sphinx/application.py:192 +#: sphinx/application.py:198 #, python-format msgid "Running Sphinx v%s" msgstr "" -#: sphinx/application.py:214 +#: sphinx/application.py:202 +msgid "" +"For security reason, parallel mode is disabled on macOS and python3.8 and" +" above. For more details, please read https://github.com/sphinx-" +"doc/sphinx/issues/6803" +msgstr "" + +#: sphinx/application.py:226 #, python-format msgid "" "This project needs at least Sphinx v%s and therefore cannot be built with" " this version." msgstr "" -#: sphinx/application.py:234 +#: sphinx/application.py:246 msgid "making output directory" msgstr "" -#: sphinx/application.py:239 sphinx/registry.py:470 +#: sphinx/application.py:251 sphinx/registry.py:399 #, python-format msgid "while setting up extension %s:" msgstr "" -#: sphinx/application.py:245 +#: sphinx/application.py:257 msgid "" "'setup' as currently defined in conf.py isn't a Python callable. Please " "modify its definition to make it a callable function. This is needed for " "conf.py to behave as a Sphinx extension." msgstr "" -#: sphinx/application.py:269 +#: sphinx/application.py:282 #, python-format msgid "loading translations [%s]... " msgstr "" -#: sphinx/application.py:285 sphinx/builders/html.py:852 -#: sphinx/builders/html.py:870 sphinx/builders/html.py:1133 -#: sphinx/builders/html.py:1151 sphinx/util/__init__.py:702 +#: sphinx/application.py:296 sphinx/util/__init__.py:646 msgid "done" msgstr "" -#: sphinx/application.py:287 +#: sphinx/application.py:298 msgid "not available for built-in messages" msgstr "" -#: sphinx/application.py:298 +#: sphinx/application.py:308 msgid "loading pickled environment" msgstr "" -#: sphinx/application.py:303 +#: sphinx/application.py:313 #, python-format msgid "failed: %s" msgstr "" -#: sphinx/application.py:313 +#: sphinx/application.py:321 msgid "No builder selected, using default: html" msgstr "" -#: sphinx/application.py:344 +#: sphinx/application.py:349 msgid "succeeded" msgstr "" -#: sphinx/application.py:344 +#: sphinx/application.py:350 msgid "finished with problems" msgstr "" -#: sphinx/application.py:346 +#: sphinx/application.py:353 +#, python-format +msgid "build %s, %s warning (with warnings treated as errors)." +msgstr "" + +#: sphinx/application.py:357 #, python-format msgid "build %s, %s warning." msgstr "" -#: sphinx/application.py:350 +#: sphinx/application.py:363 #, python-format msgid "build %s." msgstr "" -#: sphinx/application.py:557 +#: sphinx/application.py:552 #, python-format msgid "node class %r is already registered, its visitors will be overridden" msgstr "" -#: sphinx/application.py:654 +#: sphinx/application.py:626 #, python-format msgid "directive %r is already registered, it will be overridden" msgstr "" -#: sphinx/application.py:677 sphinx/application.py:696 +#: sphinx/application.py:644 sphinx/application.py:662 #, python-format msgid "role %r is already registered, it will be overridden" msgstr "" -#: sphinx/application.py:1179 +#: sphinx/application.py:1122 #, python-format msgid "" "the %s extension does not declare if it is safe for parallel reading, " @@ -128,7 +138,12 @@ msgid "" "explicit" msgstr "" -#: sphinx/application.py:1185 +#: sphinx/application.py:1126 +#, python-format +msgid "the %s extension is not safe for parallel reading" +msgstr "" + +#: sphinx/application.py:1129 #, python-format msgid "" "the %s extension does not declare if it is safe for parallel writing, " @@ -136,55 +151,60 @@ msgid "" "explicit" msgstr "" -#: sphinx/application.py:1196 +#: sphinx/application.py:1133 +#, python-format +msgid "the %s extension is not safe for parallel writing" +msgstr "" + +#: sphinx/application.py:1141 sphinx/application.py:1145 #, python-format msgid "doing serial %s" msgstr "" -#: sphinx/config.py:220 +#: sphinx/config.py:191 #, python-format msgid "" "cannot override dictionary config setting %r, ignoring (use %r to set " "individual elements)" msgstr "" -#: sphinx/config.py:229 +#: sphinx/config.py:200 #, python-format msgid "invalid number %r for config value %r, ignoring" msgstr "" -#: sphinx/config.py:234 +#: sphinx/config.py:205 #, python-format msgid "cannot override config setting %r with unsupported type, ignoring" msgstr "" -#: sphinx/config.py:264 +#: sphinx/config.py:233 #, python-format msgid "unknown config value %r in override, ignoring" msgstr "" -#: sphinx/config.py:282 +#: sphinx/config.py:250 #, python-format msgid "No such config value: %s" msgstr "" -#: sphinx/config.py:312 +#: sphinx/config.py:274 #, python-format msgid "Config value %r already present" msgstr "" -#: sphinx/config.py:363 +#: sphinx/config.py:321 #, python-format msgid "There is a syntax error in your configuration file: %s\n" msgstr "" -#: sphinx/config.py:366 +#: sphinx/config.py:324 msgid "" "The configuration file (or one of the modules it imports) called " "sys.exit()" msgstr "" -#: sphinx/config.py:370 +#: sphinx/config.py:331 #, python-format msgid "" "There is a programmable error in your configuration file:\n" @@ -192,52 +212,52 @@ msgid "" "%s" msgstr "" -#: sphinx/config.py:397 +#: sphinx/config.py:357 #, python-format msgid "" "The config value `source_suffix' expects a string, list of strings, or " "dictionary. But `%r' is given." msgstr "" -#: sphinx/config.py:405 +#: sphinx/config.py:364 #, python-format msgid "Section %s" msgstr "" -#: sphinx/config.py:406 +#: sphinx/config.py:365 #, python-format msgid "Fig. %s" msgstr "" -#: sphinx/config.py:407 +#: sphinx/config.py:366 #, python-format msgid "Table %s" msgstr "" -#: sphinx/config.py:408 +#: sphinx/config.py:367 #, python-format msgid "Listing %s" msgstr "" -#: sphinx/config.py:447 +#: sphinx/config.py:404 msgid "" "The config value `{name}` has to be a one of {candidates}, but " "`{current}` is given." msgstr "" -#: sphinx/config.py:465 +#: sphinx/config.py:422 msgid "" "The config value `{name}' has type `{current.__name__}'; expected " "{permitted}." msgstr "" -#: sphinx/config.py:478 +#: sphinx/config.py:435 msgid "" "The config value `{name}' has type `{current.__name__}', defaults to " "`{default.__name__}'." msgstr "" -#: sphinx/config.py:497 +#: sphinx/config.py:453 #, python-format msgid "" "the config value %r is set to a string with non-ASCII characters; this " @@ -245,892 +265,898 @@ msgid "" "%r." msgstr "" -#: sphinx/config.py:506 +#: sphinx/config.py:461 #, python-format msgid "primary_domain %r not found, ignored." msgstr "" -#: sphinx/config.py:518 +#: sphinx/config.py:473 msgid "" "Since v2.0, Sphinx uses \"index\" as master_doc by default. Please add " "\"master_doc = 'contents'\" to your conf.py." msgstr "" -#: sphinx/events.py:54 +#: sphinx/events.py:71 #, python-format msgid "Event %r already present" msgstr "" -#: sphinx/events.py:60 +#: sphinx/events.py:77 #, python-format msgid "Unknown event name: %s" msgstr "" -#: sphinx/extension.py:52 +#: sphinx/extension.py:51 #, python-format msgid "" "The %s extension is required by needs_extensions settings, but it is not " "loaded." msgstr "" -#: sphinx/extension.py:57 +#: sphinx/extension.py:56 #, python-format msgid "" "This project needs the extension %s at least in version %s and therefore " "cannot be built with the loaded version (%s)." msgstr "" -#: sphinx/highlighting.py:142 +#: sphinx/highlighting.py:121 #, python-format msgid "Pygments lexer name %r is not known" msgstr "" -#: sphinx/highlighting.py:163 +#: sphinx/highlighting.py:147 #, python-format msgid "Could not lex literal_block as \"%s\". Highlighting skipped." msgstr "" -#: sphinx/project.py:59 +#: sphinx/project.py:61 msgid "document not readable. Ignored." msgstr "" -#: sphinx/registry.py:131 +#: sphinx/registry.py:126 #, python-format msgid "Builder class %s has no \"name\" attribute" msgstr "" -#: sphinx/registry.py:133 +#: sphinx/registry.py:128 #, python-format msgid "Builder %r already exists (in module %s)" msgstr "" -#: sphinx/registry.py:147 +#: sphinx/registry.py:141 #, python-format msgid "Builder name %s not registered or available through entry point" msgstr "" -#: sphinx/registry.py:155 +#: sphinx/registry.py:148 #, python-format msgid "Builder name %s not registered" msgstr "" -#: sphinx/registry.py:163 +#: sphinx/registry.py:155 #, python-format msgid "domain %s already registered" msgstr "" -#: sphinx/registry.py:197 sphinx/registry.py:212 sphinx/registry.py:223 +#: sphinx/registry.py:178 sphinx/registry.py:191 sphinx/registry.py:202 #, python-format msgid "domain %s not yet registered" msgstr "" -#: sphinx/registry.py:201 +#: sphinx/registry.py:182 #, python-format msgid "The %r directive is already registered to domain %s" msgstr "" -#: sphinx/registry.py:215 +#: sphinx/registry.py:194 #, python-format msgid "The %r role is already registered to domain %s" msgstr "" -#: sphinx/registry.py:226 +#: sphinx/registry.py:205 #, python-format msgid "The %r index is already registered to domain %s" msgstr "" -#: sphinx/registry.py:250 +#: sphinx/registry.py:229 #, python-format msgid "The %r object_type is already registered" msgstr "" -#: sphinx/registry.py:270 +#: sphinx/registry.py:249 #, python-format msgid "The %r crossref_type is already registered" msgstr "" -#: sphinx/registry.py:278 +#: sphinx/registry.py:256 #, python-format msgid "source_suffix %r is already registered" msgstr "" -#: sphinx/registry.py:308 +#: sphinx/registry.py:266 #, python-format msgid "source_parser for %r is already registered" msgstr "" -#: sphinx/registry.py:324 +#: sphinx/registry.py:275 #, python-format msgid "Source parser for %s not registered" msgstr "" -#: sphinx/registry.py:344 -#, python-format -msgid "source_input for %r is already registered" -msgstr "" - -#: sphinx/registry.py:363 +#: sphinx/registry.py:301 #, python-format msgid "Translator for %r already exists" msgstr "" -#: sphinx/registry.py:375 +#: sphinx/registry.py:313 #, python-format msgid "kwargs for add_node() must be a (visit, depart) function tuple: %r=%r" msgstr "" -#: sphinx/registry.py:445 +#: sphinx/registry.py:374 #, python-format msgid "enumerable_node %r already registered" msgstr "" -#: sphinx/registry.py:453 +#: sphinx/registry.py:383 #, python-format msgid "math renderer %s is already registred" msgstr "" -#: sphinx/registry.py:464 +#: sphinx/registry.py:393 #, python-format msgid "" "the extension %r was already merged with Sphinx since version %s; this " "extension is ignored." msgstr "" -#: sphinx/registry.py:475 +#: sphinx/registry.py:404 msgid "Original exception:\n" msgstr "" -#: sphinx/registry.py:476 +#: sphinx/registry.py:405 #, python-format msgid "Could not import extension %s" msgstr "" -#: sphinx/registry.py:479 +#: sphinx/registry.py:409 #, python-format msgid "" "extension %r has no setup() function; is it really a Sphinx extension " "module?" msgstr "" -#: sphinx/registry.py:488 +#: sphinx/registry.py:418 #, python-format msgid "" "The %s extension used by this project needs at least Sphinx v%s; it " "therefore cannot be built with this version." msgstr "" -#: sphinx/registry.py:496 +#: sphinx/registry.py:426 #, python-format msgid "" "extension %r returned an unsupported object from its setup() function; it" " should return None or a metadata dictionary" msgstr "" -#: sphinx/roles.py:221 sphinx/roles.py:272 +#: sphinx/roles.py:221 sphinx/roles.py:271 #, python-format msgid "Python Enhancement Proposals; PEP %s" msgstr "" -#: sphinx/theming.py:79 +#: sphinx/theming.py:78 #, python-format msgid "theme %r doesn't have \"theme\" setting" msgstr "" -#: sphinx/theming.py:81 +#: sphinx/theming.py:80 #, python-format msgid "theme %r doesn't have \"inherit\" setting" msgstr "" -#: sphinx/theming.py:87 +#: sphinx/theming.py:86 #, python-format msgid "no theme named %r found, inherited by %r" msgstr "" -#: sphinx/theming.py:112 +#: sphinx/theming.py:109 #, python-format msgid "setting %s.%s occurs in none of the searched theme configs" msgstr "" -#: sphinx/theming.py:132 +#: sphinx/theming.py:128 #, python-format msgid "unsupported theme option %r given" msgstr "" -#: sphinx/theming.py:242 +#: sphinx/theming.py:228 #, python-format msgid "file %r on theme path is not a valid zipfile or contains no theme" msgstr "" -#: sphinx/theming.py:258 +#: sphinx/theming.py:243 msgid "" "sphinx_rtd_theme is no longer a hard dependency since version 1.4.0. " "Please install it manually.(pip install sphinx_rtd_theme)" msgstr "" -#: sphinx/theming.py:262 +#: sphinx/theming.py:247 #, python-format msgid "no theme named %r found (missing theme.conf?)" msgstr "" -#: sphinx/builders/__init__.py:205 +#: sphinx/builders/__init__.py:195 #, python-format msgid "a suitable image for %s builder not found: %s (%s)" msgstr "" -#: sphinx/builders/__init__.py:209 +#: sphinx/builders/__init__.py:199 #, python-format msgid "a suitable image for %s builder not found: %s" msgstr "" -#: sphinx/builders/__init__.py:231 +#: sphinx/builders/__init__.py:219 msgid "building [mo]: " msgstr "" -#: sphinx/builders/__init__.py:232 sphinx/builders/__init__.py:574 -#: sphinx/builders/__init__.py:602 +#: sphinx/builders/__init__.py:220 sphinx/builders/__init__.py:539 +#: sphinx/builders/__init__.py:565 msgid "writing output... " msgstr "" -#: sphinx/builders/__init__.py:245 +#: sphinx/builders/__init__.py:228 #, python-format msgid "all of %d po files" msgstr "" -#: sphinx/builders/__init__.py:266 +#: sphinx/builders/__init__.py:246 #, python-format msgid "targets for %d po files that are specified" msgstr "" -#: sphinx/builders/__init__.py:276 +#: sphinx/builders/__init__.py:253 #, python-format msgid "targets for %d po files that are out of date" msgstr "" -#: sphinx/builders/__init__.py:284 +#: sphinx/builders/__init__.py:260 msgid "all source files" msgstr "" -#: sphinx/builders/__init__.py:298 +#: sphinx/builders/__init__.py:273 #, python-format msgid "file %r given on command line is not under the source directory, ignoring" msgstr "" -#: sphinx/builders/__init__.py:303 +#: sphinx/builders/__init__.py:277 #, python-format msgid "file %r given on command line does not exist, ignoring" msgstr "" -#: sphinx/builders/__init__.py:314 +#: sphinx/builders/__init__.py:288 #, python-format msgid "%d source files given on command line" msgstr "" -#: sphinx/builders/__init__.py:325 +#: sphinx/builders/__init__.py:298 #, python-format msgid "targets for %d source files that are out of date" msgstr "" -#: sphinx/builders/__init__.py:335 +#: sphinx/builders/__init__.py:307 sphinx/builders/gettext.py:267 #, python-format -msgid "building [%s]" +msgid "building [%s]: " msgstr "" -#: sphinx/builders/__init__.py:342 +#: sphinx/builders/__init__.py:314 msgid "looking for now-outdated files... " msgstr "" -#: sphinx/builders/__init__.py:347 +#: sphinx/builders/__init__.py:319 #, python-format msgid "%d found" msgstr "" -#: sphinx/builders/__init__.py:349 +#: sphinx/builders/__init__.py:321 msgid "none found" msgstr "" -#: sphinx/builders/__init__.py:354 +#: sphinx/builders/__init__.py:326 msgid "pickling environment" msgstr "" -#: sphinx/builders/__init__.py:360 +#: sphinx/builders/__init__.py:332 msgid "checking consistency" msgstr "" -#: sphinx/builders/__init__.py:364 +#: sphinx/builders/__init__.py:336 msgid "no targets are out of date." msgstr "" -#: sphinx/builders/__init__.py:404 +#: sphinx/builders/__init__.py:375 msgid "updating environment: " msgstr "" -#: sphinx/builders/__init__.py:423 +#: sphinx/builders/__init__.py:396 #, python-format msgid "%s added, %s changed, %s removed" msgstr "" -#: sphinx/builders/__init__.py:462 sphinx/builders/__init__.py:492 +#: sphinx/builders/__init__.py:434 sphinx/builders/__init__.py:461 msgid "reading sources... " msgstr "" -#: sphinx/builders/__init__.py:497 sphinx/builders/__init__.py:612 +#: sphinx/builders/__init__.py:466 sphinx/builders/__init__.py:575 msgid "waiting for workers..." msgstr "" -#: sphinx/builders/__init__.py:551 +#: sphinx/builders/__init__.py:517 #, python-format msgid "docnames to write: %s" msgstr "" -#: sphinx/builders/__init__.py:560 sphinx/builders/singlehtml.py:166 +#: sphinx/builders/__init__.py:526 sphinx/builders/singlehtml.py:155 msgid "preparing documents" msgstr "" -#: sphinx/builders/_epub_base.py:218 +#: sphinx/builders/_epub_base.py:211 #, python-format msgid "duplicated ToC entry found: %s" msgstr "" -#: sphinx/builders/_epub_base.py:414 sphinx/builders/html.py:761 -#: sphinx/builders/latex/__init__.py:415 sphinx/builders/texinfo.py:190 +#: sphinx/builders/_epub_base.py:395 sphinx/builders/html/__init__.py:687 +#: sphinx/builders/latex/__init__.py:416 sphinx/builders/texinfo.py:178 msgid "copying images... " msgstr "" -#: sphinx/builders/_epub_base.py:421 +#: sphinx/builders/_epub_base.py:402 #, python-format msgid "cannot read image file %r: copying it instead" msgstr "" -#: sphinx/builders/_epub_base.py:427 sphinx/builders/html.py:769 -#: sphinx/builders/latex/__init__.py:423 sphinx/builders/texinfo.py:199 +#: sphinx/builders/_epub_base.py:408 sphinx/builders/html/__init__.py:695 +#: sphinx/builders/latex/__init__.py:424 sphinx/builders/texinfo.py:187 #, python-format msgid "cannot copy image file %r: %s" msgstr "" -#: sphinx/builders/_epub_base.py:444 +#: sphinx/builders/_epub_base.py:425 #, python-format msgid "cannot write image file %r: %s" msgstr "" -#: sphinx/builders/_epub_base.py:455 +#: sphinx/builders/_epub_base.py:435 msgid "Pillow not found - copying image files" msgstr "" -#: sphinx/builders/_epub_base.py:490 sphinx/builders/_epub_base.py:503 -#: sphinx/builders/_epub_base.py:539 sphinx/builders/_epub_base.py:724 -#: sphinx/builders/_epub_base.py:757 sphinx/builders/epub3.py:183 +#: sphinx/builders/_epub_base.py:467 sphinx/builders/_epub_base.py:479 +#: sphinx/builders/_epub_base.py:513 sphinx/builders/_epub_base.py:694 +#: sphinx/builders/_epub_base.py:726 sphinx/builders/epub3.py:173 #, python-format msgid "writing %s file..." msgstr "" -#: sphinx/builders/_epub_base.py:565 +#: sphinx/builders/_epub_base.py:539 #, python-format msgid "unknown mimetype for %s, ignoring" msgstr "" -#: sphinx/builders/changes.py:39 +#: sphinx/builders/changes.py:36 #, python-format msgid "The overview file is in %(outdir)s." msgstr "" -#: sphinx/builders/changes.py:68 +#: sphinx/builders/changes.py:62 #, python-format msgid "no changes in version %s." msgstr "" -#: sphinx/builders/changes.py:70 +#: sphinx/builders/changes.py:64 msgid "writing summary file..." msgstr "" -#: sphinx/builders/changes.py:86 +#: sphinx/builders/changes.py:80 msgid "Builtins" msgstr "" -#: sphinx/builders/changes.py:88 +#: sphinx/builders/changes.py:82 msgid "Module level" msgstr "" -#: sphinx/builders/changes.py:133 +#: sphinx/builders/changes.py:126 msgid "copying source files..." msgstr "" -#: sphinx/builders/changes.py:140 +#: sphinx/builders/changes.py:133 #, python-format msgid "could not read %r for changelog creation" msgstr "" -#: sphinx/builders/dummy.py:24 +#: sphinx/builders/dummy.py:22 msgid "The dummy builder generates no files." msgstr "" -#: sphinx/builders/epub3.py:68 +#: sphinx/builders/epub3.py:65 #, python-format msgid "The ePub file is in %(outdir)s." msgstr "" -#: sphinx/builders/epub3.py:211 +#: sphinx/builders/epub3.py:200 msgid "" "conf value \"epub_language\" (or \"language\") should not be empty for " "EPUB3" msgstr "" -#: sphinx/builders/epub3.py:215 +#: sphinx/builders/epub3.py:204 msgid "conf value \"epub_uid\" should be XML NAME for EPUB3" msgstr "" -#: sphinx/builders/epub3.py:218 +#: sphinx/builders/epub3.py:207 msgid "" "conf value \"epub_title\" (or \"html_title\") should not be empty for " "EPUB3" msgstr "" -#: sphinx/builders/epub3.py:222 +#: sphinx/builders/epub3.py:211 msgid "conf value \"epub_author\" should not be empty for EPUB3" msgstr "" -#: sphinx/builders/epub3.py:225 +#: sphinx/builders/epub3.py:214 msgid "conf value \"epub_contributor\" should not be empty for EPUB3" msgstr "" -#: sphinx/builders/epub3.py:228 +#: sphinx/builders/epub3.py:217 msgid "conf value \"epub_description\" should not be empty for EPUB3" msgstr "" -#: sphinx/builders/epub3.py:231 +#: sphinx/builders/epub3.py:220 msgid "conf value \"epub_publisher\" should not be empty for EPUB3" msgstr "" -#: sphinx/builders/epub3.py:234 +#: sphinx/builders/epub3.py:223 msgid "" "conf value \"epub_copyright\" (or \"copyright\")should not be empty for " "EPUB3" msgstr "" -#: sphinx/builders/epub3.py:238 +#: sphinx/builders/epub3.py:227 msgid "conf value \"epub_identifier\" should not be empty for EPUB3" msgstr "" -#: sphinx/builders/epub3.py:241 +#: sphinx/builders/epub3.py:230 msgid "conf value \"version\" should not be empty for EPUB3" msgstr "" -#: sphinx/builders/epub3.py:256 sphinx/builders/html.py:1166 +#: sphinx/builders/epub3.py:244 sphinx/builders/html/__init__.py:1059 #, python-format msgid "invalid css_file: %r, ignored" msgstr "" -#: sphinx/builders/gettext.py:221 +#: sphinx/builders/gettext.py:246 #, python-format msgid "The message catalogs are in %(outdir)s." msgstr "" -#: sphinx/builders/gettext.py:245 -#, python-format -msgid "building [%s]: " -msgstr "" - -#: sphinx/builders/gettext.py:246 +#: sphinx/builders/gettext.py:268 #, python-format msgid "targets for %d template files" msgstr "" -#: sphinx/builders/gettext.py:250 +#: sphinx/builders/gettext.py:272 msgid "reading templates... " msgstr "" -#: sphinx/builders/gettext.py:277 +#: sphinx/builders/gettext.py:300 msgid "writing message catalogs... " msgstr "" -#: sphinx/builders/html.py:182 +#: sphinx/builders/linkcheck.py:78 #, python-format -msgid "build info file is broken: %r" +msgid "Look for any errors in the above output or in %(outdir)s/output.txt" msgstr "" -#: sphinx/builders/html.py:217 +#: sphinx/builders/linkcheck.py:150 #, python-format -msgid "The HTML pages are in %(outdir)s." +msgid "Anchor '%s' not found" msgstr "" -#: sphinx/builders/html.py:399 +#: sphinx/builders/linkcheck.py:259 #, python-format -msgid "Failed to read build info file: %r" +msgid "broken link: %s (%s)" msgstr "" -#: sphinx/builders/html.py:488 sphinx/builders/latex/__init__.py:206 -#: sphinx/transforms/__init__.py:121 sphinx/writers/manpage.py:118 -#: sphinx/writers/texinfo.py:243 +#: sphinx/builders/manpage.py:40 #, python-format -msgid "%b %d, %Y" +msgid "The manual pages are in %(outdir)s." msgstr "" -#: sphinx/builders/html.py:500 -msgid "html_use_opensearch config value must now be a string" +#: sphinx/builders/manpage.py:47 +msgid "no \"man_pages\" config value found; no manual pages will be written" msgstr "" -#: sphinx/builders/html.py:506 sphinx/themes/basic/defindex.html:30 -msgid "General Index" +#: sphinx/builders/latex/__init__.py:292 sphinx/builders/manpage.py:58 +#: sphinx/builders/singlehtml.py:163 sphinx/builders/texinfo.py:111 +msgid "writing" msgstr "" -#: sphinx/builders/html.py:506 -msgid "index" +#: sphinx/builders/manpage.py:69 +#, python-format +msgid "\"man_pages\" config value references unknown document %s" msgstr "" -#: sphinx/builders/html.py:570 -msgid "next" +#: sphinx/builders/singlehtml.py:36 +#, python-format +msgid "The HTML page is in %(outdir)s." msgstr "" -#: sphinx/builders/html.py:579 -msgid "previous" +#: sphinx/builders/singlehtml.py:158 +msgid "assembling single document" msgstr "" -#: sphinx/builders/html.py:677 -msgid "generating indices..." +#: sphinx/builders/singlehtml.py:176 +msgid "writing additional files" msgstr "" -#: sphinx/builders/html.py:695 -msgid "writing additional pages..." +#: sphinx/builders/texinfo.py:47 +#, python-format +msgid "The Texinfo files are in %(outdir)s." msgstr "" -#: sphinx/builders/html.py:780 -msgid "copying downloadable files... " +#: sphinx/builders/texinfo.py:49 +msgid "" +"\n" +"Run 'make' in that directory to run these through makeinfo\n" +"(use 'make info' here to do that automatically)." msgstr "" -#: sphinx/builders/html.py:788 -#, python-format -msgid "cannot copy downloadable file %r: %s" +#: sphinx/builders/texinfo.py:77 +msgid "no \"texinfo_documents\" config value found; no documents will be written" msgstr "" -#: sphinx/builders/html.py:795 -msgid "copying static files... " +#: sphinx/builders/texinfo.py:85 +#, python-format +msgid "\"texinfo_documents\" config value references unknown document %s" msgstr "" -#: sphinx/builders/html.py:830 +#: sphinx/builders/latex/__init__.py:275 sphinx/builders/texinfo.py:107 #, python-format -msgid "html_static_path entry %r does not exist" +msgid "processing %s" msgstr "" -#: sphinx/builders/html.py:839 sphinx/builders/latex/__init__.py:400 -#, python-format -msgid "logo file %r does not exist" +#: sphinx/builders/latex/__init__.py:343 sphinx/builders/texinfo.py:154 +msgid "resolving references..." msgstr "" -#: sphinx/builders/html.py:847 -#, python-format -msgid "favicon file %r does not exist" +#: sphinx/builders/latex/__init__.py:353 sphinx/builders/texinfo.py:163 +msgid " (in " msgstr "" -#: sphinx/builders/html.py:854 -#, python-format -msgid "cannot copy static file %r" +#: sphinx/builders/texinfo.py:192 +msgid "copying Texinfo support files" msgstr "" -#: sphinx/builders/html.py:860 -msgid "copying extra files... " +#: sphinx/builders/texinfo.py:196 +#, python-format +msgid "error writing file Makefile: %s" msgstr "" -#: sphinx/builders/html.py:866 +#: sphinx/builders/text.py:30 #, python-format -msgid "html_extra_path entry %r does not exist" +msgid "The text files are in %(outdir)s." msgstr "" -#: sphinx/builders/html.py:872 +#: sphinx/builders/html/__init__.py:1012 sphinx/builders/text.py:77 +#: sphinx/builders/xml.py:96 #, python-format -msgid "cannot copy extra file %r" +msgid "error writing file %s: %s" msgstr "" -#: sphinx/builders/html.py:880 +#: sphinx/builders/xml.py:40 #, python-format -msgid "Failed to write build info file: %r" +msgid "The XML files are in %(outdir)s." msgstr "" -#: sphinx/builders/html.py:927 -msgid "" -"search index couldn't be loaded, but not all documents will be built: the" -" index will be incomplete." +#: sphinx/builders/xml.py:108 +#, python-format +msgid "The pseudo-XML files are in %(outdir)s." msgstr "" -#: sphinx/builders/html.py:996 +#: sphinx/builders/html/__init__.py:144 #, python-format -msgid "page %s matches two patterns in html_sidebars: %r and %r" +msgid "build info file is broken: %r" msgstr "" -#: sphinx/builders/html.py:1094 +#: sphinx/builders/html/__init__.py:176 #, python-format -msgid "" -"a Unicode error occurred when rendering the page %s. Please make sure all" -" config values that contain non-ASCII content are Unicode strings." +msgid "The HTML pages are in %(outdir)s." msgstr "" -#: sphinx/builders/html.py:1099 +#: sphinx/builders/html/__init__.py:344 #, python-format -msgid "" -"An error happened in rendering the page %s.\n" -"Reason: %r" +msgid "Failed to read build info file: %r" msgstr "" -#: sphinx/builders/html.py:1111 sphinx/builders/text.py:85 -#: sphinx/builders/xml.py:99 +#: sphinx/builders/html/__init__.py:430 sphinx/builders/latex/__init__.py:192 +#: sphinx/transforms/__init__.py:116 sphinx/writers/manpage.py:112 +#: sphinx/writers/texinfo.py:237 #, python-format -msgid "error writing file %s: %s" +msgid "%b %d, %Y" msgstr "" -#: sphinx/builders/html.py:1131 -msgid "dumping object inventory... " +#: sphinx/builders/html/__init__.py:439 +msgid "html_use_opensearch config value must now be a string" msgstr "" -#: sphinx/builders/html.py:1138 -#, python-format -msgid "dumping search index in %s ... " +#: sphinx/builders/html/__init__.py:445 sphinx/themes/basic/defindex.html:29 +msgid "General Index" msgstr "" -#: sphinx/builders/html.py:1184 -#, python-format -msgid "invalid js_file: %r, ignored" +#: sphinx/builders/html/__init__.py:445 +msgid "index" msgstr "" -#: sphinx/builders/html.py:1228 -msgid "Many math_renderers are registered. But no math_renderer is selected." +#: sphinx/builders/html/__init__.py:508 +msgid "next" msgstr "" -#: sphinx/builders/html.py:1231 -#, python-format -msgid "Unknown math_renderer %r is given." +#: sphinx/builders/html/__init__.py:517 +msgid "previous" msgstr "" -#: sphinx/builders/html.py:1264 -#, python-format -msgid "%s %s documentation" +#: sphinx/builders/html/__init__.py:611 +msgid "generating indices" msgstr "" -#: sphinx/builders/linkcheck.py:80 -#, python-format -msgid "Look for any errors in the above output or in %(outdir)s/output.txt" +#: sphinx/builders/html/__init__.py:626 +msgid "writing additional pages" msgstr "" -#: sphinx/builders/linkcheck.py:144 -#, python-format -msgid "Anchor '%s' not found" +#: sphinx/builders/html/__init__.py:705 +msgid "copying downloadable files... " msgstr "" -#: sphinx/builders/linkcheck.py:242 +#: sphinx/builders/html/__init__.py:713 #, python-format -msgid "broken link: %s (%s)" +msgid "cannot copy downloadable file %r: %s" msgstr "" -#: sphinx/builders/manpage.py:43 -#, python-format -msgid "The manual pages are in %(outdir)s." +#: sphinx/builders/html/__init__.py:761 +msgid "copying static files... " msgstr "" -#: sphinx/builders/manpage.py:51 -msgid "no \"man_pages\" config value found; no manual pages will be written" +#: sphinx/builders/html/__init__.py:777 +#, python-format +msgid "cannot copy static file %r" msgstr "" -#: sphinx/builders/latex/__init__.py:272 sphinx/builders/manpage.py:64 -#: sphinx/builders/singlehtml.py:174 sphinx/builders/texinfo.py:120 -msgid "writing" +#: sphinx/builders/html/__init__.py:782 +msgid "copying extra files" msgstr "" -#: sphinx/builders/manpage.py:76 +#: sphinx/builders/html/__init__.py:788 #, python-format -msgid "\"man_pages\" config value references unknown document %s" +msgid "cannot copy extra file %r" msgstr "" -#: sphinx/builders/singlehtml.py:37 +#: sphinx/builders/html/__init__.py:795 #, python-format -msgid "The HTML page is in %(outdir)s." +msgid "Failed to write build info file: %r" msgstr "" -#: sphinx/builders/singlehtml.py:169 -msgid "assembling single document" +#: sphinx/builders/html/__init__.py:843 +msgid "" +"search index couldn't be loaded, but not all documents will be built: the" +" index will be incomplete." msgstr "" -#: sphinx/builders/singlehtml.py:188 -msgid "writing additional files" +#: sphinx/builders/html/__init__.py:912 +#, python-format +msgid "page %s matches two patterns in html_sidebars: %r and %r" msgstr "" -#: sphinx/builders/texinfo.py:50 +#: sphinx/builders/html/__init__.py:995 #, python-format -msgid "The Texinfo files are in %(outdir)s." +msgid "" +"a Unicode error occurred when rendering the page %s. Please make sure all" +" config values that contain non-ASCII content are Unicode strings." msgstr "" -#: sphinx/builders/texinfo.py:52 +#: sphinx/builders/html/__init__.py:1000 +#, python-format msgid "" -"\n" -"Run 'make' in that directory to run these through makeinfo\n" -"(use 'make info' here to do that automatically)." +"An error happened in rendering the page %s.\n" +"Reason: %r" msgstr "" -#: sphinx/builders/texinfo.py:85 -msgid "no \"texinfo_documents\" config value found; no documents will be written" +#: sphinx/builders/html/__init__.py:1029 +msgid "dumping object inventory" msgstr "" -#: sphinx/builders/texinfo.py:93 +#: sphinx/builders/html/__init__.py:1034 #, python-format -msgid "\"texinfo_documents\" config value references unknown document %s" +msgid "dumping search index in %s" msgstr "" -#: sphinx/builders/latex/__init__.py:255 sphinx/builders/texinfo.py:116 +#: sphinx/builders/html/__init__.py:1076 #, python-format -msgid "processing %s" +msgid "invalid js_file: %r, ignored" msgstr "" -#: sphinx/builders/latex/__init__.py:323 sphinx/builders/texinfo.py:164 -msgid "resolving references..." +#: sphinx/builders/html/__init__.py:1117 +msgid "Many math_renderers are registered. But no math_renderer is selected." msgstr "" -#: sphinx/builders/latex/__init__.py:333 sphinx/builders/texinfo.py:173 -msgid " (in " +#: sphinx/builders/html/__init__.py:1120 +#, python-format +msgid "Unknown math_renderer %r is given." msgstr "" -#: sphinx/builders/texinfo.py:205 -msgid "copying Texinfo support files" +#: sphinx/builders/html/__init__.py:1128 +#, python-format +msgid "html_extra_path entry %r does not exist" msgstr "" -#: sphinx/builders/texinfo.py:209 +#: sphinx/builders/html/__init__.py:1132 #, python-format -msgid "error writing file Makefile: %s" +msgid "html_extra_path entry %r is placed inside outdir" msgstr "" -#: sphinx/builders/text.py:33 +#: sphinx/builders/html/__init__.py:1141 #, python-format -msgid "The text files are in %(outdir)s." +msgid "html_static_path entry %r does not exist" msgstr "" -#: sphinx/builders/xml.py:38 +#: sphinx/builders/html/__init__.py:1145 #, python-format -msgid "The XML files are in %(outdir)s." +msgid "html_static_path entry %r is placed inside outdir" msgstr "" -#: sphinx/builders/xml.py:112 +#: sphinx/builders/html/__init__.py:1152 sphinx/builders/latex/__init__.py:428 #, python-format -msgid "The pseudo-XML files are in %(outdir)s." +msgid "logo file %r does not exist" msgstr "" -#: sphinx/builders/latex/__init__.py:123 +#: sphinx/builders/html/__init__.py:1159 +#, python-format +msgid "favicon file %r does not exist" +msgstr "" + +#: sphinx/builders/html/__init__.py:1178 +#, python-format +msgid "%s %s documentation" +msgstr "" + +#: sphinx/builders/latex/__init__.py:115 #, python-format msgid "The LaTeX files are in %(outdir)s." msgstr "" -#: sphinx/builders/latex/__init__.py:125 +#: sphinx/builders/latex/__init__.py:117 msgid "" "\n" "Run 'make' in that directory to run these through (pdf)latex\n" "(use `make latexpdf' here to do that automatically)." msgstr "" -#: sphinx/builders/latex/__init__.py:165 +#: sphinx/builders/latex/__init__.py:154 msgid "no \"latex_documents\" config value found; no documents will be written" msgstr "" -#: sphinx/builders/latex/__init__.py:173 +#: sphinx/builders/latex/__init__.py:162 #, python-format msgid "\"latex_documents\" config value references unknown document %s" msgstr "" -#: sphinx/builders/latex/__init__.py:213 sphinx/domains/std.py:501 -#: sphinx/templates/latex/latex.tex_t:79 -#: sphinx/themes/basic/genindex-single.html:30 -#: sphinx/themes/basic/genindex-single.html:55 -#: sphinx/themes/basic/genindex-split.html:11 -#: sphinx/themes/basic/genindex-split.html:14 -#: sphinx/themes/basic/genindex.html:30 sphinx/themes/basic/genindex.html:33 -#: sphinx/themes/basic/genindex.html:66 sphinx/themes/basic/layout.html:150 -#: sphinx/writers/texinfo.py:522 +#: sphinx/builders/latex/__init__.py:199 sphinx/domains/std.py:586 +#: sphinx/templates/latex/latex.tex_t:68 +#: sphinx/themes/basic/genindex-single.html:19 +#: sphinx/themes/basic/genindex-single.html:36 +#: sphinx/themes/basic/genindex-split.html:10 +#: sphinx/themes/basic/genindex-split.html:13 +#: sphinx/themes/basic/genindex.html:19 sphinx/themes/basic/genindex.html:22 +#: sphinx/themes/basic/genindex.html:44 sphinx/themes/basic/layout.html:51 +#: sphinx/writers/texinfo.py:502 msgid "Index" msgstr "" -#: sphinx/builders/latex/__init__.py:216 sphinx/templates/latex/latex.tex_t:64 +#: sphinx/builders/latex/__init__.py:202 sphinx/templates/latex/latex.tex_t:55 msgid "Release" msgstr "" -#: sphinx/builders/latex/__init__.py:224 sphinx/writers/latex.py:549 +#: sphinx/builders/latex/__init__.py:209 sphinx/writers/latex.py:388 #, python-format msgid "no Babel option known for language %r" msgstr "" -#: sphinx/builders/latex/__init__.py:363 +#: sphinx/builders/latex/__init__.py:374 msgid "copying TeX support files" msgstr "" -#: sphinx/builders/latex/__init__.py:384 +#: sphinx/builders/latex/__init__.py:394 msgid "copying TeX support files..." msgstr "" -#: sphinx/builders/latex/__init__.py:404 +#: sphinx/builders/latex/__init__.py:407 msgid "copying additional files" msgstr "" -#: sphinx/builders/latex/__init__.py:447 +#: sphinx/builders/latex/__init__.py:489 #, python-format msgid "Unknown configure key: latex_elements[%r]. ignored." msgstr "" -#: sphinx/cmd/build.py:38 +#: sphinx/builders/latex/theming.py:79 +#, python-format +msgid "%r doesn't have \"theme\" setting" +msgstr "" + +#: sphinx/builders/latex/theming.py:81 +#, python-format +msgid "%r doesn't have \"%s\" setting" +msgstr "" + +#: sphinx/cmd/build.py:34 msgid "Exception occurred while building, starting debugger:" msgstr "" -#: sphinx/cmd/build.py:48 -msgid "interrupted!" +#: sphinx/cmd/build.py:44 +msgid "Interrupted!" msgstr "" -#: sphinx/cmd/build.py:50 +#: sphinx/cmd/build.py:46 msgid "reST markup error:" msgstr "" -#: sphinx/cmd/build.py:56 +#: sphinx/cmd/build.py:52 msgid "Encoding error:" msgstr "" -#: sphinx/cmd/build.py:59 sphinx/cmd/build.py:74 +#: sphinx/cmd/build.py:55 sphinx/cmd/build.py:70 #, python-format msgid "" "The full traceback has been saved in %s, if you want to report the issue " "to the developers." msgstr "" -#: sphinx/cmd/build.py:63 +#: sphinx/cmd/build.py:59 msgid "Recursion error:" msgstr "" -#: sphinx/cmd/build.py:66 +#: sphinx/cmd/build.py:62 msgid "" "This can happen with very large or deeply nested source files. You can " "carefully increase the default Python recursion limit of 1000 in conf.py " "with e.g.:" msgstr "" -#: sphinx/cmd/build.py:69 -msgid " import sys; sys.setrecursionlimit(1500)" -msgstr "" - -#: sphinx/cmd/build.py:71 +#: sphinx/cmd/build.py:67 msgid "Exception occurred:" msgstr "" -#: sphinx/cmd/build.py:77 +#: sphinx/cmd/build.py:73 msgid "" "Please also report this if it was a user error, so that a better error " "message can be provided next time." msgstr "" -#: sphinx/cmd/build.py:80 +#: sphinx/cmd/build.py:76 msgid "" "A bug report can be filed in the tracker at . Thanks!" msgstr "" -#: sphinx/cmd/build.py:97 +#: sphinx/cmd/build.py:92 msgid "job number should be a positive number" msgstr "" -#: sphinx/cmd/build.py:106 sphinx/cmd/quickstart.py:497 -#: sphinx/ext/apidoc.py:298 sphinx/ext/autosummary/generate.py:363 +#: sphinx/cmd/build.py:100 sphinx/cmd/quickstart.py:478 +#: sphinx/ext/apidoc.py:350 sphinx/ext/autosummary/generate.py:434 msgid "For more information, visit ." msgstr "" -#: sphinx/cmd/build.py:107 +#: sphinx/cmd/build.py:101 msgid "" "\n" "Generate documentation from source files.\n" @@ -1153,262 +1179,255 @@ msgid "" "files can be built by specifying individual filenames.\n" msgstr "" -#: sphinx/cmd/build.py:128 +#: sphinx/cmd/build.py:122 msgid "path to documentation source files" msgstr "" -#: sphinx/cmd/build.py:130 +#: sphinx/cmd/build.py:124 msgid "path to output directory" msgstr "" -#: sphinx/cmd/build.py:132 +#: sphinx/cmd/build.py:126 msgid "a list of specific files to rebuild. Ignored if -a is specified" msgstr "" -#: sphinx/cmd/build.py:135 +#: sphinx/cmd/build.py:129 msgid "general options" msgstr "" -#: sphinx/cmd/build.py:138 +#: sphinx/cmd/build.py:132 msgid "builder to use (default: html)" msgstr "" -#: sphinx/cmd/build.py:140 +#: sphinx/cmd/build.py:134 msgid "write all files (default: only write new and changed files)" msgstr "" -#: sphinx/cmd/build.py:143 +#: sphinx/cmd/build.py:137 msgid "don't use a saved environment, always read all files" msgstr "" -#: sphinx/cmd/build.py:146 +#: sphinx/cmd/build.py:140 msgid "" "path for the cached environment and doctree files (default: " "OUTPUTDIR/.doctrees)" msgstr "" -#: sphinx/cmd/build.py:149 +#: sphinx/cmd/build.py:143 msgid "" "build in parallel with N processes where possible (special value \"auto\"" " will set N to cpu-count)" msgstr "" -#: sphinx/cmd/build.py:153 +#: sphinx/cmd/build.py:147 msgid "" "path where configuration file (conf.py) is located (default: same as " "SOURCEDIR)" msgstr "" -#: sphinx/cmd/build.py:156 +#: sphinx/cmd/build.py:150 msgid "use no config file at all, only -D options" msgstr "" -#: sphinx/cmd/build.py:159 +#: sphinx/cmd/build.py:153 msgid "override a setting in configuration file" msgstr "" -#: sphinx/cmd/build.py:162 +#: sphinx/cmd/build.py:156 msgid "pass a value into HTML templates" msgstr "" -#: sphinx/cmd/build.py:165 +#: sphinx/cmd/build.py:159 msgid "define tag: include \"only\" blocks with TAG" msgstr "" -#: sphinx/cmd/build.py:167 +#: sphinx/cmd/build.py:161 msgid "nit-picky mode, warn about all missing references" msgstr "" -#: sphinx/cmd/build.py:170 +#: sphinx/cmd/build.py:164 msgid "console output options" msgstr "" -#: sphinx/cmd/build.py:172 +#: sphinx/cmd/build.py:166 msgid "increase verbosity (can be repeated)" msgstr "" -#: sphinx/cmd/build.py:174 +#: sphinx/cmd/build.py:168 sphinx/ext/apidoc.py:373 msgid "no output on stdout, just warnings on stderr" msgstr "" -#: sphinx/cmd/build.py:176 +#: sphinx/cmd/build.py:170 msgid "no output at all, not even warnings" msgstr "" -#: sphinx/cmd/build.py:179 +#: sphinx/cmd/build.py:173 msgid "do emit colored output (default: auto-detect)" msgstr "" -#: sphinx/cmd/build.py:182 +#: sphinx/cmd/build.py:176 msgid "do not emit colored output (default: auto-detect)" msgstr "" -#: sphinx/cmd/build.py:185 +#: sphinx/cmd/build.py:179 msgid "write warnings (and errors) to given file" msgstr "" -#: sphinx/cmd/build.py:187 +#: sphinx/cmd/build.py:181 msgid "turn warnings into errors" msgstr "" -#: sphinx/cmd/build.py:189 -msgid "With -W, Keep going when getting warnings" +#: sphinx/cmd/build.py:183 +msgid "With -W, keep going when getting warnings" msgstr "" -#: sphinx/cmd/build.py:191 +#: sphinx/cmd/build.py:185 msgid "show full traceback on exception" msgstr "" -#: sphinx/cmd/build.py:193 +#: sphinx/cmd/build.py:187 msgid "run Pdb on exception" msgstr "" -#: sphinx/cmd/build.py:227 +#: sphinx/cmd/build.py:219 #, python-format msgid "cannot find files %r" msgstr "" -#: sphinx/cmd/build.py:230 +#: sphinx/cmd/build.py:222 msgid "cannot combine -a option and filenames" msgstr "" -#: sphinx/cmd/build.py:249 +#: sphinx/cmd/build.py:241 #, python-format msgid "cannot open warning file %r: %s" msgstr "" -#: sphinx/cmd/build.py:259 +#: sphinx/cmd/build.py:251 msgid "-D option argument must be in the form name=value" msgstr "" -#: sphinx/cmd/build.py:266 +#: sphinx/cmd/build.py:258 msgid "-A option argument must be in the form name=value" msgstr "" -#: sphinx/cmd/quickstart.py:52 +#: sphinx/cmd/quickstart.py:49 msgid "automatically insert docstrings from modules" msgstr "" -#: sphinx/cmd/quickstart.py:53 +#: sphinx/cmd/quickstart.py:50 msgid "automatically test code snippets in doctest blocks" msgstr "" -#: sphinx/cmd/quickstart.py:54 +#: sphinx/cmd/quickstart.py:51 msgid "link between Sphinx documentation of different projects" msgstr "" -#: sphinx/cmd/quickstart.py:55 +#: sphinx/cmd/quickstart.py:52 msgid "write \"todo\" entries that can be shown or hidden on build" msgstr "" -#: sphinx/cmd/quickstart.py:56 +#: sphinx/cmd/quickstart.py:53 msgid "checks for documentation coverage" msgstr "" -#: sphinx/cmd/quickstart.py:57 +#: sphinx/cmd/quickstart.py:54 msgid "include math, rendered as PNG or SVG images" msgstr "" -#: sphinx/cmd/quickstart.py:58 +#: sphinx/cmd/quickstart.py:55 msgid "include math, rendered in the browser by MathJax" msgstr "" -#: sphinx/cmd/quickstart.py:59 +#: sphinx/cmd/quickstart.py:56 msgid "conditional inclusion of content based on config values" msgstr "" -#: sphinx/cmd/quickstart.py:61 +#: sphinx/cmd/quickstart.py:57 msgid "include links to the source code of documented Python objects" msgstr "" -#: sphinx/cmd/quickstart.py:63 +#: sphinx/cmd/quickstart.py:58 msgid "create .nojekyll file to publish the document on GitHub pages" msgstr "" -#: sphinx/cmd/quickstart.py:107 +#: sphinx/cmd/quickstart.py:100 msgid "Please enter a valid path name." msgstr "" -#: sphinx/cmd/quickstart.py:119 +#: sphinx/cmd/quickstart.py:110 msgid "Please enter some text." msgstr "" -#: sphinx/cmd/quickstart.py:128 +#: sphinx/cmd/quickstart.py:117 #, python-format msgid "Please enter one of %s." msgstr "" -#: sphinx/cmd/quickstart.py:136 +#: sphinx/cmd/quickstart.py:124 msgid "Please enter either 'y' or 'n'." msgstr "" -#: sphinx/cmd/quickstart.py:143 +#: sphinx/cmd/quickstart.py:130 msgid "Please enter a file suffix, e.g. '.rst' or '.txt'." msgstr "" -#: sphinx/cmd/quickstart.py:169 +#: sphinx/cmd/quickstart.py:153 msgid "" "* Note: non-ASCII characters entered and terminal encoding unknown -- " "assuming UTF-8 or Latin-1." msgstr "" -#: sphinx/cmd/quickstart.py:248 +#: sphinx/cmd/quickstart.py:227 #, python-format msgid "Welcome to the Sphinx %s quickstart utility." msgstr "" -#: sphinx/cmd/quickstart.py:249 +#: sphinx/cmd/quickstart.py:229 msgid "" -"\n" "Please enter values for the following settings (just press Enter to\n" "accept a default value, if one is given in brackets)." msgstr "" -#: sphinx/cmd/quickstart.py:254 +#: sphinx/cmd/quickstart.py:234 #, python-format -msgid "" -"\n" -"Selected root path: %s" +msgid "Selected root path: %s" msgstr "" -#: sphinx/cmd/quickstart.py:257 -msgid "" -"\n" -"Enter the root path for documentation." +#: sphinx/cmd/quickstart.py:237 +msgid "Enter the root path for documentation." msgstr "" -#: sphinx/cmd/quickstart.py:259 +#: sphinx/cmd/quickstart.py:238 msgid "Root path for the documentation" msgstr "" -#: sphinx/cmd/quickstart.py:264 +#: sphinx/cmd/quickstart.py:243 msgid "Error: an existing conf.py has been found in the selected root path." msgstr "" -#: sphinx/cmd/quickstart.py:266 +#: sphinx/cmd/quickstart.py:245 msgid "sphinx-quickstart will not overwrite existing Sphinx projects." msgstr "" -#: sphinx/cmd/quickstart.py:268 +#: sphinx/cmd/quickstart.py:247 msgid "Please enter a new root path (or just Enter to exit)" msgstr "" -#: sphinx/cmd/quickstart.py:274 +#: sphinx/cmd/quickstart.py:254 msgid "" -"\n" "You have two options for placing the build directory for Sphinx output.\n" "Either, you use a directory \"_build\" within the root path, or you " "separate\n" "\"source\" and \"build\" directories within the root path." msgstr "" -#: sphinx/cmd/quickstart.py:278 +#: sphinx/cmd/quickstart.py:257 msgid "Separate source and build directories (y/n)" msgstr "" -#: sphinx/cmd/quickstart.py:282 +#: sphinx/cmd/quickstart.py:261 msgid "" -"\n" "Inside the root directory, two more directories will be created; " "\"_templates\"\n" "for custom HTML templates and \"_static\" for custom stylesheets and " @@ -1417,27 +1436,24 @@ msgid "" "underscore." msgstr "" -#: sphinx/cmd/quickstart.py:286 +#: sphinx/cmd/quickstart.py:264 msgid "Name prefix for templates and static dir" msgstr "" -#: sphinx/cmd/quickstart.py:289 -msgid "" -"\n" -"The project name will occur in several places in the built documentation." +#: sphinx/cmd/quickstart.py:268 +msgid "The project name will occur in several places in the built documentation." msgstr "" -#: sphinx/cmd/quickstart.py:291 +#: sphinx/cmd/quickstart.py:269 msgid "Project name" msgstr "" -#: sphinx/cmd/quickstart.py:293 +#: sphinx/cmd/quickstart.py:271 msgid "Author name(s)" msgstr "" -#: sphinx/cmd/quickstart.py:296 +#: sphinx/cmd/quickstart.py:275 msgid "" -"\n" "Sphinx has the notion of a \"version\" and a \"release\" for the\n" "software. Each version can have multiple releases. For example, for\n" "Python the version is something like 2.5 or 3.0, while the release is\n" @@ -1445,138 +1461,134 @@ msgid "" "just set both to the same value." msgstr "" -#: sphinx/cmd/quickstart.py:302 +#: sphinx/cmd/quickstart.py:280 msgid "Project version" msgstr "" -#: sphinx/cmd/quickstart.py:304 +#: sphinx/cmd/quickstart.py:282 msgid "Project release" msgstr "" -#: sphinx/cmd/quickstart.py:307 +#: sphinx/cmd/quickstart.py:286 msgid "" -"\n" "If the documents are to be written in a language other than English,\n" "you can select a language here by its language code. Sphinx will then\n" "translate text that it generates into that language.\n" "\n" "For a list of supported codes, see\n" -"https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-language." +"https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-" +"language." msgstr "" -#: sphinx/cmd/quickstart.py:314 +#: sphinx/cmd/quickstart.py:292 msgid "Project language" msgstr "" -#: sphinx/cmd/quickstart.py:319 +#: sphinx/cmd/quickstart.py:298 msgid "" -"\n" "The file name suffix for source files. Commonly, this is either \".txt\"\n" "or \".rst\". Only files with this suffix are considered documents." msgstr "" -#: sphinx/cmd/quickstart.py:322 +#: sphinx/cmd/quickstart.py:300 msgid "Source file suffix" msgstr "" -#: sphinx/cmd/quickstart.py:325 +#: sphinx/cmd/quickstart.py:304 msgid "" -"\n" "One document is special in that it is considered the top node of the\n" "\"contents tree\", that is, it is the root of the hierarchical structure\n" "of the documents. Normally, this is \"index\", but if your \"index\"\n" "document is a custom template, you can also set this to another filename." msgstr "" -#: sphinx/cmd/quickstart.py:330 +#: sphinx/cmd/quickstart.py:308 msgid "Name of your master document (without suffix)" msgstr "" -#: sphinx/cmd/quickstart.py:336 +#: sphinx/cmd/quickstart.py:313 #, python-format msgid "" "Error: the master file %s has already been found in the selected root " "path." msgstr "" -#: sphinx/cmd/quickstart.py:338 +#: sphinx/cmd/quickstart.py:315 msgid "sphinx-quickstart will not overwrite the existing file." msgstr "" -#: sphinx/cmd/quickstart.py:340 +#: sphinx/cmd/quickstart.py:317 msgid "Please enter a new file name, or rename the existing file and press Enter" msgstr "" -#: sphinx/cmd/quickstart.py:344 +#: sphinx/cmd/quickstart.py:321 msgid "Indicate which of the following Sphinx extensions should be enabled:" msgstr "" -#: sphinx/cmd/quickstart.py:353 +#: sphinx/cmd/quickstart.py:329 msgid "" "Note: imgmath and mathjax cannot be enabled at the same time. imgmath has" " been deselected." msgstr "" -#: sphinx/cmd/quickstart.py:358 +#: sphinx/cmd/quickstart.py:335 msgid "" -"\n" "A Makefile and a Windows command file can be generated for you so that " "you\n" "only have to run e.g. `make html' instead of invoking sphinx-build\n" "directly." msgstr "" -#: sphinx/cmd/quickstart.py:362 +#: sphinx/cmd/quickstart.py:338 msgid "Create Makefile? (y/n)" msgstr "" -#: sphinx/cmd/quickstart.py:365 +#: sphinx/cmd/quickstart.py:341 msgid "Create Windows command file? (y/n)" msgstr "" -#: sphinx/cmd/quickstart.py:408 sphinx/ext/apidoc.py:74 +#: sphinx/cmd/quickstart.py:382 sphinx/ext/apidoc.py:107 #, python-format msgid "Creating file %s." msgstr "" -#: sphinx/cmd/quickstart.py:413 sphinx/ext/apidoc.py:72 +#: sphinx/cmd/quickstart.py:387 sphinx/ext/apidoc.py:104 #, python-format msgid "File %s already exists, skipping." msgstr "" -#: sphinx/cmd/quickstart.py:449 +#: sphinx/cmd/quickstart.py:423 msgid "Finished: An initial directory structure has been created." msgstr "" -#: sphinx/cmd/quickstart.py:450 +#: sphinx/cmd/quickstart.py:425 #, python-format msgid "" -"\n" "You should now populate your master file %s and create other " "documentation\n" "source files. " msgstr "" -#: sphinx/cmd/quickstart.py:452 +#: sphinx/cmd/quickstart.py:428 msgid "" "Use the Makefile to build the docs, like so:\n" -" make builder\n" +" make builder" msgstr "" -#: sphinx/cmd/quickstart.py:455 +#: sphinx/cmd/quickstart.py:431 #, python-format msgid "" "Use the sphinx-build command to build the docs, like so:\n" -" sphinx-build -b builder %s %s\n" +" sphinx-build -b builder %s %s" msgstr "" -#: sphinx/cmd/quickstart.py:458 +#: sphinx/cmd/quickstart.py:433 msgid "" "where \"builder\" is one of the supported builders, e.g. html, latex or " -"linkcheck.\n" +"linkcheck." msgstr "" -#: sphinx/cmd/quickstart.py:498 +#: sphinx/cmd/quickstart.py:468 msgid "" "\n" "Generate required files for a Sphinx project.\n" @@ -1587,137 +1599,137 @@ msgid "" "Makefile to be used with sphinx-build.\n" msgstr "" -#: sphinx/cmd/quickstart.py:508 +#: sphinx/cmd/quickstart.py:483 msgid "quiet mode" msgstr "" -#: sphinx/cmd/quickstart.py:513 -msgid "output path" +#: sphinx/cmd/quickstart.py:488 +msgid "project root" msgstr "" -#: sphinx/cmd/quickstart.py:515 +#: sphinx/cmd/quickstart.py:490 msgid "Structure options" msgstr "" -#: sphinx/cmd/quickstart.py:517 +#: sphinx/cmd/quickstart.py:492 msgid "if specified, separate source and build dirs" msgstr "" -#: sphinx/cmd/quickstart.py:519 +#: sphinx/cmd/quickstart.py:494 msgid "replacement for dot in _templates etc." msgstr "" -#: sphinx/cmd/quickstart.py:521 +#: sphinx/cmd/quickstart.py:496 msgid "Project basic options" msgstr "" -#: sphinx/cmd/quickstart.py:523 +#: sphinx/cmd/quickstart.py:498 msgid "project name" msgstr "" -#: sphinx/cmd/quickstart.py:525 +#: sphinx/cmd/quickstart.py:500 msgid "author names" msgstr "" -#: sphinx/cmd/quickstart.py:527 +#: sphinx/cmd/quickstart.py:502 msgid "version of project" msgstr "" -#: sphinx/cmd/quickstart.py:529 +#: sphinx/cmd/quickstart.py:504 msgid "release of project" msgstr "" -#: sphinx/cmd/quickstart.py:531 +#: sphinx/cmd/quickstart.py:506 msgid "document language" msgstr "" -#: sphinx/cmd/quickstart.py:533 +#: sphinx/cmd/quickstart.py:508 msgid "source file suffix" msgstr "" -#: sphinx/cmd/quickstart.py:535 +#: sphinx/cmd/quickstart.py:510 msgid "master document name" msgstr "" -#: sphinx/cmd/quickstart.py:537 +#: sphinx/cmd/quickstart.py:512 msgid "use epub" msgstr "" -#: sphinx/cmd/quickstart.py:539 +#: sphinx/cmd/quickstart.py:514 msgid "Extension options" msgstr "" -#: sphinx/cmd/quickstart.py:543 sphinx/ext/apidoc.py:379 +#: sphinx/cmd/quickstart.py:518 sphinx/ext/apidoc.py:433 #, python-format msgid "enable %s extension" msgstr "" -#: sphinx/cmd/quickstart.py:545 sphinx/ext/apidoc.py:375 +#: sphinx/cmd/quickstart.py:520 sphinx/ext/apidoc.py:429 msgid "enable arbitrary extensions" msgstr "" -#: sphinx/cmd/quickstart.py:547 +#: sphinx/cmd/quickstart.py:522 msgid "Makefile and Batchfile creation" msgstr "" -#: sphinx/cmd/quickstart.py:549 +#: sphinx/cmd/quickstart.py:524 msgid "create makefile" msgstr "" -#: sphinx/cmd/quickstart.py:551 +#: sphinx/cmd/quickstart.py:526 msgid "do not create makefile" msgstr "" -#: sphinx/cmd/quickstart.py:553 +#: sphinx/cmd/quickstart.py:528 msgid "create batchfile" msgstr "" -#: sphinx/cmd/quickstart.py:556 +#: sphinx/cmd/quickstart.py:531 msgid "do not create batchfile" msgstr "" -#: sphinx/cmd/quickstart.py:559 +#: sphinx/cmd/quickstart.py:534 msgid "use make-mode for Makefile/make.bat" msgstr "" -#: sphinx/cmd/quickstart.py:562 +#: sphinx/cmd/quickstart.py:537 msgid "do not use make-mode for Makefile/make.bat" msgstr "" -#: sphinx/cmd/quickstart.py:564 +#: sphinx/cmd/quickstart.py:539 sphinx/ext/apidoc.py:435 msgid "Project templating" msgstr "" -#: sphinx/cmd/quickstart.py:567 +#: sphinx/cmd/quickstart.py:542 sphinx/ext/apidoc.py:438 msgid "template directory for template files" msgstr "" -#: sphinx/cmd/quickstart.py:570 +#: sphinx/cmd/quickstart.py:545 msgid "define a template variable" msgstr "" -#: sphinx/cmd/quickstart.py:604 +#: sphinx/cmd/quickstart.py:578 msgid "" "\"quiet\" is specified, but any of \"project\" or \"author\" is not " "specified." msgstr "" -#: sphinx/cmd/quickstart.py:618 +#: sphinx/cmd/quickstart.py:592 msgid "Error: specified path is not a directory, or sphinx files already exist." msgstr "" -#: sphinx/cmd/quickstart.py:620 +#: sphinx/cmd/quickstart.py:594 msgid "" "sphinx-quickstart only generate into a empty directory. Please specify a " "new root path." msgstr "" -#: sphinx/cmd/quickstart.py:635 +#: sphinx/cmd/quickstart.py:609 #, python-format msgid "Invalid template variable: %s" msgstr "" -#: sphinx/directives/code.py:74 +#: sphinx/directives/code.py:75 msgid "Over dedent has detected" msgstr "" @@ -1726,489 +1738,554 @@ msgstr "" msgid "Invalid caption: %s" msgstr "" -#: sphinx/directives/code.py:140 sphinx/directives/code.py:292 -#: sphinx/directives/code.py:462 +#: sphinx/directives/code.py:140 sphinx/directives/code.py:284 +#: sphinx/directives/code.py:450 #, python-format msgid "line number spec is out of range(1-%d): %r" msgstr "" -#: sphinx/directives/code.py:222 +#: sphinx/directives/code.py:219 #, python-format msgid "Cannot use both \"%s\" and \"%s\" options" msgstr "" -#: sphinx/directives/code.py:235 +#: sphinx/directives/code.py:231 #, python-format msgid "Include file %r not found or reading it failed" msgstr "" -#: sphinx/directives/code.py:237 +#: sphinx/directives/code.py:233 #, python-format msgid "" "Encoding %r used for reading included file %r seems to be wrong, try " "giving an :encoding: option" msgstr "" -#: sphinx/directives/code.py:275 +#: sphinx/directives/code.py:268 #, python-format msgid "Object named %r not found in include file %r" msgstr "" -#: sphinx/directives/code.py:301 +#: sphinx/directives/code.py:293 msgid "Cannot use \"lineno-match\" with a disjoint set of \"lines\"" msgstr "" -#: sphinx/directives/code.py:306 +#: sphinx/directives/code.py:298 #, python-format msgid "Line spec %r: no lines pulled from include file %r" msgstr "" -#: sphinx/directives/other.py:172 +#: sphinx/directives/other.py:171 msgid "Section author: " msgstr "" -#: sphinx/directives/other.py:174 +#: sphinx/directives/other.py:173 msgid "Module author: " msgstr "" -#: sphinx/directives/other.py:176 +#: sphinx/directives/other.py:175 msgid "Code author: " msgstr "" -#: sphinx/directives/other.py:178 +#: sphinx/directives/other.py:177 msgid "Author: " msgstr "" -#: sphinx/domains/__init__.py:344 +#: sphinx/domains/__init__.py:393 #, python-format msgid "%s %s" msgstr "" -#: sphinx/domains/c.py:64 sphinx/domains/cpp.py:6445 -#: sphinx/domains/python.py:210 sphinx/ext/napoleon/docstring.py:696 +#: sphinx/domains/c.py:67 sphinx/domains/cpp.py:6410 +#: sphinx/domains/python.py:242 sphinx/ext/napoleon/docstring.py:661 msgid "Parameters" msgstr "" -#: sphinx/domains/c.py:67 sphinx/domains/cpp.py:6454 -#: sphinx/domains/javascript.py:209 sphinx/domains/python.py:222 +#: sphinx/domains/c.py:70 sphinx/domains/cpp.py:6419 +#: sphinx/domains/javascript.py:216 sphinx/domains/python.py:254 msgid "Returns" msgstr "" -#: sphinx/domains/c.py:69 sphinx/domains/javascript.py:211 -#: sphinx/domains/python.py:224 +#: sphinx/domains/c.py:72 sphinx/domains/javascript.py:218 +#: sphinx/domains/python.py:256 msgid "Return type" msgstr "" -#: sphinx/domains/c.py:189 +#: sphinx/domains/c.py:187 #, python-format msgid "%s (C function)" msgstr "" -#: sphinx/domains/c.py:191 +#: sphinx/domains/c.py:189 #, python-format msgid "%s (C member)" msgstr "" -#: sphinx/domains/c.py:193 +#: sphinx/domains/c.py:191 #, python-format msgid "%s (C macro)" msgstr "" -#: sphinx/domains/c.py:195 +#: sphinx/domains/c.py:193 #, python-format msgid "%s (C type)" msgstr "" -#: sphinx/domains/c.py:197 +#: sphinx/domains/c.py:195 #, python-format msgid "%s (C variable)" msgstr "" -#: sphinx/domains/c.py:258 sphinx/domains/cpp.py:7029 -#: sphinx/domains/javascript.py:297 sphinx/domains/python.py:742 +#: sphinx/domains/c.py:248 sphinx/domains/cpp.py:6991 +#: sphinx/domains/javascript.py:321 sphinx/domains/python.py:995 msgid "function" msgstr "" -#: sphinx/domains/c.py:259 sphinx/domains/cpp.py:7030 +#: sphinx/domains/c.py:249 sphinx/domains/cpp.py:6992 msgid "member" msgstr "" -#: sphinx/domains/c.py:260 +#: sphinx/domains/c.py:250 msgid "macro" msgstr "" -#: sphinx/domains/c.py:261 sphinx/domains/cpp.py:7031 +#: sphinx/domains/c.py:251 sphinx/domains/cpp.py:6993 msgid "type" msgstr "" -#: sphinx/domains/c.py:262 +#: sphinx/domains/c.py:252 msgid "variable" msgstr "" -#: sphinx/domains/changeset.py:33 +#: sphinx/domains/c.py:280 +#, python-format +msgid "" +"duplicate C object description of %s, other instance in %s, use :noindex:" +" for one of them" +msgstr "" + +#: sphinx/domains/changeset.py:31 #, python-format msgid "New in version %s" msgstr "" -#: sphinx/domains/changeset.py:34 +#: sphinx/domains/changeset.py:32 #, python-format msgid "Changed in version %s" msgstr "" -#: sphinx/domains/changeset.py:35 +#: sphinx/domains/changeset.py:33 #, python-format msgid "Deprecated since version %s" msgstr "" -#: sphinx/domains/cpp.py:4323 +#: sphinx/domains/citation.py:77 +#, python-format +msgid "duplicate citation %s, other instance in %s" +msgstr "" + +#: sphinx/domains/citation.py:88 +#, python-format +msgid "Citation [%s] is not referenced." +msgstr "" + +#: sphinx/domains/cpp.py:4220 #, python-format msgid "" "Duplicate declaration, also defined in '%s'.\n" "Declaration is '%s'." msgstr "" -#: sphinx/domains/cpp.py:6448 +#: sphinx/domains/cpp.py:6413 msgid "Template Parameters" msgstr "" -#: sphinx/domains/cpp.py:6451 sphinx/domains/javascript.py:206 +#: sphinx/domains/cpp.py:6416 sphinx/domains/javascript.py:213 msgid "Throws" msgstr "" -#: sphinx/domains/cpp.py:6579 +#: sphinx/domains/cpp.py:6539 #, python-format msgid "%s (C++ %s)" msgstr "" -#: sphinx/domains/cpp.py:7027 sphinx/domains/javascript.py:299 -#: sphinx/domains/python.py:744 +#: sphinx/domains/cpp.py:6989 sphinx/domains/javascript.py:323 +#: sphinx/domains/python.py:997 msgid "class" msgstr "" -#: sphinx/domains/cpp.py:7028 +#: sphinx/domains/cpp.py:6990 msgid "union" msgstr "" -#: sphinx/domains/cpp.py:7032 +#: sphinx/domains/cpp.py:6994 msgid "concept" msgstr "" -#: sphinx/domains/cpp.py:7033 +#: sphinx/domains/cpp.py:6995 msgid "enum" msgstr "" -#: sphinx/domains/cpp.py:7034 +#: sphinx/domains/cpp.py:6996 msgid "enumerator" msgstr "" -#: sphinx/domains/cpp.py:7127 +#: sphinx/domains/cpp.py:7086 #, python-format msgid "" "Duplicate declaration, also defined in '%s'.\n" "Name of declaration is '%s'." msgstr "" -#: sphinx/domains/javascript.py:130 sphinx/domains/python.py:430 +#: sphinx/domains/javascript.py:131 sphinx/domains/python.py:457 +#: sphinx/domains/python.py:489 #, python-format msgid "%s() (built-in function)" msgstr "" -#: sphinx/domains/javascript.py:131 sphinx/domains/python.py:495 +#: sphinx/domains/javascript.py:132 sphinx/domains/python.py:595 +#: sphinx/domains/python.py:693 #, python-format msgid "%s() (%s method)" msgstr "" -#: sphinx/domains/javascript.py:133 +#: sphinx/domains/javascript.py:134 #, python-format msgid "%s() (class)" msgstr "" -#: sphinx/domains/javascript.py:135 +#: sphinx/domains/javascript.py:136 #, python-format msgid "%s (global variable or constant)" msgstr "" -#: sphinx/domains/javascript.py:137 sphinx/domains/python.py:533 +#: sphinx/domains/javascript.py:138 sphinx/domains/python.py:633 +#: sphinx/domains/python.py:770 #, python-format msgid "%s (%s attribute)" msgstr "" -#: sphinx/domains/javascript.py:203 +#: sphinx/domains/javascript.py:210 msgid "Arguments" msgstr "" -#: sphinx/domains/javascript.py:264 sphinx/domains/python.py:610 +#: sphinx/domains/javascript.py:281 sphinx/domains/python.py:842 #, python-format msgid "%s (module)" msgstr "" -#: sphinx/domains/javascript.py:298 sphinx/domains/python.py:746 +#: sphinx/domains/javascript.py:322 sphinx/domains/python.py:999 msgid "method" msgstr "" -#: sphinx/domains/javascript.py:300 sphinx/domains/python.py:743 +#: sphinx/domains/javascript.py:324 sphinx/domains/python.py:996 msgid "data" msgstr "" -#: sphinx/domains/javascript.py:301 sphinx/domains/python.py:749 +#: sphinx/domains/javascript.py:325 sphinx/domains/python.py:1002 msgid "attribute" msgstr "" -#: sphinx/domains/javascript.py:302 sphinx/domains/python.py:49 -#: sphinx/domains/python.py:750 +#: sphinx/domains/javascript.py:326 sphinx/domains/python.py:60 +#: sphinx/domains/python.py:1003 msgid "module" msgstr "" -#: sphinx/domains/math.py:101 sphinx/writers/latex.py:2459 +#: sphinx/domains/javascript.py:357 #, python-format -msgid "Invalid math_eqref_format: %r" +msgid "duplicate %s description of %s, other %s in %s" msgstr "" -#: sphinx/domains/math.py:126 +#: sphinx/domains/math.py:69 sphinx/domains/math.py:148 #, python-format msgid "duplicate label of equation %s, other instance in %s" msgstr "" -#: sphinx/domains/python.py:50 +#: sphinx/domains/math.py:123 sphinx/writers/latex.py:2031 +#, python-format +msgid "Invalid math_eqref_format: %r" +msgstr "" + +#: sphinx/domains/python.py:61 msgid "keyword" msgstr "" -#: sphinx/domains/python.py:51 +#: sphinx/domains/python.py:62 msgid "operator" msgstr "" -#: sphinx/domains/python.py:52 +#: sphinx/domains/python.py:63 msgid "object" msgstr "" -#: sphinx/domains/python.py:53 sphinx/domains/python.py:745 +#: sphinx/domains/python.py:64 sphinx/domains/python.py:998 msgid "exception" msgstr "" -#: sphinx/domains/python.py:54 +#: sphinx/domains/python.py:65 msgid "statement" msgstr "" -#: sphinx/domains/python.py:55 +#: sphinx/domains/python.py:66 msgid "built-in function" msgstr "" -#: sphinx/domains/python.py:215 +#: sphinx/domains/python.py:247 msgid "Variables" msgstr "" -#: sphinx/domains/python.py:219 +#: sphinx/domains/python.py:251 msgid "Raises" msgstr "" -#: sphinx/domains/python.py:431 sphinx/domains/python.py:489 -#: sphinx/domains/python.py:501 sphinx/domains/python.py:514 +#: sphinx/domains/python.py:458 sphinx/domains/python.py:487 +#: sphinx/domains/python.py:589 sphinx/domains/python.py:601 +#: sphinx/domains/python.py:614 sphinx/domains/python.py:682 #, python-format msgid "%s() (in module %s)" msgstr "" -#: sphinx/domains/python.py:434 +#: sphinx/domains/python.py:461 sphinx/domains/python.py:536 #, python-format msgid "%s (built-in variable)" msgstr "" -#: sphinx/domains/python.py:435 sphinx/domains/python.py:527 +#: sphinx/domains/python.py:462 sphinx/domains/python.py:534 +#: sphinx/domains/python.py:627 sphinx/domains/python.py:766 #, python-format msgid "%s (in module %s)" msgstr "" -#: sphinx/domains/python.py:455 +#: sphinx/domains/python.py:552 #, python-format msgid "%s (built-in class)" msgstr "" -#: sphinx/domains/python.py:456 +#: sphinx/domains/python.py:553 #, python-format msgid "%s (class in %s)" msgstr "" -#: sphinx/domains/python.py:493 +#: sphinx/domains/python.py:593 #, python-format msgid "%s() (%s.%s method)" msgstr "" -#: sphinx/domains/python.py:505 +#: sphinx/domains/python.py:605 #, python-format msgid "%s() (%s.%s static method)" msgstr "" -#: sphinx/domains/python.py:508 +#: sphinx/domains/python.py:608 sphinx/domains/python.py:691 #, python-format msgid "%s() (%s static method)" msgstr "" -#: sphinx/domains/python.py:518 +#: sphinx/domains/python.py:618 #, python-format msgid "%s() (%s.%s class method)" msgstr "" -#: sphinx/domains/python.py:521 +#: sphinx/domains/python.py:621 sphinx/domains/python.py:687 #, python-format msgid "%s() (%s class method)" msgstr "" -#: sphinx/domains/python.py:531 +#: sphinx/domains/python.py:631 #, python-format msgid "%s (%s.%s attribute)" msgstr "" -#: sphinx/domains/python.py:668 +#: sphinx/domains/python.py:689 +#, python-format +msgid "%s() (%s property)" +msgstr "" + +#: sphinx/domains/python.py:923 msgid "Python Module Index" msgstr "" -#: sphinx/domains/python.py:669 +#: sphinx/domains/python.py:924 msgid "modules" msgstr "" -#: sphinx/domains/python.py:720 +#: sphinx/domains/python.py:974 msgid "Deprecated" msgstr "" -#: sphinx/domains/python.py:747 +#: sphinx/domains/python.py:1000 msgid "class method" msgstr "" -#: sphinx/domains/python.py:748 +#: sphinx/domains/python.py:1001 msgid "static method" msgstr "" -#: sphinx/domains/python.py:880 +#: sphinx/domains/python.py:1050 +#, python-format +msgid "" +"duplicate object description of %s, other instance in %s, use :noindex: " +"for one of them" +msgstr "" + +#: sphinx/domains/python.py:1156 #, python-format msgid "more than one target found for cross-reference %r: %s" msgstr "" -#: sphinx/domains/python.py:918 +#: sphinx/domains/python.py:1194 msgid " (deprecated)" msgstr "" -#: sphinx/domains/rst.py:62 +#: sphinx/domains/rst.py:105 sphinx/domains/rst.py:166 #, python-format msgid "%s (directive)" msgstr "" -#: sphinx/domains/rst.py:64 +#: sphinx/domains/rst.py:167 sphinx/domains/rst.py:171 +#, python-format +msgid ":%s: (directive option)" +msgstr "" + +#: sphinx/domains/rst.py:200 #, python-format msgid "%s (role)" msgstr "" -#: sphinx/domains/rst.py:116 +#: sphinx/domains/rst.py:209 msgid "directive" msgstr "" -#: sphinx/domains/rst.py:117 +#: sphinx/domains/rst.py:210 +msgid "directive-option" +msgstr "" + +#: sphinx/domains/rst.py:211 msgid "role" msgstr "" -#: sphinx/domains/std.py:88 sphinx/domains/std.py:105 +#: sphinx/domains/rst.py:233 +#, python-format +msgid "duplicate description of %s %s, other instance in %s" +msgstr "" + +#: sphinx/domains/std.py:103 sphinx/domains/std.py:120 #, python-format msgid "environment variable; %s" msgstr "" -#: sphinx/domains/std.py:166 +#: sphinx/domains/std.py:194 #, python-format msgid "" "Malformed option description %r, should look like \"opt\", \"-opt args\"," " \"--opt args\", \"/opt args\" or \"+opt args\"" msgstr "" -#: sphinx/domains/std.py:207 +#: sphinx/domains/std.py:235 #, python-format -msgid "%scommand line option; %s" +msgid "%s command line option" +msgstr "" + +#: sphinx/domains/std.py:237 +msgid "command line option" +msgstr "" + +#: sphinx/domains/std.py:368 +msgid "glossary term must be preceded by empty line" +msgstr "" + +#: sphinx/domains/std.py:376 +msgid "glossary terms must not be separated by empty lines" +msgstr "" + +#: sphinx/domains/std.py:382 sphinx/domains/std.py:395 +msgid "glossary seems to be misformatted, check indentation" msgstr "" -#: sphinx/domains/std.py:458 +#: sphinx/domains/std.py:545 msgid "glossary term" msgstr "" -#: sphinx/domains/std.py:459 +#: sphinx/domains/std.py:546 msgid "grammar token" msgstr "" -#: sphinx/domains/std.py:460 +#: sphinx/domains/std.py:547 msgid "reference label" msgstr "" -#: sphinx/domains/std.py:462 +#: sphinx/domains/std.py:549 msgid "environment variable" msgstr "" -#: sphinx/domains/std.py:463 +#: sphinx/domains/std.py:550 msgid "program option" msgstr "" -#: sphinx/domains/std.py:464 +#: sphinx/domains/std.py:551 msgid "document" msgstr "" -#: sphinx/domains/std.py:502 +#: sphinx/domains/std.py:587 msgid "Module Index" msgstr "" -#: sphinx/domains/std.py:503 sphinx/themes/basic/defindex.html:25 +#: sphinx/domains/std.py:588 sphinx/themes/basic/defindex.html:24 msgid "Search Page" msgstr "" -#: sphinx/domains/std.py:598 -#, python-format -msgid "duplicate citation %s, other instance in %s" -msgstr "" - -#: sphinx/domains/std.py:631 sphinx/ext/autosectionlabel.py:56 +#: sphinx/domains/std.py:638 sphinx/domains/std.py:727 +#: sphinx/ext/autosectionlabel.py:53 #, python-format msgid "duplicate label %s, other instance in %s" msgstr "" -#: sphinx/domains/std.py:665 +#: sphinx/domains/std.py:657 #, python-format -msgid "Citation [%s] is not referenced." +msgid "duplicate %s description of %s, other instance in %s" msgstr "" -#: sphinx/domains/std.py:748 +#: sphinx/domains/std.py:833 msgid "numfig is disabled. :numref: is ignored." msgstr "" -#: sphinx/domains/std.py:756 +#: sphinx/domains/std.py:841 #, python-format msgid "no number is assigned for %s: %s" msgstr "" -#: sphinx/domains/std.py:767 +#: sphinx/domains/std.py:852 #, python-format msgid "the link has no caption: %s" msgstr "" -#: sphinx/domains/std.py:781 +#: sphinx/domains/std.py:866 #, python-format msgid "invalid numfig_format: %s (%r)" msgstr "" -#: sphinx/domains/std.py:784 +#: sphinx/domains/std.py:869 #, python-format msgid "invalid numfig_format: %s" msgstr "" -#: sphinx/environment/__init__.py:69 +#: sphinx/environment/__init__.py:73 msgid "new config" msgstr "" -#: sphinx/environment/__init__.py:70 +#: sphinx/environment/__init__.py:74 msgid "config changed" msgstr "" -#: sphinx/environment/__init__.py:71 +#: sphinx/environment/__init__.py:75 msgid "extensions changed" msgstr "" -#: sphinx/environment/__init__.py:210 +#: sphinx/environment/__init__.py:202 msgid "build environment version not current" msgstr "" -#: sphinx/environment/__init__.py:212 +#: sphinx/environment/__init__.py:204 msgid "source directory has changed" msgstr "" @@ -2218,40 +2295,40 @@ msgid "" " another doctree directory." msgstr "" -#: sphinx/environment/__init__.py:404 +#: sphinx/environment/__init__.py:396 #, python-format msgid "Failed to scan documents in %s: %r" msgstr "" -#: sphinx/environment/__init__.py:532 +#: sphinx/environment/__init__.py:515 #, python-format msgid "Domain %r is not registered" msgstr "" -#: sphinx/environment/__init__.py:617 +#: sphinx/environment/__init__.py:596 msgid "self referenced toctree found. Ignored." msgstr "" -#: sphinx/environment/__init__.py:658 +#: sphinx/environment/__init__.py:636 msgid "document isn't included in any toctree" msgstr "" -#: sphinx/environment/adapters/indexentries.py:82 +#: sphinx/environment/adapters/indexentries.py:80 #, python-format msgid "see %s" msgstr "" -#: sphinx/environment/adapters/indexentries.py:86 +#: sphinx/environment/adapters/indexentries.py:84 #, python-format msgid "see also %s" msgstr "" -#: sphinx/environment/adapters/indexentries.py:89 +#: sphinx/environment/adapters/indexentries.py:87 #, python-format msgid "unknown index entry type %r" msgstr "" -#: sphinx/environment/adapters/indexentries.py:156 +#: sphinx/environment/adapters/indexentries.py:172 #: sphinx/templates/latex/sphinxmessages.sty_t:11 msgid "Symbols" msgstr "" @@ -2278,7 +2355,7 @@ msgstr "" msgid "toctree contains reference to nonexisting document %r" msgstr "" -#: sphinx/environment/collectors/asset.py:90 +#: sphinx/environment/collectors/asset.py:87 #, python-format msgid "image file not readable: %s" msgstr "" @@ -2288,22 +2365,22 @@ msgstr "" msgid "image file %s not readable: %s" msgstr "" -#: sphinx/environment/collectors/asset.py:134 +#: sphinx/environment/collectors/asset.py:132 #, python-format msgid "download file not readable: %s" msgstr "" -#: sphinx/environment/collectors/toctree.py:196 +#: sphinx/environment/collectors/toctree.py:191 #, python-format msgid "%s is already assigned section numbers (nested numbered toctree?)" msgstr "" -#: sphinx/ext/apidoc.py:69 +#: sphinx/ext/apidoc.py:100 #, python-format msgid "Would create file %s." msgstr "" -#: sphinx/ext/apidoc.py:299 +#: sphinx/ext/apidoc.py:351 msgid "" "\n" "Look recursively in for Python modules and packages and " @@ -2318,177 +2395,181 @@ msgid "" "Note: By default this script will not overwrite already created files." msgstr "" -#: sphinx/ext/apidoc.py:312 +#: sphinx/ext/apidoc.py:364 msgid "path to module to document" msgstr "" -#: sphinx/ext/apidoc.py:314 +#: sphinx/ext/apidoc.py:366 msgid "fnmatch-style file and/or directory patterns to exclude from generation" msgstr "" -#: sphinx/ext/apidoc.py:319 +#: sphinx/ext/apidoc.py:371 msgid "directory to place all output" msgstr "" -#: sphinx/ext/apidoc.py:322 +#: sphinx/ext/apidoc.py:376 msgid "maximum depth of submodules to show in the TOC (default: 4)" msgstr "" -#: sphinx/ext/apidoc.py:325 +#: sphinx/ext/apidoc.py:379 msgid "overwrite existing files" msgstr "" -#: sphinx/ext/apidoc.py:328 +#: sphinx/ext/apidoc.py:382 msgid "" "follow symbolic links. Powerful when combined with " "collective.recipe.omelette." msgstr "" -#: sphinx/ext/apidoc.py:331 +#: sphinx/ext/apidoc.py:385 msgid "run the script without creating files" msgstr "" -#: sphinx/ext/apidoc.py:334 +#: sphinx/ext/apidoc.py:388 msgid "put documentation for each module on its own page" msgstr "" -#: sphinx/ext/apidoc.py:337 +#: sphinx/ext/apidoc.py:391 msgid "include \"_private\" modules" msgstr "" -#: sphinx/ext/apidoc.py:339 +#: sphinx/ext/apidoc.py:393 msgid "filename of table of contents (default: modules)" msgstr "" -#: sphinx/ext/apidoc.py:341 +#: sphinx/ext/apidoc.py:395 msgid "don't create a table of contents file" msgstr "" -#: sphinx/ext/apidoc.py:344 +#: sphinx/ext/apidoc.py:398 msgid "" "don't create headings for the module/package packages (e.g. when the " "docstrings already contain them)" msgstr "" -#: sphinx/ext/apidoc.py:349 +#: sphinx/ext/apidoc.py:403 msgid "put module documentation before submodule documentation" msgstr "" -#: sphinx/ext/apidoc.py:353 +#: sphinx/ext/apidoc.py:407 msgid "" "interpret module paths according to PEP-0420 implicit namespaces " "specification" msgstr "" -#: sphinx/ext/apidoc.py:357 +#: sphinx/ext/apidoc.py:411 msgid "file suffix (default: rst)" msgstr "" -#: sphinx/ext/apidoc.py:359 +#: sphinx/ext/apidoc.py:413 msgid "generate a full project with sphinx-quickstart" msgstr "" -#: sphinx/ext/apidoc.py:362 +#: sphinx/ext/apidoc.py:416 msgid "append module_path to sys.path, used when --full is given" msgstr "" -#: sphinx/ext/apidoc.py:364 +#: sphinx/ext/apidoc.py:418 msgid "project name (default: root module name)" msgstr "" -#: sphinx/ext/apidoc.py:366 +#: sphinx/ext/apidoc.py:420 msgid "project author(s), used when --full is given" msgstr "" -#: sphinx/ext/apidoc.py:368 +#: sphinx/ext/apidoc.py:422 msgid "project version, used when --full is given" msgstr "" -#: sphinx/ext/apidoc.py:370 +#: sphinx/ext/apidoc.py:424 msgid "project release, used when --full is given, defaults to --doc-version" msgstr "" -#: sphinx/ext/apidoc.py:373 +#: sphinx/ext/apidoc.py:427 msgid "extension options" msgstr "" -#: sphinx/ext/apidoc.py:402 +#: sphinx/ext/apidoc.py:460 #, python-format msgid "%s is not a directory." msgstr "" -#: sphinx/ext/coverage.py:46 +#: sphinx/ext/coverage.py:42 #, python-format msgid "invalid regex %r in %s" msgstr "" -#: sphinx/ext/coverage.py:55 +#: sphinx/ext/coverage.py:51 #, python-format msgid "" "Testing of coverage in the sources finished, look at the results in " "%(outdir)spython.txt." msgstr "" -#: sphinx/ext/coverage.py:70 +#: sphinx/ext/coverage.py:65 #, python-format msgid "invalid regex %r in coverage_c_regexes" msgstr "" -#: sphinx/ext/coverage.py:152 +#: sphinx/ext/coverage.py:150 #, python-format msgid "module %s could not be imported: %s" msgstr "" -#: sphinx/ext/doctest.py:132 +#: sphinx/ext/doctest.py:131 #, python-format msgid "missing '+' or '-' in '%s' option." msgstr "" -#: sphinx/ext/doctest.py:137 +#: sphinx/ext/doctest.py:136 #, python-format msgid "'%s' is not a valid option." msgstr "" -#: sphinx/ext/doctest.py:151 +#: sphinx/ext/doctest.py:150 #, python-format msgid "'%s' is not a valid pyversion option" msgstr "" -#: sphinx/ext/doctest.py:222 +#: sphinx/ext/doctest.py:219 msgid "invalid TestCode type" msgstr "" -#: sphinx/ext/doctest.py:283 +#: sphinx/ext/doctest.py:277 #, python-format msgid "" "Testing of doctests in the sources finished, look at the results in " "%(outdir)s/output.txt." msgstr "" -#: sphinx/ext/doctest.py:446 +#: sphinx/ext/doctest.py:427 #, python-format msgid "no code/output in %s block at %s:%s" msgstr "" -#: sphinx/ext/doctest.py:535 +#: sphinx/ext/doctest.py:513 #, python-format msgid "ignoring invalid doctest code: %r" msgstr "" -#: sphinx/ext/graphviz.py:140 +#: sphinx/ext/duration.py:80 +msgid "====================== slowest reading durations =======================" +msgstr "" + +#: sphinx/ext/graphviz.py:132 msgid "Graphviz directive cannot have both content and a filename argument" msgstr "" -#: sphinx/ext/graphviz.py:150 +#: sphinx/ext/graphviz.py:142 #, python-format msgid "External Graphviz file %r not found or reading it failed" msgstr "" -#: sphinx/ext/graphviz.py:156 +#: sphinx/ext/graphviz.py:148 msgid "Ignoring \"graphviz\" directive without content." msgstr "" -#: sphinx/ext/graphviz.py:250 +#: sphinx/ext/graphviz.py:248 #, python-format msgid "" "dot did not produce an output file:\n" @@ -2498,14 +2579,14 @@ msgid "" "%r" msgstr "" -#: sphinx/ext/graphviz.py:254 +#: sphinx/ext/graphviz.py:252 #, python-format msgid "" "dot command %r cannot be run (needed for graphviz output), check the " "graphviz_dot setting" msgstr "" -#: sphinx/ext/graphviz.py:261 +#: sphinx/ext/graphviz.py:259 #, python-format msgid "" "dot exited with error:\n" @@ -2515,32 +2596,32 @@ msgid "" "%r" msgstr "" -#: sphinx/ext/graphviz.py:271 +#: sphinx/ext/graphviz.py:269 #, python-format msgid "graphviz_output_format must be one of 'png', 'svg', but is %r" msgstr "" -#: sphinx/ext/graphviz.py:275 sphinx/ext/graphviz.py:329 -#: sphinx/ext/graphviz.py:367 +#: sphinx/ext/graphviz.py:273 sphinx/ext/graphviz.py:324 +#: sphinx/ext/graphviz.py:361 #, python-format msgid "dot code %r: %s" msgstr "" -#: sphinx/ext/graphviz.py:382 sphinx/ext/graphviz.py:391 +#: sphinx/ext/graphviz.py:374 sphinx/ext/graphviz.py:382 #, python-format msgid "[graph: %s]" msgstr "" -#: sphinx/ext/graphviz.py:384 sphinx/ext/graphviz.py:393 +#: sphinx/ext/graphviz.py:376 sphinx/ext/graphviz.py:384 msgid "[graph]" msgstr "" -#: sphinx/ext/imgconverter.py:43 sphinx/ext/imgconverter.py:68 +#: sphinx/ext/imgconverter.py:41 sphinx/ext/imgconverter.py:65 #, python-format msgid "convert command %r cannot be run.check the image_converter setting" msgstr "" -#: sphinx/ext/imgconverter.py:48 sphinx/ext/imgconverter.py:73 +#: sphinx/ext/imgconverter.py:46 sphinx/ext/imgconverter.py:70 #, python-format msgid "" "convert exited with error:\n" @@ -2550,75 +2631,75 @@ msgid "" "%r" msgstr "" -#: sphinx/ext/imgmath.py:139 +#: sphinx/ext/imgmath.py:170 #, python-format msgid "" "LaTeX command %r cannot be run (needed for math display), check the " "imgmath_latex setting" msgstr "" -#: sphinx/ext/imgmath.py:154 +#: sphinx/ext/imgmath.py:184 #, python-format msgid "" "%s command %r cannot be run (needed for math display), check the " "imgmath_%s setting" msgstr "" -#: sphinx/ext/imgmath.py:289 +#: sphinx/ext/imgmath.py:329 #, python-format msgid "display latex %r: %s" msgstr "" -#: sphinx/ext/imgmath.py:316 +#: sphinx/ext/imgmath.py:355 #, python-format msgid "inline latex %r: %s" msgstr "" -#: sphinx/ext/imgmath.py:323 sphinx/ext/mathjax.py:54 +#: sphinx/ext/imgmath.py:362 sphinx/ext/mathjax.py:49 msgid "Permalink to this equation" msgstr "" -#: sphinx/ext/intersphinx.py:182 +#: sphinx/ext/intersphinx.py:173 #, python-format msgid "intersphinx inventory has moved: %s -> %s" msgstr "" -#: sphinx/ext/intersphinx.py:217 +#: sphinx/ext/intersphinx.py:204 #, python-format msgid "loading intersphinx inventory from %s..." msgstr "" -#: sphinx/ext/intersphinx.py:232 +#: sphinx/ext/intersphinx.py:218 msgid "" "encountered some issues with some of the inventories, but they had " "working alternatives:" msgstr "" -#: sphinx/ext/intersphinx.py:238 +#: sphinx/ext/intersphinx.py:224 msgid "failed to reach any of the inventories with the following issues:" msgstr "" -#: sphinx/ext/intersphinx.py:311 +#: sphinx/ext/intersphinx.py:314 #, python-format msgid "(in %s v%s)" msgstr "" -#: sphinx/ext/intersphinx.py:313 +#: sphinx/ext/intersphinx.py:316 #, python-format msgid "(in %s)" msgstr "" -#: sphinx/ext/intersphinx.py:347 +#: sphinx/ext/intersphinx.py:349 #, python-format msgid "intersphinx identifier %r is not string. Ignored" msgstr "" -#: sphinx/ext/intersphinx.py:360 +#: sphinx/ext/intersphinx.py:362 #, python-format msgid "Fail to read intersphinx_mapping[%s], Ignored: %r" msgstr "" -#: sphinx/ext/linkcode.py:72 sphinx/ext/viewcode.py:117 +#: sphinx/ext/linkcode.py:70 sphinx/ext/viewcode.py:113 msgid "[source]" msgstr "" @@ -2626,65 +2707,65 @@ msgstr "" msgid "Todo" msgstr "" -#: sphinx/ext/todo.py:111 +#: sphinx/ext/todo.py:103 sphinx/ext/todo.py:130 #, python-format msgid "TODO entry found: %s" msgstr "" -#: sphinx/ext/todo.py:160 +#: sphinx/ext/todo.py:193 sphinx/ext/todo.py:242 msgid "<>" msgstr "" -#: sphinx/ext/todo.py:163 +#: sphinx/ext/todo.py:195 sphinx/ext/todo.py:245 #, python-format msgid "(The <> is located in %s, line %d.)" msgstr "" -#: sphinx/ext/todo.py:172 +#: sphinx/ext/todo.py:205 sphinx/ext/todo.py:253 msgid "original entry" msgstr "" -#: sphinx/ext/viewcode.py:158 +#: sphinx/ext/viewcode.py:153 msgid "highlighting module code... " msgstr "" -#: sphinx/ext/viewcode.py:187 +#: sphinx/ext/viewcode.py:182 msgid "[docs]" msgstr "" -#: sphinx/ext/viewcode.py:201 +#: sphinx/ext/viewcode.py:196 msgid "Module code" msgstr "" -#: sphinx/ext/viewcode.py:207 +#: sphinx/ext/viewcode.py:202 #, python-format msgid "

    Source code for %s

    " msgstr "" -#: sphinx/ext/viewcode.py:233 +#: sphinx/ext/viewcode.py:228 msgid "Overview: module code" msgstr "" -#: sphinx/ext/viewcode.py:234 +#: sphinx/ext/viewcode.py:229 msgid "

    All modules for which code is available

    " msgstr "" -#: sphinx/ext/autodoc/__init__.py:300 +#: sphinx/ext/autodoc/__init__.py:298 #, python-format msgid "invalid signature for auto%s (%r)" msgstr "" -#: sphinx/ext/autodoc/__init__.py:400 +#: sphinx/ext/autodoc/__init__.py:395 #, python-format msgid "error while formatting arguments for %s: %s" msgstr "" -#: sphinx/ext/autodoc/__init__.py:512 +#: sphinx/ext/autodoc/__init__.py:502 #, python-format msgid "missing attribute %s in object %s" msgstr "" -#: sphinx/ext/autodoc/__init__.py:600 +#: sphinx/ext/autodoc/__init__.py:617 #, python-format msgid "" "autodoc: failed to determine %r to be documented.the following exception " @@ -2692,7 +2773,7 @@ msgid "" "%s" msgstr "" -#: sphinx/ext/autodoc/__init__.py:692 +#: sphinx/ext/autodoc/__init__.py:706 #, python-format msgid "" "don't know which module to import for autodocumenting %r (try placing a " @@ -2700,86 +2781,106 @@ msgid "" "explicit module name)" msgstr "" -#: sphinx/ext/autodoc/__init__.py:786 +#: sphinx/ext/autodoc/__init__.py:799 msgid "\"::\" in automodule name doesn't make sense" msgstr "" -#: sphinx/ext/autodoc/__init__.py:794 +#: sphinx/ext/autodoc/__init__.py:806 #, python-format msgid "signature arguments or return annotation given for automodule %s" msgstr "" -#: sphinx/ext/autodoc/__init__.py:827 +#: sphinx/ext/autodoc/__init__.py:837 #, python-format msgid "" "__all__ should be a list of strings, not %r (in module %s) -- ignoring " "__all__" msgstr "" -#: sphinx/ext/autodoc/__init__.py:842 +#: sphinx/ext/autodoc/__init__.py:852 #, python-format msgid "" "missing attribute mentioned in :members: or __all__: module %s, attribute" " %s" msgstr "" -#: sphinx/ext/autodoc/__init__.py:1126 +#: sphinx/ext/autodoc/__init__.py:1208 #, python-format msgid "Bases: %s" msgstr "" -#: sphinx/ext/autodoc/__init__.py:1183 +#: sphinx/ext/autodoc/__init__.py:1267 #, python-format msgid "alias of :class:`%s`" msgstr "" -#: sphinx/ext/autodoc/__init__.py:1468 +#: sphinx/ext/autodoc/type_comment.py:132 #, python-format -msgid "Ignoring invalid option in autodoc_default_flags: %r" +msgid "Failed to parse type_comment for %r: %s" msgstr "" -#: sphinx/ext/autosummary/__init__.py:256 +#: sphinx/ext/autosummary/__init__.py:255 #, python-format -msgid "toctree references excluded document %r" +msgid "autosummary references excluded document %r. Ignored." msgstr "" -#: sphinx/ext/autosummary/__init__.py:258 +#: sphinx/ext/autosummary/__init__.py:257 #, python-format -msgid "toctree references unknown document %r" +msgid "" +"autosummary: stub file not found %r. Check your autosummary_generate " +"setting." msgstr "" -#: sphinx/ext/autosummary/__init__.py:292 +#: sphinx/ext/autosummary/__init__.py:296 #, python-format msgid "failed to import %s" msgstr "" -#: sphinx/ext/autosummary/__init__.py:307 +#: sphinx/ext/autosummary/__init__.py:311 #, python-format msgid "failed to parse name %s" msgstr "" -#: sphinx/ext/autosummary/__init__.py:311 +#: sphinx/ext/autosummary/__init__.py:315 #, python-format msgid "failed to import object %s" msgstr "" -#: sphinx/ext/autosummary/__init__.py:702 +#: sphinx/ext/autosummary/__init__.py:730 +#, python-format +msgid "autosummary_generate: file not found: %s" +msgstr "" + +#: sphinx/ext/autosummary/__init__.py:738 msgid "" "autosummary generats .rst files internally. But your source_suffix does " "not contain .rst. Skipped." msgstr "" -#: sphinx/ext/autosummary/generate.py:100 +#: sphinx/ext/autosummary/generate.py:153 +#, python-format +msgid "" +"autosummary: failed to determine %r to be documented.the following " +"exception was raised:\n" +"%s" +msgstr "" + +#: sphinx/ext/autosummary/generate.py:247 #, python-format msgid "[autosummary] generating autosummary for: %s" msgstr "" -#: sphinx/ext/autosummary/generate.py:104 +#: sphinx/ext/autosummary/generate.py:251 #, python-format msgid "[autosummary] writing to %s" msgstr "" -#: sphinx/ext/autosummary/generate.py:364 +#: sphinx/ext/autosummary/generate.py:277 +#, python-format +msgid "[autosummary] failed to import %r: %s" +msgstr "" + +#: sphinx/ext/autosummary/generate.py:435 msgid "" "\n" "Generate ReStructuredText using autosummary directives.\n" @@ -2796,110 +2897,107 @@ msgid "" " pydoc sphinx.ext.autosummary\n" msgstr "" -#: sphinx/ext/autosummary/generate.py:381 +#: sphinx/ext/autosummary/generate.py:452 msgid "source files to generate rST files for" msgstr "" -#: sphinx/ext/autosummary/generate.py:385 +#: sphinx/ext/autosummary/generate.py:456 msgid "directory to place all output in" msgstr "" -#: sphinx/ext/autosummary/generate.py:388 +#: sphinx/ext/autosummary/generate.py:459 #, python-format msgid "default suffix for files (default: %(default)s)" msgstr "" -#: sphinx/ext/autosummary/generate.py:392 +#: sphinx/ext/autosummary/generate.py:463 #, python-format msgid "custom template directory (default: %(default)s)" msgstr "" -#: sphinx/ext/autosummary/generate.py:396 +#: sphinx/ext/autosummary/generate.py:467 #, python-format msgid "document imported members (default: %(default)s)" msgstr "" -#: sphinx/ext/napoleon/__init__.py:330 sphinx/ext/napoleon/docstring.py:669 +#: sphinx/ext/napoleon/__init__.py:325 sphinx/ext/napoleon/docstring.py:638 msgid "Keyword Arguments" msgstr "" -#: sphinx/ext/napoleon/docstring.py:627 +#: sphinx/ext/napoleon/docstring.py:599 msgid "Example" msgstr "" -#: sphinx/ext/napoleon/docstring.py:628 +#: sphinx/ext/napoleon/docstring.py:600 msgid "Examples" msgstr "" -#: sphinx/ext/napoleon/docstring.py:684 +#: sphinx/ext/napoleon/docstring.py:651 msgid "Notes" msgstr "" -#: sphinx/ext/napoleon/docstring.py:688 +#: sphinx/ext/napoleon/docstring.py:654 msgid "Other Parameters" msgstr "" -#: sphinx/ext/napoleon/docstring.py:717 +#: sphinx/ext/napoleon/docstring.py:680 msgid "References" msgstr "" -#: sphinx/ext/napoleon/docstring.py:754 +#: sphinx/ext/napoleon/docstring.py:714 msgid "Warns" msgstr "" -#: sphinx/ext/napoleon/docstring.py:759 +#: sphinx/ext/napoleon/docstring.py:718 msgid "Yields" msgstr "" -#: sphinx/locale/__init__.py:307 +#: sphinx/locale/__init__.py:250 msgid "Attention" msgstr "" -#: sphinx/locale/__init__.py:308 +#: sphinx/locale/__init__.py:251 msgid "Caution" msgstr "" -#: sphinx/locale/__init__.py:309 +#: sphinx/locale/__init__.py:252 msgid "Danger" msgstr "" -#: sphinx/locale/__init__.py:310 +#: sphinx/locale/__init__.py:253 msgid "Error" msgstr "" -#: sphinx/locale/__init__.py:311 +#: sphinx/locale/__init__.py:254 msgid "Hint" msgstr "" -#: sphinx/locale/__init__.py:312 +#: sphinx/locale/__init__.py:255 msgid "Important" msgstr "" -#: sphinx/locale/__init__.py:313 +#: sphinx/locale/__init__.py:256 msgid "Note" msgstr "" -#: sphinx/locale/__init__.py:314 +#: sphinx/locale/__init__.py:257 msgid "See also" msgstr "" -#: sphinx/locale/__init__.py:315 +#: sphinx/locale/__init__.py:258 msgid "Tip" msgstr "" -#: sphinx/locale/__init__.py:316 +#: sphinx/locale/__init__.py:259 msgid "Warning" msgstr "" -#: sphinx/templates/latex/longtable.tex_t:23 +#: sphinx/templates/latex/longtable.tex_t:18 #: sphinx/templates/latex/sphinxmessages.sty_t:8 msgid "continued from previous page" msgstr "" -#: sphinx/templates/latex/longtable.tex_t:29 -msgid "Continued on next page" -msgstr "" - +#: sphinx/templates/latex/longtable.tex_t:24 #: sphinx/templates/latex/sphinxmessages.sty_t:9 msgid "continues on next page" msgstr "" @@ -2916,122 +3014,121 @@ msgstr "" msgid "page" msgstr "" -#: sphinx/themes/agogo/layout.html:46 sphinx/themes/basic/globaltoc.html:10 -#: sphinx/themes/basic/localtoc.html:11 sphinx/themes/scrolls/layout.html:41 +#: sphinx/themes/agogo/layout.html:27 sphinx/themes/basic/globaltoc.html:10 +#: sphinx/themes/basic/localtoc.html:10 sphinx/themes/scrolls/layout.html:31 msgid "Table of Contents" msgstr "" -#: sphinx/themes/agogo/layout.html:51 sphinx/themes/basic/layout.html:153 -#: sphinx/themes/basic/search.html:11 sphinx/themes/basic/search.html:21 -#: sphinx/themes/basic/searchresults.html:10 +#: sphinx/themes/agogo/layout.html:30 sphinx/themes/basic/layout.html:52 +#: sphinx/themes/basic/search.html:10 sphinx/themes/basic/search.html:18 msgid "Search" msgstr "" -#: sphinx/themes/agogo/layout.html:54 sphinx/themes/basic/searchbox.html:16 +#: sphinx/themes/agogo/layout.html:33 sphinx/themes/basic/searchbox.html:15 msgid "Go" msgstr "" -#: sphinx/themes/agogo/layout.html:79 sphinx/themes/basic/sourcelink.html:15 +#: sphinx/themes/agogo/layout.html:66 sphinx/themes/basic/sourcelink.html:14 msgid "Show Source" msgstr "" -#: sphinx/themes/basic/defindex.html:11 +#: sphinx/themes/basic/defindex.html:10 msgid "Overview" msgstr "" -#: sphinx/themes/basic/defindex.html:15 +#: sphinx/themes/basic/defindex.html:14 msgid "Welcome! This is" msgstr "" -#: sphinx/themes/basic/defindex.html:16 +#: sphinx/themes/basic/defindex.html:15 msgid "the documentation for" msgstr "" -#: sphinx/themes/basic/defindex.html:17 +#: sphinx/themes/basic/defindex.html:16 msgid "last updated" msgstr "" -#: sphinx/themes/basic/defindex.html:20 +#: sphinx/themes/basic/defindex.html:19 msgid "Indices and tables:" msgstr "" -#: sphinx/themes/basic/defindex.html:23 +#: sphinx/themes/basic/defindex.html:22 msgid "Complete Table of Contents" msgstr "" -#: sphinx/themes/basic/defindex.html:24 +#: sphinx/themes/basic/defindex.html:23 msgid "lists all sections and subsections" msgstr "" -#: sphinx/themes/basic/defindex.html:26 +#: sphinx/themes/basic/defindex.html:25 msgid "search this documentation" msgstr "" -#: sphinx/themes/basic/defindex.html:28 +#: sphinx/themes/basic/defindex.html:27 msgid "Global Module Index" msgstr "" -#: sphinx/themes/basic/defindex.html:29 +#: sphinx/themes/basic/defindex.html:28 msgid "quick access to all modules" msgstr "" -#: sphinx/themes/basic/defindex.html:31 +#: sphinx/themes/basic/defindex.html:30 msgid "all functions, classes, terms" msgstr "" -#: sphinx/themes/basic/genindex-single.html:33 +#: sphinx/themes/basic/genindex-single.html:22 #, python-format msgid "Index – %(key)s" msgstr "" -#: sphinx/themes/basic/genindex-single.html:61 -#: sphinx/themes/basic/genindex-split.html:24 -#: sphinx/themes/basic/genindex-split.html:38 -#: sphinx/themes/basic/genindex.html:72 +#: sphinx/themes/basic/genindex-single.html:41 +#: sphinx/themes/basic/genindex-split.html:22 +#: sphinx/themes/basic/genindex-split.html:35 +#: sphinx/themes/basic/genindex.html:49 msgid "Full index on one page" msgstr "" -#: sphinx/themes/basic/genindex-split.html:16 +#: sphinx/themes/basic/genindex-split.html:15 msgid "Index pages by letter" msgstr "" -#: sphinx/themes/basic/genindex-split.html:25 +#: sphinx/themes/basic/genindex-split.html:23 msgid "can be huge" msgstr "" -#: sphinx/themes/basic/layout.html:31 +#: sphinx/themes/basic/layout.html:16 msgid "Navigation" msgstr "" -#: sphinx/themes/basic/layout.html:138 +#: sphinx/themes/basic/layout.html:47 #, python-format msgid "Search within %(docstitle)s" msgstr "" -#: sphinx/themes/basic/layout.html:147 +#: sphinx/themes/basic/layout.html:50 msgid "About these documents" msgstr "" -#: sphinx/themes/basic/layout.html:156 +#: sphinx/themes/basic/layout.html:53 msgid "Copyright" msgstr "" -#: sphinx/themes/basic/layout.html:201 +#: sphinx/themes/basic/layout.html:69 #, python-format msgid "©
    Copyright %(copyright)s." msgstr "" -#: sphinx/themes/basic/layout.html:203 +#: sphinx/themes/basic/layout.html:70 #, python-format msgid "© Copyright %(copyright)s." msgstr "" -#: sphinx/themes/basic/layout.html:207 +#: sphinx/themes/basic/layout.html:71 #, python-format msgid "Last updated on %(last_updated)s." msgstr "" -#: sphinx/themes/basic/layout.html:210 +#: sphinx/themes/basic/layout.html:72 #, python-format msgid "" "Created using Sphinx " @@ -3043,57 +3140,55 @@ msgstr "" msgid "Search %(docstitle)s" msgstr "" -#: sphinx/themes/basic/relations.html:11 +#: sphinx/themes/basic/relations.html:10 msgid "Previous topic" msgstr "" -#: sphinx/themes/basic/relations.html:13 +#: sphinx/themes/basic/relations.html:12 msgid "previous chapter" msgstr "" -#: sphinx/themes/basic/relations.html:16 +#: sphinx/themes/basic/relations.html:13 msgid "Next topic" msgstr "" -#: sphinx/themes/basic/relations.html:18 +#: sphinx/themes/basic/relations.html:15 msgid "next chapter" msgstr "" -#: sphinx/themes/basic/search.html:25 +#: sphinx/themes/basic/search.html:22 msgid "" "Please activate JavaScript to enable the search\n" " functionality." msgstr "" -#: sphinx/themes/basic/search.html:30 +#: sphinx/themes/basic/search.html:27 msgid "" -"From here you can search these documents. Enter your search\n" -" words into the box below and click \"search\". Note that the search\n" -" function will automatically search for all of the words. Pages\n" -" containing fewer words won't appear in the result list." +"Searching for multiple words only shows matches that contain\n" +" all words." msgstr "" -#: sphinx/themes/basic/search.html:37 sphinx/themes/basic/searchresults.html:17 +#: sphinx/themes/basic/search.html:32 msgid "search" msgstr "" -#: sphinx/themes/basic/search.html:41 sphinx/themes/basic/searchresults.html:21 -#: sphinx/themes/basic/static/searchtools.js:285 +#: sphinx/themes/basic/search.html:36 +#: sphinx/themes/basic/static/searchtools.js:301 msgid "Search Results" msgstr "" -#: sphinx/themes/basic/search.html:43 sphinx/themes/basic/searchresults.html:23 -#: sphinx/themes/basic/static/searchtools.js:287 +#: sphinx/themes/basic/search.html:38 +#: sphinx/themes/basic/static/searchtools.js:303 msgid "" "Your search did not match any documents. Please make sure that all words " "are spelled correctly and that you've selected enough categories." msgstr "" -#: sphinx/themes/basic/searchbox.html:12 +#: sphinx/themes/basic/searchbox.html:11 msgid "Quick search" msgstr "" -#: sphinx/themes/basic/sourcelink.html:12 +#: sphinx/themes/basic/sourcelink.html:11 msgid "This Page" msgstr "" @@ -3125,15 +3220,15 @@ msgstr "" msgid "Other changes" msgstr "" -#: sphinx/themes/basic/static/doctools.js:194 sphinx/writers/html.py:454 -#: sphinx/writers/html.py:459 sphinx/writers/html5.py:400 -#: sphinx/writers/html5.py:405 +#: sphinx/themes/basic/static/doctools.js:194 sphinx/writers/html.py:403 +#: sphinx/writers/html.py:408 sphinx/writers/html5.py:355 +#: sphinx/writers/html5.py:360 msgid "Permalink to this headline" msgstr "" -#: sphinx/themes/basic/static/doctools.js:200 sphinx/writers/html.py:134 -#: sphinx/writers/html.py:145 sphinx/writers/html5.py:103 -#: sphinx/writers/html5.py:114 +#: sphinx/themes/basic/static/doctools.js:200 sphinx/writers/html.py:121 +#: sphinx/writers/html.py:130 sphinx/writers/html5.py:93 +#: sphinx/writers/html5.py:102 msgid "Permalink to this definition" msgstr "" @@ -3141,20 +3236,20 @@ msgstr "" msgid "Hide Search Matches" msgstr "" -#: sphinx/themes/basic/static/searchtools.js:121 +#: sphinx/themes/basic/static/searchtools.js:136 msgid "Searching" msgstr "" -#: sphinx/themes/basic/static/searchtools.js:126 +#: sphinx/themes/basic/static/searchtools.js:141 msgid "Preparing search..." msgstr "" -#: sphinx/themes/basic/static/searchtools.js:289 +#: sphinx/themes/basic/static/searchtools.js:305 #, python-format msgid "Search finished, found %s page(s) matching the search query." msgstr "" -#: sphinx/themes/basic/static/searchtools.js:342 +#: sphinx/themes/basic/static/searchtools.js:359 msgid ", in " msgstr "" @@ -3167,224 +3262,215 @@ msgstr "" msgid "Collapse sidebar" msgstr "" -#: sphinx/themes/haiku/layout.html:24 +#: sphinx/themes/haiku/layout.html:19 msgid "Contents" msgstr "" -#: sphinx/transforms/__init__.py:261 +#: sphinx/transforms/__init__.py:224 #, python-format msgid "4 column based index found. It might be a bug of extensions you use: %r" msgstr "" -#: sphinx/transforms/__init__.py:303 +#: sphinx/transforms/__init__.py:263 #, python-format msgid "Footnote [%s] is not referenced." msgstr "" -#: sphinx/transforms/__init__.py:309 +#: sphinx/transforms/__init__.py:269 msgid "Footnote [#] is not referenced." msgstr "" -#: sphinx/transforms/i18n.py:293 sphinx/transforms/i18n.py:363 +#: sphinx/transforms/i18n.py:296 sphinx/transforms/i18n.py:366 msgid "" "inconsistent footnote references in translated message. original: {0}, " "translated: {1}" msgstr "" -#: sphinx/transforms/i18n.py:335 +#: sphinx/transforms/i18n.py:338 msgid "" "inconsistent references in translated message. original: {0}, translated:" " {1}" msgstr "" -#: sphinx/transforms/i18n.py:382 +#: sphinx/transforms/i18n.py:385 msgid "" "inconsistent citation references in translated message. original: {0}, " "translated: {1}" msgstr "" -#: sphinx/transforms/i18n.py:402 +#: sphinx/transforms/i18n.py:405 msgid "" "inconsistent term references in translated message. original: {0}, " "translated: {1}" msgstr "" -#: sphinx/transforms/post_transforms/__init__.py:110 +#: sphinx/transforms/post_transforms/__init__.py:138 #, python-format msgid "more than one target found for 'any' cross-reference %r: could be %s" msgstr "" -#: sphinx/transforms/post_transforms/__init__.py:142 +#: sphinx/transforms/post_transforms/__init__.py:170 #, python-format msgid "%s:%s reference target not found: %%(target)s" msgstr "" -#: sphinx/transforms/post_transforms/__init__.py:145 +#: sphinx/transforms/post_transforms/__init__.py:173 #, python-format msgid "%r reference target not found: %%(target)s" msgstr "" -#: sphinx/transforms/post_transforms/images.py:92 +#: sphinx/transforms/post_transforms/images.py:86 #, python-format msgid "Could not fetch remote image: %s [%d]" msgstr "" -#: sphinx/transforms/post_transforms/images.py:120 +#: sphinx/transforms/post_transforms/images.py:114 #, python-format msgid "Could not fetch remote image: %s [%s]" msgstr "" -#: sphinx/transforms/post_transforms/images.py:140 +#: sphinx/transforms/post_transforms/images.py:132 #, python-format msgid "Unknown image format: %s..." msgstr "" -#: sphinx/util/__init__.py:415 +#: sphinx/util/__init__.py:379 #, python-format msgid "undecodable source characters, replacing with \"?\": %r" msgstr "" -#: sphinx/util/__init__.py:695 +#: sphinx/util/__init__.py:639 msgid "skipped" msgstr "" -#: sphinx/util/__init__.py:700 +#: sphinx/util/__init__.py:644 msgid "failed" msgstr "" -#: sphinx/util/docutils.py:320 -msgid "when adding directive classes, no additional arguments may be given" -msgstr "" - -#: sphinx/util/i18n.py:74 +#: sphinx/util/i18n.py:68 #, python-format msgid "reading error: %s, %s" msgstr "" -#: sphinx/util/i18n.py:81 +#: sphinx/util/i18n.py:75 #, python-format msgid "writing error: %s, %s" msgstr "" -#: sphinx/util/i18n.py:215 +#: sphinx/util/i18n.py:260 #, python-format msgid "" "Invalid date format. Quote the string by single quote if you want to " "output it directly: %s" msgstr "" -#: sphinx/util/nodes.py:428 +#: sphinx/util/nodes.py:426 #, python-format msgid "toctree contains ref to nonexisting file %r" msgstr "" -#: sphinx/util/nodes.py:501 +#: sphinx/util/nodes.py:526 #, python-format msgid "exception while evaluating only directive expression: %s" msgstr "" -#: sphinx/util/pycompat.py:82 +#: sphinx/util/pycompat.py:77 #, python-format msgid "" "Support for evaluating Python 2 syntax is deprecated and will be removed " "in Sphinx 4.0. Convert %s to Python 3 syntax." msgstr "" -#: sphinx/util/rst.py:49 +#: sphinx/util/rst.py:74 #, python-format msgid "default role %s not found" msgstr "" -#: sphinx/writers/html.py:345 sphinx/writers/html5.py:313 +#: sphinx/writers/html.py:306 sphinx/writers/html5.py:278 #, python-format msgid "numfig_format is not defined for %s" msgstr "" -#: sphinx/writers/html.py:355 sphinx/writers/html5.py:323 +#: sphinx/writers/html.py:316 sphinx/writers/html5.py:288 #, python-format msgid "Any IDs not assigned for %s node" msgstr "" -#: sphinx/writers/html.py:463 sphinx/writers/html5.py:409 +#: sphinx/writers/html.py:412 sphinx/writers/html5.py:364 msgid "Permalink to this table" msgstr "" -#: sphinx/writers/html.py:510 sphinx/writers/html5.py:456 +#: sphinx/writers/html.py:456 sphinx/writers/html5.py:408 msgid "Permalink to this code" msgstr "" -#: sphinx/writers/html.py:512 sphinx/writers/html5.py:458 +#: sphinx/writers/html.py:458 sphinx/writers/html5.py:410 msgid "Permalink to this image" msgstr "" -#: sphinx/writers/html.py:514 sphinx/writers/html5.py:460 +#: sphinx/writers/html.py:460 sphinx/writers/html5.py:412 msgid "Permalink to this toctree" msgstr "" -#: sphinx/writers/html.py:672 sphinx/writers/html5.py:606 +#: sphinx/writers/html.py:584 sphinx/writers/html5.py:525 msgid "Could not obtain image size. :scale: option is ignored." msgstr "" -#: sphinx/writers/latex.py:510 +#: sphinx/writers/latex.py:352 #, python-format msgid "unknown %r toplevel_sectioning for class %r" msgstr "" -#: sphinx/writers/latex.py:603 +#: sphinx/writers/latex.py:404 msgid "too large :maxdepth:, ignored." msgstr "" -#: sphinx/writers/latex.py:893 +#: sphinx/writers/latex.py:653 msgid "document title is not a single Text node" msgstr "" -#: sphinx/writers/latex.py:926 sphinx/writers/texinfo.py:658 +#: sphinx/writers/latex.py:685 sphinx/writers/texinfo.py:626 msgid "encountered title node not in section, topic, table, admonition or sidebar" msgstr "" -#: sphinx/writers/latex.py:1102 sphinx/writers/manpage.py:277 -#: sphinx/writers/texinfo.py:675 +#: sphinx/writers/latex.py:827 sphinx/writers/manpage.py:238 +#: sphinx/writers/texinfo.py:641 msgid "Footnotes" msgstr "" -#: sphinx/writers/latex.py:1150 +#: sphinx/writers/latex.py:879 msgid "both tabularcolumns and :widths: option are given. :widths: is ignored." msgstr "" -#: sphinx/writers/latex.py:1521 +#: sphinx/writers/latex.py:1202 #, python-format msgid "dimension unit %s is invalid. Ignored." msgstr "" -#: sphinx/writers/latex.py:1847 +#: sphinx/writers/latex.py:1513 #, python-format msgid "unknown index entry type %s found" msgstr "" -#: sphinx/writers/latex.py:2560 -#, python-format -msgid "Unknown configure key: latex_elements[%r] is ignored." -msgstr "" - -#: sphinx/writers/manpage.py:333 sphinx/writers/text.py:887 +#: sphinx/writers/manpage.py:287 sphinx/writers/text.py:788 #, python-format msgid "[image: %s]" msgstr "" -#: sphinx/writers/manpage.py:334 sphinx/writers/text.py:888 +#: sphinx/writers/manpage.py:288 sphinx/writers/text.py:789 msgid "[image]" msgstr "" -#: sphinx/writers/texinfo.py:1330 +#: sphinx/writers/texinfo.py:1185 msgid "caption not inside a figure." msgstr "" -#: sphinx/writers/texinfo.py:1422 +#: sphinx/writers/texinfo.py:1261 #, python-format msgid "unimplemented node type: %r" msgstr "" -#: sphinx/writers/texinfo.py:1427 +#: sphinx/writers/texinfo.py:1265 #, python-format msgid "unknown node type: %r" msgstr "" From 6ef573acb9b2cc6cc95b75bad405ab7dfd545e10 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 9 Mar 2020 01:29:22 +0900 Subject: [PATCH 60/72] Update CHANGES for PR #7278 --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 72a53269cfe..aaf2ac743ae 100644 --- a/CHANGES +++ b/CHANGES @@ -97,6 +97,8 @@ Bugs fixed * #7267: autodoc: error message for invalid directive options has wrong location * #5637: inheritance_diagram: Incorrect handling of nested class names * #7139: ``code-block:: guess`` does not work +* #7278: html search: Fix use of ``html_file_suffix`` instead of + ``html_link_suffix`` in search results Testing -------- From e76b5073094973e3be9d877074cd4126b5377b84 Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Sun, 8 Mar 2020 21:47:08 -0300 Subject: [PATCH 61/72] build: start description lower-cased --- sphinx/cmd/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/cmd/build.py b/sphinx/cmd/build.py index bcf2a20c336..012fd72adf0 100644 --- a/sphinx/cmd/build.py +++ b/sphinx/cmd/build.py @@ -180,7 +180,7 @@ def get_parser() -> argparse.ArgumentParser: group.add_argument('-W', action='store_true', dest='warningiserror', help=__('turn warnings into errors')) group.add_argument('--keep-going', action='store_true', dest='keep_going', - help=__("With -W, keep going when getting warnings")) + help=__("with -W, keep going when getting warnings")) group.add_argument('-T', action='store_true', dest='traceback', help=__('show full traceback on exception')) group.add_argument('-P', action='store_true', dest='pdb', From 5bee0fc7e1297c772c9490d837cf83f98bc935b2 Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Sun, 8 Mar 2020 21:47:11 -0300 Subject: [PATCH 62/72] put comma between sentences --- sphinx/builders/latex/__init__.py | 2 +- sphinx/ext/autodoc/__init__.py | 2 +- sphinx/ext/autosummary/generate.py | 2 +- sphinx/ext/imgconverter.py | 4 ++-- sphinx/ext/intersphinx.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index 4a7ec5e3139..1348f0e1257 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -486,7 +486,7 @@ def docclass(self) -> str: def validate_config_values(app: Sphinx, config: Config) -> None: for key in list(config.latex_elements): if key not in DEFAULT_SETTINGS: - msg = __("Unknown configure key: latex_elements[%r]. ignored.") + msg = __("Unknown configure key: latex_elements[%r], ignored.") logger.warning(msg % (key,)) config.latex_elements.pop(key) diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 9a6b3263788..36875c86253 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -614,7 +614,7 @@ def is_filtered_inherited_member(name: str) -> bool: if skip_user is not None: keep = not skip_user except Exception as exc: - logger.warning(__('autodoc: failed to determine %r to be documented.' + logger.warning(__('autodoc: failed to determine %r to be documented, ' 'the following exception was raised:\n%s'), member, exc, type='autodoc') keep = False diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index 8bea3b4a337..f1766ae51cd 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -150,7 +150,7 @@ def skip_member(obj: Any, name: str, objtype: str) -> bool: return app.emit_firstresult('autodoc-skip-member', objtype, name, obj, False, {}) except Exception as exc: - logger.warning(__('autosummary: failed to determine %r to be documented.' + logger.warning(__('autosummary: failed to determine %r to be documented, ' 'the following exception was raised:\n%s'), name, exc, type='autosummary') return False diff --git a/sphinx/ext/imgconverter.py b/sphinx/ext/imgconverter.py index 95d3fe65aa0..3af93b6423a 100644 --- a/sphinx/ext/imgconverter.py +++ b/sphinx/ext/imgconverter.py @@ -38,7 +38,7 @@ def is_available(self) -> bool: subprocess.run(args, stdout=PIPE, stderr=PIPE, check=True) return True except OSError: - logger.warning(__('convert command %r cannot be run.' + logger.warning(__('convert command %r cannot be run, ' 'check the image_converter setting'), self.config.image_converter) return False @@ -62,7 +62,7 @@ def convert(self, _from: str, _to: str) -> bool: subprocess.run(args, stdout=PIPE, stderr=PIPE, check=True) return True except OSError: - logger.warning(__('convert command %r cannot be run.' + logger.warning(__('convert command %r cannot be run, ' 'check the image_converter setting'), self.config.image_converter) return False diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index 362583fa2cb..5aa9c75f0fa 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -359,7 +359,7 @@ def normalize_intersphinx_mapping(app: Sphinx, config: Config) -> None: else: config.intersphinx_mapping[key] = (name, (uri, inv)) except Exception as exc: - logger.warning(__('Fail to read intersphinx_mapping[%s], Ignored: %r'), key, exc) + logger.warning(__('Failed to read intersphinx_mapping[%s], ignored: %r'), key, exc) config.intersphinx_mapping.pop(key) From 6a396c7eb85e4f2e2291652c454352bad1a397f4 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 9 Mar 2020 11:41:14 +0100 Subject: [PATCH 63/72] Enable intersphinx with Sphinx's documentation --- doc/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/conf.py b/doc/conf.py index 9951aed2dbf..77d2a577a4d 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -7,6 +7,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.autosummary', 'sphinx.ext.extlinks', + 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode', 'sphinx.ext.inheritance_diagram'] master_doc = 'contents' From fbe48aa339a0c8c6f191ffbc6a4c6432f039b183 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 9 Mar 2020 12:20:45 +0100 Subject: [PATCH 64/72] Disable comments from Codecov on CI Fixes https://github.com/sphinx-doc/sphinx/issues/7279. --- .codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.codecov.yml b/.codecov.yml index f6272f5f158..652520158ff 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,3 +1,4 @@ +comment: false coverage: status: project: From f09adc0af8bebc061eb697597de8c2988f560a25 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 9 Mar 2020 12:21:37 +0100 Subject: [PATCH 65/72] Enable patch status check for Codecov --- .codecov.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index 652520158ff..22f35d71012 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -4,6 +4,3 @@ coverage: project: default: enabled: no - patch: - default: - enabled: no From f50144a40bea898146e9d9409b57209adbcac095 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 9 Mar 2020 12:56:09 +0100 Subject: [PATCH 66/72] Unpin pytest Was done in https://github.com/sphinx-doc/sphinx/pull/7037 to work around a bug in 5.3.3. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bb6273033a7..43f197566bd 100644 --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ 'docutils-stubs', ], 'test': [ - 'pytest < 5.3.3', + 'pytest', 'pytest-cov', 'html5lib', 'typed_ast', # for py35-37 From d28b3a2ffe2372e935b079165ac84f8c1343efd4 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 9 Mar 2020 13:13:39 +0100 Subject: [PATCH 67/72] Fix warnings with Travis config > Build config validation > root: deprecated key sudo (The key `sudo` has no effect anymore.) > jobs.include.node_js: unexpected sequence, using the first value (10.7) > root: missing os, using the default linux > root: key matrix is an alias for jobs, using jobs --- .travis.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 008f4e44260..3ceb2e3f2f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ -language: python -sudo: false +os: linux dist: xenial +language: python cache: pip env: @@ -9,7 +9,7 @@ env: - SKIP_LATEX_BUILD=1 - IS_PYTHON=true -matrix: +jobs: include: - python: '3.5' env: @@ -37,8 +37,7 @@ matrix: env: TOXENV=flake8 - language: node_js - node_js: - - 10.7 + node_js: '10.7' env: IS_PYTHON=false services: xvfb From 1dba84096d888a15ae171c56572f12ca8d1cbd0d Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Tue, 10 Mar 2020 01:01:52 +0900 Subject: [PATCH 68/72] test: Disable codecov To avoid the error of codecov on CI, this disables it temporarily. --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 008f4e44260..9e90bc77d6e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,9 @@ matrix: - python: '3.8' env: - TOXENV=du15 - - PYTEST_ADDOPTS="--cov ./ --cov-append --cov-config setup.cfg" + # Disable codecov temporarily + # refs: https://github.com/sphinx-doc/sphinx/pull/7286#issuecomment-596617853 + # - PYTEST_ADDOPTS="--cov ./ --cov-append --cov-config setup.cfg" - python: 'nightly' env: - TOXENV=du16 From 97133d4bf047175c921e43770cce818126ef5086 Mon Sep 17 00:00:00 2001 From: Septatrix <24257556+Septatrix@users.noreply.github.com> Date: Tue, 10 Mar 2020 18:57:49 +0100 Subject: [PATCH 69/72] Configure plantuml to generate svg files (fixes #7134) --- doc/_static/Makefile | 4 ++-- doc/_static/translation.png | Bin 20416 -> 0 bytes doc/_static/translation.svg | 29 +++++++++++++++++++++++++++++ doc/usage/advanced/intl.rst | 2 +- 4 files changed, 32 insertions(+), 3 deletions(-) delete mode 100644 doc/_static/translation.png create mode 100644 doc/_static/translation.svg diff --git a/doc/_static/Makefile b/doc/_static/Makefile index 39ce6526036..acb8e2cdaf1 100644 --- a/doc/_static/Makefile +++ b/doc/_static/Makefile @@ -1,4 +1,4 @@ -translation.png: translation.puml - plantuml $< +translation.svg: translation.puml + plantuml -tsvg $< clean: rm translation.png diff --git a/doc/_static/translation.png b/doc/_static/translation.png deleted file mode 100644 index a47c19f8ded0d5e7ca711b014648e03ac04f8b8b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20416 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 diff --git a/doc/_static/translation.svg b/doc/_static/translation.svg new file mode 100644 index 00000000000..74b78a1e7aa --- /dev/null +++ b/doc/_static/translation.svg @@ -0,0 +1,29 @@ +SphinxProject.rst.pot.po.motranslatorTranslatedBuildsphinx-build gettextPootlemsgfmtsphinx-buid -Dlanguage= \ No newline at end of file diff --git a/doc/usage/advanced/intl.rst b/doc/usage/advanced/intl.rst index 0dd89b65fe4..431c0904d69 100644 --- a/doc/usage/advanced/intl.rst +++ b/doc/usage/advanced/intl.rst @@ -9,7 +9,7 @@ Complementary to translations provided for Sphinx-generated messages such as navigation bars, Sphinx provides mechanisms facilitating *document* translations in itself. See the :ref:`intl-options` for details on configuration. -.. figure:: /_static/translation.png +.. figure:: /_static/translation.svg :width: 100% Workflow visualization of translations in Sphinx. (The figure is created by From 99476f37e7af75a729aa7bbf301e94aa8da57196 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 12 Mar 2020 01:25:18 +0900 Subject: [PATCH 70/72] Fix mypy violations (with mypy-0.770) --- setup.py | 2 +- sphinx/application.py | 2 +- sphinx/util/console.py | 2 +- sphinx/util/nodes.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 55578350f76..37c7afcc541 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ 'html5lib', 'flake8>=3.5.0', 'flake8-import-order', - 'mypy>=0.761', + 'mypy>=0.770', 'docutils-stubs', ], } diff --git a/sphinx/application.py b/sphinx/application.py index 744e62a4e8c..152c8bb43bf 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -497,7 +497,7 @@ def add_config_value(self, name: str, default: Any, rebuild: Union[bool, str], other values. """ logger.debug('[app] adding config value: %r', - (name, default, rebuild) + ((types,) if types else ())) # type: ignore + (name, default, rebuild) + ((types,) if types else ())) if rebuild in (False, True): rebuild = 'env' if rebuild else '' self.config.add(name, default, rebuild, types) diff --git a/sphinx/util/console.py b/sphinx/util/console.py index f4c80d288bf..08aad8ab126 100644 --- a/sphinx/util/console.py +++ b/sphinx/util/console.py @@ -35,7 +35,7 @@ def get_terminal_width() -> int: import termios import fcntl import struct - call = fcntl.ioctl(0, termios.TIOCGWINSZ, + call = fcntl.ioctl(0, termios.TIOCGWINSZ, # type: ignore struct.pack('hhhh', 0, 0, 0, 0)) height, width = struct.unpack('hhhh', call)[:2] terminal_width = width diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index 7c7300c6094..a5ab83f5dc3 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -199,7 +199,7 @@ def is_translatable(node: Node) -> bool: if isinstance(node, addnodes.translatable): return True - if isinstance(node, nodes.Inline) and 'translatable' not in node: + if isinstance(node, nodes.Inline) and 'translatable' not in node: # type: ignore # inline node must not be translated if 'translatable' is not set return False From f4ba4e8c79a77b0fb561753413d061e0aab174db Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Thu, 12 Mar 2020 16:31:49 +0100 Subject: [PATCH 71/72] Revert "test: Disable codecov" This reverts commit 1dba84096d888a15ae171c56572f12ca8d1cbd0d. --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1b0b104ad7b..3ceb2e3f2f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,9 +23,7 @@ jobs: - python: '3.8' env: - TOXENV=du15 - # Disable codecov temporarily - # refs: https://github.com/sphinx-doc/sphinx/pull/7286#issuecomment-596617853 - # - PYTEST_ADDOPTS="--cov ./ --cov-append --cov-config setup.cfg" + - PYTEST_ADDOPTS="--cov ./ --cov-append --cov-config setup.cfg" - python: 'nightly' env: - TOXENV=du16 From 2495277b86c88bde59fcb3e5bc601395bc137c96 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Thu, 12 Mar 2020 16:41:34 +0100 Subject: [PATCH 72/72] pytest: configure testpaths This avoids having unrelated files being collected. --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 6207a6c5365..21e1581d8b9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -59,6 +59,7 @@ markers = apidoc setup_command test_params +testpaths = tests [coverage:run] branch = True