diff options
Diffstat (limited to 'sphinx')
77 files changed, 1451 insertions, 2202 deletions
diff --git a/sphinx/__init__.py b/sphinx/__init__.py index a7cf04ccf..4889f35d5 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -32,8 +32,8 @@ if 'PYTHONWARNINGS' not in os.environ: warnings.filterwarnings('ignore', "'U' mode is deprecated", DeprecationWarning, module='docutils.io') -__version__ = '2.4.4+' -__released__ = '2.4.4' # used when Sphinx builds its own docs +__version__ = '3.0.0+' +__released__ = '3.0.0' # used when Sphinx builds its own docs #: Version info for better programmatic use. #: @@ -43,7 +43,7 @@ __released__ = '2.4.4' # used when Sphinx builds its own docs #: #: .. versionadded:: 1.2 #: Before version 1.2, check the string ``sphinx.__version__``. -version_info = (2, 4, 4, 'beta', 0) +version_info = (3, 0, 0, 'beta', 0) package_dir = path.abspath(path.dirname(__file__)) @@ -56,8 +56,8 @@ if __version__.endswith('+'): __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 diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py index 5dac63867..15d5fc46b 100644 --- a/sphinx/addnodes.py +++ b/sphinx/addnodes.py @@ -14,7 +14,7 @@ from typing import Any, Dict, List, Sequence from docutils import nodes from docutils.nodes import Node -from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning +from sphinx.deprecation import RemovedInSphinx40Warning if False: # For type annotation @@ -199,59 +199,6 @@ class production(nodes.Part, nodes.Inline, nodes.FixedTextElement): """Node for a single grammar production rule.""" -# math nodes - - -class math(nodes.math): - """Node for inline equations. - - .. warning:: This node is provided to keep compatibility only. - It will be removed in nearly future. Don't use this from your extension. - - .. deprecated:: 1.8 - Use ``docutils.nodes.math`` instead. - """ - - def __getitem__(self, key): - """Special accessor for supporting ``node['latex']``.""" - if key == 'latex' and 'latex' not in self.attributes: - warnings.warn("math node for Sphinx was replaced by docutils'. " - "Therefore please use ``node.astext()`` to get an equation instead.", - RemovedInSphinx30Warning, stacklevel=2) - return self.astext() - else: - return super().__getitem__(key) - - -class math_block(nodes.math_block): - """Node for block level equations. - - .. warning:: This node is provided to keep compatibility only. - It will be removed in nearly future. Don't use this from your extension. - - .. deprecated:: 1.8 - """ - - def __getitem__(self, key): - if key == 'latex' and 'latex' not in self.attributes: - warnings.warn("displaymath node for Sphinx was replaced by docutils'. " - "Therefore please use ``node.astext()`` to get an equation instead.", - RemovedInSphinx30Warning, stacklevel=2) - return self.astext() - else: - return super().__getitem__(key) - - -class displaymath(math_block): - """Node for block level equations. - - .. warning:: This node is provided to keep compatibility only. - It will be removed in nearly future. Don't use this from your extension. - - .. deprecated:: 1.8 - """ - - # other directive-level nodes class index(nodes.Invisible, nodes.Inline, nodes.TextElement): @@ -389,7 +336,6 @@ def setup(app: "Sphinx") -> Dict[str, Any]: app.add_node(seealso) app.add_node(productionlist) app.add_node(production) - app.add_node(displaymath) app.add_node(index) app.add_node(centered) app.add_node(acks) diff --git a/sphinx/application.py b/sphinx/application.py index 744e62a4e..fbc637e60 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -16,7 +16,6 @@ import platform import sys import warnings from collections import deque -from inspect import isclass from io import StringIO from os import path from typing import Any, Callable, Dict, IO, List, Tuple, Union @@ -30,9 +29,7 @@ from pygments.lexer import Lexer import sphinx from sphinx import package_dir, locale from sphinx.config import Config -from sphinx.deprecation import ( - RemovedInSphinx30Warning, RemovedInSphinx40Warning, deprecated_alias -) +from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.domains import Domain, Index from sphinx.environment import BuildEnvironment from sphinx.environment.collectors import EnvironmentCollector @@ -46,11 +43,10 @@ from sphinx.registry import SphinxComponentRegistry from sphinx.roles import XRefRole from sphinx.theming import Theme from sphinx.util import docutils -from sphinx.util import import_object, progress_message from sphinx.util import logging +from sphinx.util import progress_message from sphinx.util.build_phase import BuildPhase from sphinx.util.console import bold # type: ignore -from sphinx.util.docutils import directive_helper from sphinx.util.i18n import CatalogRepository from sphinx.util.logging import prefixed_warnings from sphinx.util.osutil import abspath, ensuredir, relpath @@ -105,7 +101,6 @@ builtin_extensions = ( 'sphinx.transforms.post_transforms', 'sphinx.transforms.post_transforms.code', 'sphinx.transforms.post_transforms.images', - 'sphinx.transforms.post_transforms.compat', 'sphinx.util.compat', 'sphinx.versioning', # collectors should be loaded by specific order @@ -408,29 +403,26 @@ class Sphinx: if version > sphinx.__display_version__[:3]: raise VersionRequirementError(version) - def import_object(self, objname: str, source: str = None) -> Any: - """Import an object from a ``module.name`` string. - - .. deprecated:: 1.8 - Use ``sphinx.util.import_object()`` instead. - """ - warnings.warn('app.import_object() is deprecated. ' - 'Use sphinx.util.add_object_type() instead.', - RemovedInSphinx30Warning, stacklevel=2) - return import_object(objname, source=None) - # event interface - def connect(self, event: str, callback: Callable) -> int: + def connect(self, event: str, callback: Callable, priority: int = 500) -> int: """Register *callback* to be called when *event* is emitted. For details on available core events and the arguments of callback functions, please see :ref:`events`. + Registered callbacks will be invoked on event in the order of *priority* and + registration. The priority is ascending order. + The method returns a "listener ID" that can be used as an argument to :meth:`disconnect`. + + .. versionchanged:: 3.0 + + Support *priority* """ - listener_id = self.events.connect(event, callback) - logger.debug('[app] connecting event %r: %r [id=%s]', event, callback, listener_id) + listener_id = self.events.connect(event, callback, priority) + logger.debug('[app] connecting event %r (%d): %r [id=%s]', + event, priority, callback, listener_id) return listener_id def disconnect(self, listener_id: int) -> None: @@ -592,36 +584,13 @@ class Sphinx: self.registry.add_enumerable_node(node, figtype, title_getter, override=override) self.add_node(node, override=override, **kwargs) - @property - def enumerable_nodes(self) -> Dict["Type[Node]", Tuple[str, TitleGetter]]: - warnings.warn('app.enumerable_nodes() is deprecated. ' - 'Use app.get_domain("std").enumerable_nodes instead.', - RemovedInSphinx30Warning, stacklevel=2) - return self.registry.enumerable_nodes - - def add_directive(self, name: str, obj: Any, content: bool = None, - arguments: Tuple[int, int, bool] = None, override: bool = False, - **options: Any) -> None: + def add_directive(self, name: str, cls: "Type[Directive]", override: bool = False) -> None: """Register a Docutils directive. - *name* must be the prospective directive name. There are two possible - ways to write a directive: - - - In the docutils 0.4 style, *obj* is the directive function. - *content*, *arguments* and *options* are set as attributes on the - function and determine whether the directive has content, arguments - and options, respectively. **This style is deprecated.** - - - In the docutils 0.5 style, *obj* is the directive class. - It must already have attributes named *has_content*, - *required_arguments*, *optional_arguments*, - *final_argument_whitespace* and *option_spec* that correspond to the - options for the function way. See `the Docutils docs - <http://docutils.sourceforge.net/docs/howto/rst-directives.html>`_ - for details. - - The directive class must inherit from the class - ``docutils.parsers.rst.Directive``. + *name* must be the prospective directive name. *cls* is a directive + class which inherits ``docutils.parsers.rst.Directive``. For more + details, see `the Docutils docs + <http://docutils.sourceforge.net/docs/howto/rst-directives.html>`_ . For example, the (already existing) :rst:dir:`literalinclude` directive would be added like this: @@ -652,17 +621,12 @@ class Sphinx: .. versionchanged:: 1.8 Add *override* keyword. """ - logger.debug('[app] adding directive: %r', - (name, obj, content, arguments, options)) + logger.debug('[app] adding directive: %r', (name, cls)) if not override and docutils.is_directive_registered(name): logger.warning(__('directive %r is already registered, it will be overridden'), name, type='app', subtype='add_directive') - if not isclass(obj) or not issubclass(obj, Directive): - directive = directive_helper(obj, content, arguments, **options) - docutils.register_directive(name, directive) - else: - docutils.register_directive(name, obj) + docutils.register_directive(name, cls) def add_role(self, name: str, role: Any, override: bool = False) -> None: """Register a Docutils role. @@ -712,25 +676,8 @@ class Sphinx: """ self.registry.add_domain(domain, override=override) - def override_domain(self, domain: "Type[Domain]") -> None: - """Override a registered domain. - - Make the given *domain* class known to Sphinx, assuming that there is - already a domain with its ``.name``. The new domain must be a subclass - of the existing one. - - .. versionadded:: 1.0 - .. deprecated:: 1.8 - Integrated to :meth:`add_domain`. - """ - warnings.warn('app.override_domain() is deprecated. ' - 'Use app.add_domain() with override option instead.', - RemovedInSphinx30Warning, stacklevel=2) - self.registry.add_domain(domain, override=True) - - def add_directive_to_domain(self, domain: str, name: str, obj: Any, - has_content: bool = None, argument_spec: Any = None, - override: bool = False, **option_spec: Any) -> None: + def add_directive_to_domain(self, domain: str, name: str, + cls: "Type[Directive]", override: bool = False) -> None: """Register a Docutils directive in a domain. Like :meth:`add_directive`, but the directive is added to the domain @@ -740,9 +687,7 @@ class Sphinx: .. versionchanged:: 1.8 Add *override* keyword. """ - self.registry.add_directive_to_domain(domain, name, obj, - has_content, argument_spec, override=override, - **option_spec) + self.registry.add_directive_to_domain(domain, name, cls, override=override) def add_role_to_domain(self, domain: str, name: str, role: Union[RoleFunction, XRefRole], override: bool = False) -> None: @@ -924,8 +869,10 @@ class Sphinx: Add *filename* to the list of JavaScript files that the default HTML template will include. The filename must be relative to the HTML - static path , or a full URI with scheme. The keyword arguments are - also accepted for attributes of ``<script>`` tag. + static path , or a full URI with scheme. If the keyword argument + ``body`` is given, its value will be added between the + ``<script>`` tags. Extra keyword arguments are included as + attributes of the ``<script>`` tag. Example:: @@ -935,6 +882,9 @@ class Sphinx: app.add_js_file('example.js', async="async") # => <script src="_static/example.js" async="async"></script> + app.add_js_file(None, body="var myVariable = 'foo';") + # => <script>var myVariable = 'foo';</script> + .. versionadded:: 0.5 .. versionchanged:: 1.8 @@ -1197,12 +1147,6 @@ class Sphinx: return True - @property - def _setting_up_extension(self) -> List[str]: - warnings.warn('app._setting_up_extension is deprecated.', - RemovedInSphinx30Warning) - return ['?'] - class TemplateBridge: """ @@ -1239,12 +1183,3 @@ class TemplateBridge: specified context (a Python dictionary). """ raise NotImplementedError('must be implemented in subclasses') - - -from sphinx.config import CONFIG_FILENAME # NOQA - -deprecated_alias('sphinx.application', - { - 'CONFIG_FILENAME': CONFIG_FILENAME, - }, - RemovedInSphinx30Warning) diff --git a/sphinx/builders/html.py b/sphinx/builders/html/__init__.py index b3d2f1da2..cf8cd56ce 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html/__init__.py @@ -28,7 +28,7 @@ from sphinx import package_dir, __display_version__ from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.config import Config -from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning +from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.domains import Domain, Index, IndexEntry from sphinx.environment.adapters.asset import ImageAdapter from sphinx.environment.adapters.indexentries import IndexEntries @@ -53,7 +53,7 @@ if False: from typing import Type # for python3.5.1 -# HTML5 Writer is avialable or not +# HTML5 Writer is available or not if is_html5_writer_available(): from sphinx.writers.html5 import HTML5Translator html5_ready = True @@ -103,35 +103,6 @@ class Stylesheet(str): return self -class JSContainer(list): - """The container for JavaScript scripts.""" - def insert(self, index: int, obj: str) -> None: - warnings.warn('To modify script_files in the theme is deprecated. ' - 'Please insert a <script> tag directly in your theme instead.', - RemovedInSphinx30Warning, stacklevel=3) - super().insert(index, obj) - - def extend(self, other: List[str]) -> None: # type: ignore - warnings.warn('To modify script_files in the theme is deprecated. ' - 'Please insert a <script> tag directly in your theme instead.', - RemovedInSphinx30Warning, stacklevel=3) - for item in other: - self.append(item) - - def __iadd__(self, other: List[str]) -> "JSContainer": # type: ignore - warnings.warn('To modify script_files in the theme is deprecated. ' - 'Please insert a <script> tag directly in your theme instead.', - RemovedInSphinx30Warning, stacklevel=3) - for item in other: - self.append(item) - return self - - def __add__(self, other: List[str]) -> "JSContainer": - ret = JSContainer(self) - ret += other - return ret - - class JavaScript(str): """A metadata of javascript file. @@ -234,7 +205,7 @@ class StandaloneHTMLBuilder(Builder): self.css_files = [] # type: List[Dict[str, str]] # JS files - self.script_files = JSContainer() # type: List[JavaScript] + self.script_files = [] # type: List[JavaScript] def init(self) -> None: self.build_info = self.create_build_info() @@ -836,13 +807,17 @@ class StandaloneHTMLBuilder(Builder): if self.config.html_scaled_image_link and self.html_scaled_image_link: for node in doctree.traverse(nodes.image): - scale_keys = ('scale', 'width', 'height') - if not any((key in node) for key in scale_keys) or \ - isinstance(node.parent, nodes.reference): - # docutils does unfortunately not preserve the - # ``target`` attribute on images, so we need to check - # the parent node here. + if not any((key in node) for key in ['scale', 'width', 'height']): + # resizing options are not given. scaled image link is available + # only for resized images. + continue + elif isinstance(node.parent, nodes.reference): + # A image having hyperlink target continue + elif 'no-scaled-link' in node['classes']: + # scaled image link is disabled for this node + continue + uri = node['uri'] reference = nodes.reference('', '', internal=True) if uri in self.images: @@ -876,7 +851,11 @@ class StandaloneHTMLBuilder(Builder): if self.indexer is not None and title: filename = self.env.doc2path(pagename, base=None) try: - self.indexer.feed(pagename, filename, title, doctree) + metadata = self.env.metadata.get(pagename, {}) + if 'nosearch' in metadata: + self.indexer.feed(pagename, filename, '', new_document('')) + else: + self.indexer.feed(pagename, filename, title, doctree) except TypeError: # fallback for old search-adapters self.indexer.feed(pagename, title, doctree) # type: ignore @@ -1000,15 +979,6 @@ class StandaloneHTMLBuilder(Builder): return False ctx['hasdoc'] = hasdoc - def warn(*args: Any, **kwargs: Any) -> str: - """Simple warn() wrapper for themes.""" - warnings.warn('The template function warn() was deprecated. ' - 'Use warning() instead.', - RemovedInSphinx30Warning, stacklevel=2) - logger.warning(*args, **kwargs) - return '' # return empty string - ctx['warn'] = warn - ctx['toctree'] = lambda **kwargs: self._get_local_toctree(pagename, **kwargs) self.add_sidebars(pagename, ctx) ctx.update(addctx) diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index 6712ffc25..96e6d13f4 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -408,31 +408,31 @@ def patch_settings(settings: Any) -> Any: class Values(type(settings)): # type: ignore @property - def author(self): + def author(self) -> str: warnings.warn('settings.author is deprecated', RemovedInSphinx40Warning, stacklevel=2) return self._author @property - def title(self): + def title(self) -> str: warnings.warn('settings.title is deprecated', RemovedInSphinx40Warning, stacklevel=2) return self._title @property - def contentsname(self): + def contentsname(self) -> str: warnings.warn('settings.contentsname is deprecated', RemovedInSphinx40Warning, stacklevel=2) return self._contentsname @property - def docname(self): + def docname(self) -> str: warnings.warn('settings.docname is deprecated', RemovedInSphinx40Warning, stacklevel=2) return self._docname @property - def docclass(self): + def docclass(self) -> str: warnings.warn('settings.docclass is deprecated', RemovedInSphinx40Warning, stacklevel=2) return self._docclass diff --git a/sphinx/builders/latex/constants.py b/sphinx/builders/latex/constants.py index 39fbe0195..70fd0b1d1 100644 --- a/sphinx/builders/latex/constants.py +++ b/sphinx/builders/latex/constants.py @@ -184,6 +184,8 @@ ADDITIONAL_SETTINGS = { 'babel': '\\usepackage{babel}', }, ('xelatex', 'zh'): { + 'polyglossia': '', + 'babel': '\\usepackage{babel}', 'fontenc': '\\usepackage{xeCJK}', }, ('xelatex', 'el'): { diff --git a/sphinx/builders/latex/transforms.py b/sphinx/builders/latex/transforms.py index 3ef0ff5d9..28841ad77 100644 --- a/sphinx/builders/latex/transforms.py +++ b/sphinx/builders/latex/transforms.py @@ -591,7 +591,7 @@ class IndexInSectionTitleTransform(SphinxTransform): """ default_priority = 400 - def apply(self): + def apply(self, **kwargs: Any) -> None: for node in self.document.traverse(nodes.title): if isinstance(node.parent, nodes.section): for i, index in enumerate(node.traverse(addnodes.index)): diff --git a/sphinx/builders/latex/util.py b/sphinx/builders/latex/util.py index 8155d1fd7..b7d79121c 100644 --- a/sphinx/builders/latex/util.py +++ b/sphinx/builders/latex/util.py @@ -8,12 +8,8 @@ :license: BSD, see LICENSE for details. """ -import warnings - from docutils.writers.latex2e import Babel -from sphinx.deprecation import RemovedInSphinx30Warning - class ExtBabel(Babel): cyrillic_languages = ('bulgarian', 'kazakh', 'mongolian', 'russian', 'ukrainian') @@ -24,12 +20,6 @@ class ExtBabel(Babel): self.supported = True super().__init__(language_code or '') - def get_shorthandoff(self) -> str: - warnings.warn('ExtBabel.get_shorthandoff() is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - from sphinx.writers.latex import SHORTHANDOFF - return SHORTHANDOFF - def uses_cyrillic(self) -> bool: return self.language in self.cyrillic_languages diff --git a/sphinx/cmd/quickstart.py b/sphinx/cmd/quickstart.py index eb7e9eb02..8f8ae58a1 100644 --- a/sphinx/cmd/quickstart.py +++ b/sphinx/cmd/quickstart.py @@ -54,10 +54,8 @@ EXTENSIONS = OrderedDict([ ('imgmath', __('include math, rendered as PNG or SVG images')), ('mathjax', __('include math, rendered in the browser by MathJax')), ('ifconfig', __('conditional inclusion of content based on config values')), - ('viewcode', - __('include links to the source code of documented Python objects')), - ('githubpages', - __('create .nojekyll file to publish the document on GitHub pages')), + ('viewcode', __('include links to the source code of documented Python objects')), + ('githubpages', __('create .nojekyll file to publish the document on GitHub pages')), ]) DEFAULTS = { @@ -129,8 +127,7 @@ def boolean(x: str) -> bool: def suffix(x: str) -> str: if not (x[0:1] == '.' and len(x) > 1): - raise ValidationError(__("Please enter a file suffix, " - "e.g. '.rst' or '.txt'.")) + raise ValidationError(__("Please enter a file suffix, e.g. '.rst' or '.txt'.")) return x @@ -228,16 +225,16 @@ def ask_user(d: Dict) -> None: """ print(bold(__('Welcome to the Sphinx %s quickstart utility.')) % __display_version__) - print(__(''' -Please enter values for the following settings (just press Enter to -accept a default value, if one is given in brackets).''')) + print() + print(__('Please enter values for the following settings (just press Enter to\n' + 'accept a default value, if one is given in brackets).')) if 'path' in d: - print(bold(__(''' -Selected root path: %s''') % d['path'])) + print() + print(bold(__('Selected root path: %s')) % d['path']) else: - print(__(''' -Enter the root path for documentation.''')) + print() + print(__('Enter the root path for documentation.')) d['path'] = do_prompt(__('Root path for the documentation'), '.', is_path) while path.isfile(path.join(d['path'], 'conf.py')) or \ @@ -247,70 +244,68 @@ Enter the root path for documentation.''')) 'selected root path.'))) print(__('sphinx-quickstart will not overwrite existing Sphinx projects.')) print() - d['path'] = do_prompt(__('Please enter a new root path (or just Enter ' - 'to exit)'), '', is_path) + d['path'] = do_prompt(__('Please enter a new root path (or just Enter to exit)'), + '', is_path) if not d['path']: sys.exit(1) if 'sep' not in d: - print(__(''' -You have two options for placing the build directory for Sphinx output. -Either, you use a directory "_build" within the root path, or you separate -"source" and "build" directories within the root path.''')) - d['sep'] = do_prompt(__('Separate source and build directories (y/n)'), - 'n', boolean) + print() + print(__('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.')) + d['sep'] = do_prompt(__('Separate source and build directories (y/n)'), 'n', boolean) if 'dot' not in d: - print(__(''' -Inside the root directory, two more directories will be created; "_templates" -for custom HTML templates and "_static" for custom stylesheets and other static -files. You can enter another prefix (such as ".") to replace the underscore.''')) + print() + print(__('Inside the root directory, two more directories will be created; "_templates"\n' # NOQA + 'for custom HTML templates and "_static" for custom stylesheets and other static\n' # NOQA + 'files. You can enter another prefix (such as ".") to replace the underscore.')) # NOQA d['dot'] = do_prompt(__('Name prefix for templates and static dir'), '_', ok) if 'project' not in d: - print(__(''' -The project name will occur in several places in the built documentation.''')) + print() + print(__('The project name will occur in several places in the built documentation.')) d['project'] = do_prompt(__('Project name')) if 'author' not in d: d['author'] = do_prompt(__('Author name(s)')) if 'version' not in d: - print(__(''' -Sphinx has the notion of a "version" and a "release" for the -software. Each version can have multiple releases. For example, for -Python the version is something like 2.5 or 3.0, while the release is -something like 2.5.1 or 3.0a1. If you don't need this dual structure, -just set both to the same value.''')) + print() + print(__('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' + 'something like 2.5.1 or 3.0a1. If you don\'t need this dual structure,\n' + 'just set both to the same value.')) d['version'] = do_prompt(__('Project version'), '', allow_empty) if 'release' not in d: d['release'] = do_prompt(__('Project release'), d['version'], allow_empty) if 'language' not in d: - print(__(''' -If the documents are to be written in a language other than English, -you can select a language here by its language code. Sphinx will then -translate text that it generates into that language. - -For a list of supported codes, see -https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-language.''')) + print() + print(__('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.')) # NOQA d['language'] = do_prompt(__('Project language'), 'en') if d['language'] == 'en': d['language'] = None if 'suffix' not in d: - print(__(''' -The file name suffix for source files. Commonly, this is either ".txt" -or ".rst". Only files with this suffix are considered documents.''')) + print() + print(__('The file name suffix for source files. Commonly, this is either ".txt"\n' + 'or ".rst". Only files with this suffix are considered documents.')) d['suffix'] = do_prompt(__('Source file suffix'), '.rst', suffix) if 'master' not in d: - print(__(''' -One document is special in that it is considered the top node of the -"contents tree", that is, it is the root of the hierarchical structure -of the documents. Normally, this is "index", but if your "index" -document is a custom template, you can also set this to another filename.''')) - d['master'] = do_prompt(__('Name of your master document (without suffix)'), - 'index') + print() + print(__('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.')) + d['master'] = do_prompt(__('Name of your master document (without suffix)'), 'index') while path.isfile(path.join(d['path'], d['master'] + d['suffix'])) or \ path.isfile(path.join(d['path'], 'source', d['master'] + d['suffix'])): @@ -323,8 +318,7 @@ document is a custom template, you can also set this to another filename.''')) 'existing file and press Enter'), d['master']) if 'extensions' not in d: - print(__('Indicate which of the following Sphinx extensions should be ' - 'enabled:')) + print(__('Indicate which of the following Sphinx extensions should be enabled:')) d['extensions'] = [] for name, description in EXTENSIONS.items(): if do_prompt('%s: %s (y/n)' % (name, description), 'n', boolean): @@ -332,20 +326,19 @@ document is a custom template, you can also set this to another filename.''')) # Handle conflicting options if {'sphinx.ext.imgmath', 'sphinx.ext.mathjax'}.issubset(d['extensions']): - print(__('Note: imgmath and mathjax cannot be enabled at the same ' - 'time. imgmath has been deselected.')) + print(__('Note: imgmath and mathjax cannot be enabled at the same time. ' + 'imgmath has been deselected.')) d['extensions'].remove('sphinx.ext.imgmath') if 'makefile' not in d: - print(__(''' -A Makefile and a Windows command file can be generated for you so that you -only have to run e.g. `make html' instead of invoking sphinx-build -directly.''')) + print() + print(__('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.')) d['makefile'] = do_prompt(__('Create Makefile? (y/n)'), 'y', boolean) if 'batchfile' not in d: - d['batchfile'] = do_prompt(__('Create Windows command file? (y/n)'), - 'y', boolean) + d['batchfile'] = do_prompt(__('Create Windows command file? (y/n)'), 'y', boolean) print() @@ -428,17 +421,18 @@ def generate(d: Dict, overwrite: bool = True, silent: bool = False, templatedir: return print() print(bold(__('Finished: An initial directory structure has been created.'))) - print(__(''' -You should now populate your master file %s and create other documentation -source files. ''') % masterfile + ((d['makefile'] or d['batchfile']) and __('''\ -Use the Makefile to build the docs, like so: - make builder -''') or __('''\ -Use the sphinx-build command to build the docs, like so: - sphinx-build -b builder %s %s -''') % (srcdir, builddir)) + __('''\ -where "builder" is one of the supported builders, e.g. html, latex or linkcheck. -''')) + print() + print(__('You should now populate your master file %s and create other documentation\n' + 'source files. ') % masterfile, end='') + if d['makefile'] or d['batchfile']: + print(__('Use the Makefile to build the docs, like so:\n' + ' make builder')) + else: + print(__('Use the sphinx-build command to build the docs, like so:\n' + ' sphinx-build -b builder %s %s') % (srcdir, builddir)) + print(__('where "builder" is one of the supported builders, ' + 'e.g. html, latex or linkcheck.')) + print() def valid_dir(d: Dict) -> bool: @@ -471,16 +465,18 @@ def valid_dir(d: Dict) -> bool: def get_parser() -> argparse.ArgumentParser: + description = __( + "\n" + "Generate required files for a Sphinx project.\n" + "\n" + "sphinx-quickstart is an interactive tool that asks some questions about your\n" + "project and then generates a complete documentation directory and sample\n" + "Makefile to be used with sphinx-build.\n" + ) parser = argparse.ArgumentParser( usage='%(prog)s [OPTIONS] <PROJECT_DIR>', epilog=__("For more information, visit <http://sphinx-doc.org/>."), - description=__(""" -Generate required files for a Sphinx project. - -sphinx-quickstart is an interactive tool that asks some questions about your -project and then generates a complete documentation directory and sample -Makefile to be used with sphinx-build. -""")) + description=description) parser.add_argument('-q', '--quiet', action='store_true', dest='quiet', default=None, @@ -579,8 +575,8 @@ def main(argv: List[str] = sys.argv[1:]) -> int: try: if 'quiet' in d: if not {'project', 'author'}.issubset(d): - print(__('''"quiet" is specified, but any of "project" or \ -"author" is not specified.''')) + print(__('"quiet" is specified, but any of "project" or ' + '"author" is not specified.')) return 1 if {'quiet', 'project', 'author'}.issubset(d): diff --git a/sphinx/cmdline.py b/sphinx/cmdline.py deleted file mode 100644 index f938f8234..000000000 --- a/sphinx/cmdline.py +++ /dev/null @@ -1,43 +0,0 @@ -""" - sphinx.cmdline - ~~~~~~~~~~~~~~ - - sphinx-build command-line handling. - - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" - -import argparse -import sys -import warnings -from typing import Any, IO, List, Union - -from sphinx.application import Sphinx -from sphinx.cmd import build -from sphinx.deprecation import RemovedInSphinx30Warning - - -def handle_exception(app: Sphinx, args: Any, exception: Union[Exception, KeyboardInterrupt], - stderr: IO = sys.stderr) -> None: - warnings.warn('sphinx.cmdline module is deprecated. Use sphinx.cmd.build instead.', - RemovedInSphinx30Warning, stacklevel=2) - build.handle_exception(app, args, exception, stderr) - - -def jobs_argument(value: str) -> int: - warnings.warn('sphinx.cmdline module is deprecated. Use sphinx.cmd.build instead.', - RemovedInSphinx30Warning, stacklevel=2) - return build.jobs_argument(value) - - -def get_parser() -> argparse.ArgumentParser: - warnings.warn('sphinx.cmdline module is deprecated. Use sphinx.cmd.build instead.', - RemovedInSphinx30Warning, stacklevel=2) - return build.get_parser() - - -def main(argv: List[str] = sys.argv[1:]) -> int: - warnings.warn('sphinx.cmdline module is deprecated. Use sphinx.cmd.build instead.', - RemovedInSphinx30Warning, stacklevel=2) - return build.main(argv) diff --git a/sphinx/config.py b/sphinx/config.py index bba994389..87007c33d 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -18,7 +18,7 @@ from typing import ( Any, Callable, Dict, Generator, Iterator, List, NamedTuple, Set, Tuple, Union ) -from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning +from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.errors import ConfigError, ExtensionError from sphinx.locale import _, __ from sphinx.util import logging @@ -154,26 +154,7 @@ class Config: 'env', []), } # type: Dict[str, Tuple] - def __init__(self, *args: Any) -> None: - if len(args) == 4: - # old style arguments: (dirname, filename, overrides, tags) - warnings.warn('The argument of Config() class has been changed. ' - 'Use Config.read() to read configuration from conf.py.', - RemovedInSphinx30Warning, stacklevel=2) - dirname, filename, overrides, tags = args - if dirname is None: - config = {} # type: Dict[str, Any] - else: - config = eval_config_file(path.join(dirname, filename), tags) - else: - # new style arguments: (config={}, overrides={}) - if len(args) == 0: - config, overrides = {}, {} - elif len(args) == 1: - config, overrides = args[0], {} - else: - config, overrides = args[:2] - + def __init__(self, config: Dict[str, Any] = {}, overrides: Dict[str, Any] = {}) -> None: self.overrides = dict(overrides) self.values = Config.config_values.copy() self._raw_config = config @@ -193,16 +174,6 @@ class Config: namespace = eval_config_file(filename, tags) return cls(namespace, overrides or {}) - def check_types(self) -> None: - warnings.warn('Config.check_types() is deprecated. Use check_confval_types() instead.', - RemovedInSphinx30Warning, stacklevel=2) - check_confval_types(None, self) - - def check_unicode(self) -> None: - warnings.warn('Config.check_unicode() is deprecated. Use check_unicode() instead.', - RemovedInSphinx30Warning, stacklevel=2) - check_unicode(self) - def convert_overrides(self, name: str, value: Any) -> Any: if not isinstance(value, str): return value @@ -353,6 +324,9 @@ def eval_config_file(filename: str, tags: Tags) -> Dict[str, Any]: msg = __("The configuration file (or one of the modules it imports) " "called sys.exit()") raise ConfigError(msg) + except ConfigError: + # pass through ConfigError from conf.py as is. It will be shown in console. + raise except Exception: msg = __("There is a programmable error in your configuration file:\n\n%s") raise ConfigError(msg % traceback.format_exc()) diff --git a/sphinx/deprecation.py b/sphinx/deprecation.py index dec5efa85..5e5e673d2 100644 --- a/sphinx/deprecation.py +++ b/sphinx/deprecation.py @@ -15,15 +15,15 @@ from typing import Any, Dict from typing import Type # for python3.5.1 -class RemovedInSphinx30Warning(DeprecationWarning): +class RemovedInSphinx40Warning(DeprecationWarning): pass -class RemovedInSphinx40Warning(PendingDeprecationWarning): +class RemovedInSphinx50Warning(PendingDeprecationWarning): pass -RemovedInNextVersionWarning = RemovedInSphinx30Warning +RemovedInNextVersionWarning = RemovedInSphinx40Warning def deprecated_alias(modname: str, objects: Dict, warning: Type[Warning]) -> None: diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py index 9a2fb4412..0d9490f31 100644 --- a/sphinx/directives/__init__.py +++ b/sphinx/directives/__init__.py @@ -18,7 +18,9 @@ from docutils.parsers.rst import directives, roles from sphinx import addnodes from sphinx.addnodes import desc_signature -from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias +from sphinx.deprecation import ( + RemovedInSphinx40Warning, RemovedInSphinx50Warning, deprecated_alias +) from sphinx.util import docutils from sphinx.util.docfields import DocFieldTransformer, Field, TypedField from sphinx.util.docutils import SphinxDirective @@ -34,7 +36,7 @@ nl_escape_re = re.compile(r'\\\n') strip_backslash_re = re.compile(r'\\(.)') -def optional_int(argument): +def optional_int(argument: str) -> int: """ Check for an integer argument or None value; raise ``ValueError`` if not. """ @@ -160,6 +162,8 @@ class ObjectDescription(SphinxDirective): # 'desctype' is a backwards compatible attribute node['objtype'] = node['desctype'] = self.objtype node['noindex'] = noindex = ('noindex' in self.options) + if self.domain: + node['classes'].append(self.domain) self.names = [] # type: List[Any] signatures = self.get_signatures() @@ -167,7 +171,7 @@ class ObjectDescription(SphinxDirective): # add a signature node for each signature in the current unit # and add a reference target for it signode = addnodes.desc_signature(sig, '') - signode['first'] = False + self.set_source_info(signode) node.append(signode) try: # name can also be a tuple, e.g. (classname, objname); @@ -285,9 +289,11 @@ deprecated_alias('sphinx.directives', }, RemovedInSphinx40Warning) - -# backwards compatible old name (will be marked deprecated in 3.0) -DescDirective = ObjectDescription +deprecated_alias('sphinx.directives', + { + 'DescDirective': ObjectDescription, + }, + RemovedInSphinx50Warning) def setup(app: "Sphinx") -> Dict[str, Any]: diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index 87d769b41..e4fcc0f5c 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -85,14 +85,14 @@ class TocTree(SphinxDirective): ret.append(wrappernode) return ret - def parse_content(self, toctree): + def parse_content(self, toctree: addnodes.toctree) -> List[Node]: suffixes = self.config.source_suffix # glob target documents all_docnames = self.env.found_docs.copy() all_docnames.remove(self.env.docname) # remove current document - ret = [] + ret = [] # type: List[Node] excluded = Matcher(self.config.exclude_patterns) for entry in self.content: if not entry: diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py index b521cd025..11b3a4604 100644 --- a/sphinx/domains/__init__.py +++ b/sphinx/domains/__init__.py @@ -11,6 +11,7 @@ import copy from typing import Any, Callable, Dict, Iterable, List, NamedTuple, Tuple, Union +from typing import cast from docutils import nodes from docutils.nodes import Element, Node, system_message @@ -70,6 +71,9 @@ class Index: a domain, subclass Index, overriding the three name attributes: * `name` is an identifier used for generating file names. + It is also used for a hyperlink target for the index. Therefore, users can + refer the index page using ``ref`` role and a string which is combined + domain name and ``name`` attribute (ex. ``:ref:`py-modindex```). * `localname` is the section title for the index. * `shortname` is a short name for the index, for use in the relation bar in HTML output. Can be empty to disable entries in the relation bar. @@ -77,6 +81,11 @@ class Index: and providing a :meth:`generate()` method. Then, add the index class to your domain's `indices` list. Extensions can add indices to existing domains using :meth:`~sphinx.application.Sphinx.add_index_to_domain()`. + + .. versionchanged:: 3.0 + + Index pages can be referred by domain name and index name via + :rst:role:`ref` role. """ name = None # type: str @@ -219,6 +228,17 @@ class Domain: self.objtypes_for_role = self._role2type.get # type: Callable[[str], List[str]] self.role_for_objtype = self._type2role.get # type: Callable[[str], str] + def setup(self) -> None: + """Set up domain object.""" + from sphinx.domains.std import StandardDomain + + # Add special hyperlink target for index pages (ex. py-modindex) + std = cast(StandardDomain, self.env.get_domain('std')) + for index in self.indices: + if index.name and index.localname: + docname = "%s-%s" % (self.name, index.name) + std.note_hyperlink_target(docname, docname, '', index.localname) + def add_object_type(self, name: str, objtype: ObjType) -> None: """Add an object type.""" self.object_types[name] = objtype diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index cc24df47e..539a52e59 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -203,7 +203,6 @@ class CObject(ObjectDescription): if targetname not in self.state.document.ids: signode['names'].append(targetname) signode['ids'].append(targetname) - signode['first'] = (not self.names) self.state.document.note_explicit_target(signode) domain = cast(CDomain, self.env.get_domain('c')) diff --git a/sphinx/domains/changeset.py b/sphinx/domains/changeset.py index f3f1d8aef..a07944db8 100644 --- a/sphinx/domains/changeset.py +++ b/sphinx/domains/changeset.py @@ -16,8 +16,6 @@ from docutils import nodes from docutils.nodes import Node from sphinx import addnodes -from sphinx import locale -from sphinx.deprecation import DeprecatedDict, RemovedInSphinx30Warning from sphinx.domains import Domain from sphinx.locale import _ from sphinx.util.docutils import SphinxDirective @@ -41,13 +39,6 @@ versionlabel_classes = { 'deprecated': 'deprecated', } -locale.versionlabels = DeprecatedDict( - versionlabels, - 'sphinx.locale.versionlabels is deprecated. ' - 'Please use sphinx.domains.changeset.versionlabels instead.', - RemovedInSphinx30Warning -) - # TODO: move to typing.NamedTuple after dropping py35 support (see #5958) ChangeSet = namedtuple('ChangeSet', diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 53c958601..3a23ff15a 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -367,6 +367,8 @@ _keywords = [ _max_id = 4 _id_prefix = [None, '', '_CPPv2', '_CPPv3', '_CPPv4'] +# Ids are used in lookup keys which are used across pickled files, +# so when _max_id changes, make sure to update the ENV_VERSION. # ------------------------------------------------------------------------------ # Id v1 constants @@ -1790,7 +1792,8 @@ class ASTTemplateIntroduction(ASTBase): class ASTTemplateDeclarationPrefix(ASTBase): - def __init__(self, templates: List[Any]) -> None: + def __init__(self, templates: List[Union[ASTTemplateParams, ASTTemplateIntroduction]])\ + -> None: # templates is None means it's an explicit instantiation of a variable self.templates = templates @@ -3547,10 +3550,25 @@ class SymbolLookupResult: self.templateArgs = templateArgs +class LookupKey: + def __init__(self, data: List[Tuple[ASTNestedNameElement, + Union[ASTTemplateParams, + ASTTemplateIntroduction], + str]]) -> None: + self.data = data + + class Symbol: + debug_indent = 0 + debug_indent_string = " " debug_lookup = False debug_show_tree = False + @staticmethod + def debug_print(*args): + print(Symbol.debug_indent_string * Symbol.debug_indent, end="") + print(*args) + def _assert_invariants(self) -> None: if not self.parent: # parent == None means global scope, so declaration means a parent @@ -3570,9 +3588,12 @@ class Symbol: return super().__setattr__(key, value) def __init__(self, parent: "Symbol", identOrOp: Union[ASTIdentifier, ASTOperator], - templateParams: Any, templateArgs: Any, declaration: ASTDeclaration, - docname: str) -> None: + templateParams: Union[ASTTemplateParams, ASTTemplateIntroduction], + templateArgs: Any, declaration: ASTDeclaration, docname: str) -> None: self.parent = parent + # declarations in a single directive are linked together + self.siblingAbove = None # type: Symbol + self.siblingBelow = None # type: Symbol self.identOrOp = identOrOp self.templateParams = templateParams # template<templateParams> self.templateArgs = templateArgs # identifier<templateArgs> @@ -3607,6 +3628,9 @@ class Symbol: self._add_template_and_function_params() def _add_template_and_function_params(self): + if Symbol.debug_lookup: + Symbol.debug_indent += 1 + Symbol.debug_print("_add_template_and_function_params:") # Note: we may be called from _fill_empty, so the symbols we want # to add may actually already be present (as empty symbols). @@ -3636,6 +3660,8 @@ class Symbol: assert not nn.rooted assert len(nn.names) == 1 self._add_symbols(nn, [], decl, self.docname) + if Symbol.debug_lookup: + Symbol.debug_indent -= 1 def remove(self): if self.parent is None: @@ -3645,12 +3671,18 @@ class Symbol: self.parent = None def clear_doc(self, docname: str) -> None: - newChildren = [] + newChildren = [] # type: List[Symbol] for sChild in self._children: sChild.clear_doc(docname) if sChild.declaration and sChild.docname == docname: sChild.declaration = None sChild.docname = None + if sChild.siblingAbove is not None: + sChild.siblingAbove.siblingBelow = sChild.siblingBelow + if sChild.siblingBelow is not None: + sChild.siblingBelow.siblingAbove = sChild.siblingAbove + sChild.siblingAbove = None + sChild.siblingBelow = None newChildren.append(sChild) self._children = newChildren @@ -3669,7 +3701,11 @@ class Symbol: yield from c.children_recurse_anon - def get_lookup_key(self) -> List[Tuple[ASTNestedNameElement, Any]]: + def get_lookup_key(self) -> "LookupKey": + # The pickle files for the environment and for each document are distinct. + # The environment has all the symbols, but the documents has xrefs that + # must know their scope. A lookup key is essentially a specification of + # how to find a specific symbol. symbols = [] s = self while s.parent: @@ -3679,14 +3715,23 @@ class Symbol: key = [] for s in symbols: nne = ASTNestedNameElement(s.identOrOp, s.templateArgs) - key.append((nne, s.templateParams)) - return key + if s.declaration is not None: + key.append((nne, s.templateParams, s.declaration.get_newest_id())) + else: + key.append((nne, s.templateParams, None)) + return LookupKey(key) def get_full_nested_name(self) -> ASTNestedName: + symbols = [] + s = self + while s.parent: + symbols.append(s) + s = s.parent + symbols.reverse() names = [] templates = [] - for nne, templateParams in self.get_lookup_key(): - names.append(nne) + for s in symbols: + names.append(ASTNestedNameElement(s.identOrOp, s.templateArgs)) templates.append(False) return ASTNestedName(names, templates, rooted=False) @@ -3695,9 +3740,12 @@ class Symbol: templateShorthand: bool, matchSelf: bool, recurseInAnon: bool, correctPrimaryTemplateArgs: bool ) -> "Symbol": + if Symbol.debug_lookup: + Symbol.debug_print("_find_first_named_symbol ->") res = self._find_named_symbols(identOrOp, templateParams, templateArgs, templateShorthand, matchSelf, recurseInAnon, - correctPrimaryTemplateArgs) + correctPrimaryTemplateArgs, + searchInSiblings=False) try: return next(res) except StopIteration: @@ -3706,8 +3754,22 @@ class Symbol: def _find_named_symbols(self, identOrOp: Union[ASTIdentifier, ASTOperator], templateParams: Any, templateArgs: ASTTemplateArgs, templateShorthand: bool, matchSelf: bool, - recurseInAnon: bool, correctPrimaryTemplateArgs: bool - ) -> Iterator["Symbol"]: + recurseInAnon: bool, correctPrimaryTemplateArgs: bool, + searchInSiblings: bool) -> Iterator["Symbol"]: + if Symbol.debug_lookup: + Symbol.debug_indent += 1 + Symbol.debug_print("_find_named_symbols:") + Symbol.debug_indent += 1 + Symbol.debug_print("self:") + print(self.to_string(Symbol.debug_indent + 1), end="") + Symbol.debug_print("identOrOp: ", identOrOp) + Symbol.debug_print("templateParams: ", templateParams) + Symbol.debug_print("templateArgs: ", templateArgs) + Symbol.debug_print("templateShorthand: ", templateShorthand) + Symbol.debug_print("matchSelf: ", matchSelf) + Symbol.debug_print("recurseInAnon: ", recurseInAnon) + Symbol.debug_print("correctPrimaryTemplateAargs:", correctPrimaryTemplateArgs) + Symbol.debug_print("searchInSiblings: ", searchInSiblings) def isSpecialization(): # the names of the template parameters must be given exactly as args @@ -3759,20 +3821,64 @@ class Symbol: if str(s.templateArgs) != str(templateArgs): return False return True - if matchSelf and matches(self): - yield self - children = self.children_recurse_anon if recurseInAnon else self._children - for s in children: + + def candidates(): + s = self + if Symbol.debug_lookup: + Symbol.debug_print("searching in self:") + print(s.to_string(Symbol.debug_indent + 1), end="") + while True: + if matchSelf: + yield s + if recurseInAnon: + yield from s.children_recurse_anon + else: + yield from s._children + + if s.siblingAbove is None: + break + s = s.siblingAbove + if Symbol.debug_lookup: + Symbol.debug_print("searching in sibling:") + print(s.to_string(Symbol.debug_indent + 1), end="") + + for s in candidates(): + if Symbol.debug_lookup: + Symbol.debug_print("candidate:") + print(s.to_string(Symbol.debug_indent + 1), end="") if matches(s): + if Symbol.debug_lookup: + Symbol.debug_indent += 1 + Symbol.debug_print("matches") + Symbol.debug_indent -= 3 yield s + if Symbol.debug_lookup: + Symbol.debug_indent += 2 + if Symbol.debug_lookup: + Symbol.debug_indent -= 2 def _symbol_lookup(self, nestedName: ASTNestedName, templateDecls: List[Any], onMissingQualifiedSymbol: Callable[["Symbol", Union[ASTIdentifier, ASTOperator], Any, ASTTemplateArgs], "Symbol"], # NOQA strictTemplateParamArgLists: bool, ancestorLookupType: str, templateShorthand: bool, matchSelf: bool, - recurseInAnon: bool, correctPrimaryTemplateArgs: bool - ) -> SymbolLookupResult: + recurseInAnon: bool, correctPrimaryTemplateArgs: bool, + searchInSiblings: bool) -> SymbolLookupResult: # ancestorLookupType: if not None, specifies the target type of the lookup + if Symbol.debug_lookup: + Symbol.debug_indent += 1 + Symbol.debug_print("_symbol_lookup:") + Symbol.debug_indent += 1 + Symbol.debug_print("self:") + print(self.to_string(Symbol.debug_indent + 1), end="") + Symbol.debug_print("nestedName: ", nestedName) + Symbol.debug_print("templateDecls: ", templateDecls) + Symbol.debug_print("strictTemplateParamArgLists:", strictTemplateParamArgLists) + Symbol.debug_print("ancestorLookupType:", ancestorLookupType) + Symbol.debug_print("templateShorthand: ", templateShorthand) + Symbol.debug_print("matchSelf: ", matchSelf) + Symbol.debug_print("recurseInAnon: ", recurseInAnon) + Symbol.debug_print("correctPrimaryTemplateArgs: ", correctPrimaryTemplateArgs) + Symbol.debug_print("searchInSiblings: ", searchInSiblings) if strictTemplateParamArgLists: # Each template argument list must have a template parameter list. @@ -3796,7 +3902,8 @@ class Symbol: while parentSymbol.parent: if parentSymbol.find_identifier(firstName.identOrOp, matchSelf=matchSelf, - recurseInAnon=recurseInAnon): + recurseInAnon=recurseInAnon, + searchInSiblings=searchInSiblings): # if we are in the scope of a constructor but wants to # reference the class we need to walk one extra up if (len(names) == 1 and ancestorLookupType == 'class' and matchSelf and @@ -3807,6 +3914,10 @@ class Symbol: break parentSymbol = parentSymbol.parent + if Symbol.debug_lookup: + Symbol.debug_print("starting point:") + print(parentSymbol.to_string(Symbol.debug_indent + 1), end="") + # and now the actual lookup iTemplateDecl = 0 for name in names[:-1]: @@ -3840,6 +3951,8 @@ class Symbol: symbol = onMissingQualifiedSymbol(parentSymbol, identOrOp, templateParams, templateArgs) if symbol is None: + if Symbol.debug_lookup: + Symbol.debug_indent -= 2 return None # We have now matched part of a nested name, and need to match more # so even if we should matchSelf before, we definitely shouldn't @@ -3847,6 +3960,10 @@ class Symbol: matchSelf = False parentSymbol = symbol + if Symbol.debug_lookup: + Symbol.debug_print("handle last name from:") + print(parentSymbol.to_string(Symbol.debug_indent + 1), end="") + # handle the last name name = names[-1] identOrOp = name.identOrOp @@ -3861,7 +3978,11 @@ class Symbol: symbols = parentSymbol._find_named_symbols( identOrOp, templateParams, templateArgs, templateShorthand=templateShorthand, matchSelf=matchSelf, - recurseInAnon=recurseInAnon, correctPrimaryTemplateArgs=False) + recurseInAnon=recurseInAnon, correctPrimaryTemplateArgs=False, + searchInSiblings=searchInSiblings) + if Symbol.debug_lookup: + symbols = list(symbols) # type: ignore + Symbol.debug_indent -= 2 return SymbolLookupResult(symbols, parentSymbol, identOrOp, templateParams, templateArgs) @@ -3871,21 +3992,26 @@ class Symbol: # be an actual declaration. if Symbol.debug_lookup: - print("_add_symbols:") - print(" tdecls:", templateDecls) - print(" nn: ", nestedName) - print(" decl: ", declaration) - print(" doc: ", docname) + Symbol.debug_indent += 1 + Symbol.debug_print("_add_symbols:") + Symbol.debug_indent += 1 + Symbol.debug_print("tdecls:", templateDecls) + Symbol.debug_print("nn: ", nestedName) + Symbol.debug_print("decl: ", declaration) + Symbol.debug_print("doc: ", docname) def onMissingQualifiedSymbol(parentSymbol: "Symbol", identOrOp: Union[ASTIdentifier, ASTOperator], templateParams: Any, templateArgs: ASTTemplateArgs ) -> "Symbol": if Symbol.debug_lookup: - print(" _add_symbols, onMissingQualifiedSymbol:") - print(" templateParams:", templateParams) - print(" identOrOp: ", identOrOp) - print(" templateARgs: ", templateArgs) + Symbol.debug_indent += 1 + Symbol.debug_print("_add_symbols, onMissingQualifiedSymbol:") + Symbol.debug_indent += 1 + Symbol.debug_print("templateParams:", templateParams) + Symbol.debug_print("identOrOp: ", identOrOp) + Symbol.debug_print("templateARgs: ", templateArgs) + Symbol.debug_indent -= 2 return Symbol(parent=parentSymbol, identOrOp=identOrOp, templateParams=templateParams, templateArgs=templateArgs, declaration=None, @@ -3898,32 +4024,40 @@ class Symbol: templateShorthand=False, matchSelf=False, recurseInAnon=True, - correctPrimaryTemplateArgs=True) + correctPrimaryTemplateArgs=True, + searchInSiblings=False) assert lookupResult is not None # we create symbols all the way, so that can't happen symbols = list(lookupResult.symbols) if len(symbols) == 0: if Symbol.debug_lookup: - print(" _add_symbols, result, no symbol:") - print(" templateParams:", lookupResult.templateParams) - print(" identOrOp: ", lookupResult.identOrOp) - print(" templateArgs: ", lookupResult.templateArgs) - print(" declaration: ", declaration) - print(" docname: ", docname) + Symbol.debug_print("_add_symbols, result, no symbol:") + Symbol.debug_indent += 1 + Symbol.debug_print("templateParams:", lookupResult.templateParams) + Symbol.debug_print("identOrOp: ", lookupResult.identOrOp) + Symbol.debug_print("templateArgs: ", lookupResult.templateArgs) + Symbol.debug_print("declaration: ", declaration) + Symbol.debug_print("docname: ", docname) + Symbol.debug_indent -= 1 symbol = Symbol(parent=lookupResult.parentSymbol, identOrOp=lookupResult.identOrOp, templateParams=lookupResult.templateParams, templateArgs=lookupResult.templateArgs, declaration=declaration, docname=docname) + if Symbol.debug_lookup: + Symbol.debug_indent -= 2 return symbol if Symbol.debug_lookup: - print(" _add_symbols, result, symbols:") - print(" number symbols:", len(symbols)) + Symbol.debug_print("_add_symbols, result, symbols:") + Symbol.debug_indent += 1 + Symbol.debug_print("number symbols:", len(symbols)) + Symbol.debug_indent -= 1 if not declaration: if Symbol.debug_lookup: - print(" no delcaration") + Symbol.debug_print("no delcaration") + Symbol.debug_indent -= 2 # good, just a scope creation # TODO: what if we have more than one symbol? return symbols[0] @@ -3939,9 +4073,9 @@ class Symbol: else: withDecl.append(s) if Symbol.debug_lookup: - print(" #noDecl: ", len(noDecl)) - print(" #withDecl:", len(withDecl)) - print(" #dupDecl: ", len(dupDecl)) + Symbol.debug_print("#noDecl: ", len(noDecl)) + Symbol.debug_print("#withDecl:", len(withDecl)) + Symbol.debug_print("#dupDecl: ", len(dupDecl)) # With partial builds we may start with a large symbol tree stripped of declarations. # Essentially any combination of noDecl, withDecl, and dupDecls seems possible. # TODO: make partial builds fully work. What should happen when the primary symbol gets @@ -3952,7 +4086,7 @@ class Symbol: # otherwise there should be only one symbol with a declaration. def makeCandSymbol(): if Symbol.debug_lookup: - print(" begin: creating candidate symbol") + Symbol.debug_print("begin: creating candidate symbol") symbol = Symbol(parent=lookupResult.parentSymbol, identOrOp=lookupResult.identOrOp, templateParams=lookupResult.templateParams, @@ -3960,7 +4094,7 @@ class Symbol: declaration=declaration, docname=docname) if Symbol.debug_lookup: - print(" end: creating candidate symbol") + Symbol.debug_print("end: creating candidate symbol") return symbol if len(withDecl) == 0: candSymbol = None @@ -3969,7 +4103,10 @@ class Symbol: def handleDuplicateDeclaration(symbol, candSymbol): if Symbol.debug_lookup: - print(" redeclaration") + Symbol.debug_indent += 1 + Symbol.debug_print("redeclaration") + Symbol.debug_indent -= 1 + Symbol.debug_indent -= 2 # Redeclaration of the same symbol. # Let the new one be there, but raise an error to the client # so it can use the real symbol as subscope. @@ -3985,11 +4122,11 @@ class Symbol: # a function, so compare IDs candId = declaration.get_newest_id() if Symbol.debug_lookup: - print(" candId:", candId) + Symbol.debug_print("candId:", candId) for symbol in withDecl: oldId = symbol.declaration.get_newest_id() if Symbol.debug_lookup: - print(" oldId: ", oldId) + Symbol.debug_print("oldId: ", oldId) if candId == oldId: handleDuplicateDeclaration(symbol, candSymbol) # (not reachable) @@ -3997,14 +4134,16 @@ class Symbol: # if there is an empty symbol, fill that one if len(noDecl) == 0: if Symbol.debug_lookup: - print(" no match, no empty, candSybmol is not None?:", candSymbol is not None) # NOQA + Symbol.debug_print("no match, no empty, candSybmol is not None?:", candSymbol is not None) # NOQA + Symbol.debug_indent -= 2 if candSymbol is not None: return candSymbol else: return makeCandSymbol() else: if Symbol.debug_lookup: - print(" no match, but fill an empty declaration, candSybmol is not None?:", candSymbol is not None) # NOQA + Symbol.debug_print("no match, but fill an empty declaration, candSybmol is not None?:", candSymbol is not None) # NOQA + Symbol.debug_indent -= 2 if candSymbol is not None: candSymbol.remove() # assert len(noDecl) == 1 @@ -4021,6 +4160,9 @@ class Symbol: def merge_with(self, other: "Symbol", docnames: List[str], env: "BuildEnvironment") -> None: + if Symbol.debug_lookup: + Symbol.debug_indent += 1 + Symbol.debug_print("merge_with:") assert other is not None for otherChild in other._children: ourChild = self._find_first_named_symbol( @@ -4050,17 +4192,28 @@ class Symbol: # just ignore it, right? pass ourChild.merge_with(otherChild, docnames, env) + if Symbol.debug_lookup: + Symbol.debug_indent -= 1 def add_name(self, nestedName: ASTNestedName, templatePrefix: ASTTemplateDeclarationPrefix = None) -> "Symbol": + if Symbol.debug_lookup: + Symbol.debug_indent += 1 + Symbol.debug_print("add_name:") if templatePrefix: templateDecls = templatePrefix.templates else: templateDecls = [] - return self._add_symbols(nestedName, templateDecls, - declaration=None, docname=None) + res = self._add_symbols(nestedName, templateDecls, + declaration=None, docname=None) + if Symbol.debug_lookup: + Symbol.debug_indent -= 1 + return res def add_declaration(self, declaration: ASTDeclaration, docname: str) -> "Symbol": + if Symbol.debug_lookup: + Symbol.debug_indent += 1 + Symbol.debug_print("add_declaration:") assert declaration assert docname nestedName = declaration.name @@ -4068,37 +4221,105 @@ class Symbol: templateDecls = declaration.templatePrefix.templates else: templateDecls = [] - return self._add_symbols(nestedName, templateDecls, declaration, docname) + res = self._add_symbols(nestedName, templateDecls, declaration, docname) + if Symbol.debug_lookup: + Symbol.debug_indent -= 1 + return res def find_identifier(self, identOrOp: Union[ASTIdentifier, ASTOperator], - matchSelf: bool, recurseInAnon: bool) -> "Symbol": - if matchSelf and self.identOrOp == identOrOp: - return self - children = self.children_recurse_anon if recurseInAnon else self._children - for s in children: - if s.identOrOp == identOrOp: - return s + matchSelf: bool, recurseInAnon: bool, searchInSiblings: bool + ) -> "Symbol": + if Symbol.debug_lookup: + Symbol.debug_indent += 1 + Symbol.debug_print("find_identifier:") + Symbol.debug_indent += 1 + Symbol.debug_print("identOrOp: ", identOrOp) + Symbol.debug_print("matchSelf: ", matchSelf) + Symbol.debug_print("recurseInAnon: ", recurseInAnon) + Symbol.debug_print("searchInSiblings:", searchInSiblings) + print(self.to_string(Symbol.debug_indent + 1), end="") + Symbol.debug_indent -= 2 + current = self + while current is not None: + if Symbol.debug_lookup: + Symbol.debug_indent += 2 + Symbol.debug_print("trying:") + print(current.to_string(Symbol.debug_indent + 1), end="") + Symbol.debug_indent -= 2 + if matchSelf and current.identOrOp == identOrOp: + return current + children = current.children_recurse_anon if recurseInAnon else current._children + for s in children: + if s.identOrOp == identOrOp: + return s + if not searchInSiblings: + break + current = current.siblingAbove return None - def direct_lookup(self, key: List[Tuple[ASTNestedNameElement, Any]]) -> "Symbol": + def direct_lookup(self, key: "LookupKey") -> "Symbol": + if Symbol.debug_lookup: + Symbol.debug_indent += 1 + Symbol.debug_print("direct_lookup:") + Symbol.debug_indent += 1 s = self - for name, templateParams in key: - identOrOp = name.identOrOp - templateArgs = name.templateArgs - s = s._find_first_named_symbol(identOrOp, - templateParams, templateArgs, - templateShorthand=False, - matchSelf=False, - recurseInAnon=False, - correctPrimaryTemplateArgs=False) - if not s: + for name, templateParams, id_ in key.data: + if id_ is not None: + res = None + for cand in s._children: + if cand.declaration is None: + continue + if cand.declaration.get_newest_id() == id_: + res = cand + break + s = res + else: + identOrOp = name.identOrOp + templateArgs = name.templateArgs + s = s._find_first_named_symbol(identOrOp, + templateParams, templateArgs, + templateShorthand=False, + matchSelf=False, + recurseInAnon=False, + correctPrimaryTemplateArgs=False) + if Symbol.debug_lookup: + Symbol.debug_print("name: ", name) + Symbol.debug_print("templateParams:", templateParams) + Symbol.debug_print("id: ", id_) + if s is not None: + print(s.to_string(Symbol.debug_indent + 1), end="") + else: + Symbol.debug_print("not found") + if s is None: + if Symbol.debug_lookup: + Symbol.debug_indent -= 2 return None + if Symbol.debug_lookup: + Symbol.debug_indent -= 2 return s def find_name(self, nestedName: ASTNestedName, templateDecls: List[Any], typ: str, templateShorthand: bool, matchSelf: bool, - recurseInAnon: bool) -> List["Symbol"]: + recurseInAnon: bool, searchInSiblings: bool) -> Tuple[List["Symbol"], str]: # templateShorthand: missing template parameter lists for templates is ok + # If the first component is None, + # then the second component _may_ be a string explaining why. + if Symbol.debug_lookup: + Symbol.debug_indent += 1 + Symbol.debug_print("find_name:") + Symbol.debug_indent += 1 + Symbol.debug_print("self:") + print(self.to_string(Symbol.debug_indent + 1), end="") + Symbol.debug_print("nestedName: ", nestedName) + Symbol.debug_print("templateDecls: ", templateDecls) + Symbol.debug_print("typ: ", typ) + Symbol.debug_print("templateShorthand:", templateShorthand) + Symbol.debug_print("matchSelf: ", matchSelf) + Symbol.debug_print("recurseInAnon: ", recurseInAnon) + Symbol.debug_print("searchInSiblings: ", searchInSiblings) + + class QualifiedSymbolIsTemplateParam(Exception): + pass def onMissingQualifiedSymbol(parentSymbol: "Symbol", identOrOp: Union[ASTIdentifier, ASTOperator], @@ -4108,37 +4329,58 @@ class Symbol: # Though, the correctPrimaryTemplateArgs does # that for primary templates. # Is there another case where it would be good? + if parentSymbol.declaration is not None: + if parentSymbol.declaration.objectType == 'templateParam': + raise QualifiedSymbolIsTemplateParam() return None - lookupResult = self._symbol_lookup(nestedName, templateDecls, - onMissingQualifiedSymbol, - strictTemplateParamArgLists=False, - ancestorLookupType=typ, - templateShorthand=templateShorthand, - matchSelf=matchSelf, - recurseInAnon=recurseInAnon, - correctPrimaryTemplateArgs=False) + try: + lookupResult = self._symbol_lookup(nestedName, templateDecls, + onMissingQualifiedSymbol, + strictTemplateParamArgLists=False, + ancestorLookupType=typ, + templateShorthand=templateShorthand, + matchSelf=matchSelf, + recurseInAnon=recurseInAnon, + correctPrimaryTemplateArgs=False, + searchInSiblings=searchInSiblings) + except QualifiedSymbolIsTemplateParam: + return None, "templateParamInQualified" + if lookupResult is None: # if it was a part of the qualification that could not be found - return None + if Symbol.debug_lookup: + Symbol.debug_indent -= 2 + return None, None res = list(lookupResult.symbols) if len(res) != 0: - return res + if Symbol.debug_lookup: + Symbol.debug_indent -= 2 + return res, None + + if lookupResult.parentSymbol.declaration is not None: + if lookupResult.parentSymbol.declaration.objectType == 'templateParam': + return None, "templateParamInQualified" # try without template params and args symbol = lookupResult.parentSymbol._find_first_named_symbol( lookupResult.identOrOp, None, None, templateShorthand=templateShorthand, matchSelf=matchSelf, recurseInAnon=recurseInAnon, correctPrimaryTemplateArgs=False) + if Symbol.debug_lookup: + Symbol.debug_indent -= 2 if symbol is not None: - return [symbol] + return [symbol], None else: - return None + return None, None def find_declaration(self, declaration: ASTDeclaration, typ: str, templateShorthand: bool, matchSelf: bool, recurseInAnon: bool) -> "Symbol": # templateShorthand: missing template parameter lists for templates is ok + if Symbol.debug_lookup: + Symbol.debug_indent += 1 + Symbol.debug_print("find_declaration:") nestedName = declaration.name if declaration.templatePrefix: templateDecls = declaration.templatePrefix.templates @@ -4158,8 +4400,10 @@ class Symbol: templateShorthand=templateShorthand, matchSelf=matchSelf, recurseInAnon=recurseInAnon, - correctPrimaryTemplateArgs=False) - + correctPrimaryTemplateArgs=False, + searchInSiblings=False) + if Symbol.debug_lookup: + Symbol.debug_indent -= 1 if lookupResult is None: return None @@ -4185,14 +4429,14 @@ class Symbol: return None def to_string(self, indent: int) -> str: - res = ['\t' * indent] + res = [Symbol.debug_indent_string * indent] if not self.parent: res.append('::') else: if self.templateParams: res.append(str(self.templateParams)) res.append('\n') - res.append('\t' * indent) + res.append(Symbol.debug_indent_string * indent) if self.identOrOp: res.append(str(self.identOrOp)) else: @@ -5931,14 +6175,15 @@ class DefinitionParser: def _parse_template_declaration_prefix(self, objectType: str ) -> ASTTemplateDeclarationPrefix: - templates = [] # type: List[str] + templates = [] # type: List[Union[ASTTemplateParams, ASTTemplateIntroduction]] while 1: self.skip_ws() # the saved position is only used to provide a better error message + params = None # type: Union[ASTTemplateParams, ASTTemplateIntroduction] pos = self.pos if self.skip_word("template"): try: - params = self._parse_template_parameter_list() # type: Any + params = self._parse_template_parameter_list() except DefinitionError as e: if objectType == 'member' and len(templates) == 0: return ASTTemplateDeclarationPrefix(None) @@ -5990,7 +6235,7 @@ class DefinitionParser: msg += str(nestedName) self.warn(msg) - newTemplates = [] + newTemplates = [] # type: List[Union[ASTTemplateParams, ASTTemplateIntroduction]] for i in range(numExtra): newTemplates.append(ASTTemplateParams([])) if templatePrefix and not isMemberInstantiation: @@ -6176,7 +6421,8 @@ class CPPObject(ObjectDescription): return targetSymbol = parentSymbol.parent - s = targetSymbol.find_identifier(symbol.identOrOp, matchSelf=False, recurseInAnon=True) + s = targetSymbol.find_identifier(symbol.identOrOp, matchSelf=False, recurseInAnon=True, + searchInSiblings=False) if s is not None: # something is already declared with that name return @@ -6244,7 +6490,6 @@ class CPPObject(ObjectDescription): continue if id not in self.state.document.ids: signode['ids'].append(id) - signode['first'] = (not self.names) # hmm, what is this about? self.state.document.note_explicit_target(signode) @property @@ -6291,6 +6536,10 @@ class CPPObject(ObjectDescription): symbol = parentSymbol.add_name(name) env.temp_data['cpp:last_symbol'] = symbol return [] + # When multiple declarations are made in the same directive + # they need to know about each other to provide symbol lookup for function parameters. + # We use last_symbol to store the latest added declaration in a directive. + env.temp_data['cpp:last_symbol'] = None return super().run() def handle_signature(self, sig: str, signode: desc_signature) -> ASTDeclaration: @@ -6311,6 +6560,13 @@ class CPPObject(ObjectDescription): try: symbol = parentSymbol.add_declaration(ast, docname=self.env.docname) + # append the new declaration to the sibling list + assert symbol.siblingAbove is None + assert symbol.siblingBelow is None + symbol.siblingAbove = self.env.temp_data['cpp:last_symbol'] + if symbol.siblingAbove is not None: + assert symbol.siblingAbove.siblingBelow is None + symbol.siblingAbove.siblingBelow = symbol self.env.temp_data['cpp:last_symbol'] = symbol except _DuplicateSymbolError as e: # Assume we are actually in the old symbol, @@ -6329,10 +6585,10 @@ class CPPObject(ObjectDescription): return ast def before_content(self) -> None: - lastSymbol = self.env.temp_data['cpp:last_symbol'] + lastSymbol = self.env.temp_data['cpp:last_symbol'] # type: Symbol assert lastSymbol self.oldParentSymbol = self.env.temp_data['cpp:parent_symbol'] - self.oldParentKey = self.env.ref_context['cpp:parent_key'] + self.oldParentKey = self.env.ref_context['cpp:parent_key'] # type: LookupKey self.env.temp_data['cpp:parent_symbol'] = lastSymbol self.env.ref_context['cpp:parent_key'] = lastSymbol.get_lookup_key() @@ -6515,14 +6771,13 @@ class AliasTransform(SphinxTransform): if ast is None: # could not be parsed, so stop here signode = addnodes.desc_signature(sig, '') - signode['first'] = False signode.clear() signode += addnodes.desc_name(sig, sig) node.replace_self(signode) continue - rootSymbol = self.env.domains['cpp'].data['root_symbol'] - parentSymbol = rootSymbol.direct_lookup(parentKey) + rootSymbol = self.env.domains['cpp'].data['root_symbol'] # type: Symbol + parentSymbol = rootSymbol.direct_lookup(parentKey) # type: Symbol if not parentSymbol: print("Target: ", sig) print("ParentKey: ", parentKey) @@ -6537,9 +6792,13 @@ class AliasTransform(SphinxTransform): templateDecls = ns.templatePrefix.templates else: templateDecls = [] - symbols = parentSymbol.find_name(name, templateDecls, 'any', - templateShorthand=True, - matchSelf=True, recurseInAnon=True) + symbols, failReason = parentSymbol.find_name( + nestedName=name, + templateDecls=templateDecls, + typ='any', + templateShorthand=True, + matchSelf=True, recurseInAnon=True, + searchInSiblings=False) if symbols is None: symbols = [] else: @@ -6555,7 +6814,6 @@ class AliasTransform(SphinxTransform): if len(symbols) == 0: signode = addnodes.desc_signature(sig, '') - signode['first'] = False node.append(signode) signode.clear() signode += addnodes.desc_name(sig, sig) @@ -6569,7 +6827,6 @@ class AliasTransform(SphinxTransform): options['tparam-line-spec'] = False for s in symbols: signode = addnodes.desc_signature(sig, '') - signode['first'] = False nodes.append(signode) s.declaration.describe_signature(signode, 'markName', self.env, options) node.replace_self(nodes) @@ -6824,13 +7081,13 @@ class CPPDomain(Domain): t, ex = findWarning(e) warner.warn('Unparseable C++ cross-reference: %r\n%s' % (t, ex)) return None, None - parentKey = node.get("cpp:parent_key", None) + parentKey = node.get("cpp:parent_key", None) # type: LookupKey rootSymbol = self.data['root_symbol'] if parentKey: - parentSymbol = rootSymbol.direct_lookup(parentKey) + parentSymbol = rootSymbol.direct_lookup(parentKey) # type: Symbol if not parentSymbol: print("Target: ", target) - print("ParentKey: ", parentKey) + print("ParentKey: ", parentKey.data) print(rootSymbol.dump(1)) assert parentSymbol # should be there else: @@ -6843,11 +7100,23 @@ class CPPDomain(Domain): templateDecls = ns.templatePrefix.templates else: templateDecls = [] - symbols = parentSymbol.find_name(name, templateDecls, typ, - templateShorthand=True, - matchSelf=True, recurseInAnon=True) - # just refer to the arbitrarily first symbol - s = None if symbols is None else symbols[0] + # let's be conservative with the sibling lookup for now + searchInSiblings = (not name.rooted) and len(name.names) == 1 + symbols, failReason = parentSymbol.find_name( + name, templateDecls, typ, + templateShorthand=True, + matchSelf=True, recurseInAnon=True, + searchInSiblings=searchInSiblings) + if symbols is None: + if typ == 'identifier': + if failReason == 'templateParamInQualified': + # this is an xref we created as part of a signature, + # so don't warn for names nested in template parameters + raise NoUri(str(name), typ) + s = None + else: + # just refer to the arbitrarily first symbol + s = symbols[0] else: decl = ast # type: ASTDeclaration name = decl.name @@ -6977,8 +7246,8 @@ class CPPDomain(Domain): target = node.get('reftarget', None) if target is None: return None - parentKey = node.get("cpp:parent_key", None) - if parentKey is None or len(parentKey) <= 0: + parentKey = node.get("cpp:parent_key", None) # type: LookupKey + if parentKey is None or len(parentKey.data) <= 0: return None rootSymbol = self.data['root_symbol'] @@ -6996,7 +7265,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/sphinx/domains/index.py b/sphinx/domains/index.py index d0bdbcfe0..18a256bac 100644 --- a/sphinx/domains/index.py +++ b/sphinx/domains/index.py @@ -12,6 +12,7 @@ from typing import Any, Dict, Iterable, List, Tuple from docutils import nodes from docutils.nodes import Node, system_message +from docutils.parsers.rst import directives from sphinx import addnodes from sphinx.domains import Domain @@ -68,19 +69,27 @@ class IndexDirective(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = {} # type: Dict + option_spec = { + 'name': directives.unchanged, + } def run(self) -> List[Node]: arguments = self.arguments[0].split('\n') - targetid = 'index-%s' % self.env.new_serialno('index') - targetnode = nodes.target('', '', ids=[targetid]) + + if 'name' in self.options: + targetname = self.options['name'] + targetnode = nodes.target('', '', names=[targetname]) + else: + targetid = 'index-%s' % self.env.new_serialno('index') + targetnode = nodes.target('', '', ids=[targetid]) + self.state.document.note_explicit_target(targetnode) indexnode = addnodes.index() indexnode['entries'] = [] indexnode['inline'] = False self.set_source_info(indexnode) for entry in arguments: - indexnode['entries'].extend(process_index_entry(entry, targetid)) + indexnode['entries'].extend(process_index_entry(entry, targetnode['ids'][0])) return [indexnode, targetnode] diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py index feb39bc9d..d7e44ee60 100644 --- a/sphinx/domains/javascript.py +++ b/sphinx/domains/javascript.py @@ -28,12 +28,30 @@ from sphinx.roles import XRefRole 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 + + +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. @@ -106,21 +124,23 @@ class JSObject(ObjectDescription): 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_')) - signode['first'] = not self.names - 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=(self.env.docname, self.lineno)) + # 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 @@ -251,18 +271,25 @@ class JSModule(SphinxDirective): 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)) + domain.note_object(mod_name, 'module', node_id, + location=(self.env.docname, self.lineno)) + + 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) - targetnode = nodes.target('', '', ids=['module-' + mod_name], - ismod=True) - self.state.document.note_explicit_target(targetnode) - ret.append(targetnode) + 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 @@ -317,47 +344,48 @@ class JavaScriptDomain(Domain): 'mod': JSXRefRole(), } initial_data = { - 'objects': {}, # fullname -> docname, objtype - 'modules': {}, # modname -> docname + '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 object description of %s, other instance in %s'), - fullname, docname, location=location) - self.objects[fullname] = (self.env.docname, objtype) + logger.warning(__('duplicate %s description of %s, other %s in %s'), + objtype, fullname, objtype, docname, location=location) + self.objects[fullname] = (self.env.docname, node_id, 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()): + for fullname, (pkg_docname, node_id, _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] 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) - for mod_name, pkg_docname in otherdata['modules'].items(): + 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 + 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] @@ -389,8 +417,7 @@ class JavaScriptDomain(Domain): 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 @@ -400,13 +427,12 @@ class JavaScriptDomain(Domain): 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') @@ -423,7 +449,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/sphinx/domains/math.py b/sphinx/domains/math.py index b07b2603b..88b6e4eb8 100644 --- a/sphinx/domains/math.py +++ b/sphinx/domains/math.py @@ -15,7 +15,6 @@ from docutils import nodes from docutils.nodes import Element, Node, system_message from docutils.nodes import make_id -from sphinx.addnodes import math_block as displaymath from sphinx.addnodes import pending_xref from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.domains import Domain @@ -54,7 +53,6 @@ class MathDomain(Domain): 'eq': 'equation not found: %(target)s', } enumerable_nodes = { # node_class -> (figtype, title_getter) - displaymath: ('displaymath', None), nodes.math_block: ('displaymath', None), } roles = { diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 3a49d6745..c9c8d5704 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -10,6 +10,7 @@ import re import warnings +from inspect import Parameter from typing import Any, Dict, Iterable, Iterator, List, Tuple from typing import cast @@ -17,13 +18,11 @@ from docutils import nodes from docutils.nodes import Element, Node from docutils.parsers.rst import directives -from sphinx import addnodes, locale +from sphinx import addnodes from sphinx.addnodes import pending_xref, desc_signature from sphinx.application import Sphinx from sphinx.builders import Builder -from sphinx.deprecation import ( - DeprecatedDict, RemovedInSphinx30Warning, RemovedInSphinx40Warning -) +from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType, Index, IndexEntry from sphinx.environment import BuildEnvironment @@ -32,6 +31,7 @@ from sphinx.roles import XRefRole from sphinx.util import logging from sphinx.util.docfields import Field, GroupedField, TypedField from sphinx.util.docutils import SphinxDirective +from sphinx.util.inspect import signature_from_str from sphinx.util.nodes import make_refnode from sphinx.util.typing import TextlikeNode @@ -63,12 +63,46 @@ pairindextypes = { 'builtin': _('built-in function'), } -locale.pairindextypes = DeprecatedDict( - pairindextypes, - 'sphinx.locale.pairindextypes is deprecated. ' - 'Please use sphinx.domains.python.pairindextypes instead.', - RemovedInSphinx30Warning -) + +def _parse_arglist(arglist: str) -> addnodes.desc_parameterlist: + """Parse a list of arguments using AST parser""" + params = addnodes.desc_parameterlist(arglist) + sig = signature_from_str('(%s)' % arglist) + last_kind = None + for param in sig.parameters.values(): + if param.kind != param.POSITIONAL_ONLY and last_kind == param.POSITIONAL_ONLY: + # PEP-570: Separator for Positional Only Parameter: / + params += addnodes.desc_parameter('', nodes.Text('/')) + if param.kind == param.KEYWORD_ONLY and last_kind in (param.POSITIONAL_OR_KEYWORD, + param.POSITIONAL_ONLY, + None): + # PEP-3102: Separator for Keyword Only Parameter: * + params += addnodes.desc_parameter('', nodes.Text('*')) + + node = addnodes.desc_parameter() + if param.kind == param.VAR_POSITIONAL: + node += nodes.Text('*' + param.name) + elif param.kind == param.VAR_KEYWORD: + node += nodes.Text('**' + param.name) + else: + node += nodes.Text(param.name) + + if param.annotation is not param.empty: + node += nodes.Text(': ' + param.annotation) + if param.default is not param.empty: + if param.annotation is not param.empty: + node += nodes.Text(' = ' + str(param.default)) + else: + node += nodes.Text('=' + str(param.default)) + + params += node + last_kind = param.kind + + if last_kind == Parameter.POSITIONAL_ONLY: + # PEP-570: Separator for Positional Only Parameter: / + params += addnodes.desc_parameter('', nodes.Text('/')) + + return params def _pseudo_parse_arglist(signode: desc_signature, arglist: str) -> None: @@ -293,7 +327,15 @@ class PyObject(ObjectDescription): signode += addnodes.desc_name(name, name) if arglist: - _pseudo_parse_arglist(signode, arglist) + try: + signode += _parse_arglist(arglist) + except SyntaxError: + # fallback to parse arglist original parser. + # it supports to represent optional arguments (ex. "func(foo [, bar])") + _pseudo_parse_arglist(signode, arglist) + except NotImplementedError as exc: + logger.warning(exc) + _pseudo_parse_arglist(signode, arglist) else: if self.needs_arglist(): # for callables, add an empty parameter list @@ -320,12 +362,10 @@ class PyObject(ObjectDescription): if fullname not in self.state.document.ids: signode['names'].append(fullname) signode['ids'].append(fullname) - signode['first'] = (not self.names) self.state.document.note_explicit_target(signode) domain = cast(PythonDomain, self.env.get_domain('py')) - domain.note_object(fullname, self.objtype, - location=(self.env.docname, self.lineno)) + domain.note_object(fullname, self.objtype, location=signode) indextext = self.get_index_text(modname, name_cls) if indextext: @@ -811,6 +851,21 @@ class PyXRefRole(XRefRole): return title, target +def filter_meta_fields(app: Sphinx, domain: str, objtype: str, content: Element) -> None: + """Filter ``:meta:`` field from its docstring.""" + if domain != 'py': + return + + for node in content: + if isinstance(node, nodes.field_list): + fields = cast(List[nodes.field], node) + for field in fields: + field_name = cast(nodes.field_body, field[0]).astext().strip() + if field_name == 'meta' or field_name.startswith('meta '): + node.remove(field) + break + + class PythonModuleIndex(Index): """ Index subclass to provide the Python module index. @@ -1119,7 +1174,10 @@ class PythonDomain(Domain): def setup(app: Sphinx) -> Dict[str, Any]: + app.setup_extension('sphinx.directives') + app.add_domain(PythonDomain) + app.connect('object-description-transform', filter_meta_fields) return { 'version': 'builtin', diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py index 81287c815..c0117d89f 100644 --- a/sphinx/domains/rst.py +++ b/sphinx/domains/rst.py @@ -43,11 +43,10 @@ class ReSTMarkup(ObjectDescription): if targetname not in self.state.document.ids: signode['names'].append(targetname) signode['ids'].append(targetname) - signode['first'] = (not self.names) self.state.document.note_explicit_target(signode) domain = cast(ReSTDomain, self.env.get_domain('rst')) - domain.note_object(self.objtype, name, location=(self.env.docname, self.lineno)) + domain.note_object(self.objtype, name, location=signode) indextext = self.get_index_text(self.objtype, name) if indextext: @@ -133,12 +132,11 @@ class ReSTDirectiveOption(ReSTMarkup): if targetname not in self.state.document.ids: signode['names'].append(targetname) signode['ids'].append(targetname) - signode['first'] = (not self.names) self.state.document.note_explicit_target(signode) objname = ':'.join(filter(None, [directive_name, name])) domain = cast(ReSTDomain, self.env.get_domain('rst')) - domain.note_object(self.objtype, objname, location=(self.env.docname, self.lineno)) + domain.note_object(self.objtype, objname, location=signode) if directive_name: key = name[0].upper() diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 4e46c1551..abf3be716 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -22,10 +22,9 @@ from docutils.statemachine import StringList from sphinx import addnodes from sphinx.addnodes import desc_signature, pending_xref -from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning +from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType -from sphinx.errors import NoUri from sphinx.locale import _, __ from sphinx.roles import XRefRole from sphinx.util import ws_re, logging, docname_join @@ -82,7 +81,7 @@ class GenericObject(ObjectDescription): targetname, '', None)) std = cast(StandardDomain, self.env.get_domain('std')) - std.add_object(self.objtype, name, self.env.docname, targetname) + std.note_object(self.objtype, name, targetname, location=signode) class EnvVar(GenericObject): @@ -127,6 +126,7 @@ class Target(SphinxDirective): fullname = ws_re.sub(' ', self.arguments[0].strip()) targetname = '%s-%s' % (self.name, fullname) node = nodes.target('', '', ids=[targetname]) + self.set_source_info(node) self.state.document.note_explicit_target(node) ret = [node] # type: List[Node] if self.indextemplate: @@ -144,7 +144,7 @@ class Target(SphinxDirective): _, name = self.name.split(':', 1) std = cast(StandardDomain, self.env.get_domain('std')) - std.add_object(name, fullname, self.env.docname, targetname) + std.note_object(name, fullname, targetname, location=node) return ret @@ -165,7 +165,7 @@ class Cmdoption(ObjectDescription): logger.warning(__('Malformed option description %r, should ' 'look like "opt", "-opt args", "--opt args", ' '"/opt args" or "+opt args"'), potential_option, - location=(self.env.docname, self.lineno)) + location=signode) continue optname, args = m.groups() if count: @@ -274,7 +274,7 @@ def make_glossary_term(env: "BuildEnvironment", textnodes: Iterable[Node], index term['ids'].append(node_id) std = cast(StandardDomain, env.get_domain('std')) - std.add_object('term', termtext.lower(), env.docname, node_id) + std.note_object('term', termtext.lower(), node_id, location=term) # add an index entry too indexnode = addnodes.index() @@ -406,7 +406,9 @@ class Glossary(SphinxDirective): return messages + [node] -def token_xrefs(text: str) -> List[Node]: +def token_xrefs(text: str, productionGroup: str = '') -> List[Node]: + if len(productionGroup) != 0: + productionGroup += ':' retnodes = [] # type: List[Node] pos = 0 for m in token_re.finditer(text): @@ -414,7 +416,7 @@ def token_xrefs(text: str) -> List[Node]: txt = text[pos:m.start()] retnodes.append(nodes.Text(txt, txt)) refnode = pending_xref(m.group(1), reftype='token', refdomain='std', - reftarget=m.group(1)) + reftarget=productionGroup + m.group(1)) refnode += nodes.literal(m.group(1), m.group(1), classes=['xref']) retnodes.append(refnode) pos = m.end() @@ -437,11 +439,16 @@ class ProductionList(SphinxDirective): def run(self) -> List[Node]: domain = cast(StandardDomain, self.env.get_domain('std')) node = addnodes.productionlist() # type: Element - i = 0 + self.set_source_info(node) + # The backslash handling is from ObjectDescription.get_signatures + nl_escape_re = re.compile(r'\\\n') + lines = nl_escape_re.sub('', self.arguments[0]).split('\n') - for rule in self.arguments[0].split('\n'): + productionGroup = "" + i = 0 + for rule in lines: if i == 0 and ':' not in rule: - # production group + productionGroup = rule.strip() continue i += 1 try: @@ -451,16 +458,41 @@ class ProductionList(SphinxDirective): subnode = addnodes.production(rule) subnode['tokenname'] = name.strip() if subnode['tokenname']: - idname = nodes.make_id('grammar-token-%s' % 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) self.state.document.note_implicit_target(subnode, subnode) - domain.add_object('token', subnode['tokenname'], self.env.docname, idname) - subnode.extend(token_xrefs(tokens)) + if len(productionGroup) != 0: + objName = "%s:%s" % (productionGroup, name) + else: + objName = name + domain.note_object(objtype='token', name=objName, labelid=idname, + location=node) + subnode.extend(token_xrefs(tokens, productionGroup)) node.append(subnode) return [node] +class TokenXRefRole(XRefRole): + def process_link(self, env: "BuildEnvironment", refnode: Element, has_explicit_title: bool, + title: str, target: str) -> Tuple[str, str]: + target = target.lstrip('~') # a title-specific thing + if not self.has_explicit_title and title[0] == '~': + if ':' in title: + _, title = title.split(':') + else: + title = title[1:] + return title, target + + class StandardDomain(Domain): """ Domain for all objects that don't fit into another domain or are added @@ -492,7 +524,7 @@ class StandardDomain(Domain): 'option': OptionXRefRole(warn_dangling=True), 'envvar': EnvVarXRefRole(), # links to tokens in grammar productions - 'token': XRefRole(), + 'token': TokenXRefRole(), # links to terms in glossary 'term': XRefRole(lowercase=True, innernodeclass=nodes.inline, warn_dangling=True), @@ -547,10 +579,51 @@ class StandardDomain(Domain): for node, settings in env.app.registry.enumerable_nodes.items(): self.enumerable_nodes[node] = settings + def note_hyperlink_target(self, name: str, docname: str, node_id: str, + title: str = '') -> None: + """Add a hyperlink target for cross reference. + + .. warning:: + + This is only for internal use. Please don't use this from your extension. + ``document.note_explicit_target()`` or ``note_implicit_target()`` are recommended to + add a hyperlink target to the document. + + This only adds a hyperlink target to the StandardDomain. And this does not add a + node_id to node. Therefore, it is very fragile to calling this without + understanding hyperlink target framework in both docutils and Sphinx. + + .. versionadded:: 3.0 + """ + if name in self.anonlabels and self.anonlabels[name] != (docname, node_id): + logger.warning(__('duplicate label %s, other instance in %s'), + name, self.env.doc2path(self.anonlabels[name][0])) + + self.anonlabels[name] = (docname, node_id) + if title: + self.labels[name] = (docname, node_id, title) + @property def objects(self) -> Dict[Tuple[str, str], Tuple[str, str]]: return self.data.setdefault('objects', {}) # (objtype, name) -> docname, labelid + def note_object(self, objtype: str, name: str, labelid: str, location: Any = None + ) -> None: + """Note a generic object for cross reference. + + .. versionadded:: 3.0 + """ + if (objtype, name) in self.objects: + docname = self.objects[objtype, name][0] + logger.warning(__('duplicate %s description of %s, other instance in %s'), + objtype, name, docname, location=location) + self.objects[objtype, name] = (self.env.docname, labelid) + + def add_object(self, objtype: str, name: str, docname: str, labelid: str) -> None: + warnings.warn('StandardDomain.add_object() is deprecated.', + RemovedInSphinx50Warning) + self.objects[objtype, name] = (docname, labelid) + @property def progoptions(self) -> Dict[Tuple[str, str], Tuple[str, str]]: return self.data.setdefault('progoptions', {}) # (program, name) -> docname, labelid @@ -632,9 +705,6 @@ class StandardDomain(Domain): continue self.labels[name] = docname, labelid, sectname - def add_object(self, objtype: str, name: str, docname: str, labelid: str) -> None: - self.objects[objtype, name] = (docname, labelid) - def add_program_option(self, program: str, name: str, docname: str, labelid: str) -> None: self.progoptions[program, name] = (docname, labelid) @@ -814,30 +884,6 @@ class StandardDomain(Domain): return make_refnode(builder, fromdocname, docname, labelid, contnode) - def _resolve_citation_xref(self, env: "BuildEnvironment", fromdocname: str, - builder: "Builder", typ: str, target: str, - node: pending_xref, contnode: Element) -> Element: - warnings.warn('StandardDomain._resolve_citation_xref() is deprecated.', - RemovedInSphinx30Warning) - docname, labelid, lineno = self.data['citations'].get(target, ('', '', 0)) - if not docname: - if 'ids' in node: - # remove ids attribute that annotated at - # transforms.CitationReference.apply. - del node['ids'][:] - return None - - try: - return make_refnode(builder, fromdocname, docname, - labelid, contnode) - except NoUri: - # remove the ids we added in the CitationReferences - # transform since they can't be transfered to - # the contnode (if it's a Text node) - if not isinstance(contnode, Element): - del node['ids'][:] - raise - def _resolve_obj_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder", typ: str, target: str, node: pending_xref, contnode: Element) -> Element: @@ -934,16 +980,6 @@ class StandardDomain(Domain): figtype, _ = self.enumerable_nodes.get(node.__class__, (None, None)) return figtype - def get_figtype(self, node: Node) -> str: - """Get figure type of nodes. - - .. deprecated:: 1.8 - """ - warnings.warn('StandardDomain.get_figtype() is deprecated. ' - 'Please use get_enumerable_node_type() instead.', - RemovedInSphinx30Warning, stacklevel=2) - return self.get_enumerable_node_type(node) - def get_fignumber(self, env: "BuildEnvironment", builder: "Builder", figtype: str, docname: str, target_node: Element) -> Tuple[int, ...]: if figtype == 'section': diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index f1ae6024d..d40a6cbb3 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -13,9 +13,8 @@ import pickle import warnings from collections import defaultdict from copy import copy -from io import BytesIO from os import path -from typing import Any, Callable, Dict, Generator, IO, Iterator, List, Set, Tuple, Union +from typing import Any, Callable, Dict, Generator, Iterator, List, Set, Tuple, Union from typing import cast from docutils import nodes @@ -23,9 +22,7 @@ from docutils.nodes import Node from sphinx import addnodes from sphinx.config import Config -from sphinx.deprecation import ( - RemovedInSphinx30Warning, RemovedInSphinx40Warning, deprecated_alias -) +from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.domains import Domain from sphinx.environment.adapters.toctree import TocTree from sphinx.errors import SphinxError, BuildEnvironmentError, DocumentError, ExtensionError @@ -221,6 +218,10 @@ class BuildEnvironment: for domain in app.registry.create_domains(self): self.domains[domain.name] = domain + # setup domains (must do after all initialization) + for domain in self.domains.values(): + domain.setup() + # initialize config self._update_config(app.config) @@ -640,113 +641,6 @@ class BuildEnvironment: domain.check_consistency() self.events.emit('env-check-consistency', self) - # --------- METHODS FOR COMPATIBILITY -------------------------------------- - - def update(self, config: Config, srcdir: str, doctreedir: str) -> List[str]: - warnings.warn('env.update() is deprecated. Please use builder.read() instead.', - RemovedInSphinx30Warning, stacklevel=2) - return self.app.builder.read() - - def _read_serial(self, docnames: List[str], app: "Sphinx") -> None: - warnings.warn('env._read_serial() is deprecated. Please use builder.read() instead.', - RemovedInSphinx30Warning, stacklevel=2) - return self.app.builder._read_serial(docnames) - - def _read_parallel(self, docnames: List[str], app: "Sphinx", nproc: int) -> None: - warnings.warn('env._read_parallel() is deprecated. Please use builder.read() instead.', - RemovedInSphinx30Warning, stacklevel=2) - return self.app.builder._read_parallel(docnames, nproc) - - def read_doc(self, docname: str, app: "Sphinx" = None) -> None: - warnings.warn('env.read_doc() is deprecated. Please use builder.read_doc() instead.', - RemovedInSphinx30Warning, stacklevel=2) - self.app.builder.read_doc(docname) - - def write_doctree(self, docname: str, doctree: nodes.document) -> None: - warnings.warn('env.write_doctree() is deprecated. ' - 'Please use builder.write_doctree() instead.', - RemovedInSphinx30Warning, stacklevel=2) - self.app.builder.write_doctree(docname, doctree) - - @property - def _nitpick_ignore(self) -> List[str]: - warnings.warn('env._nitpick_ignore is deprecated. ' - 'Please use config.nitpick_ignore instead.', - RemovedInSphinx30Warning, stacklevel=2) - return self.config.nitpick_ignore - - @staticmethod - def load(f: IO, app: "Sphinx" = None) -> "BuildEnvironment": - warnings.warn('BuildEnvironment.load() is deprecated. ' - 'Please use pickle.load() instead.', - RemovedInSphinx30Warning, stacklevel=2) - try: - env = pickle.load(f) - except Exception as exc: - # This can happen for example when the pickle is from a - # different version of Sphinx. - raise OSError(exc) - if app: - env.app = app - env.config.values = app.config.values - return env - - @classmethod - def loads(cls, string: bytes, app: "Sphinx" = None) -> "BuildEnvironment": - warnings.warn('BuildEnvironment.loads() is deprecated. ' - 'Please use pickle.loads() instead.', - RemovedInSphinx30Warning, stacklevel=2) - io = BytesIO(string) - return cls.load(io, app) - - @classmethod - def frompickle(cls, filename: str, app: "Sphinx") -> "BuildEnvironment": - warnings.warn('BuildEnvironment.frompickle() is deprecated. ' - 'Please use pickle.load() instead.', - RemovedInSphinx30Warning, stacklevel=2) - with open(filename, 'rb') as f: - return cls.load(f, app) - - @staticmethod - def dump(env: "BuildEnvironment", f: IO) -> None: - warnings.warn('BuildEnvironment.dump() is deprecated. ' - 'Please use pickle.dump() instead.', - RemovedInSphinx30Warning, stacklevel=2) - pickle.dump(env, f, pickle.HIGHEST_PROTOCOL) - - @classmethod - def dumps(cls, env: "BuildEnvironment") -> bytes: - warnings.warn('BuildEnvironment.dumps() is deprecated. ' - 'Please use pickle.dumps() instead.', - RemovedInSphinx30Warning, stacklevel=2) - io = BytesIO() - cls.dump(env, io) - return io.getvalue() - - def topickle(self, filename: str) -> None: - warnings.warn('env.topickle() is deprecated. ' - 'Please use pickle.dump() instead.', - RemovedInSphinx30Warning, stacklevel=2) - with open(filename, 'wb') as f: - self.dump(self, f) - - @property - def versionchanges(self) -> Dict[str, List[Tuple[str, str, int, str, str, str]]]: - warnings.warn('env.versionchanges() is deprecated. ' - 'Please use ChangeSetDomain instead.', - RemovedInSphinx30Warning, stacklevel=2) - return self.domaindata['changeset']['changes'] - - def note_versionchange(self, type: str, version: str, - node: addnodes.versionmodified, lineno: int) -> None: - warnings.warn('env.note_versionchange() is deprecated. ' - 'Please use ChangeSetDomain.note_changeset() instead.', - RemovedInSphinx30Warning, stacklevel=2) - node['type'] = type - node['version'] = version - node.line = lineno - self.get_domain('changeset').note_changeset(node) # type: ignore - @property def indexentries(self) -> Dict[str, List[Tuple[str, str, str, str, str]]]: warnings.warn('env.indexentries() is deprecated. Please use IndexDomain instead.', @@ -762,13 +656,3 @@ class BuildEnvironment: from sphinx.domains.index import IndexDomain domain = cast(IndexDomain, self.get_domain('index')) domain.data['entries'] = entries - - -from sphinx.errors import NoUri # NOQA - - -deprecated_alias('sphinx.environment', - { - 'NoUri': NoUri, - }, - RemovedInSphinx30Warning) diff --git a/sphinx/events.py b/sphinx/events.py index e6ea379eb..ff49f290c 100644 --- a/sphinx/events.py +++ b/sphinx/events.py @@ -11,8 +11,9 @@ """ import warnings -from collections import OrderedDict, defaultdict -from typing import Any, Callable, Dict, List +from collections import defaultdict +from operator import attrgetter +from typing import Any, Callable, Dict, List, NamedTuple from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.errors import ExtensionError @@ -26,6 +27,10 @@ if False: logger = logging.getLogger(__name__) +EventListener = NamedTuple('EventListener', [('id', int), + ('handler', Callable), + ('priority', int)]) + # List of all known core events. Maps name to arguments description. core_events = { @@ -57,7 +62,7 @@ class EventManager: RemovedInSphinx40Warning) self.app = app self.events = core_events.copy() - self.listeners = defaultdict(OrderedDict) # type: Dict[str, Dict[int, Callable]] + self.listeners = defaultdict(list) # type: Dict[str, List[EventListener]] self.next_listener_id = 0 def add(self, name: str) -> None: @@ -66,20 +71,22 @@ class EventManager: raise ExtensionError(__('Event %r already present') % name) self.events[name] = '' - def connect(self, name: str, callback: Callable) -> int: + def connect(self, name: str, callback: Callable, priority: int) -> int: """Connect a handler to specific event.""" if name not in self.events: raise ExtensionError(__('Unknown event name: %s') % name) listener_id = self.next_listener_id self.next_listener_id += 1 - self.listeners[name][listener_id] = callback + self.listeners[name].append(EventListener(listener_id, callback, priority)) return listener_id def disconnect(self, listener_id: int) -> None: """Disconnect a handler.""" - for event in self.listeners.values(): - event.pop(listener_id, None) + for listeners in self.listeners.values(): + for listener in listeners[:]: + if listener.id == listener_id: + listeners.remove(listener) def emit(self, name: str, *args: Any) -> List: """Emit a Sphinx event.""" @@ -91,12 +98,13 @@ class EventManager: pass results = [] - for callback in self.listeners[name].values(): + listeners = sorted(self.listeners[name], key=attrgetter("priority")) + for listener in listeners: if self.app is None: # for compatibility; RemovedInSphinx40Warning - results.append(callback(*args)) + results.append(listener.handler(*args)) else: - results.append(callback(self.app, *args)) + results.append(listener.handler(self.app, *args)) return results def emit_firstresult(self, name: str, *args: Any) -> Any: diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 08e4e5301..7abd6c879 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -20,10 +20,8 @@ from docutils.statemachine import StringList import sphinx from sphinx.application import Sphinx -from sphinx.config import Config, ENUM -from sphinx.deprecation import ( - RemovedInSphinx30Warning, RemovedInSphinx40Warning, deprecated_alias -) +from sphinx.config import ENUM +from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.environment import BuildEnvironment from sphinx.ext.autodoc.importer import import_object, get_module_members, get_object_members from sphinx.ext.autodoc.mock import mock @@ -32,7 +30,7 @@ from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.util import inspect from sphinx.util import logging from sphinx.util import rpartition -from sphinx.util.docstrings import prepare_docstring +from sphinx.util.docstrings import extract_metadata, prepare_docstring from sphinx.util.inspect import getdoc, object_description, safe_getattr, stringify_signature from sphinx.util.typing import stringify as stringify_typehint @@ -84,6 +82,14 @@ def members_set_option(arg: Any) -> Union[object, Set[str]]: return {x.strip() for x in arg.split(',') if x.strip()} +def inherited_members_option(arg: Any) -> Union[object, Set[str]]: + """Used to convert the :members: option to auto directives.""" + if arg is None: + return 'object' + else: + return arg + + SUPPRESS = object() @@ -516,6 +522,17 @@ class Documenter: The user can override the skipping decision by connecting to the ``autodoc-skip-member`` event. """ + def is_filtered_inherited_member(name: str) -> bool: + if inspect.isclass(self.object): + for cls in self.object.__mro__: + if cls.__name__ == self.options.inherited_members and cls != self.object: + # given member is a member of specified *super class* + return True + elif name in cls.__dict__: + return False + + return False + ret = [] # search for members in source code too @@ -545,32 +562,45 @@ class Documenter: doc = None has_doc = bool(doc) + metadata = extract_metadata(doc) + if 'private' in metadata: + # consider a member private if docstring has "private" metadata + isprivate = True + else: + isprivate = membername.startswith('_') + keep = False if want_all and membername.startswith('__') and \ membername.endswith('__') and len(membername) > 4: # special __methods__ - if self.options.special_members is ALL and \ - membername != '__doc__': - keep = has_doc or self.options.undoc_members - elif self.options.special_members and \ - self.options.special_members is not ALL and \ - membername in self.options.special_members: - keep = has_doc or self.options.undoc_members + if self.options.special_members is ALL: + if membername == '__doc__': + keep = False + elif is_filtered_inherited_member(membername): + keep = False + else: + keep = has_doc or self.options.undoc_members + elif self.options.special_members: + if membername in self.options.special_members: + keep = has_doc or self.options.undoc_members elif (namespace, membername) in attr_docs: - if want_all and membername.startswith('_'): + if want_all and isprivate: # ignore members whose name starts with _ by default keep = self.options.private_members else: # keep documented attributes keep = True isattr = True - elif want_all and membername.startswith('_'): + elif want_all and isprivate: # ignore members whose name starts with _ by default keep = self.options.private_members and \ (has_doc or self.options.undoc_members) else: - # ignore undocumented members if :undoc-members: is not given - keep = has_doc or self.options.undoc_members + if self.options.members is ALL and is_filtered_inherited_member(membername): + keep = False + else: + # ignore undocumented members if :undoc-members: is not given + keep = has_doc or self.options.undoc_members # give the user a chance to decide whether this member # should be skipped @@ -744,7 +774,7 @@ class ModuleDocumenter(Documenter): option_spec = { 'members': members_option, 'undoc-members': bool_option, - 'noindex': bool_option, 'inherited-members': bool_option, + 'noindex': bool_option, 'inherited-members': inherited_members_option, 'show-inheritance': bool_option, 'synopsis': identity, 'platform': identity, 'deprecated': bool_option, 'member-order': identity, 'exclude-members': members_set_option, @@ -1035,7 +1065,7 @@ class DecoratorDocumenter(FunctionDocumenter): # must be lower than FunctionDocumenter priority = -1 - def format_args(self, **kwargs): + def format_args(self, **kwargs: Any) -> Any: args = super().format_args(**kwargs) if ',' in args: return args @@ -1051,7 +1081,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: member_order = 20 option_spec = { 'members': members_option, 'undoc-members': bool_option, - 'noindex': bool_option, 'inherited-members': bool_option, + 'noindex': bool_option, 'inherited-members': inherited_members_option, 'show-inheritance': bool_option, 'member-order': identity, 'exclude-members': members_set_option, 'private-members': bool_option, 'special-members': members_option, @@ -1116,7 +1146,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: if hasattr(self.object, '__bases__') and len(self.object.__bases__): bases = [':class:`%s`' % b.__name__ if b.__module__ in ('__builtin__', 'builtins') - else ':class:`%s.%s`' % (b.__module__, b.__name__) + else ':class:`%s.%s`' % (b.__module__, b.__qualname__) for b in self.object.__bases__] self.add_line(' ' + _('Bases: %s') % ', '.join(bases), sourcename) @@ -1575,38 +1605,6 @@ def autodoc_attrgetter(app: Sphinx, obj: Any, name: str, *defargs: Any) -> Any: return safe_getattr(obj, name, *defargs) -def merge_autodoc_default_flags(app: Sphinx, config: Config) -> None: - """This merges the autodoc_default_flags to autodoc_default_options.""" - if not config.autodoc_default_flags: - return - - # Note: this option will be removed in Sphinx-4.0. But I marked this as - # RemovedInSphinx *30* Warning because we have to emit warnings for users - # who will be still in use with Sphinx-3.x. So we should replace this by - # logger.warning() on 3.0.0 release. - warnings.warn('autodoc_default_flags is now deprecated. ' - 'Please use autodoc_default_options instead.', - RemovedInSphinx30Warning, stacklevel=2) - - for option in config.autodoc_default_flags: - if isinstance(option, str): - config.autodoc_default_options[option] = None - else: - logger.warning( - __("Ignoring invalid option in autodoc_default_flags: %r"), - option, type='autodoc' - ) - - -from sphinx.ext.autodoc.mock import _MockImporter # NOQA - -deprecated_alias('sphinx.ext.autodoc', - { - '_MockImporter': _MockImporter, - }, - RemovedInSphinx40Warning) - - def setup(app: Sphinx) -> Dict[str, Any]: app.add_autodocumenter(ModuleDocumenter) app.add_autodocumenter(ClassDocumenter) @@ -1635,7 +1633,6 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_event('autodoc-process-signature') app.add_event('autodoc-skip-member') - app.connect('config-inited', merge_autodoc_default_flags) app.setup_extension('sphinx.ext.autodoc.type_comment') return {'version': sphinx.__display_version__, 'parallel_read_safe': True} diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py index c0381d7b4..e98b97915 100644 --- a/sphinx/ext/autodoc/importer.py +++ b/sphinx/ext/autodoc/importer.py @@ -180,12 +180,11 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable, from sphinx.ext.autodoc.mock import ( # NOQA - _MockImporter, _MockModule, _MockObject, MockFinder, MockLoader, mock + _MockModule, _MockObject, MockFinder, MockLoader, mock ) deprecated_alias('sphinx.ext.autodoc.importer', { - '_MockImporter': _MockImporter, '_MockModule': _MockModule, '_MockObject': _MockObject, 'MockFinder': MockFinder, diff --git a/sphinx/ext/autodoc/mock.py b/sphinx/ext/autodoc/mock.py index 0ee0fddc1..25f50d27e 100644 --- a/sphinx/ext/autodoc/mock.py +++ b/sphinx/ext/autodoc/mock.py @@ -11,13 +11,11 @@ import contextlib import os import sys -import warnings from importlib.abc import Loader, MetaPathFinder from importlib.machinery import ModuleSpec from types import FunctionType, MethodType, ModuleType from typing import Any, Generator, Iterator, List, Sequence, Tuple, Union -from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.util import logging logger = logging.getLogger(__name__) @@ -81,15 +79,11 @@ class _MockModule(ModuleType): """Used by autodoc_mock_imports.""" __file__ = os.devnull - def __init__(self, name: str, loader: "_MockImporter" = None) -> None: + def __init__(self, name: str) -> None: super().__init__(name) self.__all__ = [] # type: List[str] self.__path__ = [] # type: List[str] - if loader is not None: - warnings.warn('The loader argument for _MockModule is deprecated.', - RemovedInSphinx30Warning) - def __getattr__(self, name: str) -> _MockObject: return _make_subclass(name, self.__name__)() @@ -97,44 +91,6 @@ class _MockModule(ModuleType): return self.__name__ -class _MockImporter(MetaPathFinder): - def __init__(self, names: List[str]) -> None: - self.names = names - self.mocked_modules = [] # type: List[str] - # enable hook by adding itself to meta_path - sys.meta_path.insert(0, self) - - warnings.warn('_MockImporter is now deprecated.', - RemovedInSphinx30Warning) - - def disable(self) -> None: - # remove `self` from `sys.meta_path` to disable import hook - sys.meta_path = [i for i in sys.meta_path if i is not self] - # remove mocked modules from sys.modules to avoid side effects after - # running auto-documenter - for m in self.mocked_modules: - if m in sys.modules: - del sys.modules[m] - - def find_module(self, name: str, path: Sequence[Union[bytes, str]] = None) -> Any: - # check if name is (or is a descendant of) one of our base_packages - for n in self.names: - if n == name or name.startswith(n + '.'): - return self - return None - - def load_module(self, name: str) -> ModuleType: - if name in sys.modules: - # module has already been imported, return it - return sys.modules[name] - else: - logger.debug('[autodoc] adding a mock module %s!', name) - module = _MockModule(name, self) - sys.modules[name] = module - self.mocked_modules.append(name) - return module - - class MockLoader(Loader): """A loader for mocking.""" def __init__(self, finder: "MockFinder") -> None: diff --git a/sphinx/ext/autodoc/type_comment.py b/sphinx/ext/autodoc/type_comment.py index fb0eebfd8..e6a77f24d 100644 --- a/sphinx/ext/autodoc/type_comment.py +++ b/sphinx/ext/autodoc/type_comment.py @@ -14,6 +14,7 @@ from typing import cast import sphinx from sphinx.application import Sphinx +from sphinx.locale import __ from sphinx.pycode.ast import ast from sphinx.pycode.ast import parse as ast_parse from sphinx.pycode.ast import unparse as ast_unparse @@ -128,7 +129,7 @@ def update_annotations_using_type_comments(app: Sphinx, obj: Any, bound_method: if 'return' not in obj.__annotations__: obj.__annotations__['return'] = type_sig.return_annotation except NotImplementedError as exc: # failed to ast.unparse() - logger.warning("Failed to parse type_comment for %r: %s", obj, exc) + logger.warning(__("Failed to parse type_comment for %r: %s"), obj, exc) def setup(app: Sphinx) -> Dict[str, Any]: diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 7a68552df..3d296cbde 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -745,7 +745,8 @@ def process_generate_options(app: Sphinx) -> None: with mock(app.config.autosummary_mock_imports): generate_autosummary_docs(genfiles, builder=app.builder, suffix=suffix, base_path=app.srcdir, - app=app, imported_members=imported_members) + app=app, imported_members=imported_members, + overwrite=app.config.autosummary_generate_overwrite) def setup(app: Sphinx) -> Dict[str, Any]: @@ -768,6 +769,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.connect('doctree-read', process_autosummary_toc) app.connect('builder-inited', process_generate_options) app.add_config_value('autosummary_generate', [], True, [bool]) + app.add_config_value('autosummary_generate_overwrite', True, False) app.add_config_value('autosummary_mock_imports', lambda config: config.autodoc_mock_imports, 'env') app.add_config_value('autosummary_imported_members', [], False, [bool]) diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index a59a97ea7..ef623e2bd 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -223,7 +223,8 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, suffix: str = '.rst', warn: Callable = None, info: Callable = None, base_path: str = None, builder: Builder = None, template_dir: str = None, - imported_members: bool = False, app: Any = None) -> None: + imported_members: bool = False, app: Any = None, + overwrite: bool = True) -> None: if info: warnings.warn('info argument for generate_autosummary_docs() is deprecated.', RemovedInSphinx40Warning) @@ -271,22 +272,27 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, try: name, obj, parent, mod_name = import_by_name(name) except ImportError as e: - _warn('[autosummary] failed to import %r: %s' % (name, e)) + _warn(__('[autosummary] failed to import %r: %s') % (name, e)) continue - fn = os.path.join(path, name + suffix) + content = generate_autosummary_content(name, obj, parent, template, template_name, + imported_members, app) - # skip it if it exists - if os.path.isfile(fn): - continue - - new_files.append(fn) + filename = os.path.join(path, name + suffix) + if os.path.isfile(filename): + with open(filename) as f: + old_content = f.read() - with open(fn, 'w') as f: - rendered = generate_autosummary_content(name, obj, parent, - template, template_name, - imported_members, app) - f.write(rendered) + if content == old_content: + continue + elif overwrite: # content has changed + with open(filename, 'w') as f: + f.write(content) + new_files.append(filename) + else: + with open(filename, 'w') as f: + f.write(content) + new_files.append(filename) # descend recursively to new files if new_files: @@ -294,7 +300,8 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, suffix=suffix, warn=warn, info=info, base_path=base_path, builder=builder, template_dir=template_dir, - imported_members=imported_members, app=app) + imported_members=imported_members, app=app, + overwrite=overwrite) # -- Finding documented entries in files --------------------------------------- diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py index 987cf2919..e8157848f 100644 --- a/sphinx/ext/coverage.py +++ b/sphinx/ext/coverage.py @@ -123,7 +123,7 @@ class CoverageBuilder(Builder): op.write(' * %-50s [%9s]\n' % (name, typ)) op.write('\n') - def ignore_pyobj(self, full_name): + def ignore_pyobj(self, full_name: str) -> bool: for exp in self.py_ignorexps: if exp.search(full_name): return True diff --git a/sphinx/ext/duration.py b/sphinx/ext/duration.py index 02e60cf7e..669baf2f1 100644 --- a/sphinx/ext/duration.py +++ b/sphinx/ext/duration.py @@ -11,8 +11,8 @@ from datetime import datetime, timedelta from itertools import islice from operator import itemgetter +from typing import Any, Dict, List from typing import cast -from typing import Dict, List from docutils import nodes @@ -82,7 +82,7 @@ def on_build_finished(app: Sphinx, error: Exception) -> None: logger.info('%d.%03d %s', d.seconds, d.microseconds / 1000, docname) -def setup(app): +def setup(app: Sphinx) -> Dict[str, Any]: app.add_domain(DurationDomain) app.connect('builder-inited', on_builder_inited) app.connect('source-read', on_source_read) diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py index 01803708d..db2a15b14 100644 --- a/sphinx/ext/inheritance_diagram.py +++ b/sphinx/ext/inheritance_diagram.py @@ -229,7 +229,7 @@ class InheritanceGraph: if module in ('__builtin__', 'builtins'): fullname = cls.__name__ else: - fullname = '%s.%s' % (module, cls.__name__) + fullname = '%s.%s' % (module, cls.__qualname__) if parts == 0: result = fullname else: @@ -247,6 +247,7 @@ class InheritanceGraph: default_graph_attrs = { 'rankdir': 'LR', 'size': '"8.0, 12.0"', + 'bgcolor': 'transparent', } default_node_attrs = { 'shape': 'box', @@ -254,7 +255,8 @@ class InheritanceGraph: 'height': 0.25, 'fontname': '"Vera Sans, DejaVu Sans, Liberation Sans, ' 'Arial, Helvetica, sans"', - 'style': '"setlinewidth(0.5)"', + 'style': '"setlinewidth(0.5),filled"', + 'fillcolor': 'white', } default_edge_attrs = { 'arrowsize': 0.5, diff --git a/sphinx/ext/mathbase.py b/sphinx/ext/mathbase.py deleted file mode 100644 index 99f117bf7..000000000 --- a/sphinx/ext/mathbase.py +++ /dev/null @@ -1,153 +0,0 @@ -""" - sphinx.ext.mathbase - ~~~~~~~~~~~~~~~~~~~ - - Set up math support in source files and LaTeX/text output. - - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" - -import warnings -from typing import Callable, List, Tuple - -from docutils import nodes -from docutils.nodes import Element, Node -from docutils.parsers.rst.roles import math_role as math_role_base - -from sphinx.addnodes import math, math_block as displaymath # NOQA # to keep compatibility -from sphinx.application import Sphinx -from sphinx.builders.latex.nodes import math_reference as eqref # NOQA # to keep compatibility -from sphinx.deprecation import RemovedInSphinx30Warning -from sphinx.directives.patches import MathDirective as MathDirectiveBase -from sphinx.domains.math import MathDomain # NOQA # to keep compatibility -from sphinx.domains.math import MathReferenceRole as EqXRefRole # NOQA # to keep compatibility -from sphinx.writers.html import HTMLTranslator -from sphinx.writers.latex import LaTeXTranslator -from sphinx.writers.manpage import ManualPageTranslator -from sphinx.writers.texinfo import TexinfoTranslator -from sphinx.writers.text import TextTranslator - - -class MathDirective(MathDirectiveBase): - def run(self) -> List[Node]: - warnings.warn('sphinx.ext.mathbase.MathDirective is moved to ' - 'sphinx.directives.patches package.', - RemovedInSphinx30Warning, stacklevel=2) - return super().run() - - -def math_role(role, rawtext, text, lineno, inliner, options={}, content=[]): - warnings.warn('sphinx.ext.mathbase.math_role() is deprecated. ' - 'Please use docutils.parsers.rst.roles.math_role() instead.', - RemovedInSphinx30Warning, stacklevel=2) - return math_role_base(role, rawtext, text, lineno, inliner, options, content) - - -def get_node_equation_number(writer: HTMLTranslator, node: nodes.math_block) -> str: - warnings.warn('sphinx.ext.mathbase.get_node_equation_number() is moved to ' - 'sphinx.util.math package.', - RemovedInSphinx30Warning, stacklevel=2) - from sphinx.util.math import get_node_equation_number - return get_node_equation_number(writer, node) - - -def wrap_displaymath(text: str, label: str, numbering: bool) -> str: - warnings.warn('sphinx.ext.mathbase.wrap_displaymath() is moved to ' - 'sphinx.util.math package.', - RemovedInSphinx30Warning, stacklevel=2) - from sphinx.util.math import wrap_displaymath - return wrap_displaymath(text, label, numbering) - - -def is_in_section_title(node: Element) -> bool: - """Determine whether the node is in a section title""" - from sphinx.util.nodes import traverse_parent - - warnings.warn('is_in_section_title() is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - - for ancestor in traverse_parent(node): - if isinstance(ancestor, nodes.title) and \ - isinstance(ancestor.parent, nodes.section): - return True - return False - - -def latex_visit_math(self: LaTeXTranslator, node: Element) -> None: - warnings.warn('latex_visit_math() is deprecated. ' - 'Please use LaTeXTranslator.visit_math() instead.', - RemovedInSphinx30Warning, stacklevel=2) - self.visit_math(node) - - -def latex_visit_displaymath(self: LaTeXTranslator, node: Element) -> None: - warnings.warn('latex_visit_displaymath() is deprecated. ' - 'Please use LaTeXTranslator.visit_math_block() instead.', - RemovedInSphinx30Warning, stacklevel=2) - self.visit_math_block(node) - - -def man_visit_math(self: ManualPageTranslator, node: Element) -> None: - warnings.warn('man_visit_math() is deprecated. ' - 'Please use ManualPageTranslator.visit_math() instead.', - RemovedInSphinx30Warning, stacklevel=2) - self.visit_math(node) - - -def man_visit_displaymath(self: ManualPageTranslator, node: Element) -> None: - warnings.warn('man_visit_displaymath() is deprecated. ' - 'Please use ManualPageTranslator.visit_math_block() instead.', - RemovedInSphinx30Warning, stacklevel=2) - self.visit_math_block(node) - - -def man_depart_displaymath(self: ManualPageTranslator, node: Element) -> None: - warnings.warn('man_depart_displaymath() is deprecated. ' - 'Please use ManualPageTranslator.depart_math_block() instead.', - RemovedInSphinx30Warning, stacklevel=2) - self.depart_math_block(node) - - -def texinfo_visit_math(self: TexinfoTranslator, node: Element) -> None: - warnings.warn('texinfo_visit_math() is deprecated. ' - 'Please use TexinfoTranslator.visit_math() instead.', - RemovedInSphinx30Warning, stacklevel=2) - self.visit_math(node) - - -def texinfo_visit_displaymath(self: TexinfoTranslator, node: Element) -> None: - warnings.warn('texinfo_visit_displaymath() is deprecated. ' - 'Please use TexinfoTranslator.visit_math_block() instead.', - RemovedInSphinx30Warning, stacklevel=2) - self.visit_math_block(node) - - -def texinfo_depart_displaymath(self: TexinfoTranslator, node: Element) -> None: - warnings.warn('texinfo_depart_displaymath() is deprecated. ' - 'Please use TexinfoTranslator.depart_math_block() instead.', - RemovedInSphinx30Warning, stacklevel=2) - - -def text_visit_math(self: TextTranslator, node: Element) -> None: - warnings.warn('text_visit_math() is deprecated. ' - 'Please use TextTranslator.visit_math() instead.', - RemovedInSphinx30Warning, stacklevel=2) - self.visit_math(node) - - -def text_visit_displaymath(self: TextTranslator, node: Element) -> None: - warnings.warn('text_visit_displaymath() is deprecated. ' - 'Please use TextTranslator.visit_math_block() instead.', - RemovedInSphinx30Warning, stacklevel=2) - self.visit_math_block(node) - - -def setup_math(app: Sphinx, - htmlinlinevisitors: Tuple[Callable, Callable], - htmldisplayvisitors: Tuple[Callable, Callable]) -> None: - warnings.warn('setup_math() is deprecated. ' - 'Please use app.add_html_math_renderer() instead.', - RemovedInSphinx30Warning, stacklevel=2) - - app.add_html_math_renderer('unknown', htmlinlinevisitors, htmldisplayvisitors) diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index 81f2496cb..7f6ebe478 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -561,7 +561,7 @@ class GoogleDocstring: lines = self._consume_to_next_section() self._parsed_lines.extend(lines) - def _parse_admonition(self, admonition, section): + def _parse_admonition(self, admonition: str, section: str) -> List[str]: # type (str, str) -> List[str] lines = self._consume_to_next_section() return self._format_admonition(admonition, lines) @@ -603,7 +603,7 @@ class GoogleDocstring: label = labels.get(section.lower(), section) return self._parse_generic_section(label, use_admonition) - def _parse_custom_generic_section(self, section): + def _parse_custom_generic_section(self, section: str) -> List[str]: # for now, no admonition for simple custom sections return self._parse_generic_section(section, False) diff --git a/sphinx/ext/napoleon/iterators.py b/sphinx/ext/napoleon/iterators.py index 72ab74120..e91a3ec14 100644 --- a/sphinx/ext/napoleon/iterators.py +++ b/sphinx/ext/napoleon/iterators.py @@ -60,9 +60,7 @@ class peek_iter: return self def __next__(self, n: int = None) -> Any: - # note: prevent 2to3 to transform self.next() in next(self) which - # causes an infinite loop ! - return getattr(self, 'next')(n) + return self.next(n) def _fillcache(self, n: int) -> None: """Cache `n` items. If `n` is 0 or None, then 1 item is cached.""" diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py index 82c2111ef..dc24a1993 100644 --- a/sphinx/ext/viewcode.py +++ b/sphinx/ext/viewcode.py @@ -9,7 +9,6 @@ """ import traceback -import warnings from typing import Any, Dict, Iterable, Iterator, Set, Tuple from docutils import nodes @@ -18,8 +17,6 @@ from docutils.nodes import Element, Node import sphinx from sphinx import addnodes from sphinx.application import Sphinx -from sphinx.config import Config -from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.environment import BuildEnvironment from sphinx.locale import _, __ from sphinx.pycode import ModuleAnalyzer @@ -57,10 +54,10 @@ def doctree_read(app: Sphinx, doctree: Node) -> None: if app.builder.name.startswith("epub") and not env.config.viewcode_enable_epub: return - def has_tag(modname, fullname, docname, refname): + def has_tag(modname: str, fullname: str, docname: str, refname: str) -> bool: entry = env._viewcode_modules.get(modname, None) # type: ignore if entry is False: - return + return False code_tags = app.emit_firstresult('viewcode-find-source', modname) if code_tags is None: @@ -69,7 +66,7 @@ def doctree_read(app: Sphinx, doctree: Node) -> None: analyzer.find_tags() except Exception: env._viewcode_modules[modname] = False # type: ignore - return + return False code = analyzer.code tags = analyzer.tags @@ -84,6 +81,8 @@ def doctree_read(app: Sphinx, doctree: Node) -> None: used[fullname] = docname return True + return False + for objnode in doctree.traverse(addnodes.desc): if objnode.get('domain') != 'py': continue @@ -234,18 +233,10 @@ def collect_pages(app: Sphinx) -> Iterator[Tuple[str, Dict[str, Any], str]]: yield ('_modules/index', context, 'page.html') -def migrate_viewcode_import(app: Sphinx, config: Config) -> None: - if config.viewcode_import is not None: - warnings.warn('viewcode_import was renamed to viewcode_follow_imported_members. ' - 'Please update your configuration.', - RemovedInSphinx30Warning, stacklevel=2) - - def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('viewcode_import', None, False) app.add_config_value('viewcode_enable_epub', False, False) app.add_config_value('viewcode_follow_imported_members', True, False) - app.connect('config-inited', migrate_viewcode_import) app.connect('doctree-read', doctree_read) app.connect('env-merge-info', env_merge_info) app.connect('html-collect-pages', collect_pages) diff --git a/sphinx/highlighting.py b/sphinx/highlighting.py index 49a4105bf..4dc9ce543 100644 --- a/sphinx/highlighting.py +++ b/sphinx/highlighting.py @@ -8,8 +8,6 @@ :license: BSD, see LICENSE for details. """ -import html -import warnings from functools import partial from importlib import import_module from typing import Any, Dict @@ -26,8 +24,6 @@ from pygments.style import Style from pygments.styles import get_style_by_name from pygments.util import ClassNotFound -from sphinx.deprecation import RemovedInSphinx30Warning -from sphinx.ext import doctest from sphinx.locale import __ from sphinx.pygments_styles import SphinxStyle, NoneStyle from sphinx.util import logging, texescape @@ -65,7 +61,7 @@ class PygmentsBridge: latex_formatter = LatexFormatter def __init__(self, dest: str = 'html', stylename: str = 'sphinx', - trim_doctest_flags: bool = None, latex_engine: str = None) -> None: + latex_engine: str = None) -> None: self.dest = dest self.latex_engine = latex_engine @@ -77,11 +73,6 @@ class PygmentsBridge: self.formatter = self.latex_formatter self.formatter_args['commandprefix'] = 'PYG' - self.trim_doctest_flags = trim_doctest_flags - if trim_doctest_flags is not None: - warnings.warn('trim_doctest_flags option for PygmentsBridge is now deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - def get_style(self, stylename: str) -> Style: if stylename is None or stylename == 'sphinx': return SphinxStyle @@ -97,19 +88,6 @@ class PygmentsBridge: kwargs.update(self.formatter_args) return self.formatter(**kwargs) - def unhighlighted(self, source: str) -> str: - warnings.warn('PygmentsBridge.unhighlighted() is now deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - if self.dest == 'html': - return '<pre>' + html.escape(source) + '</pre>\n' - else: - # first, escape highlighting characters like Pygments does - source = source.translate(escape_hl_chars) - # then, escape all characters nonrepresentable in LaTeX - source = texescape.escape(source, self.latex_engine) - return '\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n' + \ - source + '\\end{Verbatim}\n' - def get_lexer(self, source: str, lang: str, opts: Dict = None, force: bool = False, location: Any = None) -> Lexer: if not opts: @@ -127,11 +105,6 @@ class PygmentsBridge: lang = 'pycon3' else: lang = 'python3' - elif lang == 'guess': - try: - lexer = guess_lexer(source) - except Exception: - lexer = lexers['none'] if lang in lexers: # just return custom lexers here (without installing raiseonerror filter) @@ -141,7 +114,7 @@ class PygmentsBridge: else: try: if lang == 'guess': - lexer = guess_lexer(lang, **opts) + lexer = guess_lexer(source, **opts) else: lexer = get_lexer_by_name(lang, **opts) except ClassNotFound: @@ -161,11 +134,6 @@ class PygmentsBridge: lexer = self.get_lexer(source, lang, opts, force, location) - # trim doctest options if wanted - if isinstance(lexer, PythonConsoleLexer) and self.trim_doctest_flags: - source = doctest.blankline_re.sub('', source) - source = doctest.doctestopt_re.sub('', source) - # highlight via Pygments formatter = self.get_formatter(**kwargs) try: diff --git a/sphinx/io.py b/sphinx/io.py index b1290ae89..18b4f053e 100644 --- a/sphinx/io.py +++ b/sphinx/io.py @@ -9,7 +9,7 @@ """ import codecs import warnings -from typing import Any, List, Tuple +from typing import Any, List from typing import Type # for python3.5.1 from docutils import nodes @@ -19,14 +19,11 @@ from docutils.io import FileInput, Input, NullOutput from docutils.parsers import Parser from docutils.parsers.rst import Parser as RSTParser from docutils.readers import standalone -from docutils.statemachine import StringList, string2lines from docutils.transforms import Transform from docutils.transforms.references import DanglingReferences from docutils.writers import UnfilteredWriter -from sphinx.deprecation import ( - RemovedInSphinx30Warning, RemovedInSphinx40Warning, deprecated_alias -) +from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias from sphinx.environment import BuildEnvironment from sphinx.errors import FiletypeNotFoundError from sphinx.transforms import ( @@ -39,7 +36,6 @@ from sphinx.transforms.references import SphinxDomains from sphinx.util import logging, get_filetype from sphinx.util import UnicodeDecodeErrorHandler from sphinx.util.docutils import LoggingReporter -from sphinx.util.rst import append_epilog, docinfo_re, prepend_prolog from sphinx.versioning import UIDTransform if False: @@ -160,17 +156,6 @@ class SphinxI18nReader(SphinxBaseReader): if transform in self.transforms: self.transforms.remove(transform) - def set_lineno_for_reporter(self, lineno: int) -> None: - """Stores the source line number of original text.""" - warnings.warn('SphinxI18nReader.set_lineno_for_reporter() is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - - @property - def line(self) -> int: - warnings.warn('SphinxI18nReader.line is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - return 0 - class SphinxDummyWriter(UnfilteredWriter): """Dummy writer module used for generating doctree.""" @@ -186,93 +171,13 @@ def SphinxDummySourceClass(source: Any, *args: Any, **kwargs: Any) -> Any: return source -class SphinxBaseFileInput(FileInput): - """A base class of SphinxFileInput. - - It supports to replace unknown Unicode characters to '?'. - """ - - def __init__(self, app: "Sphinx", env: BuildEnvironment, - *args: Any, **kwargs: Any) -> None: - self.app = app - self.env = env - - warnings.warn('%s is deprecated.' % self.__class__.__name__, - RemovedInSphinx30Warning, stacklevel=2) - - kwargs['error_handler'] = 'sphinx' # py3: handle error on open. - super().__init__(*args, **kwargs) - - def warn_and_replace(self, error: Any) -> Tuple: - return UnicodeDecodeErrorHandler(self.env.docname)(error) - - class SphinxFileInput(FileInput): """A basic FileInput for Sphinx.""" - supported = ('*',) # RemovedInSphinx30Warning - def __init__(self, *args: Any, **kwargs: Any) -> None: kwargs['error_handler'] = 'sphinx' super().__init__(*args, **kwargs) -class SphinxRSTFileInput(SphinxBaseFileInput): - """A reST FileInput for Sphinx. - - This FileInput automatically prepends and appends text by :confval:`rst_prolog` and - :confval:`rst_epilog`. - - .. important:: - - This FileInput uses an instance of ``StringList`` as a return value of ``read()`` - method to indicate original source filename and line numbers after prepending and - appending. - For that reason, ``sphinx.parsers.RSTParser`` should be used with this to parse - a content correctly. - """ - supported = ('restructuredtext',) - - def prepend_prolog(self, text: StringList, prolog: str) -> None: - docinfo = self.count_docinfo_lines(text) - if docinfo: - # insert a blank line after docinfo - text.insert(docinfo, '', '<generated>', 0) - docinfo += 1 - - # insert prolog (after docinfo if exists) - for lineno, line in enumerate(prolog.splitlines()): - text.insert(docinfo + lineno, line, '<rst_prolog>', lineno) - - text.insert(docinfo + lineno + 1, '', '<generated>', 0) - - def append_epilog(self, text: StringList, epilog: str) -> None: - # append a blank line and rst_epilog - text.append('', '<generated>', 0) - for lineno, line in enumerate(epilog.splitlines()): - text.append(line, '<rst_epilog>', lineno) - - def read(self) -> StringList: # type: ignore - inputstring = super().read() - lines = string2lines(inputstring, convert_whitespace=True) - content = StringList() - for lineno, line in enumerate(lines): - content.append(line, self.source_path, lineno) - - prepend_prolog(content, self.env.config.rst_prolog) - append_epilog(content, self.env.config.rst_epilog) - - return content - - def count_docinfo_lines(self, content: StringList) -> int: - if len(content) == 0: - return 0 - else: - for lineno, line in enumerate(content.data): - if not docinfo_re.match(line): - break - return lineno - - def read_doc(app: "Sphinx", env: BuildEnvironment, filename: str) -> nodes.document: """Parse a document and convert to doctree.""" # set up error_handler for the target document diff --git a/sphinx/locale/__init__.py b/sphinx/locale/__init__.py index 262425a36..9812355ca 100644 --- a/sphinx/locale/__init__.py +++ b/sphinx/locale/__init__.py @@ -10,13 +10,10 @@ import gettext import locale -import warnings from collections import UserString, defaultdict from gettext import NullTranslations from typing import Any, Callable, Dict, Iterable, List, Tuple, Union -from sphinx.deprecation import RemovedInSphinx30Warning - class _TranslationProxy(UserString): """ @@ -106,24 +103,6 @@ class _TranslationProxy(UserString): return '<%s broken>' % self.__class__.__name__ -def mygettext(string: str) -> str: - """Used instead of _ when creating TranslationProxies, because _ is - not bound yet at that time. - """ - warnings.warn('sphinx.locale.mygettext() is deprecated. Please use `_()` instead.', - RemovedInSphinx30Warning, stacklevel=2) - return _(string) - - -def lazy_gettext(string: str) -> str: - """A lazy version of `gettext`.""" - # if isinstance(string, _TranslationProxy): - # return string - warnings.warn('sphinx.locale.laxy_gettext() is deprecated. Please use `_()` instead.', - RemovedInSphinx30Warning, stacklevel=2) - return _TranslationProxy(mygettext, string) # type: ignore - - translators = defaultdict(NullTranslations) # type: Dict[Tuple[str, str], NullTranslations] @@ -218,7 +197,7 @@ def _lazy_translate(catalog: str, namespace: str, message: str) -> str: return translator.gettext(message) -def get_translation(catalog, namespace='general'): +def get_translation(catalog: str, namespace: str = 'general') -> Callable: """Get a translation function based on the *catalog* and *namespace*. The extension can use this API to translate the messages on the @@ -266,12 +245,6 @@ _ = get_translation('sphinx') __ = get_translation('sphinx', 'console') -def l_(*args): - warnings.warn('sphinx.locale.l_() is deprecated. Please use `_()` instead.', - RemovedInSphinx30Warning, stacklevel=2) - return _(*args) - - # labels admonitionlabels = { 'attention': _('Attention'), diff --git a/sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po b/sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po index 0d402ede1..c651d7510 100644 --- a/sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po +++ b/sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po @@ -1,7 +1,7 @@ # Translations template for Sphinx. # Copyright (C) 2019 ORGANIZATION # This file is distributed under the same license as the Sphinx project. -# +# # Translators: # Yinian Chin <yinian1992@live.com>, 2015,2017-2018 # Hsiaoming Yang <me@lepture.com>, 2018 @@ -28,6 +28,7 @@ msgstr "" "Generated-By: Babel 2.6.0\n" "Language: zh_CN\n" "Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Poedit 2.2.4\n" #: sphinx/application.py:153 #, python-format @@ -50,9 +51,7 @@ msgstr "æ£åœ¨è¿è¡Œ Sphinx v%s" #: sphinx/application.py:214 #, python-format -msgid "" -"This project needs at least Sphinx v%s and therefore cannot be built with " -"this version." +msgid "This project needs at least Sphinx v%s and therefore cannot be built with this version." msgstr "è¯¥é¡¹ç›®éœ€è¦ Sphinx v%s åŠä»¥ä¸Šç‰ˆæœ¬ï¼Œä½¿ç”¨çŽ°æœ‰ç‰ˆæœ¬ä¸èƒ½æž„建文档。" #: sphinx/application.py:234 @@ -66,9 +65,8 @@ msgstr "åŒæ—¶è®¾ç½®æ‰©å±•å %s:" #: sphinx/application.py:245 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." +"'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 "å½“å‰ conf.py ä¸å®šä¹‰çš„ 'setup' 䏿˜¯ä¸€ä¸ªå¯è°ƒç”¨çš„ Python 对象。请把其定义改为一个å¯è°ƒç”¨çš„函数。Sphinx 扩展的 conf.py å¿…é¡»è¿™æ ·é…置。" #: sphinx/application.py:269 @@ -76,9 +74,8 @@ msgstr "å½“å‰ conf.py ä¸å®šä¹‰çš„ 'setup' 䏿˜¯ä¸€ä¸ªå¯è°ƒç”¨çš„ Python 对è msgid "loading translations [%s]... " msgstr "æ£åœ¨åŠ è½½ç¿»è¯‘ [%s]... " -#: 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: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 msgid "done" msgstr "完æˆ" @@ -134,18 +131,12 @@ msgstr "角色 %r 已注册,将被覆盖" #: sphinx/application.py:1179 #, python-format -msgid "" -"the %s extension does not declare if it is safe for parallel reading, " -"assuming it isn't - please ask the extension author to check and make it " -"explicit" +msgid "the %s extension does not declare if it is safe for parallel reading, assuming it isn't - please ask the extension author to check and make it explicit" msgstr "扩展 %s 没有声明是å¦å¹¶è¡Œè¯»å–安全,默认å‡å®šä¸ºå¦ - 请è”ç³»æ‰©å±•ä½œè€…æ£€æŸ¥æ˜¯å¦æ”¯æŒè¯¥ç‰¹æ€§å¹¶æ˜¾å¼å£°æ˜Ž" #: sphinx/application.py:1185 #, python-format -msgid "" -"the %s extension does not declare if it is safe for parallel writing, " -"assuming it isn't - please ask the extension author to check and make it " -"explicit" +msgid "the %s extension does not declare if it is safe for parallel writing, assuming it isn't - please ask the extension author to check and make it explicit" msgstr "%s 扩展没有声明是å¦å¹¶è¡Œå†™å…¥å®‰å…¨ï¼Œé»˜è®¤å‡å®šä¸ºå¦ - 请è”ç³»æ‰©å±•ä½œè€…æ£€æŸ¥æ˜¯å¦æ”¯æŒè¯¥ç‰¹æ€§å¹¶æ˜¾å¼å£°æ˜Ž" #: sphinx/application.py:1196 @@ -155,9 +146,7 @@ msgstr "æ‰§è¡Œé¡ºåº %s" #: sphinx/config.py:220 #, python-format -msgid "" -"cannot override dictionary config setting %r, ignoring (use %r to set " -"individual elements)" +msgid "cannot override dictionary config setting %r, ignoring (use %r to set individual elements)" msgstr "ä¸èƒ½è¦†ç›–å—å…¸é…置项 %r,已忽略 (请用 %r 设置å•个å—å…¸å…ƒç´ )" #: sphinx/config.py:229 @@ -191,8 +180,7 @@ msgid "There is a syntax error in your configuration file: %s\n" msgstr "é…置文件ä¸å˜åœ¨è¯æ³•错误: %s\n" #: sphinx/config.py:366 -msgid "" -"The configuration file (or one of the modules it imports) called sys.exit()" +msgid "The configuration file (or one of the modules it imports) called sys.exit()" msgstr "é…置文件(或é…置文件导入的模å—)调用了 sys.exit()" #: sphinx/config.py:370 @@ -201,7 +189,10 @@ msgid "" "There is a programmable error in your configuration file:\n" "\n" "%s" -msgstr "é…ç½®æ–‡ä»¶ä¸æœ‰ç¨‹åºä¸Šçš„错误:\n\n%s" +msgstr "" +"é…ç½®æ–‡ä»¶ä¸æœ‰ç¨‹åºä¸Šçš„错误:\n" +"\n" +"%s" #: sphinx/config.py:397 #, python-format @@ -231,9 +222,7 @@ msgid "Listing %s" msgstr "列表 %s" #: sphinx/config.py:447 -msgid "" -"The config value `{name}` has to be a one of {candidates}, but `{current}` " -"is given." +msgid "The config value `{name}` has to be a one of {candidates}, but `{current}` is given." msgstr "é…置项 `{name}` 必须设置为 {candidates} 之一,但现在是 `{current}` 。" #: sphinx/config.py:465 @@ -243,16 +232,12 @@ msgid "" msgstr "é…置值\"[name]\"的类型为\"[当å‰._name];但\"当å‰\"为\"当å‰\"。\"当å‰\"为\"当å‰\"。\"当å‰\"为\"当å‰\"。=================================预期 [å…许]。" #: sphinx/config.py:478 -msgid "" -"The config value `{name}' has type `{current.__name__}', defaults to " -"`{default.__name__}'." +msgid "The config value `{name}' has type `{current.__name__}', defaults to `{default.__name__}'." msgstr "é…置项 `{name}' 的类型是 `{current.__name__}',默认为 `{default.__name__}'。" #: sphinx/config.py:497 #, python-format -msgid "" -"the config value %r is set to a string with non-ASCII characters; this can " -"lead to Unicode errors occurring. Please use Unicode strings, e.g. %r." +msgid "the config value %r is set to a string with non-ASCII characters; this can lead to Unicode errors occurring. Please use Unicode strings, e.g. %r." msgstr "é…置项 %r 的值包å«äº†éž ASCII å—符,这会导致 Unicode 错误。请使用 Unicode å—符串,例如 %r。" #: sphinx/config.py:506 @@ -278,16 +263,12 @@ msgstr "未知事件å称:%s" #: sphinx/extension.py:52 #, python-format -msgid "" -"The %s extension is required by needs_extensions settings, but it is not " -"loaded." +msgid "The %s extension is required by needs_extensions settings, but it is not loaded." msgstr "æœªèƒ½åŠ è½½ needs_extensions é…置项所需的 %s。" #: sphinx/extension.py:57 #, python-format -msgid "" -"This project needs the extension %s at least in version %s and therefore " -"cannot be built with the loaded version (%s)." +msgid "This project needs the extension %s at least in version %s and therefore cannot be built with the loaded version (%s)." msgstr "该项目所需扩展 %s æœ€ä½Žè¦æ±‚版本 %s ,当å‰åŠ è½½ç‰ˆæœ¬ (%s) æ— æ³•æž„å»ºæ–‡æ¡£ã€‚" #: sphinx/highlighting.py:142 @@ -417,23 +398,17 @@ msgstr "æ— æ³•å¯¼å…¥æ‰©å±• %s" #: sphinx/registry.py:479 #, python-format -msgid "" -"extension %r has no setup() function; is it really a Sphinx extension " -"module?" +msgid "extension %r has no setup() function; is it really a Sphinx extension module?" msgstr "扩展 %r 未包å«setup() 函数;它确实是一个 Sphinx 扩展模å—å—?" #: sphinx/registry.py:488 #, python-format -msgid "" -"The %s extension used by this project needs at least Sphinx v%s; it " -"therefore cannot be built with this version." +msgid "The %s extension used by this project needs at least Sphinx v%s; it therefore cannot be built with this version." msgstr "该项目所用扩展 %s éœ€è¦ Sphinx 版本 %s 以上;当å‰ç‰ˆæœ¬æ— 法构建文档。" #: sphinx/registry.py:496 #, python-format -msgid "" -"extension %r returned an unsupported object from its setup() function; it " -"should return None or a metadata dictionary" +msgid "extension %r returned an unsupported object from its setup() function; it should return None or a metadata dictionary" msgstr "扩展 %r 在其 setup() 函数ä¸è¿”å›žäº†ä¸€ä¸ªä¸æ”¯æŒçš„对象;该函数应返回 None 或一个元数æ®å—å…¸" #: sphinx/roles.py:221 sphinx/roles.py:272 @@ -472,9 +447,7 @@ msgid "file %r on theme path is not a valid zipfile or contains no theme" msgstr "主题路径指定的文件 %r æ˜¯ä¸€ä¸ªæ— æ•ˆçš„æˆ–ä¸åŒ…å«ä¸»é¢˜çš„ zip 文件" #: sphinx/theming.py:258 -msgid "" -"sphinx_rtd_theme is no longer a hard dependency since version 1.4.0. Please " -"install it manually.(pip install sphinx_rtd_theme)" +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_rtd_theme 从 1.4.0 版本开始ä¸å†ä½œä¸ºå¼ºä¾èµ–。请手动安装。(pip install sphinx_rtd_theme)" #: sphinx/theming.py:262 @@ -496,8 +469,7 @@ msgstr "æ²¡æœ‰æ‰¾åˆ°é€‚åˆ %s 构建器的图åƒï¼š%s" msgid "building [mo]: " msgstr "构建 [mo]: " -#: sphinx/builders/__init__.py:232 sphinx/builders/__init__.py:574 -#: sphinx/builders/__init__.py:602 +#: sphinx/builders/__init__.py:232 sphinx/builders/__init__.py:574 sphinx/builders/__init__.py:602 msgid "writing output... " msgstr "写入输出... " @@ -522,8 +494,7 @@ msgstr "æ‰€æœ‰æºæ–‡ä»¶" #: sphinx/builders/__init__.py:298 #, python-format -msgid "" -"file %r given on command line is not under the source directory, ignoring" +msgid "file %r given on command line is not under the source directory, ignoring" msgstr "æºæ–‡ä»¶ç›®å½•下没有命令行给出的 %r 文件,将被忽略" #: sphinx/builders/__init__.py:303 @@ -602,8 +573,7 @@ msgstr "准备文件" msgid "duplicated ToC entry found: %s" msgstr "找到é‡å¤çš„ToCæ¡ç›®: %s" -#: 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:414 sphinx/builders/html.py:761 sphinx/builders/latex/__init__.py:415 sphinx/builders/texinfo.py:190 msgid "copying images... " msgstr "å¤åˆ¶å›¾åƒ... " @@ -612,8 +582,7 @@ msgstr "å¤åˆ¶å›¾åƒ... " msgid "cannot read image file %r: copying it instead" msgstr "æ— æ³•è¯»å–å›¾åƒæ–‡ä»¶ %r:直接å¤åˆ¶" -#: 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:427 sphinx/builders/html.py:769 sphinx/builders/latex/__init__.py:423 sphinx/builders/texinfo.py:199 #, python-format msgid "cannot copy image file %r: %s" msgstr "æ— æ³•å¤åˆ¶å›¾åƒæ–‡ä»¶ %r:%s" @@ -627,8 +596,7 @@ msgstr "æ— æ³•å†™å…¥å›¾åƒæ–‡ä»¶ %r:%s" msgid "Pillow not found - copying image files" msgstr "未找到Pillow - å¤åˆ¶å›¾åƒæ–‡ä»¶" -#: 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: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 #, python-format msgid "writing %s file..." @@ -762,9 +730,7 @@ msgstr "HTML 页é¢ä¿å˜åœ¨ %(outdir)s 目录。" msgid "Failed to read build info file: %r" msgstr "è¯»å–æž„å»ºä¿¡æ¯æ–‡ä»¶å¤±è´¥ï¼š%r" -#: 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/html.py:488 sphinx/builders/latex/__init__.py:206 sphinx/transforms/__init__.py:121 sphinx/writers/manpage.py:118 sphinx/writers/texinfo.py:243 #, python-format msgid "%b %d, %Y" msgstr "%Y å¹´ %m 月 %d æ—¥" @@ -850,9 +816,7 @@ msgid "Failed to write build info file: %r" msgstr "å†™å…¥æž„å»ºä¿¡æ¯æ–‡ä»¶å¤±è´¥ï¼š%r" #: sphinx/builders/html.py:927 -msgid "" -"search index couldn't be loaded, but not all documents will be built: the " -"index will be incomplete." +msgid "search index couldn't be loaded, but not all documents will be built: the index will be incomplete." msgstr "æ— æ³•åŠ è½½æœç´¢ç´¢å¼•,ä¸ä¼šæž„建所有文档:索引将ä¸å®Œæ•´ã€‚" #: sphinx/builders/html.py:996 @@ -862,9 +826,7 @@ msgstr "é¡µé¢ %s 匹é…了 html_sidebars ä¸çš„两æ¡è§„则:%r å’Œ %r" #: sphinx/builders/html.py:1094 #, 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 "a Unicode error occurred when rendering the page %s. Please make sure all config values that contain non-ASCII content are Unicode strings." msgstr "æ¸²æŸ“é¡µé¢ %s æ—¶å‘生了 Unicode é”™è¯¯ã€‚è¯·ç¡®ä¿æ‰€æœ‰åŒ…å«éž ASCII å—符的é…置项是 Unicode å—符串。" #: sphinx/builders/html.py:1099 @@ -872,10 +834,11 @@ msgstr "æ¸²æŸ“é¡µé¢ %s æ—¶å‘生了 Unicode é”™è¯¯ã€‚è¯·ç¡®ä¿æ‰€æœ‰åŒ…å«éž A msgid "" "An error happened in rendering the page %s.\n" "Reason: %r" -msgstr "æ¸²æŸ“é¡µé¢ %s æ—¶å‘生了错误。\nåŽŸå› ï¼š%r" +msgstr "" +"æ¸²æŸ“é¡µé¢ %s æ—¶å‘生了错误。\n" +"åŽŸå› ï¼š%r" -#: sphinx/builders/html.py:1111 sphinx/builders/text.py:85 -#: sphinx/builders/xml.py:99 +#: sphinx/builders/html.py:1111 sphinx/builders/text.py:85 sphinx/builders/xml.py:99 #, python-format msgid "error writing file %s: %s" msgstr "写入文件 %s æ—¶å‘生错误:%s" @@ -932,8 +895,7 @@ msgstr "手册页ä¿å˜åœ¨ %(outdir)s 目录。" msgid "no \"man_pages\" config value found; no manual pages will be written" msgstr "未找到“man_pagesâ€é…置项,ä¸ä¼šå†™å…¥æ‰‹å†Œé¡µ" -#: sphinx/builders/latex/__init__.py:272 sphinx/builders/manpage.py:64 -#: sphinx/builders/singlehtml.py:174 sphinx/builders/texinfo.py:120 +#: sphinx/builders/latex/__init__.py:272 sphinx/builders/manpage.py:64 sphinx/builders/singlehtml.py:174 sphinx/builders/texinfo.py:120 msgid "writing" msgstr "写作" @@ -965,7 +927,10 @@ msgid "" "\n" "Run 'make' in that directory to run these through makeinfo\n" "(use 'make info' here to do that automatically)." -msgstr "\n在该目录下è¿è¡Œâ€œmakeâ€å‘½ä»¤ä»¥é€šè¿‡ makeinfo è¿è¡Œè¿™äº› Texinfo文件\n(在æ¤å¤„用“make infoâ€å³å¯è‡ªåŠ¨æ‰§è¡Œï¼‰ã€‚" +msgstr "" +"\n" +"在该目录下è¿è¡Œâ€œmakeâ€å‘½ä»¤ä»¥é€šè¿‡ makeinfo è¿è¡Œè¿™äº› Texinfo文件\n" +"(在æ¤å¤„用“make infoâ€å³å¯è‡ªåŠ¨æ‰§è¡Œï¼‰ã€‚" #: sphinx/builders/texinfo.py:85 msgid "no \"texinfo_documents\" config value found; no documents will be written" @@ -1023,7 +988,10 @@ msgid "" "\n" "Run 'make' in that directory to run these through (pdf)latex\n" "(use `make latexpdf' here to do that automatically)." -msgstr "\n在该目录下è¿è¡Œâ€œmakeâ€ä»¥é€šè¿‡ (pdf)latex è¿è¡Œè¿™äº› LaTex 文件\n(在æ¤å¤„用“make latexpdfâ€å³å¯è‡ªåŠ¨æ‰§è¡Œï¼‰ã€‚" +msgstr "" +"\n" +"在该目录下è¿è¡Œâ€œmakeâ€ä»¥é€šè¿‡ (pdf)latex è¿è¡Œè¿™äº› LaTex 文件\n" +"(在æ¤å¤„用“make latexpdfâ€å³å¯è‡ªåŠ¨æ‰§è¡Œï¼‰ã€‚" #: sphinx/builders/latex/__init__.py:165 msgid "no \"latex_documents\" config value found; no documents will be written" @@ -1034,14 +1002,9 @@ msgstr "未找到“latex_documentsâ€é…置项,ä¸ä¼šå†™å…¥æ–‡æ¡£" msgid "\"latex_documents\" config value references unknown document %s" msgstr "é…置项“latex_documentsâ€å¼•用了未知文档 %s" -#: 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/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 msgid "Index" msgstr "索引" @@ -1090,9 +1053,7 @@ msgstr "ç¼–ç 错误:" #: sphinx/cmd/build.py:59 sphinx/cmd/build.py:74 #, python-format -msgid "" -"The full traceback has been saved in %s, if you want to report the issue to " -"the developers." +msgid "The full traceback has been saved in %s, if you want to report the issue to the developers." msgstr "å¦‚æžœä½ æƒ³å‘å¼€å‘者报告问题,å¯ä»¥æŸ¥é˜…å·²ç»ä¿å˜åœ¨ %s 的完整 Traceback ä¿¡æ¯ ã€‚" #: sphinx/cmd/build.py:63 @@ -1100,10 +1061,7 @@ msgid "Recursion error:" msgstr "递归错误:" #: sphinx/cmd/build.py:66 -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.:" +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 "åœ¨æºæ–‡ä»¶è¿‡å¤§æˆ–嵌套层数过深时会出现æ¤é”™è¯¯ã€‚ä½ å¯ä»¥åœ¨ conf.py ä¸å¢žå¤§é»˜è®¤çš„Python 递归 1000 层é™åˆ¶ï¼Œåƒè¿™æ ·ï¼š" #: sphinx/cmd/build.py:69 @@ -1115,23 +1073,18 @@ msgid "Exception occurred:" msgstr "抛出异常:" #: sphinx/cmd/build.py:77 -msgid "" -"Please also report this if it was a user error, so that a better error " -"message can be provided next time." +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 -msgid "" -"A bug report can be filed in the tracker at <https://github.com/sphinx-" -"doc/sphinx/issues>. Thanks!" +msgid "A bug report can be filed in the tracker at <https://github.com/sphinx-doc/sphinx/issues>. Thanks!" msgstr "è¯·å‘ Bug 追踪系统 <https://github.com/sphinx-doc/sphinx/issues> 投递 Bug 报告。谢谢ï¼" #: sphinx/cmd/build.py:97 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:106 sphinx/cmd/quickstart.py:497 sphinx/ext/apidoc.py:298 sphinx/ext/autosummary/generate.py:363 msgid "For more information, visit <http://sphinx-doc.org/>." msgstr "更多信æ¯è¯·è®¿é—® <http://sphinx-doc.org/>。" @@ -1152,7 +1105,20 @@ msgid "" "\n" "By default, everything that is outdated is built. Output only for selected\n" "files can be built by specifying individual filenames.\n" -msgstr "\nä»Žæºæ–‡ä»¶ç”Ÿæˆæ–‡æ¡£ã€‚\n\nsphinx-build 从 SOURCEDIR ä¸çš„æ–‡ä»¶ç”Ÿæˆæ–‡æ¡£ï¼Œå¹¶ä¿å˜åœ¨ OUTPUTDIR。\n它从 SOURCEDIR 的“conf.py†ä¸è¯»å–é…置。“sphinx-quickstartâ€å·¥å…·å¯ä»¥ç”Ÿ\næˆåŒ…括“conf.pyâ€åœ¨å†…çš„æ¨¡æ¿æ–‡ä»¶ã€‚\n\nsphinx-build å¯ä»¥ç”Ÿæˆå¤šç§æ ¼å¼çš„æ–‡æ¡£ã€‚åœ¨å‘½ä»¤è¡Œä¸æŒ‡å®šæž„建器åç§°å³å¯\né€‰æ‹©æ–‡æ¡£æ ¼å¼ï¼Œé»˜è®¤æ˜¯ HTML。构建器也å¯ä»¥æ‰§è¡Œæ–‡æ¡£å¤„ç†ç›¸å…³çš„å…¶ä»–\n任务。\n\n默认åªä¼šé‡æ–°æž„建过期内容。如果指定了文件å,那么åªä¼šäº§ç”Ÿè¿™äº›æ–‡ä»¶\n的输出。\n" +msgstr "" +"\n" +"ä»Žæºæ–‡ä»¶ç”Ÿæˆæ–‡æ¡£ã€‚\n" +"\n" +"sphinx-build 从 SOURCEDIR ä¸çš„æ–‡ä»¶ç”Ÿæˆæ–‡æ¡£ï¼Œå¹¶ä¿å˜åœ¨ OUTPUTDIR。\n" +"它从 SOURCEDIR 的“conf.py†ä¸è¯»å–é…置。“sphinx-quickstartâ€å·¥å…·å¯ä»¥ç”Ÿ\n" +"æˆåŒ…括“conf.pyâ€åœ¨å†…çš„æ¨¡æ¿æ–‡ä»¶ã€‚\n" +"\n" +"sphinx-build å¯ä»¥ç”Ÿæˆå¤šç§æ ¼å¼çš„æ–‡æ¡£ã€‚åœ¨å‘½ä»¤è¡Œä¸æŒ‡å®šæž„建器åç§°å³å¯\n" +"é€‰æ‹©æ–‡æ¡£æ ¼å¼ï¼Œé»˜è®¤æ˜¯ HTML。构建器也å¯ä»¥æ‰§è¡Œæ–‡æ¡£å¤„ç†ç›¸å…³çš„å…¶ä»–\n" +"任务。\n" +"\n" +"默认åªä¼šé‡æ–°æž„建过期内容。如果指定了文件å,那么åªä¼šäº§ç”Ÿè¿™äº›æ–‡ä»¶\n" +"的输出。\n" #: sphinx/cmd/build.py:128 msgid "path to documentation source files" @@ -1183,21 +1149,15 @@ msgid "don't use a saved environment, always read all files" msgstr "ä¸ä½¿ç”¨å·²ä¿å˜çš„环境,始终读å–全部文件" #: sphinx/cmd/build.py:146 -msgid "" -"path for the cached environment and doctree files (default: " -"OUTPUTDIR/.doctrees)" +msgid "path for the cached environment and doctree files (default: OUTPUTDIR/.doctrees)" msgstr "缓å˜çŽ¯å¢ƒå’Œ doctree 文件路径(默认:OUTPUTDIR/.doctrees)" #: sphinx/cmd/build.py:149 -msgid "" -"build in parallel with N processes where possible (special value \"auto\" " -"will set N to cpu-count)" +msgid "build in parallel with N processes where possible (special value \"auto\" will set N to cpu-count)" msgstr "如果å¯èƒ½ï¼Œç”¨ N 个进程并行构建文档(如果指定为“autoâ€ï¼Œåˆ™ N 为 CPU æ•°é‡ï¼‰" #: sphinx/cmd/build.py:153 -msgid "" -"path where configuration file (conf.py) is located (default: same as " -"SOURCEDIR)" +msgid "path where configuration file (conf.py) is located (default: same as SOURCEDIR)" msgstr "é…置文件(conf.py)所在目录路径(默认:与 SOURCEDIR 相åŒï¼‰" #: sphinx/cmd/build.py:156 @@ -1348,9 +1308,7 @@ msgid "Please enter a file suffix, e.g. '.rst' or '.txt'." msgstr "请输入文件åŽç¼€ï¼Œä¾‹å¦‚:“.rstâ€æˆ–者“.txtâ€ã€‚" #: sphinx/cmd/quickstart.py:169 -msgid "" -"* Note: non-ASCII characters entered and terminal encoding unknown -- " -"assuming UTF-8 or Latin-1." +msgid "* Note: non-ASCII characters entered and terminal encoding unknown -- assuming UTF-8 or Latin-1." msgstr "* æç¤ºï¼šè¾“å…¥äº†éž ASCII å—ç¬¦å¹¶ä¸”ç»ˆç«¯ç¼–ç æœªçŸ¥â€”—å‡å®šä¸º UTF-8 或 Latin-1。" #: sphinx/cmd/quickstart.py:248 @@ -1363,20 +1321,27 @@ 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 "\n请输入接下æ¥å„项设置的值(如果方括å·ä¸æŒ‡å®šäº†é»˜è®¤å€¼ï¼Œç›´æŽ¥\n按回车å³å¯ä½¿ç”¨é»˜è®¤å€¼ï¼‰ã€‚" +msgstr "" +"\n" +"请输入接下æ¥å„项设置的值(如果方括å·ä¸æŒ‡å®šäº†é»˜è®¤å€¼ï¼Œç›´æŽ¥\n" +"按回车å³å¯ä½¿ç”¨é»˜è®¤å€¼ï¼‰ã€‚" #: sphinx/cmd/quickstart.py:254 #, python-format msgid "" "\n" "Selected root path: %s" -msgstr "\nå·²é€‰æ‹©æ ¹è·¯å¾„ï¼š%s" +msgstr "" +"\n" +"å·²é€‰æ‹©æ ¹è·¯å¾„ï¼š%s" #: sphinx/cmd/quickstart.py:257 msgid "" "\n" "Enter the root path for documentation." -msgstr "\nè¾“å…¥æ–‡æ¡£çš„æ ¹è·¯å¾„ã€‚" +msgstr "" +"\n" +"è¾“å…¥æ–‡æ¡£çš„æ ¹è·¯å¾„ã€‚" #: sphinx/cmd/quickstart.py:259 msgid "Root path for the documentation" @@ -1400,7 +1365,11 @@ msgid "" "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 "\n布置用于ä¿å˜ Sphinx 输出的构建目录,有两ç§é€‰æ‹©ã€‚\nä¸€æ˜¯åœ¨æ ¹è·¯å¾„ä¸‹åˆ›å»ºâ€œ_buildâ€ç›®å½•ï¼ŒäºŒæ˜¯åœ¨æ ¹è·¯å¾„ä¸‹åˆ›å»ºâ€œsourceâ€\n和“buildâ€ä¸¤ä¸ªç‹¬ç«‹çš„目录。" +msgstr "" +"\n" +"布置用于ä¿å˜ Sphinx 输出的构建目录,有两ç§é€‰æ‹©ã€‚\n" +"ä¸€æ˜¯åœ¨æ ¹è·¯å¾„ä¸‹åˆ›å»ºâ€œ_buildâ€ç›®å½•ï¼ŒäºŒæ˜¯åœ¨æ ¹è·¯å¾„ä¸‹åˆ›å»ºâ€œsourceâ€\n" +"和“buildâ€ä¸¤ä¸ªç‹¬ç«‹çš„目录。" #: sphinx/cmd/quickstart.py:278 msgid "Separate source and build directories (y/n)" @@ -1412,7 +1381,11 @@ msgid "" "Inside the root directory, two more directories will be created; \"_templates\"\n" "for custom HTML templates and \"_static\" for custom stylesheets and other static\n" "files. You can enter another prefix (such as \".\") to replace the underscore." -msgstr "\nåœ¨æ ¹ç›®å½•ä¸‹ï¼Œè¿˜ä¼šåˆ›å»ºä¸¤ä¸ªç›®å½•ï¼šâ€œ_templatesâ€ç”¨äºŽè‡ªå®šä¹‰ HTML 模æ¿ã€\n“_staticâ€ç”¨äºŽè‡ªå®šä¹‰ CSS å’Œå…¶ä»–é™æ€æ–‡ä»¶ã€‚ä½ å¯ä»¥è¾“入其他å‰ç¼€ï¼ˆæ¯”如“.â€ï¼‰\n代替默认的下划线。" +msgstr "" +"\n" +"åœ¨æ ¹ç›®å½•ä¸‹ï¼Œè¿˜ä¼šåˆ›å»ºä¸¤ä¸ªç›®å½•ï¼šâ€œ_templatesâ€ç”¨äºŽè‡ªå®šä¹‰ HTML 模æ¿ã€\n" +"“_staticâ€ç”¨äºŽè‡ªå®šä¹‰ CSS å’Œå…¶ä»–é™æ€æ–‡ä»¶ã€‚ä½ å¯ä»¥è¾“入其他å‰ç¼€ï¼ˆæ¯”如“.â€ï¼‰\n" +"代替默认的下划线。" #: sphinx/cmd/quickstart.py:286 msgid "Name prefix for templates and static dir" @@ -1422,7 +1395,9 @@ msgstr "模æ¿ç›®å½•åå’Œé™æ€ç›®å½•åçš„å‰ç¼€" msgid "" "\n" "The project name will occur in several places in the built documentation." -msgstr "\n项目å称会出现在文档的许多地方。" +msgstr "" +"\n" +"项目å称会出现在文档的许多地方。" #: sphinx/cmd/quickstart.py:291 msgid "Project name" @@ -1440,7 +1415,12 @@ msgid "" "Python the version is something like 2.5 or 3.0, while the release is\n" "something like 2.5.1 or 3.0a1. If you don't need this dual structure,\n" "just set both to the same value." -msgstr "\n在 Sphinx ä¸ï¼Œä¼šåŒºåˆ†â€œç‰ˆæœ¬â€å’Œâ€œå‘行版本â€ä¸¤ä¸ªæ¦‚念。åŒä¸€ç‰ˆæœ¬å¯ä»¥\n有多个å‘行版本。例如,Python 版本å¯ä»¥æ˜¯ 2.5 或 3.0,而å‘行版\n本则是 2.5.1 或 3.0a1ã€‚å¦‚æžœä½ ä¸éœ€è¦è¿™æ ·çš„åŒé‡ç‰ˆæœ¬ç»“构,请把这\n两个选项设置为相åŒå€¼ã€‚" +msgstr "" +"\n" +"在 Sphinx ä¸ï¼Œä¼šåŒºåˆ†â€œç‰ˆæœ¬â€å’Œâ€œå‘行版本â€ä¸¤ä¸ªæ¦‚念。åŒä¸€ç‰ˆæœ¬å¯ä»¥\n" +"有多个å‘行版本。例如,Python 版本å¯ä»¥æ˜¯ 2.5 或 3.0,而å‘行版\n" +"本则是 2.5.1 或 3.0a1ã€‚å¦‚æžœä½ ä¸éœ€è¦è¿™æ ·çš„åŒé‡ç‰ˆæœ¬ç»“构,请把这\n" +"两个选项设置为相åŒå€¼ã€‚" #: sphinx/cmd/quickstart.py:302 msgid "Project version" @@ -1470,7 +1450,10 @@ 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 "\næºæ–‡ä»¶çš„æ–‡ä»¶ååŽç¼€ã€‚一般是“.txtâ€æˆ–“.rstâ€ã€‚åªæœ‰æ¤åŽç¼€çš„æ–‡ä»¶æ‰ä¼š\nè¢«è§†ä¸ºæ–‡æ¡£çš„æºæ–‡ä»¶ã€‚" +msgstr "" +"\n" +"æºæ–‡ä»¶çš„æ–‡ä»¶ååŽç¼€ã€‚一般是“.txtâ€æˆ–“.rstâ€ã€‚åªæœ‰æ¤åŽç¼€çš„æ–‡ä»¶æ‰ä¼š\n" +"è¢«è§†ä¸ºæ–‡æ¡£çš„æºæ–‡ä»¶ã€‚" #: sphinx/cmd/quickstart.py:322 msgid "Source file suffix" @@ -1483,7 +1466,11 @@ msgid "" "\"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 "\næœ‰ä¸€ä¸ªç‰¹æ®Šçš„æ–‡æ¡£å°†è¢«è§†ä¸ºâ€œç›®å½•æ ‘â€çš„æ ‘é¡¶èŠ‚ç‚¹ï¼Œä¹Ÿå³æ˜¯æ–‡æ¡£å±‚级\nç»“æž„çš„æ ¹ã€‚ä¸€èˆ¬ç”¨â€œindexâ€ä½œä¸ºè¿™ä¸ªç‰¹æ®Šæ–‡æ¡£ï¼Œå¦‚æžœä½ çš„â€œindexâ€æ–‡\n档使用了自定义模æ¿ï¼Œä½ 也å¯ä»¥æŒ‡å®šå…¶ä»–的文件å。" +msgstr "" +"\n" +"æœ‰ä¸€ä¸ªç‰¹æ®Šçš„æ–‡æ¡£å°†è¢«è§†ä¸ºâ€œç›®å½•æ ‘â€çš„æ ‘é¡¶èŠ‚ç‚¹ï¼Œä¹Ÿå³æ˜¯æ–‡æ¡£å±‚级\n" +"ç»“æž„çš„æ ¹ã€‚ä¸€èˆ¬ç”¨â€œindexâ€ä½œä¸ºè¿™ä¸ªç‰¹æ®Šæ–‡æ¡£ï¼Œå¦‚æžœä½ çš„â€œindexâ€æ–‡\n" +"档使用了自定义模æ¿ï¼Œä½ 也å¯ä»¥æŒ‡å®šå…¶ä»–的文件å。" #: sphinx/cmd/quickstart.py:330 msgid "Name of your master document (without suffix)" @@ -1491,8 +1478,7 @@ msgstr "主文档文件å(ä¸å«åŽç¼€ï¼‰" #: sphinx/cmd/quickstart.py:336 #, python-format -msgid "" -"Error: the master file %s has already been found in the selected root path." +msgid "Error: the master file %s has already been found in the selected root path." msgstr "é”™è¯¯ï¼šé€‰æ‹©çš„æ ¹ç›®å½•ä¸‹å·²å˜åœ¨ä¸»æ–‡æ¡£æ–‡ä»¶ %s。" #: sphinx/cmd/quickstart.py:338 @@ -1500,8 +1486,7 @@ msgid "sphinx-quickstart will not overwrite the existing file." msgstr "sphinx-quickstart ä¸ä¼šè¦†ç›–已有的文件。" #: sphinx/cmd/quickstart.py:340 -msgid "" -"Please enter a new file name, or rename the existing file and press Enter" +msgid "Please enter a new file name, or rename the existing file and press Enter" msgstr "请输入新文件å,若è¦é‡å‘½å现有文件请按回车" #: sphinx/cmd/quickstart.py:344 @@ -1509,9 +1494,7 @@ msgid "Indicate which of the following Sphinx extensions should be enabled:" msgstr "å¯ç”¨ Sphinx 扩展:" #: sphinx/cmd/quickstart.py:353 -msgid "" -"Note: imgmath and mathjax cannot be enabled at the same time. imgmath has " -"been deselected." +msgid "Note: imgmath and mathjax cannot be enabled at the same time. imgmath has been deselected." msgstr "注æ„:imgmath å’Œ mathjax ä¸èƒ½åŒæ—¶å¯ç”¨ã€‚已喿¶ˆé€‰æ‹© imgmath。" #: sphinx/cmd/quickstart.py:358 @@ -1520,7 +1503,10 @@ msgid "" "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 "\nç”Ÿæˆ Makefile å’Œ Windows æ‰¹å¤„ç†æ–‡ä»¶ï¼Œå¯ä»¥ç›´æŽ¥åƒâ€œmake htmlâ€è¿™æ ·\nè¿è¡Œï¼Œè€Œä¸éœ€è¦ç›´æŽ¥è°ƒç”¨ sphinx-build。" +msgstr "" +"\n" +"ç”Ÿæˆ Makefile å’Œ Windows æ‰¹å¤„ç†æ–‡ä»¶ï¼Œå¯ä»¥ç›´æŽ¥åƒâ€œmake htmlâ€è¿™æ ·\n" +"è¿è¡Œï¼Œè€Œä¸éœ€è¦ç›´æŽ¥è°ƒç”¨ sphinx-build。" #: sphinx/cmd/quickstart.py:362 msgid "Create Makefile? (y/n)" @@ -1556,19 +1542,21 @@ msgstr "\nä½ çŽ°åœ¨å¯ä»¥å¡«å†™ä¸»æ–‡æ¡£æ–‡ä»¶ %s å¹¶åˆ›å»ºå…¶ä»–æ–‡æ¡£æºæ–‡ä»¶ msgid "" "Use the Makefile to build the docs, like so:\n" " make builder\n" -msgstr "用 Makefile 构建文档,åƒè¿™æ ·ï¼š\n make builder\n" +msgstr "" +"用 Makefile 构建文档,åƒè¿™æ ·ï¼š\n" +" make builder\n" #: sphinx/cmd/quickstart.py:455 #, python-format msgid "" "Use the sphinx-build command to build the docs, like so:\n" " sphinx-build -b builder %s %s\n" -msgstr "用 sphinx-build 命令构建文档,åƒè¿™æ ·ï¼š\n sphinx-build -b builder %s %s\n" +msgstr "" +"用 sphinx-build 命令构建文档,åƒè¿™æ ·ï¼š\n" +" sphinx-build -b builder %s %s\n" #: sphinx/cmd/quickstart.py:458 -msgid "" -"where \"builder\" is one of the supported builders, e.g. html, latex or " -"linkcheck.\n" +msgid "where \"builder\" is one of the supported builders, e.g. html, latex or linkcheck.\n" msgstr "æ¤å¤„的“builderâ€æ˜¯æ”¯æŒçš„æž„建器å,比如 htmlã€latex 或 linkcheck。\n" #: sphinx/cmd/quickstart.py:498 @@ -1579,7 +1567,12 @@ msgid "" "sphinx-quickstart is an interactive tool that asks some questions about your\n" "project and then generates a complete documentation directory and sample\n" "Makefile to be used with sphinx-build.\n" -msgstr "\nç”Ÿæˆ Sphinx 项目的必需文件。\n\nsphinx-quickstart 是一个交互å¼å·¥å…·ï¼Œè¯¢é—®ä¸€äº›å…³äºŽé¡¹ç›®çš„问题,生æˆ\n完整的文档目录和用于 sphinx-build 的示例 Makefile。\n" +msgstr "" +"\n" +"ç”Ÿæˆ Sphinx 项目的必需文件。\n" +"\n" +"sphinx-quickstart 是一个交互å¼å·¥å…·ï¼Œè¯¢é—®ä¸€äº›å…³äºŽé¡¹ç›®çš„问题,生æˆ\n" +"完整的文档目录和用于 sphinx-build 的示例 Makefile。\n" #: sphinx/cmd/quickstart.py:508 msgid "quiet mode" @@ -1695,14 +1688,11 @@ msgid "\"quiet\" is specified, but any of \"project\" or \"author\" is not speci msgstr "指定了“quietâ€ï¼Œä½†æ˜¯æ²¡æœ‰æŒ‡å®šâ€œprojectâ€å’Œâ€œauthorâ€ã€‚" #: sphinx/cmd/quickstart.py:618 -msgid "" -"Error: specified path is not a directory, or sphinx files already exist." +msgid "Error: specified path is not a directory, or sphinx files already exist." msgstr "é”™è¯¯ï¼šæŒ‡å®šçš„è·¯å¾„ä¸æ˜¯ä¸€ä¸ªç›®å½•,或是 Sphinx 文件已å˜åœ¨ã€‚" #: sphinx/cmd/quickstart.py:620 -msgid "" -"sphinx-quickstart only generate into a empty directory. Please specify a new" -" root path." +msgid "sphinx-quickstart only generate into a empty directory. Please specify a new root path." msgstr "sphinx-quickstart åªä¼šåœ¨ç©ºç›®å½•ä¸ç”Ÿæˆæ–‡ä»¶ã€‚è¯·æŒ‡å®šä¸€ä¸ªæ–°çš„æ ¹è·¯å¾„ã€‚" #: sphinx/cmd/quickstart.py:635 @@ -1719,8 +1709,7 @@ msgstr "检测到过度的去缩进" msgid "Invalid caption: %s" msgstr "æ— æ•ˆçš„æ ‡é¢˜ï¼š%s" -#: sphinx/directives/code.py:140 sphinx/directives/code.py:292 -#: sphinx/directives/code.py:462 +#: sphinx/directives/code.py:140 sphinx/directives/code.py:292 sphinx/directives/code.py:462 #, python-format msgid "line number spec is out of range(1-%d): %r" msgstr "指定的行å·è¶…出范围(1-%d):%r" @@ -1777,18 +1766,15 @@ msgstr "作者: " msgid "%s %s" msgstr "%s %s" -#: 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:64 sphinx/domains/cpp.py:6445 sphinx/domains/python.py:210 sphinx/ext/napoleon/docstring.py:696 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:67 sphinx/domains/cpp.py:6454 sphinx/domains/javascript.py:209 sphinx/domains/python.py:222 msgid "Returns" msgstr "返回" -#: sphinx/domains/c.py:69 sphinx/domains/javascript.py:211 -#: sphinx/domains/python.py:224 +#: sphinx/domains/c.py:69 sphinx/domains/javascript.py:211 sphinx/domains/python.py:224 msgid "Return type" msgstr "返回类型" @@ -1817,8 +1803,7 @@ msgstr "%s (C 类型)" msgid "%s (C variable)" msgstr "%s (C å˜é‡)" -#: sphinx/domains/c.py:258 sphinx/domains/cpp.py:7029 -#: sphinx/domains/javascript.py:297 sphinx/domains/python.py:742 +#: sphinx/domains/c.py:258 sphinx/domains/cpp.py:7029 sphinx/domains/javascript.py:297 sphinx/domains/python.py:742 msgid "function" msgstr "函数" @@ -1858,7 +1843,9 @@ msgstr "%s 版åŽå·²ç§»é™¤" msgid "" "Duplicate declaration, also defined in '%s'.\n" "Declaration is '%s'." -msgstr "é‡å¤çš„声明,已ç»åœ¨â€œ%sâ€å¤„定义。\n定义为“%sâ€ã€‚" +msgstr "" +"é‡å¤çš„声明,已ç»åœ¨â€œ%sâ€å¤„定义。\n" +"定义为“%sâ€ã€‚" #: sphinx/domains/cpp.py:6448 msgid "Template Parameters" @@ -1873,8 +1860,7 @@ msgstr "抛出" msgid "%s (C++ %s)" msgstr "%s (C++ %s)" -#: sphinx/domains/cpp.py:7027 sphinx/domains/javascript.py:299 -#: sphinx/domains/python.py:744 +#: sphinx/domains/cpp.py:7027 sphinx/domains/javascript.py:299 sphinx/domains/python.py:744 msgid "class" msgstr "ç±»" @@ -1899,7 +1885,9 @@ msgstr "枚举å" msgid "" "Duplicate declaration, also defined in '%s'.\n" "Name of declaration is '%s'." -msgstr "é‡å¤çš„声明,已ç»åœ¨â€œ%sâ€å¤„定义。\n声明å称为“%sâ€ã€‚" +msgstr "" +"é‡å¤çš„声明,已ç»åœ¨â€œ%sâ€å¤„定义。\n" +"声明å称为“%sâ€ã€‚" #: sphinx/domains/javascript.py:130 sphinx/domains/python.py:430 #, python-format @@ -1947,8 +1935,7 @@ msgstr "æ•°æ®" msgid "attribute" msgstr "属性" -#: sphinx/domains/javascript.py:302 sphinx/domains/python.py:49 -#: sphinx/domains/python.py:750 +#: sphinx/domains/javascript.py:302 sphinx/domains/python.py:49 sphinx/domains/python.py:750 msgid "module" msgstr "模å—" @@ -1994,8 +1981,7 @@ msgstr "å˜é‡" 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:431 sphinx/domains/python.py:489 sphinx/domains/python.py:501 sphinx/domains/python.py:514 #, python-format msgid "%s() (in module %s)" msgstr "%s() (在 %s 模å—ä¸)" @@ -2104,9 +2090,7 @@ msgstr "环境å˜é‡; %s" #: sphinx/domains/std.py:166 #, python-format -msgid "" -"Malformed option description %r, should look like \"opt\", \"-opt args\", \"" -"--opt args\", \"/opt args\" or \"+opt args\"" +msgid "Malformed option description %r, should look like \"opt\", \"-opt args\", \"--opt args\", \"/opt args\" or \"+opt args\"" msgstr "畸形的选项æè¿° %r,应是“optâ€ã€â€œ-opt argsâ€ã€â€œ--opt argsâ€ã€â€œ/opt argsâ€æˆ–“+opt argsâ€å½¢å¼" #: sphinx/domains/std.py:207 @@ -2206,9 +2190,7 @@ msgid "source directory has changed" msgstr "æºæ–‡ä»¶ç›®å½•å·²å˜åŒ–" #: sphinx/environment/__init__.py:283 -msgid "" -"This environment is incompatible with the selected builder, please choose " -"another doctree directory." +msgid "This environment is incompatible with the selected builder, please choose another doctree directory." msgstr "本环境与选择的构建器ä¸å…¼å®¹ï¼Œè¯·é€‰æ‹©å…¶ä»–çš„æ–‡æ¡£æ ‘ç›®å½•ã€‚" #: sphinx/environment/__init__.py:404 @@ -2244,8 +2226,7 @@ msgstr "å‚è§ %s" msgid "unknown index entry type %r" msgstr "未知的索引æ¡ç›®ç±»åž‹ %r" -#: sphinx/environment/adapters/indexentries.py:156 -#: sphinx/templates/latex/sphinxmessages.sty_t:11 +#: sphinx/environment/adapters/indexentries.py:156 sphinx/templates/latex/sphinxmessages.sty_t:11 msgid "Symbols" msgstr "符å·" @@ -2256,9 +2237,7 @@ msgstr "åœ¨æ–‡æ¡£æ ‘ä¸æ£€æµ‹åˆ°å¾ªçŽ¯å¼•ç”¨ï¼Œå·²å¿½ç•¥ï¼š%s <- %s" #: sphinx/environment/adapters/toctree.py:172 #, python-format -msgid "" -"toctree contains reference to document %r that doesn't have a title: no link" -" will be generated" +msgid "toctree contains reference to document %r that doesn't have a title: no link will be generated" msgstr "ç›®å½•æ ‘å¼•ç”¨çš„æ–‡æ¡£ %r ç¼ºå°‘æ ‡é¢˜ï¼šä¸ä¼šç”Ÿæˆé“¾æŽ¥" #: sphinx/environment/adapters/toctree.py:178 @@ -2306,15 +2285,21 @@ msgid "" "excluded from generation.\n" "\n" "Note: By default this script will not overwrite already created files." -msgstr "\n在 <MODULE_PATH> ä¸é€’归查找 Python 模å—和包,然åŽåœ¨ <OUTPUT_PATH> ä¸ä¸ºæ¯ä¸ªä½¿ç”¨äº†\nautomodule 指令的包创建一个 reST 文件。\n\n<EXCLUDE_PATTERN> å¯ä»¥æŽ’除生æˆç¬¦åˆè§„则的文件/目录的文档。\n\næç¤ºï¼šæœ¬è„šæœ¬é»˜è®¤ä¸ä¼šè¦†ç›–已有文件。" +msgstr "" +"\n" +"在 <MODULE_PATH> ä¸é€’归查找 Python 模å—和包,然åŽåœ¨ <OUTPUT_PATH> ä¸ä¸ºæ¯ä¸ªä½¿ç”¨äº†\n" +"automodule 指令的包创建一个 reST 文件。\n" +"\n" +"<EXCLUDE_PATTERN> å¯ä»¥æŽ’除生æˆç¬¦åˆè§„则的文件/目录的文档。\n" +"\n" +"æç¤ºï¼šæœ¬è„šæœ¬é»˜è®¤ä¸ä¼šè¦†ç›–已有文件。" #: sphinx/ext/apidoc.py:312 msgid "path to module to document" msgstr "è¦ç”Ÿæˆæ–‡æ¡£çš„æ¨¡å—路径" #: sphinx/ext/apidoc.py:314 -msgid "" -"fnmatch-style file and/or directory patterns to exclude from generation" +msgid "fnmatch-style file and/or directory patterns to exclude from generation" msgstr "排除的文件/目录,fnmatch é£Žæ ¼çš„è§„åˆ™" #: sphinx/ext/apidoc.py:319 @@ -2330,9 +2315,7 @@ msgid "overwrite existing files" msgstr "覆盖已有文件" #: sphinx/ext/apidoc.py:328 -msgid "" -"follow symbolic links. Powerful when combined with " -"collective.recipe.omelette." +msgid "follow symbolic links. Powerful when combined with collective.recipe.omelette." msgstr "éµå¾ªç¬¦å·é“¾æŽ¥ã€‚é…åˆ collective.recipe.omelette ä½¿ç”¨å°¤å…¶å¥æ•ˆã€‚" #: sphinx/ext/apidoc.py:331 @@ -2356,9 +2339,7 @@ msgid "don't create a table of contents file" msgstr "ä¸åˆ›å»ºç›®å½•文件" #: sphinx/ext/apidoc.py:344 -msgid "" -"don't create headings for the module/package packages (e.g. when the " -"docstrings already contain them)" +msgid "don't create headings for the module/package packages (e.g. when the docstrings already contain them)" msgstr "ä¸åˆ›å»ºæ¨¡å—/åŒ…çš„æ ‡é¢˜ï¼ˆæ¯”å¦‚å½“ Docstring ä¸å·²ç»æœ‰æ ‡é¢˜æ—¶ï¼Œå¯ä»¥ä½¿ç”¨è¿™ä¸ªé€‰é¡¹ï¼‰" #: sphinx/ext/apidoc.py:349 @@ -2366,9 +2347,7 @@ msgid "put module documentation before submodule documentation" msgstr "æ¨¡å—æ–‡æ¡£å…ˆäºŽåæ¨¡å—æ–‡æ¡£" #: sphinx/ext/apidoc.py:353 -msgid "" -"interpret module paths according to PEP-0420 implicit namespaces " -"specification" +msgid "interpret module paths according to PEP-0420 implicit namespaces specification" msgstr "æ ¹æ® PEP-0420 éšå¼å‘½å空间规范解释模å—路径" #: sphinx/ext/apidoc.py:357 @@ -2415,9 +2394,7 @@ msgstr "æ— æ•ˆçš„æ£åˆ™è¡¨è¾¾å¼ %r 在 %s" #: sphinx/ext/coverage.py:55 #, python-format -msgid "" -"Testing of coverage in the sources finished, look at the results in " -"%(outdir)spython.txt." +msgid "Testing of coverage in the sources finished, look at the results in %(outdir)spython.txt." msgstr "å·²å®Œæˆæºæ–‡ä»¶çš„覆盖率测试,请在 %(outdir)s/python.txt 䏿Ÿ¥çœ‹ç»“果。" #: sphinx/ext/coverage.py:70 @@ -2451,9 +2428,7 @@ msgstr "æ— æ•ˆçš„ TestCode 类型" #: sphinx/ext/doctest.py:283 #, python-format -msgid "" -"Testing of doctests in the sources finished, look at the results in " -"%(outdir)s/output.txt." +msgid "Testing of doctests in the sources finished, look at the results in %(outdir)s/output.txt." msgstr "å·²å®Œæˆæºæ–‡ä»¶çš„æ–‡æ¡£æµ‹è¯•,请在 %(outdir)s/output.txt 䏿Ÿ¥çœ‹ç»“果。" #: sphinx/ext/doctest.py:446 @@ -2491,9 +2466,7 @@ msgstr "dot没有生æˆè¾“出文件:\n[stderr]\n%r\n[stdout]\n%r" #: sphinx/ext/graphviz.py:254 #, python-format -msgid "" -"dot command %r cannot be run (needed for graphviz output), check the " -"graphviz_dot setting" +msgid "dot command %r cannot be run (needed for graphviz output), check the graphviz_dot setting" msgstr "æ— æ³•è¿è¡Œ Dot 命令 %r (Graphviz 输出所需),请检查 graphviz_dot é…ç½®" #: sphinx/ext/graphviz.py:261 @@ -2511,8 +2484,7 @@ msgstr "点退出错误:\n[stderr]\n%r\n[stdout]\n%r" msgid "graphviz_output_format must be one of 'png', 'svg', but is %r" msgstr "graphviz_output_format 必须是 'png' 或 'svg' ä¸ä¹‹ä¸€ï¼ŒçŽ°ä¸º %r" -#: sphinx/ext/graphviz.py:275 sphinx/ext/graphviz.py:329 -#: sphinx/ext/graphviz.py:367 +#: sphinx/ext/graphviz.py:275 sphinx/ext/graphviz.py:329 sphinx/ext/graphviz.py:367 #, python-format msgid "dot code %r: %s" msgstr "点 代ç %r: %s" @@ -2543,16 +2515,12 @@ msgstr "转æ¢é€€å‡ºæ—¶å‡ºé”™:\n[stderr]\n%r\n[stdout]\n%r" #: sphinx/ext/imgmath.py:139 #, python-format -msgid "" -"LaTeX command %r cannot be run (needed for math display), check the " -"imgmath_latex setting" +msgid "LaTeX command %r cannot be run (needed for math display), check the imgmath_latex setting" msgstr "æ— æ³•è¿è¡Œ LaTeX 命令 %r (数å¦å…¬å¼æ˜¾ç¤ºå¿…需),请检查 imgmath_latex 设置" #: sphinx/ext/imgmath.py:154 #, python-format -msgid "" -"%s command %r cannot be run (needed for math display), check the imgmath_%s " -"setting" +msgid "%s command %r cannot be run (needed for math display), check the imgmath_%s setting" msgstr "æ— æ³•è¿è¡Œ %s 命令 %r (数å¦å…¬å¼æ˜¾ç¤ºå¿…需),请检查 imgmath_%s 设置" #: sphinx/ext/imgmath.py:289 @@ -2680,14 +2648,15 @@ msgstr "属性 %s ä¸å˜åœ¨ï¼Œåœ¨å¯¹è±¡ %s 上" msgid "" "autodoc: failed to determine %r to be documented.the following exception was raised:\n" "%s" -msgstr "autodocï¼šæ— æ³•åˆ¤æ–æ˜¯å¦ç”Ÿæˆ %r 的文档。抛出了下列异常:\n%s" +msgstr "" +"autodocï¼šæ— æ³•åˆ¤æ–æ˜¯å¦ç”Ÿæˆ %r 的文档。抛出了下列异常:\n" +"%s" #: sphinx/ext/autodoc/__init__.py:692 #, python-format msgid "" -"don't know which module to import for autodocumenting %r (try placing a " -"\"module\" or \"currentmodule\" directive in the document, or giving an " -"explicit module name)" +"don't know which module to import for autodocumenting %r (try placing a \"module\" or \"currentmodule\" directive in the document, or giving an explicit module " +"name)" msgstr "æ— æ³•åˆ¤æ–å¯¼å…¥å“ªä¸ªæ¨¡å—æ¥è‡ªåŠ¨ç”Ÿæˆæ–‡æ¡£ %r(å°è¯•在文档ä¸ä½¿ç”¨â€œmoduleâ€æˆ–“currentmoduleâ€æŒ‡ä»¤ï¼Œæˆ–者显å¼ç»™å®šæ¨¡å—å)" #: sphinx/ext/autodoc/__init__.py:786 @@ -2701,15 +2670,12 @@ msgstr "automodule %s 给定了函数ç¾å傿•°æˆ–è¿”å›žç±»åž‹æ ‡æ³¨" #: sphinx/ext/autodoc/__init__.py:827 #, python-format -msgid "" -"__all__ should be a list of strings, not %r (in module %s) -- ignoring " -"__all__" +msgid "__all__ should be a list of strings, not %r (in module %s) -- ignoring __all__" msgstr "__all__ 应是一个å—ç¬¦ä¸²åˆ—è¡¨ï¼Œè€Œä¸æ˜¯ %r ï¼ˆå‡ºçŽ°åœ¨æ¨¡å— %s ä¸ï¼‰ -- 已忽略__all__" #: sphinx/ext/autodoc/__init__.py:842 #, python-format -msgid "" -"missing attribute mentioned in :members: or __all__: module %s, attribute %s" +msgid "missing attribute mentioned in :members: or __all__: module %s, attribute %s" msgstr ":members: 或 __all__ æåŠçš„属性ä¸å˜åœ¨ï¼šæ¨¡å— %s,属性 %s" #: sphinx/ext/autodoc/__init__.py:1126 @@ -2753,9 +2719,7 @@ msgid "failed to import object %s" msgstr "æ— æ³•å¯¼å…¥å¯¹è±¡ %s" #: sphinx/ext/autosummary/__init__.py:702 -msgid "" -"autosummary generats .rst files internally. But your source_suffix does not " -"contain .rst. Skipped." +msgid "autosummary generats .rst files internally. But your source_suffix does not contain .rst. Skipped." msgstr "autosummary å†…éƒ¨ç”Ÿæˆ .rst 文件,但是 source_suffix ä¸ä¸åŒ…å« .rst,已跳过。" #: sphinx/ext/autosummary/generate.py:100 @@ -2781,7 +2745,17 @@ msgid "" "``sphinx.ext.autosummary`` Python module and can be read using::\n" "\n" " pydoc sphinx.ext.autosummary\n" -msgstr "\n用 autosummary æŒ‡ä»¤ç”Ÿæˆ ReStructuredText\n\nsphinx-autogen 是 sphinx.ext.autosummary.generate çš„å‰ç«¯ï¼Œå®ƒæ ¹æ®ç»™å®š\n的输入文件ä¸çš„ autosummary æŒ‡ä»¤ç”Ÿæˆ reStructuredText 文件\n\nautosummary æŒ‡ä»¤çš„æ ¼å¼è§ Python æ¨¡å— ``sphinx.ext.autosummary`` 的文\n档,并且å¯ä»¥è¿™æ ·è°ƒå‡ºæ–‡æ¡£é˜…读::\n\n pydoc sphinx.ext.autosummary\n" +msgstr "" +"\n" +"用 autosummary æŒ‡ä»¤ç”Ÿæˆ ReStructuredText\n" +"\n" +"sphinx-autogen 是 sphinx.ext.autosummary.generate çš„å‰ç«¯ï¼Œå®ƒæ ¹æ®ç»™å®š\n" +"的输入文件ä¸çš„ autosummary æŒ‡ä»¤ç”Ÿæˆ reStructuredText 文件\n" +"\n" +"autosummary æŒ‡ä»¤çš„æ ¼å¼è§ Python æ¨¡å— ``sphinx.ext.autosummary`` 的文\n" +"档,并且å¯ä»¥è¿™æ ·è°ƒå‡ºæ–‡æ¡£é˜…读::\n" +"\n" +" pydoc sphinx.ext.autosummary\n" #: sphinx/ext/autosummary/generate.py:381 msgid "source files to generate rST files for" @@ -2878,8 +2852,7 @@ msgstr "尿Ѐ巧" msgid "Warning" msgstr "è¦å‘Š" -#: sphinx/templates/latex/longtable.tex_t:23 -#: sphinx/templates/latex/sphinxmessages.sty_t:8 +#: sphinx/templates/latex/longtable.tex_t:23 sphinx/templates/latex/sphinxmessages.sty_t:8 msgid "continued from previous page" msgstr "ç»ä¸Šé¡µ" @@ -2903,13 +2876,11 @@ 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:46 sphinx/themes/basic/globaltoc.html:10 sphinx/themes/basic/localtoc.html:11 sphinx/themes/scrolls/layout.html:41 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/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 msgid "Search" msgstr "æœç´¢" @@ -2971,9 +2942,7 @@ msgstr "所的函数,类,术è¯" msgid "Index – %(key)s" msgstr "索引 – %(key)s" -#: 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-single.html:61 sphinx/themes/basic/genindex-split.html:24 sphinx/themes/basic/genindex-split.html:38 #: sphinx/themes/basic/genindex.html:72 msgid "Full index on one page" msgstr "一页的全部索引" @@ -3020,9 +2989,7 @@ msgstr "æœ€åŽæ›´æ–°äºŽ %(last_updated)s." #: sphinx/themes/basic/layout.html:210 #, python-format -msgid "" -"Created using <a href=\"http://sphinx-doc.org/\">Sphinx</a> " -"%(sphinx_version)s." +msgid "Created using <a href=\"http://sphinx-doc.org/\">Sphinx</a> %(sphinx_version)s." msgstr "ç”± <a href=\"http://sphinx-doc.org/\">Sphinx</a> %(sphinx_version)s 创建。" #: sphinx/themes/basic/opensearch.xml:4 @@ -3060,23 +3027,16 @@ msgid "" " containing fewer words won't appear in the result list." msgstr "åœ¨è¿™å„¿ï¼Œä½ å¯ä»¥å¯¹è¿™äº›æ–‡æ¡£è¿›è¡Œæœç´¢ã€‚呿œç´¢æ¡†ä¸è¾“å…¥ä½ æ‰€è¦æœç´¢çš„关键å—并点击“æœç´¢â€ã€‚注æ„:æœç´¢å¼•擎会自动æœç´¢æ‰€æœ‰çš„关键å—。将ä¸ä¼šæœç´¢åˆ°éƒ¨åˆ†å…³é”®å—的页é¢." -#: sphinx/themes/basic/search.html:37 -#: sphinx/themes/basic/searchresults.html:17 +#: sphinx/themes/basic/search.html:37 sphinx/themes/basic/searchresults.html:17 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:41 sphinx/themes/basic/searchresults.html:21 sphinx/themes/basic/static/searchtools.js:285 msgid "Search Results" msgstr "æœç´¢ç»“æžœ" -#: sphinx/themes/basic/search.html:43 -#: sphinx/themes/basic/searchresults.html:23 -#: sphinx/themes/basic/static/searchtools.js:287 -msgid "" -"Your search did not match any documents. Please make sure that all words are" -" spelled correctly and that you've selected enough categories." +#: sphinx/themes/basic/search.html:43 sphinx/themes/basic/searchresults.html:23 sphinx/themes/basic/static/searchtools.js:287 +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 @@ -3087,8 +3047,7 @@ msgstr "快速æœç´¢" msgid "This Page" msgstr "本页" -#: sphinx/themes/basic/changes/frameset.html:5 -#: sphinx/themes/basic/changes/versionchanges.html:12 +#: sphinx/themes/basic/changes/frameset.html:5 sphinx/themes/basic/changes/versionchanges.html:12 #, python-format msgid "Changes in Version %(version)s — %(docstitle)s" msgstr "更改å‘生在版本 %(version)s— %(docstitle)s" @@ -3115,15 +3074,11 @@ msgstr "C API 更改" 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:454 sphinx/writers/html.py:459 sphinx/writers/html5.py:400 sphinx/writers/html5.py:405 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:134 sphinx/writers/html.py:145 sphinx/writers/html5.py:103 sphinx/writers/html5.py:114 msgid "Permalink to this definition" msgstr "æ°¸ä¹…é“¾æŽ¥è‡³ç›®æ ‡" @@ -3152,8 +3107,7 @@ msgstr ", 在 " msgid "Expand sidebar" msgstr "展开边æ " -#: sphinx/themes/classic/static/sidebar.js_t:96 -#: sphinx/themes/classic/static/sidebar.js_t:124 +#: sphinx/themes/classic/static/sidebar.js_t:96 sphinx/themes/classic/static/sidebar.js_t:124 msgid "Collapse sidebar" msgstr "折å è¾¹æ " @@ -3163,8 +3117,7 @@ msgstr "目录" #: sphinx/transforms/__init__.py:261 #, python-format -msgid "" -"4 column based index found. It might be a bug of extensions you use: %r" +msgid "4 column based index found. It might be a bug of extensions you use: %r" msgstr "å‘现使用了 4 列布局的索引页。å¯èƒ½æ˜¯ä½ 所用的扩展出现了 Bug:%r" #: sphinx/transforms/__init__.py:303 @@ -3177,27 +3130,19 @@ msgid "Footnote [#] is not referenced." msgstr "脚注 [#] 没有被引用过。" #: sphinx/transforms/i18n.py:293 sphinx/transforms/i18n.py:363 -msgid "" -"inconsistent footnote references in translated message. original: {0}, " -"translated: {1}" +msgid "inconsistent footnote references in translated message. original: {0}, translated: {1}" msgstr "译文ä¸çš„脚注引用与原文ä¸ä¸€è‡´ã€‚原始为:{0},翻译åŽä¸ºï¼š{1}" #: sphinx/transforms/i18n.py:335 -msgid "" -"inconsistent references in translated message. original: {0}, translated: " -"{1}" +msgid "inconsistent references in translated message. original: {0}, translated: {1}" msgstr "译文ä¸çš„引用与原文ä¸ä¸€è‡´ã€‚原始为:{0},翻译åŽä¸ºï¼š{1}" #: sphinx/transforms/i18n.py:382 -msgid "" -"inconsistent citation references in translated message. original: {0}, " -"translated: {1}" +msgid "inconsistent citation references in translated message. original: {0}, translated: {1}" msgstr "译文ä¸çš„引文引用与原文ä¸ä¸€è‡´ã€‚原始为:{0},翻译åŽä¸ºï¼š{1}" #: sphinx/transforms/i18n.py:402 -msgid "" -"inconsistent term references in translated message. original: {0}, " -"translated: {1}" +msgid "inconsistent term references in translated message. original: {0}, translated: {1}" msgstr "译文ä¸çš„æœ¯è¯å¼•用与原文ä¸ä¸€è‡´ã€‚原始为:{0},翻译åŽä¸ºï¼š{1}" #: sphinx/transforms/post_transforms/__init__.py:110 @@ -3259,9 +3204,7 @@ msgstr "写入时å‘生错误:%s,%s" #: sphinx/util/i18n.py:215 #, python-format -msgid "" -"Invalid date format. Quote the string by single quote if you want to output " -"it directly: %s" +msgid "Invalid date format. Quote the string by single quote if you want to output it directly: %s" msgstr "æ— æ•ˆçš„æ—¥æœŸæ ¼å¼ã€‚å¦‚æžœä½ æƒ³ç›´æŽ¥è¾“å‡ºæ—¥æœŸå—符串,请用å•引å·ï¼š%s" #: sphinx/util/nodes.py:428 @@ -3330,12 +3273,10 @@ msgid "document title is not a single Text node" msgstr "æ–‡æ¡£æ ‡é¢˜ä¸æ˜¯ä¸€ä¸ªå•纯文本节点" #: sphinx/writers/latex.py:926 sphinx/writers/texinfo.py:658 -msgid "" -"encountered title node not in section, topic, table, admonition or sidebar" +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:1102 sphinx/writers/manpage.py:277 sphinx/writers/texinfo.py:675 msgid "Footnotes" msgstr "脚注" diff --git a/sphinx/make_mode.py b/sphinx/make_mode.py deleted file mode 100644 index 792f4ab85..000000000 --- a/sphinx/make_mode.py +++ /dev/null @@ -1,38 +0,0 @@ -""" - sphinx.make_mode - ~~~~~~~~~~~~~~~~ - - sphinx-build -M command-line handling. - - This replaces the old, platform-dependent and once-generated content - of Makefile / make.bat. - - This is in its own module so that importing it is fast. It should not - import the main Sphinx modules (like sphinx.applications, sphinx.builders). - - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" - -import warnings - -from sphinx.cmd import make_mode -from sphinx.deprecation import RemovedInSphinx30Warning - - -BUILDERS = make_mode.BUILDERS - - -class Make(make_mode.Make): - def __init__(self, *args): - warnings.warn('sphinx.make_mode.Make is deprecated. ' - 'Please use sphinx.cmd.make_mode.Make instead.', - RemovedInSphinx30Warning, stacklevel=2) - super().__init__(*args) - - -def run_make_mode(args): - warnings.warn('sphinx.make_mode.run_make_mode() is deprecated. ' - 'Please use sphinx.cmd.make_mode.run_make_mode() instead.', - RemovedInSphinx30Warning, stacklevel=2) - return make_mode.run_make_mode(args) diff --git a/sphinx/parsers.py b/sphinx/parsers.py index 2a61971f3..3974d1c66 100644 --- a/sphinx/parsers.py +++ b/sphinx/parsers.py @@ -8,6 +8,7 @@ :license: BSD, see LICENSE for details. """ +import warnings from typing import Any, Dict, List, Union import docutils.parsers @@ -17,6 +18,7 @@ from docutils.parsers.rst import states from docutils.statemachine import StringList from docutils.transforms.universal import SmartQuotes +from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.util.rst import append_epilog, prepend_prolog if False: @@ -47,6 +49,8 @@ class Parser(docutils.parsers.Parser): .. deprecated:: 1.6 ``warn()`` and ``info()`` is deprecated. Use :mod:`sphinx.util.logging` instead. + .. deprecated:: 3.0 + parser.app is deprecated. """ def set_application(self, app: "Sphinx") -> None: @@ -54,10 +58,15 @@ class Parser(docutils.parsers.Parser): :param sphinx.application.Sphinx app: Sphinx application object """ - self.app = app + self._app = app self.config = app.config self.env = app.env + @property + def app(self) -> "Sphinx": + warnings.warn('parser.app is deprecated.', RemovedInSphinx50Warning) + return self._app + class RSTParser(docutils.parsers.rst.Parser, Parser): """A reST parser for Sphinx.""" diff --git a/sphinx/pycode/ast.py b/sphinx/pycode/ast.py index 22207b715..52617e3bc 100644 --- a/sphinx/pycode/ast.py +++ b/sphinx/pycode/ast.py @@ -9,6 +9,7 @@ """ import sys +from typing import List if sys.version_info > (3, 8): import ast @@ -40,6 +41,13 @@ def unparse(node: ast.AST) -> str: return None elif isinstance(node, str): return node + elif isinstance(node, ast.arg): + if node.annotation: + return "%s: %s" % (node.arg, unparse(node.annotation)) + else: + return node.arg + elif isinstance(node, ast.arguments): + return unparse_arguments(node) elif isinstance(node, ast.Attribute): return "%s.%s" % (unparse(node.value), node.attr) elif isinstance(node, ast.Bytes): @@ -58,7 +66,7 @@ def unparse(node: ast.AST) -> str: elif isinstance(node, ast.Index): return unparse(node.value) elif isinstance(node, ast.Lambda): - return "<function <lambda>>" # TODO + return "lambda %s: ..." % unparse(node.args) elif isinstance(node, ast.List): return "[" + ", ".join(unparse(e) for e in node.elts) + "]" elif isinstance(node, ast.Name): @@ -80,3 +88,61 @@ def unparse(node: ast.AST) -> str: return repr(node.value) else: raise NotImplementedError('Unable to parse %s object' % type(node).__name__) + + +def unparse_arguments(node: ast.arguments) -> str: + """Unparse an arguments to string.""" + defaults = list(node.defaults) + positionals = len(node.args) + posonlyargs = 0 + if hasattr(node, "posonlyargs"): # for py38+ + posonlyargs += len(node.posonlyargs) # type:ignore + positionals += posonlyargs + for _ in range(len(defaults), positionals): + defaults.insert(0, None) + + kw_defaults = list(node.kw_defaults) + for _ in range(len(kw_defaults), len(node.kwonlyargs)): + kw_defaults.insert(0, None) + + args = [] # type: List[str] + if hasattr(node, "posonlyargs"): # for py38+ + for i, arg in enumerate(node.posonlyargs): # type: ignore + name = unparse(arg) + if defaults[i]: + if arg.annotation: + name += " = %s" % unparse(defaults[i]) + else: + name += "=%s" % unparse(defaults[i]) + args.append(name) + + if node.posonlyargs: # type: ignore + args.append('/') + + for i, arg in enumerate(node.args): + name = unparse(arg) + if defaults[i + posonlyargs]: + if arg.annotation: + name += " = %s" % unparse(defaults[i + posonlyargs]) + else: + name += "=%s" % unparse(defaults[i + posonlyargs]) + args.append(name) + + if node.vararg: + args.append("*" + unparse(node.vararg)) + + if node.kwonlyargs and not node.vararg: + args.append('*') + for i, arg in enumerate(node.kwonlyargs): + name = unparse(arg) + if kw_defaults[i]: + if arg.annotation: + name += " = %s" % unparse(kw_defaults[i]) + else: + name += "=%s" % unparse(kw_defaults[i]) + args.append(name) + + if node.kwarg: + args.append("**" + unparse(node.kwarg)) + + return ", ".join(args) diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py index 1f803e950..cb3cf0cc1 100644 --- a/sphinx/pycode/parser.py +++ b/sphinx/pycode/parser.py @@ -142,7 +142,7 @@ class TokenProcessor: def fetch_token(self) -> Token: """Fetch a next token from source code. - Returns ``False`` if sequence finished. + Returns ``None`` if sequence finished. """ try: self.previous = self.current diff --git a/sphinx/registry.py b/sphinx/registry.py index ea423298f..93a7ebc25 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -9,9 +9,7 @@ """ import traceback -import warnings from importlib import import_module -from inspect import isclass from types import MethodType from typing import Any, Callable, Dict, Iterator, List, Tuple, Union @@ -25,18 +23,15 @@ from pkg_resources import iter_entry_points from sphinx.builders import Builder from sphinx.config import Config -from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.domains import Domain, Index, ObjType from sphinx.domains.std import GenericObject, Target from sphinx.environment import BuildEnvironment from sphinx.errors import ExtensionError, SphinxError, VersionRequirementError from sphinx.extension import Extension -from sphinx.io import SphinxFileInput from sphinx.locale import __ from sphinx.parsers import Parser as SphinxParser from sphinx.roles import XRefRole from sphinx.util import logging -from sphinx.util.docutils import directive_helper from sphinx.util.logging import prefixed_warnings from sphinx.util.typing import RoleFunction, TitleGetter @@ -176,17 +171,9 @@ class SphinxComponentRegistry: yield domain - def override_domain(self, domain: "Type[Domain]") -> None: - warnings.warn('registry.override_domain() is deprecated. ' - 'Use app.add_domain(domain, override=True) instead.', - RemovedInSphinx30Warning, stacklevel=2) - self.add_domain(domain, override=True) - - def add_directive_to_domain(self, domain: str, name: str, obj: Any, - has_content: bool = None, argument_spec: Any = None, - override: bool = False, **option_spec: Any) -> None: - logger.debug('[app] adding directive to domain: %r', - (domain, name, obj, has_content, argument_spec, option_spec)) + def add_directive_to_domain(self, domain: str, name: str, + cls: "Type[Directive]", override: bool = False) -> None: + logger.debug('[app] adding directive to domain: %r', (domain, name, cls)) if domain not in self.domains: raise ExtensionError(__('domain %s not yet registered') % domain) @@ -194,10 +181,7 @@ class SphinxComponentRegistry: if name in directives and not override: raise ExtensionError(__('The %r directive is already registered to domain %s') % (name, domain)) - if not isclass(obj) or not issubclass(obj, Directive): - directives[name] = directive_helper(obj, has_content, argument_spec, **option_spec) - else: - directives[name] = obj + directives[name] = cls def add_role_to_domain(self, domain: str, name: str, role: Union[RoleFunction, XRefRole], override: bool = False @@ -273,27 +257,8 @@ class SphinxComponentRegistry: else: self.source_suffix[suffix] = filetype - def add_source_parser(self, *args: Any, **kwargs: Any) -> None: - logger.debug('[app] adding search source_parser: %r', args) - if len(args) == 1: - # new sytle arguments: (source_parser) - suffix = None # type: str - parser = args[0] # type: Type[Parser] - else: - # old style arguments: (suffix, source_parser) - warnings.warn('app.add_source_parser() does not support suffix argument. ' - 'Use app.add_source_suffix() instead.', - RemovedInSphinx30Warning, stacklevel=3) - suffix = args[0] - parser = args[1] - - if suffix: - self.add_source_suffix(suffix, suffix, override=True) - - if len(parser.supported) == 0: - warnings.warn('Old source_parser has been detected. Please fill Parser.supported ' - 'attribute: %s' % parser.__name__, - RemovedInSphinx30Warning, stacklevel=3) + def add_source_parser(self, parser: "Type[Parser]", **kwargs: Any) -> None: + logger.debug('[app] adding search source_parser: %r', parser) # create a map from filetype to parser for filetype in parser.supported: @@ -303,12 +268,6 @@ class SphinxComponentRegistry: else: self.source_parsers[filetype] = parser - # also maps suffix to parser - # - # This rescues old styled parsers which does not have ``supported`` filetypes. - if suffix: - self.source_parsers[suffix] = parser - def get_source_parser(self, filetype: str) -> "Type[Parser]": try: return self.source_parsers[filetype] @@ -325,16 +284,6 @@ class SphinxComponentRegistry: parser.set_application(app) return parser - def add_source_input(self, input_class: "Type[SphinxFileInput]", override: bool = False - ) -> None: - warnings.warn('registry.source_input() is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - for filetype in input_class.supported: - if filetype in self.source_inputs and not override: - raise ExtensionError(__('source_input for %r is already registered') % - filetype) - self.source_inputs[filetype] = input_class - def get_source_input(self, filetype: str) -> "Type[Input]": try: return self.source_inputs[filetype] @@ -407,7 +356,7 @@ class SphinxComponentRegistry: attrgetter: Callable[[Any, str, Any], Any]) -> None: self.autodoc_attrgettrs[typ] = attrgetter - def add_css_files(self, filename, **attributes): + def add_css_files(self, filename: str, **attributes: str) -> None: self.css_files.append((filename, attributes)) def add_js_file(self, filename: str, **attributes: str) -> None: diff --git a/sphinx/search/ja.py b/sphinx/search/ja.py index 668248b6b..d1f444be1 100644 --- a/sphinx/search/ja.py +++ b/sphinx/search/ja.py @@ -19,7 +19,6 @@ import os import re import sys -import warnings from typing import Any, Dict, List try: @@ -34,7 +33,6 @@ try: except ImportError: janome_module = False -from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.errors import SphinxError, ExtensionError from sphinx.search import SearchLanguage from sphinx.util import import_object @@ -525,21 +523,9 @@ class SearchJapanese(SearchLanguage): """ lang = 'ja' language_name = 'Japanese' - splitters = { - 'default': 'sphinx.search.ja.DefaultSplitter', - 'mecab': 'sphinx.search.ja.MecabSplitter', - 'janome': 'sphinx.search.ja.JanomeSplitter', - } def init(self, options: Dict) -> None: - type = options.get('type', 'sphinx.search.ja.DefaultSplitter') - if type in self.splitters: - dotted_path = self.splitters[type] - warnings.warn('html_search_options["type"]: %s is deprecated. ' - 'Please give "%s" instead.' % (type, dotted_path), - RemovedInSphinx30Warning, stacklevel=2) - else: - dotted_path = type + dotted_path = options.get('type', 'sphinx.search.ja.DefaultSplitter') try: self.splitter = import_object(dotted_path)(options) except ExtensionError: diff --git a/sphinx/templates/latex/longtable.tex_t b/sphinx/templates/latex/longtable.tex_t index f9e8802e4..8d4cd748c 100644 --- a/sphinx/templates/latex/longtable.tex_t +++ b/sphinx/templates/latex/longtable.tex_t @@ -26,7 +26,7 @@ \endhead \hline -\multicolumn{<%= table.colcount %>}{r}{\makebox[0pt][r]{\sphinxtablecontinued{<%= _('Continued on next page') %>}}}\\ +\multicolumn{<%= table.colcount %>}{r}{\makebox[0pt][r]{\sphinxtablecontinued{<%= _('continues on next page') %>}}}\\ \endfoot \endlastfoot diff --git a/sphinx/testing/comparer.py b/sphinx/testing/comparer.py index 0bcd194a8..ddd818394 100644 --- a/sphinx/testing/comparer.py +++ b/sphinx/testing/comparer.py @@ -9,7 +9,7 @@ """ import difflib import pathlib -from typing import List, Union +from typing import Any, List, Union class PathComparer: @@ -38,13 +38,13 @@ class PathComparer: """ self.path = pathlib.Path(path) - def __str__(self): + def __str__(self) -> str: return self.path.as_posix() - def __repr__(self): + def __repr__(self) -> str: return "<{0.__class__.__name__}: '{0}'>".format(self) - def __eq__(self, other): + def __eq__(self, other: Union[str, pathlib.Path]) -> bool: # type: ignore return not bool(self.ldiff(other)) def diff(self, other: Union[str, pathlib.Path]) -> List[str]: @@ -94,8 +94,10 @@ class PathComparer: return [line.strip() for line in difflib.Differ().compare([s_path], [o_path])] -def pytest_assertrepr_compare(op, left, right): +def pytest_assertrepr_compare(op: str, left: Any, right: Any) -> List[str]: if isinstance(left, PathComparer) and op == "==": return ['Comparing path:'] + left.ldiff(right) - if isinstance(right, PathComparer) and op == "==": + elif isinstance(right, PathComparer) and op == "==": return ['Comparing path:'] + right.rdiff(left) + else: + return [] diff --git a/sphinx/testing/fixtures.py b/sphinx/testing/fixtures.py index c6dd7ecdf..eec3b4208 100644 --- a/sphinx/testing/fixtures.py +++ b/sphinx/testing/fixtures.py @@ -14,20 +14,44 @@ import sys from collections import namedtuple from io import StringIO from subprocess import PIPE -from typing import Any, Dict +from typing import Any, Callable, Dict, Generator, Tuple import pytest -from . import util +from sphinx.testing import util +from sphinx.testing.util import SphinxTestApp, SphinxTestAppWrapperForSkipBuilding @pytest.fixture(scope='session') -def rootdir() -> None: +def rootdir() -> str: return None +class SharedResult: + cache = {} # type: Dict[str, Dict[str, str]] + + def store(self, key: str, app_: SphinxTestApp) -> Any: + if key in self.cache: + return + data = { + 'status': app_._status.getvalue(), + 'warning': app_._warning.getvalue(), + } + self.cache[key] = data + + def restore(self, key: str) -> Dict[str, StringIO]: + if key not in self.cache: + return {} + data = self.cache[key] + return { + 'status': StringIO(data['status']), + 'warning': StringIO(data['warning']), + } + + @pytest.fixture -def app_params(request, test_params, shared_result, sphinx_test_tempdir, rootdir): +def app_params(request: Any, test_params: Dict, shared_result: SharedResult, + sphinx_test_tempdir: str, rootdir: str) -> Tuple[Dict, Dict]: """ parameters that is specified by 'pytest.mark.sphinx' for sphinx.application.Sphinx initialization @@ -40,7 +64,7 @@ def app_params(request, test_params, shared_result, sphinx_test_tempdir, rootdir else: markers = request.node.get_marker("sphinx") pargs = {} - kwargs = {} # type: Dict[str, str] + kwargs = {} # type: Dict[str, Any] if markers is not None: # to avoid stacking positional args @@ -75,7 +99,7 @@ def app_params(request, test_params, shared_result, sphinx_test_tempdir, rootdir @pytest.fixture -def test_params(request): +def test_params(request: Any) -> Dict: """ test parameters that is specified by 'pytest.mark.test_params' @@ -102,7 +126,8 @@ def test_params(request): @pytest.fixture(scope='function') -def app(test_params, app_params, make_app, shared_result): +def app(test_params: Dict, app_params: Tuple[Dict, Dict], make_app: Callable, + shared_result: SharedResult) -> Generator[SphinxTestApp, None, None]: """ provides sphinx.application.Sphinx object """ @@ -122,7 +147,7 @@ def app(test_params, app_params, make_app, shared_result): @pytest.fixture(scope='function') -def status(app): +def status(app: SphinxTestApp) -> StringIO: """ compat for testing with previous @with_app decorator """ @@ -130,7 +155,7 @@ def status(app): @pytest.fixture(scope='function') -def warning(app): +def warning(app: SphinxTestApp) -> StringIO: """ compat for testing with previous @with_app decorator """ @@ -138,7 +163,7 @@ def warning(app): @pytest.fixture() -def make_app(test_params, monkeypatch): +def make_app(test_params: Dict, monkeypatch: Any) -> Generator[Callable, None, None]: """ provides make_app function to initialize SphinxTestApp instance. if you want to initialize 'app' in your test function. please use this @@ -153,10 +178,10 @@ def make_app(test_params, monkeypatch): status, warning = StringIO(), StringIO() kwargs.setdefault('status', status) kwargs.setdefault('warning', warning) - app_ = util.SphinxTestApp(*args, **kwargs) # type: Any + app_ = SphinxTestApp(*args, **kwargs) # type: Any apps.append(app_) if test_params['shared_result']: - app_ = util.SphinxTestAppWrapperForSkipBuilding(app_) + app_ = SphinxTestAppWrapperForSkipBuilding(app_) return app_ yield make @@ -165,40 +190,18 @@ def make_app(test_params, monkeypatch): app_.cleanup() -class SharedResult: - cache = {} # type: Dict[str, Dict[str, str]] - - def store(self, key, app_): - if key in self.cache: - return - data = { - 'status': app_._status.getvalue(), - 'warning': app_._warning.getvalue(), - } - self.cache[key] = data - - def restore(self, key): - if key not in self.cache: - return {} - data = self.cache[key] - return { - 'status': StringIO(data['status']), - 'warning': StringIO(data['warning']), - } - - @pytest.fixture -def shared_result(): +def shared_result() -> SharedResult: return SharedResult() @pytest.fixture(scope='module', autouse=True) -def _shared_result_cache(): +def _shared_result_cache() -> None: SharedResult.cache.clear() @pytest.fixture -def if_graphviz_found(app): +def if_graphviz_found(app: SphinxTestApp) -> None: """ The test will be skipped when using 'if_graphviz_found' fixture and graphviz dot command is not found. @@ -215,7 +218,7 @@ def if_graphviz_found(app): @pytest.fixture(scope='session') -def sphinx_test_tempdir(tmpdir_factory): +def sphinx_test_tempdir(tmpdir_factory: Any) -> "util.path": """ temporary directory that wrapped with `path` class. """ @@ -227,7 +230,7 @@ def sphinx_test_tempdir(tmpdir_factory): @pytest.fixture -def tempdir(tmpdir): +def tempdir(tmpdir: str) -> "util.path": """ temporary directory that wrapped with `path` class. this fixture is for compat with old test implementation. diff --git a/sphinx/testing/path.py b/sphinx/testing/path.py index 1c883af2f..4c3702e3d 100644 --- a/sphinx/testing/path.py +++ b/sphinx/testing/path.py @@ -10,8 +10,11 @@ import builtins import os import shutil import sys +import warnings from typing import Any, Callable, IO, List +from sphinx.deprecation import RemovedInSphinx50Warning + FILESYSTEMENCODING = sys.getfilesystemencoding() or sys.getdefaultencoding() @@ -138,6 +141,14 @@ class path(str): """ Returns the text in the file. """ + warnings.warn('Path.text() is deprecated. Please use read_text() instead.', + RemovedInSphinx50Warning, stacklevel=2) + return self.read_text(encoding, **kwargs) + + def read_text(self, encoding: str = 'utf-8', **kwargs: Any) -> str: + """ + Returns the text in the file. + """ with open(self, encoding=encoding, **kwargs) as f: return f.read() @@ -145,6 +156,14 @@ class path(str): """ Returns the bytes in the file. """ + warnings.warn('Path.bytes() is deprecated. Please use read_bytes() instead.', + RemovedInSphinx50Warning, stacklevel=2) + return self.read_bytes() + + def read_bytes(self) -> builtins.bytes: + """ + Returns the bytes in the file. + """ with open(self, mode='rb') as f: return f.read() diff --git a/sphinx/testing/util.py b/sphinx/testing/util.py index f4ef35d61..75cd0f411 100644 --- a/sphinx/testing/util.py +++ b/sphinx/testing/util.py @@ -11,10 +11,12 @@ import os import re import sys import warnings +from io import StringIO from typing import Any, Dict, Generator, IO, List, Pattern from xml.etree import ElementTree from docutils import nodes +from docutils.nodes import Node from docutils.parsers.rst import directives, roles from sphinx import application, locale @@ -47,7 +49,7 @@ def assert_startswith(thing: str, prefix: str) -> None: assert False, '%r does not start with %r' % (thing, prefix) -def assert_node(node: nodes.Node, cls: Any = None, xpath: str = "", **kwargs: Any) -> None: +def assert_node(node: Node, cls: Any = None, xpath: str = "", **kwargs: Any) -> None: if cls: if isinstance(cls, list): assert_node(node, cls[0], xpath=xpath, **kwargs) @@ -101,6 +103,8 @@ class SphinxTestApp(application.Sphinx): A subclass of :class:`Sphinx` that runs on the test root, with some better default values for the initialization parameters. """ + _status = None # type: StringIO + _warning = None # type: StringIO def __init__(self, buildername: str = 'html', srcdir: path = None, freshenv: bool = False, confoverrides: Dict = None, status: IO = None, warning: IO = None, diff --git a/sphinx/texinputs/Makefile_t b/sphinx/texinputs/Makefile_t index 08ddd2f0a..96bb0fed1 100644 --- a/sphinx/texinputs/Makefile_t +++ b/sphinx/texinputs/Makefile_t @@ -10,7 +10,6 @@ ALLDVI = $(addsuffix .dvi,$(ALLDOCS)) ALLXDV = {% endif -%} ALLPS = $(addsuffix .ps,$(ALLDOCS)) -ALLIMGS = $(wildcard *.png *.gif *.jpg *.jpeg) # Prefix for archive names ARCHIVEPREFIX = @@ -46,15 +45,7 @@ LATEX = latexmk -dvi PDFLATEX = latexmk -pdf -dvi- -ps- {% endif %} -%.png %.gif %.jpg %.jpeg: FORCE_MAKE - extractbb '$@' - -{% if latex_engine in ('platex', 'uplatex') -%} -%.dvi: %.tex $(ALLIMGS) FORCE_MAKE - for f in *.pdf; do extractbb "$$f"; done - $(LATEX) $(LATEXMKOPTS) '$<' - -{% elif latex_engine != 'xelatex' -%} +{% if latex_engine != 'xelatex' -%} %.dvi: %.tex FORCE_MAKE $(LATEX) $(LATEXMKOPTS) '$<' @@ -62,12 +53,7 @@ PDFLATEX = latexmk -pdf -dvi- -ps- %.ps: %.dvi dvips '$<' -{% if latex_engine in ('platex', 'uplatex') -%} -%.pdf: %.tex $(ALLIMGS) FORCE_MAKE - for f in *.pdf; do extractbb "$$f"; done -{%- else -%} %.pdf: %.tex FORCE_MAKE -{%- endif %} $(PDFLATEX) $(LATEXMKOPTS) '$<' all: $(ALLPDF) diff --git a/sphinx/texinputs/make.bat_t b/sphinx/texinputs/make.bat_t index 83dd449c2..9dfa38c13 100644 --- a/sphinx/texinputs/make.bat_t +++ b/sphinx/texinputs/make.bat_t @@ -31,11 +31,6 @@ if "%1" == "" goto all-pdf if "%1" == "all-pdf" ( :all-pdf -{%- if latex_engine == 'platex' %} - for %%i in (*.png *.gif *.jpg *.jpeg *.pdf) do ( - extractbb %%i - ) -{%- endif %} for %%i in (*.tex) do ( %PDFLATEX% %LATEXMKOPTS% %%i ) diff --git a/sphinx/themes/basic/search.html b/sphinx/themes/basic/search.html index 02ee85abc..2673369f2 100644 --- a/sphinx/themes/basic/search.html +++ b/sphinx/themes/basic/search.html @@ -27,10 +27,8 @@ </p> </div> <p> - {% trans %}From here you can search these documents. Enter your search - words into the box below and click "search". Note that the search - function will automatically search for all of the words. Pages - containing fewer words won't appear in the result list.{% endtrans %} + {% trans %}Searching for multiple words only shows matches that contain + all words.{% endtrans %} </p> <form action="" method="get"> <input type="text" name="q" aria-labelledby="search-documentation" value="" /> diff --git a/sphinx/themes/basic/searchresults.html b/sphinx/themes/basic/searchresults.html deleted file mode 100644 index 50700c158..000000000 --- a/sphinx/themes/basic/searchresults.html +++ /dev/null @@ -1,36 +0,0 @@ -{# - basic/searchresults.html - ~~~~~~~~~~~~~~~~~~~~~~~~ - - Template for the body of the search results page. - - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -#} -<h1 id="search-documentation">{{ _('Search') }}</h1> -<p> - From here you can search these documents. Enter your search - words into the box below and click "search". -</p> -<form action="" method="get"> - <input type="text" name="q" value="" /> - <input type="submit" value="{{ _('search') }}" /> - <span id="search-progress" style="padding-left: 10px"></span> -</form> -{%- if search_performed %} - <h2>{{ _('Search Results') }}</h2> - {%- if not search_results %} - <p>{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}</p> - {%- endif %} -{%- endif %} -<div id="search-results"> - {%- if search_results %} - <ul class="search"> - {% for href, caption, context in search_results %} - <li><a href="{{ docroot }}{{ href }}/?highlight={{ q }}">{{ caption }}</a> - <div class="context">{{ context|e }}</div> - </li> - {% endfor %} - </ul> - {%- endif %} -</div> diff --git a/sphinx/transforms/post_transforms/__init__.py b/sphinx/transforms/post_transforms/__init__.py index ee459cc56..6d7c3b0eb 100644 --- a/sphinx/transforms/post_transforms/__init__.py +++ b/sphinx/transforms/post_transforms/__init__.py @@ -131,7 +131,7 @@ class ReferencesResolver(SphinxPostTransform): if not results: return None if len(results) > 1: - def stringify(name, node): + def stringify(name: str, node: Element) -> str: reftitle = node.get('reftitle', node.astext()) return ':%s:`%s`' % (name, reftitle) candidates = ' or '.join(stringify(name, role) for name, role in results) diff --git a/sphinx/transforms/post_transforms/compat.py b/sphinx/transforms/post_transforms/compat.py deleted file mode 100644 index c71191dec..000000000 --- a/sphinx/transforms/post_transforms/compat.py +++ /dev/null @@ -1,86 +0,0 @@ -""" - sphinx.transforms.post_transforms.compat - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Post transforms for compatibility - - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" - -import warnings -from typing import Any, Dict - -from docutils import nodes -from docutils.writers.docutils_xml import XMLTranslator - -from sphinx.addnodes import math_block, displaymath -from sphinx.application import Sphinx -from sphinx.deprecation import RemovedInSphinx30Warning -from sphinx.transforms import SphinxTransform -from sphinx.util import logging - - -logger = logging.getLogger(__name__) - - -class MathNodeMigrator(SphinxTransform): - """Migrate a math node to docutils'. - - For a long time, Sphinx uses an original node for math. Since 1.8, - Sphinx starts to use a math node of docutils'. This transform converts - old and new nodes to keep compatibility. - """ - default_priority = 999 - - def apply(self, **kwargs: Any) -> None: - for math_node in self.document.traverse(nodes.math): - # case: old styled ``math`` node generated by old extensions - if len(math_node) == 0: - warnings.warn("math node for Sphinx was replaced by docutils'. " - "Please use ``docutils.nodes.math`` instead.", - RemovedInSphinx30Warning) - equation = math_node['latex'] - math_node += nodes.Text(equation, equation) - - translator = self.app.builder.get_translator_class() - if hasattr(translator, 'visit_displaymath') and translator != XMLTranslator: - # case: old translators which does not support ``math_block`` node - warnings.warn("Translator for %s does not support math_block node'. " - "Please update your extension." % translator, - RemovedInSphinx30Warning) - for old_math_block_node in self.document.traverse(math_block): - alt = displaymath(latex=old_math_block_node.astext(), - number=old_math_block_node.get('number'), - label=old_math_block_node.get('label'), - nowrap=old_math_block_node.get('nowrap'), - docname=old_math_block_node.get('docname')) - old_math_block_node.replace_self(alt) - elif getattr(self.app.builder, 'math_renderer_name', None) == 'unknown': - # case: math extension provides old styled math renderer - for math_block_node in self.document.traverse(nodes.math_block): - math_block_node['latex'] = math_block_node.astext() - - # case: old styled ``displaymath`` node generated by old extensions - for math_block_node in self.document.traverse(math_block): - if len(math_block_node) == 0: - warnings.warn("math node for Sphinx was replaced by docutils'. " - "Please use ``docutils.nodes.math_block`` instead.", - RemovedInSphinx30Warning) - if isinstance(math_block_node, displaymath): - newnode = nodes.math_block('', math_block_node['latex'], - **math_block_node.attributes) - math_block_node.replace_self(newnode) - else: - latex = math_block_node['latex'] - math_block_node += nodes.Text(latex, latex) - - -def setup(app: Sphinx) -> Dict[str, Any]: - app.add_post_transform(MathNodeMigrator) - - return { - 'version': 'builtin', - 'parallel_read_safe': True, - 'parallel_write_safe': True, - } diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 262837b50..12ae051f1 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -28,16 +28,13 @@ from time import mktime, strptime from typing import Any, Callable, Dict, IO, Iterable, Iterator, List, Pattern, Set, Tuple from urllib.parse import urlsplit, urlunsplit, quote_plus, parse_qsl, urlencode -from docutils.utils import relative_path - -from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning +from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.errors import ( PycodeError, SphinxParallelError, ExtensionError, FiletypeNotFoundError ) from sphinx.locale import __ from sphinx.util import logging from sphinx.util.console import strip_colors, colorize, bold, term_width_line # type: ignore -from sphinx.util.fileutil import copy_asset_file from sphinx.util.typing import PathMatcher from sphinx.util import smartypants # noqa @@ -45,7 +42,7 @@ from sphinx.util import smartypants # noqa # prune unused ones indiscriminately from sphinx.util.osutil import ( # noqa SEP, os_path, relative_uri, ensuredir, walk, mtimes_of_files, movefile, - copyfile, copytimes, make_filename, ustrftime) + copyfile, copytimes, make_filename) from sphinx.util.nodes import ( # noqa nested_parse_with_titles, split_explicit_title, explicit_title_re, caption_ref_re) @@ -56,7 +53,7 @@ if False: # For type annotation from typing import Type # for python3.5.1 from sphinx.application import Sphinx - from sphinx.builders import Builder + logger = logging.getLogger(__name__) @@ -201,35 +198,6 @@ class DownloadFiles(dict): self.add_file(docname, filename) -def copy_static_entry(source: str, targetdir: str, builder: "Builder", context: Dict = {}, - exclude_matchers: Tuple[PathMatcher, ...] = (), level: int = 0) -> None: - """[DEPRECATED] Copy a HTML builder static_path entry from source to targetdir. - - Handles all possible cases of files, directories and subdirectories. - """ - warnings.warn('sphinx.util.copy_static_entry is deprecated for removal', - RemovedInSphinx30Warning, stacklevel=2) - - if exclude_matchers: - relpath = relative_path(path.join(builder.srcdir, 'dummy'), source) - for matcher in exclude_matchers: - if matcher(relpath): - return - if path.isfile(source): - copy_asset_file(source, targetdir, context, builder.templates) - elif path.isdir(source): - ensuredir(targetdir) - for entry in os.listdir(source): - if entry.startswith('.'): - continue - newtarget = targetdir - if path.isdir(path.join(source, entry)): - newtarget = path.join(targetdir, entry) - copy_static_entry(path.join(source, entry), newtarget, - builder, context, level=level + 1, - exclude_matchers=exclude_matchers) - - _DEBUG_HEADER = '''\ # Sphinx version: %s # Python version: %s (%s) @@ -681,7 +649,7 @@ class progress_message: def __call__(self, f: Callable) -> Callable: @functools.wraps(f) - def wrapper(*args, **kwargs): + def wrapper(*args: Any, **kwargs: Any) -> Any: with self: return f(*args, **kwargs) diff --git a/sphinx/util/compat.py b/sphinx/util/compat.py index ba3c4d27c..0135899eb 100644 --- a/sphinx/util/compat.py +++ b/sphinx/util/compat.py @@ -15,27 +15,14 @@ from typing import Any, Dict from docutils.utils import get_source_line from sphinx import addnodes -from sphinx.config import Config -from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning +from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.transforms import SphinxTransform -from sphinx.util import import_object if False: # For type annotation from sphinx.application import Sphinx -def deprecate_source_parsers(app: "Sphinx", config: Config) -> None: - if config.source_parsers: - warnings.warn('The config variable "source_parsers" is deprecated. ' - 'Please update your extension for the parser and remove the setting.', - RemovedInSphinx30Warning) - for suffix, parser in config.source_parsers.items(): - if isinstance(parser, str): - parser = import_object(parser, 'source parser') - app.add_source_parser(suffix, parser) - - def register_application_for_autosummary(app: "Sphinx") -> None: """Register application object to autosummary module. @@ -65,7 +52,6 @@ class IndexEntriesMigrator(SphinxTransform): def setup(app: "Sphinx") -> Dict[str, Any]: app.add_transform(IndexEntriesMigrator) - app.connect('config-inited', deprecate_source_parsers) app.connect('builder-inited', register_application_for_autosummary) return { diff --git a/sphinx/util/docstrings.py b/sphinx/util/docstrings.py index 8854a1f98..7b3f011d1 100644 --- a/sphinx/util/docstrings.py +++ b/sphinx/util/docstrings.py @@ -8,8 +8,38 @@ :license: BSD, see LICENSE for details. """ +import re import sys -from typing import List +from typing import Dict, List + +from docutils.parsers.rst.states import Body + + +field_list_item_re = re.compile(Body.patterns['field_marker']) + + +def extract_metadata(s: str) -> Dict[str, str]: + """Extract metadata from docstring.""" + in_other_element = False + metadata = {} # type: Dict[str, str] + + if not s: + return metadata + + for line in prepare_docstring(s): + if line.strip() == '': + in_other_element = False + else: + matched = field_list_item_re.match(line) + if matched and not in_other_element: + field_name = matched.group()[1:].split(':', 1)[0] + if field_name.startswith('meta '): + name = field_name[5:].strip() + metadata[name] = line[matched.end():].strip() + else: + in_other_element = True + + return metadata def prepare_docstring(s: str, ignore: int = 1, tabsize: int = 8) -> List[str]: diff --git a/sphinx/util/docutils.py b/sphinx/util/docutils.py index 3fbd8a420..3b7b60201 100644 --- a/sphinx/util/docutils.py +++ b/sphinx/util/docutils.py @@ -10,8 +10,6 @@ import os import re -import types -import warnings from contextlib import contextmanager from copy import copy from distutils.version import LooseVersion @@ -24,14 +22,12 @@ import docutils from docutils import nodes from docutils.io import FileOutput from docutils.nodes import Element, Node, system_message -from docutils.parsers.rst import Directive, directives, roles, convert_directive_function +from docutils.parsers.rst import Directive, directives, roles from docutils.parsers.rst.states import Inliner from docutils.statemachine import StateMachine, State, StringList from docutils.utils import Reporter, unescape -from sphinx.deprecation import RemovedInSphinx30Warning -from sphinx.errors import ExtensionError, SphinxError -from sphinx.locale import __ +from sphinx.errors import SphinxError from sphinx.util import logging from sphinx.util.typing import RoleFunction @@ -279,25 +275,6 @@ def is_html5_writer_available() -> bool: return __version_info__ > (0, 13, 0) -def directive_helper(obj: Any, has_content: bool = None, - argument_spec: Tuple[int, int, bool] = None, **option_spec: Any - ) -> Any: - warnings.warn('function based directive support is now deprecated. ' - 'Use class based directive instead.', - RemovedInSphinx30Warning) - - if isinstance(obj, (types.FunctionType, types.MethodType)): - obj.content = has_content # type: ignore - obj.arguments = argument_spec or (0, 0, False) # type: ignore - obj.options = option_spec # type: ignore - return convert_directive_function(obj) - else: - if has_content or argument_spec or option_spec: - raise ExtensionError(__('when adding directive classes, no ' - 'additional arguments may be given')) - return obj - - @contextmanager def switch_source_input(state: State, content: StringList) -> Generator[None, None, None]: """Switch current source input of state temporarily.""" @@ -467,7 +444,7 @@ class SphinxTranslator(nodes.NodeVisitor): self.config = builder.config self.settings = document.settings - def dispatch_visit(self, node): + def dispatch_visit(self, node: Node) -> None: """ Dispatch node to appropriate visitor method. The priority of visitor method is: @@ -481,11 +458,12 @@ class SphinxTranslator(nodes.NodeVisitor): if method: logger.debug('SphinxTranslator.dispatch_visit calling %s for %s', method.__name__, node) - return method(node) + method(node) + break else: super().dispatch_visit(node) - def dispatch_departure(self, node): + def dispatch_departure(self, node: Node) -> None: """ Dispatch node to appropriate departure method. The priority of departure method is: @@ -499,7 +477,8 @@ class SphinxTranslator(nodes.NodeVisitor): if method: logger.debug('SphinxTranslator.dispatch_departure calling %s for %s', method.__name__, node) - return method(node) + method(node) + break else: super().dispatch_departure(node) diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index 096a200bf..1cb75637c 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -20,7 +20,7 @@ import babel.dates from babel.messages.mofile import write_mo from babel.messages.pofile import read_po -from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning +from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.errors import SphinxError from sphinx.locale import __ from sphinx.util import logging @@ -151,9 +151,8 @@ def find_catalog_files(docname: str, srcdir: str, locale_dirs: List[str], def find_catalog_source_files(locale_dirs: List[str], locale: str, domains: List[str] = None, - gettext_compact: bool = None, charset: str = 'utf-8', - force_all: bool = False, excluded: Matcher = Matcher([]) - ) -> Set[CatalogInfo]: + charset: str = 'utf-8', force_all: bool = False, + excluded: Matcher = Matcher([])) -> Set[CatalogInfo]: """ :param list locale_dirs: list of path as `['locale_dir1', 'locale_dir2', ...]` to find @@ -169,9 +168,6 @@ def find_catalog_source_files(locale_dirs: List[str], locale: str, domains: List """ warnings.warn('find_catalog_source_files() is deprecated.', RemovedInSphinx40Warning, stacklevel=2) - if gettext_compact is not None: - warnings.warn('gettext_compact argument for find_catalog_source_files() ' - 'is deprecated.', RemovedInSphinx30Warning, stacklevel=2) catalogs = set() # type: Set[CatalogInfo] diff --git a/sphinx/util/images.py b/sphinx/util/images.py index 506e4a931..568682b37 100644 --- a/sphinx/util/images.py +++ b/sphinx/util/images.py @@ -10,16 +10,12 @@ import base64 import imghdr -import warnings from collections import OrderedDict -from io import BytesIO from os import path from typing import IO, NamedTuple, Tuple import imagesize -from sphinx.deprecation import RemovedInSphinx30Warning - try: from PIL import Image except ImportError: @@ -49,12 +45,8 @@ def get_image_size(filename: str) -> Tuple[int, int]: size = (int(size[0]), int(size[1])) if size is None and Image: # fallback to Pillow - im = Image.open(filename) - size = im.size - try: - im.fp.close() - except Exception: - pass + with Image.open(filename) as im: + size = im.size return size except Exception: @@ -69,16 +61,10 @@ def guess_mimetype_for_stream(stream: IO, default: str = None) -> str: return default -def guess_mimetype(filename: str = '', content: bytes = None, default: str = None) -> str: +def guess_mimetype(filename: str = '', default: str = None) -> str: _, ext = path.splitext(filename.lower()) if ext in mime_suffixes: return mime_suffixes[ext] - elif content: - # TODO: When the content argument is removed, make filename a required - # argument. - warnings.warn('The content argument of guess_mimetype() is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - return guess_mimetype_for_stream(BytesIO(content), default=default) elif path.exists(filename): with open(filename, 'rb') as f: return guess_mimetype_for_stream(f, default=default) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 2bcccdd60..281ef4493 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -17,12 +17,15 @@ import typing import warnings from functools import partial, partialmethod from inspect import ( # NOQA - isclass, ismethod, ismethoddescriptor, isroutine + Parameter, isclass, ismethod, ismethoddescriptor, isroutine ) from io import StringIO from typing import Any, Callable, Mapping, List, Tuple +from typing import cast -from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning +from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning +from sphinx.pycode.ast import ast # for py35-37 +from sphinx.pycode.ast import unparse as ast_unparse from sphinx.util import logging from sphinx.util.typing import stringify as stringify_annotation @@ -51,9 +54,11 @@ memory_address_re = re.compile(r' at 0x[0-9a-f]{8,16}(?=>)', re.IGNORECASE) # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, # 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 Python Software # Foundation; All Rights Reserved -def getargspec(func): +def getargspec(func: Callable) -> Any: """Like inspect.getfullargspec but supports bound methods, and wrapped methods.""" + warnings.warn('sphinx.ext.inspect.getargspec() is deprecated', + RemovedInSphinx50Warning) # On 3.5+, signature(int) or similar raises ValueError. On 3.4, it # succeeds with a bogus signature. We want a TypeError uniformly, to # match historical behavior. @@ -81,19 +86,19 @@ def getargspec(func): kind = param.kind name = param.name - if kind is inspect.Parameter.POSITIONAL_ONLY: + if kind is Parameter.POSITIONAL_ONLY: args.append(name) - elif kind is inspect.Parameter.POSITIONAL_OR_KEYWORD: + elif kind is Parameter.POSITIONAL_OR_KEYWORD: args.append(name) if param.default is not param.empty: defaults += (param.default,) # type: ignore - elif kind is inspect.Parameter.VAR_POSITIONAL: + elif kind is Parameter.VAR_POSITIONAL: varargs = name - elif kind is inspect.Parameter.KEYWORD_ONLY: + elif kind is Parameter.KEYWORD_ONLY: kwonlyargs.append(name) if param.default is not param.empty: kwdefaults[name] = param.default - elif kind is inspect.Parameter.VAR_KEYWORD: + elif kind is Parameter.VAR_KEYWORD: varkw = name if param.annotation is not param.empty: @@ -359,7 +364,7 @@ def signature(subject: Callable, bound_method: bool = False) -> inspect.Signatur # https://bugs.python.org/issue33009 if hasattr(subject, '_partialmethod'): parameters = [] - return_annotation = inspect.Parameter.empty + return_annotation = Parameter.empty else: raise @@ -427,11 +432,11 @@ def stringify_signature(sig: inspect.Signature, show_annotation: bool = True, args.append(arg.getvalue()) last_kind = param.kind - if last_kind == inspect.Parameter.POSITIONAL_ONLY: + if last_kind == Parameter.POSITIONAL_ONLY: # PEP-570: Separator for Positional Only Parameter: / args.append('/') - if (sig.return_annotation is inspect.Parameter.empty or + if (sig.return_annotation is Parameter.empty or show_annotation is False or show_return_annotation is False): return '(%s)' % ', '.join(args) @@ -440,24 +445,50 @@ def stringify_signature(sig: inspect.Signature, show_annotation: bool = True, return '(%s) -> %s' % (', '.join(args), annotation) -class Parameter: - """Fake parameter class for python2.""" - POSITIONAL_ONLY = 0 - POSITIONAL_OR_KEYWORD = 1 - VAR_POSITIONAL = 2 - KEYWORD_ONLY = 3 - VAR_KEYWORD = 4 - empty = object() +def signature_from_str(signature: str) -> inspect.Signature: + """Create a Signature object from string.""" + module = ast.parse('def func' + signature + ': pass') + definition = cast(ast.FunctionDef, module.body[0]) # type: ignore - def __init__(self, name: str, kind: int = POSITIONAL_OR_KEYWORD, - default: Any = empty) -> None: - self.name = name - self.kind = kind - self.default = default - self.annotation = self.empty + # parameters + args = definition.args + params = [] - warnings.warn('sphinx.util.inspect.Parameter is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) + if hasattr(args, "posonlyargs"): + for arg in args.posonlyargs: # type: ignore + annotation = ast_unparse(arg.annotation) or Parameter.empty + params.append(Parameter(arg.arg, Parameter.POSITIONAL_ONLY, + annotation=annotation)) + + for i, arg in enumerate(args.args): + if len(args.args) - i <= len(args.defaults): + default = ast_unparse(args.defaults[-len(args.args) + i]) + else: + default = Parameter.empty + + annotation = ast_unparse(arg.annotation) or Parameter.empty + params.append(Parameter(arg.arg, Parameter.POSITIONAL_OR_KEYWORD, + default=default, annotation=annotation)) + + if args.vararg: + annotation = ast_unparse(args.vararg.annotation) or Parameter.empty + params.append(Parameter(args.vararg.arg, Parameter.VAR_POSITIONAL, + annotation=annotation)) + + for i, arg in enumerate(args.kwonlyargs): + default = ast_unparse(args.kw_defaults[i]) + annotation = ast_unparse(arg.annotation) or Parameter.empty + params.append(Parameter(arg.arg, Parameter.KEYWORD_ONLY, default=default, + annotation=annotation)) + + if args.kwarg: + annotation = ast_unparse(args.kwarg.annotation) or Parameter.empty + params.append(Parameter(args.kwarg.arg, Parameter.VAR_KEYWORD, + annotation=annotation)) + + return_annotation = ast_unparse(definition.returns) or Parameter.empty + + return inspect.Signature(params, return_annotation=return_annotation) class Signature: @@ -528,12 +559,12 @@ class Signature: if self.has_retval: return self.signature.return_annotation else: - return inspect.Parameter.empty + return Parameter.empty else: return None def format_args(self, show_annotation: bool = True) -> str: - def get_annotation(param: inspect.Parameter) -> Any: + def get_annotation(param: Parameter) -> Any: if isinstance(param.annotation, str) and param.name in self.annotations: return self.annotations[param.name] else: @@ -585,7 +616,7 @@ class Signature: args.append(arg.getvalue()) last_kind = param.kind - if self.return_annotation is inspect.Parameter.empty or show_annotation is False: + if self.return_annotation is Parameter.empty or show_annotation is False: return '(%s)' % ', '.join(args) else: if 'return' in self.annotations: diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index 7c7300c60..474a704a1 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/sphinx/util/osutil.py b/sphinx/util/osutil.py index 23fa208d7..e44211cf9 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -15,13 +15,12 @@ import os import re import shutil import sys -import time import warnings from io import StringIO from os import path from typing import Any, Generator, Iterator, List, Tuple -from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning +from sphinx.deprecation import RemovedInSphinx40Warning try: # for ALT Linux (#6712) @@ -144,27 +143,6 @@ def make_filename_from_project(project: str) -> str: return make_filename(project_suffix_re.sub('', project)).lower() -def ustrftime(format: str, *args: Any) -> str: - """[DEPRECATED] strftime for unicode strings.""" - warnings.warn('sphinx.util.osutil.ustrtime is deprecated for removal', - RemovedInSphinx30Warning, stacklevel=2) - - if not args: - # If time is not specified, try to use $SOURCE_DATE_EPOCH variable - # See https://wiki.debian.org/ReproducibleBuilds/TimestampsProposal - source_date_epoch = os.getenv('SOURCE_DATE_EPOCH') - if source_date_epoch is not None: - time_struct = time.gmtime(float(source_date_epoch)) - args = [time_struct] # type: ignore - # On Windows, time.strftime() and Unicode characters will raise UnicodeEncodeError. - # https://bugs.python.org/issue8304 - try: - return time.strftime(format, *args) - except UnicodeEncodeError: - r = time.strftime(format.encode('unicode-escape').decode(), *args) - return r.encode().decode('unicode-escape') - - def relpath(path: str, start: str = os.curdir) -> str: """Return a relative filepath to *path* either from the current directory or from an optional *start* directory. diff --git a/sphinx/util/pycompat.py b/sphinx/util/pycompat.py index ba4f83a71..061bbcb6d 100644 --- a/sphinx/util/pycompat.py +++ b/sphinx/util/pycompat.py @@ -52,7 +52,7 @@ class UnicodeMixin: .. deprecated:: 2.0 """ - def __str__(self): + def __str__(self) -> str: warnings.warn('UnicodeMixin is deprecated', RemovedInSphinx40Warning, stacklevel=2) return self.__unicode__() # type: ignore diff --git a/sphinx/util/requests.py b/sphinx/util/requests.py index 884e7c997..d9d1d1b2c 100644 --- a/sphinx/util/requests.py +++ b/sphinx/util/requests.py @@ -14,7 +14,6 @@ from contextlib import contextmanager from typing import Any, Generator, Union from urllib.parse import urlsplit -import pkg_resources import requests import sphinx @@ -37,28 +36,6 @@ except ImportError: # for requests < 2.4.0 InsecureRequestWarning = None # type: ignore -try: - from requests.packages.urllib3.exceptions import InsecurePlatformWarning -except ImportError: - try: - # for Debian-jessie - from urllib3.exceptions import InsecurePlatformWarning # type: ignore - except ImportError: - # for requests < 2.4.0 - InsecurePlatformWarning = None # type: ignore - -# try to load requests[security] (but only if SSL is available) -try: - import ssl # NOQA -except ImportError: - pass -else: - try: - pkg_resources.require(['requests[security]']) - except (pkg_resources.DistributionNotFound, - pkg_resources.VersionConflict): - pass # ignored - useragent_header = [('User-Agent', 'Mozilla/5.0 (X11; Linux x86_64; rv:25.0) Gecko/20100101 Firefox/25.0')] diff --git a/sphinx/versioning.py b/sphinx/versioning.py index e5fa3b9b3..502bf361c 100644 --- a/sphinx/versioning.py +++ b/sphinx/versioning.py @@ -9,17 +9,14 @@ :license: BSD, see LICENSE for details. """ import pickle -import warnings from itertools import product, zip_longest from operator import itemgetter from os import path from typing import Any, Dict, Iterator from uuid import uuid4 -from docutils import nodes from docutils.nodes import Node -from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.transforms import SphinxTransform if False: @@ -177,14 +174,6 @@ class UIDTransform(SphinxTransform): list(merge_doctrees(old_doctree, self.document, env.versioning_condition)) -def prepare(document: nodes.document) -> None: - """Simple wrapper for UIDTransform.""" - warnings.warn('versioning.prepare() is deprecated. Use UIDTransform instead.', - RemovedInSphinx30Warning, stacklevel=2) - transform = UIDTransform(document) - transform.apply() - - def setup(app: "Sphinx") -> Dict[str, Any]: app.add_transform(UIDTransform) diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py index ab5712410..e74c0334f 100644 --- a/sphinx/writers/html.py +++ b/sphinx/writers/html.py @@ -11,7 +11,6 @@ import copy import os import posixpath -import sys import warnings from typing import Any, Iterable, Tuple from typing import cast @@ -22,7 +21,7 @@ from docutils.writers.html4css1 import Writer, HTMLTranslator as BaseTranslator from sphinx import addnodes from sphinx.builders import Builder -from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning +from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.locale import admonitionlabels, _, __ from sphinx.util import logging from sphinx.util.docutils import SphinxTranslator @@ -116,10 +115,6 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator): def visit_desc_signature(self, node: Element) -> None: # the id is set automatically self.body.append(self.starttag(node, 'dt')) - # anchor for per-desc interactive data - if node.parent['objtype'] != 'describe' \ - and node['ids'] and node['first']: - self.body.append('<!--[%s]-->' % node['ids'][0]) def depart_desc_signature(self, node: Element) -> None: if not node.get('is_multiline'): @@ -831,29 +826,3 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator): def unknown_visit(self, node: Node) -> None: raise NotImplementedError('Unknown node: ' + node.__class__.__name__) - - # --------- METHODS FOR COMPATIBILITY -------------------------------------- - - @property - def highlightlang(self) -> str: - warnings.warn('HTMLTranslator.highlightlang is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - return self.builder.config.highlight_language - - @property - def highlightlang_base(self) -> str: - warnings.warn('HTMLTranslator.highlightlang_base is deprecated.', - RemovedInSphinx30Warning) - return self.builder.config.highlight_language - - @property - def highlightopts(self) -> str: - warnings.warn('HTMLTranslator.highlightopts is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - return self.builder.config.highlight_options - - @property - def highlightlinenothreshold(self) -> int: - warnings.warn('HTMLTranslator.highlightlinenothreshold is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - return sys.maxsize diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py index 9f38bf4f3..0c00a1fa4 100644 --- a/sphinx/writers/html5.py +++ b/sphinx/writers/html5.py @@ -10,7 +10,6 @@ import os import posixpath -import sys import warnings from typing import Any, Iterable, Tuple from typing import cast @@ -21,7 +20,7 @@ from docutils.writers.html5_polyglot import HTMLTranslator as BaseTranslator from sphinx import addnodes from sphinx.builders import Builder -from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning +from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.locale import admonitionlabels, _, __ from sphinx.util import logging from sphinx.util.docutils import SphinxTranslator @@ -88,10 +87,6 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): def visit_desc_signature(self, node: Element) -> None: # the id is set automatically self.body.append(self.starttag(node, 'dt')) - # anchor for per-desc interactive data - if node.parent['objtype'] != 'describe' \ - and node['ids'] and node['first']: - self.body.append('<!--[%s]-->' % node['ids'][0]) def depart_desc_signature(self, node: Element) -> None: if not node.get('is_multiline'): @@ -777,29 +772,3 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): def unknown_visit(self, node: Node) -> None: raise NotImplementedError('Unknown node: ' + node.__class__.__name__) - - # --------- METHODS FOR COMPATIBILITY -------------------------------------- - - @property - def highlightlang(self) -> str: - warnings.warn('HTMLTranslator.highlightlang is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - return self.builder.config.highlight_language - - @property - def highlightlang_base(self) -> str: - warnings.warn('HTMLTranslator.highlightlang_base is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - return self.builder.config.highlight_language - - @property - def highlightopts(self) -> str: - warnings.warn('HTMLTranslator.highlightopts is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - return self.builder.config.highlight_options - - @property - def highlightlinenothreshold(self) -> int: - warnings.warn('HTMLTranslator.highlightlinenothreshold is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - return sys.maxsize diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 85579a65a..7a8822585 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -12,11 +12,10 @@ """ import re -import sys import warnings from collections import defaultdict from os import path -from typing import Any, Callable, Dict, Iterable, Iterator, List, Pattern, Tuple, Set, Union +from typing import Any, Dict, Iterable, Iterator, List, Tuple, Set, Union from typing import cast from docutils import nodes, writers @@ -24,9 +23,7 @@ from docutils.nodes import Element, Node, Text from sphinx import addnodes from sphinx import highlighting -from sphinx.deprecation import ( - RemovedInSphinx30Warning, RemovedInSphinx40Warning, deprecated_alias -) +from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias from sphinx.domains import IndexEntry from sphinx.domains.std import StandardDomain from sphinx.errors import SphinxError @@ -132,18 +129,6 @@ class Table: # (cell = rectangular area) self.cell_id = 0 # last assigned cell_id - @property - def caption_footnotetexts(self) -> List[str]: - warnings.warn('table.caption_footnotetexts is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - return [] - - @property - def header_footnotetexts(self) -> List[str]: - warnings.warn('table.header_footnotetexts is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - return [] - def is_longtable(self) -> bool: """True if and only if table uses longtable environment.""" return self.row > 30 or 'longtable' in self.classes @@ -484,25 +469,6 @@ class LaTeXTranslator(SphinxTranslator): self.body = self.bodystack.pop() return body - def restrict_footnote(self, node: Element) -> None: - warnings.warn('LaTeXWriter.restrict_footnote() is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - - if self.footnote_restricted is None: - self.footnote_restricted = node - self.pending_footnotes = [] - - def unrestrict_footnote(self, node: Element) -> None: - warnings.warn('LaTeXWriter.unrestrict_footnote() is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - - if self.footnote_restricted == node: - self.footnote_restricted = None - for footnode in self.pending_footnotes: - footnode['footnotetext'] = True - footnode.walkabout(self) - self.pending_footnotes = [] - def format_docclass(self, docclass: str) -> str: """ prepends prefix to sphinx document classes """ @@ -1501,8 +1467,8 @@ class LaTeXTranslator(SphinxTranslator): def depart_attribution(self, node: Element) -> None: self.body.append('\n\\end{flushright}\n') - def visit_index(self, node: Element, scre: Pattern = None) -> None: - def escape(value): + def visit_index(self, node: Element) -> None: + def escape(value: str) -> str: value = self.encode(value) value = value.replace(r'\{', r'\sphinxleftcurlybrace{}') value = value.replace(r'\}', r'\sphinxrightcurlybrace{}') @@ -1512,17 +1478,13 @@ class LaTeXTranslator(SphinxTranslator): value = value.replace('|', r'\textbar{}') return value - def style(string): + def style(string: str) -> str: match = EXTRA_RE.match(string) if match: return match.expand(r'\\spxentry{\1}\\spxextra{\2}') else: return '\\spxentry{%s}' % string - if scre: - warnings.warn(('LaTeXTranslator.visit_index() optional argument ' - '"scre" is deprecated. It is ignored.'), - RemovedInSphinx30Warning, stacklevel=2) if not node.get('inline', True): self.body.append('\n') entries = node['entries'] @@ -2122,61 +2084,6 @@ class LaTeXTranslator(SphinxTranslator): RemovedInSphinx40Warning, stacklevel=2) return 0 - @property - def footnotestack(self) -> List[Dict[str, List[Union[collected_footnote, bool]]]]: - warnings.warn('LaTeXWriter.footnotestack is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - return [] - - @property - def bibitems(self) -> List[List[str]]: - warnings.warn('LaTeXTranslator.bibitems() is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - return [] - - @property - def in_container_literal_block(self) -> int: - warnings.warn('LaTeXTranslator.in_container_literal_block is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - return 0 - - @property - def next_section_ids(self) -> Set[str]: - warnings.warn('LaTeXTranslator.next_section_ids is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - return set() - - @property - def next_hyperlink_ids(self) -> Dict: - warnings.warn('LaTeXTranslator.next_hyperlink_ids is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - return {} - - def push_hyperlink_ids(self, figtype: str, ids: Set[str]) -> None: - warnings.warn('LaTeXTranslator.push_hyperlink_ids() is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - pass - - def pop_hyperlink_ids(self, figtype: str) -> Set[str]: - warnings.warn('LaTeXTranslator.pop_hyperlink_ids() is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - return set() - - @property - def hlsettingstack(self) -> List[List[Union[str, int]]]: - warnings.warn('LaTeXTranslator.hlsettingstack is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - return [[self.builder.config.highlight_language, sys.maxsize]] - - def check_latex_elements(self) -> None: - warnings.warn('check_latex_elements() is deprecated.', - RemovedInSphinx30Warning, stacklevel=2) - - for key in self.builder.config.latex_elements: - if key not in self.elements: - msg = __("Unknown configure key: latex_elements[%r] is ignored.") - logger.warning(msg % key) - def babel_defmacro(self, name: str, definition: str) -> str: warnings.warn('babel_defmacro() is deprecated.', RemovedInSphinx40Warning) @@ -2190,15 +2097,6 @@ class LaTeXTranslator(SphinxTranslator): return ('%s\\def%s{%s}%s\n' % (prefix, name, definition, suffix)) - def _make_visit_admonition(name: str) -> Callable[["LaTeXTranslator", Element], None]: # type: ignore # NOQA - warnings.warn('LaTeXTranslator._make_visit_admonition() is deprecated.', - RemovedInSphinx30Warning) - - def visit_admonition(self: "LaTeXTranslator", node: Element) -> None: - self.body.append('\n\\begin{sphinxadmonition}{%s}{%s:}' % - (name, admonitionlabels[name])) - return visit_admonition - def generate_numfig_format(self, builder: "LaTeXBuilder") -> str: warnings.warn('generate_numfig_format() is deprecated.', RemovedInSphinx40Warning) @@ -2239,18 +2137,11 @@ class LaTeXTranslator(SphinxTranslator): # Import old modules here for compatibility from sphinx.builders.latex import constants # NOQA -from sphinx.builders.latex.transforms import URI_SCHEMES, ShowUrlsTransform # NOQA from sphinx.builders.latex.util import ExtBabel # NOQA deprecated_alias('sphinx.writers.latex', { - 'ShowUrlsTransform': ShowUrlsTransform, - 'URI_SCHEMES': URI_SCHEMES, - }, - RemovedInSphinx30Warning) -deprecated_alias('sphinx.writers.latex', - { 'ADDITIONAL_SETTINGS': constants.ADDITIONAL_SETTINGS, 'DEFAULT_SETTINGS': constants.DEFAULT_SETTINGS, 'LUALATEX_DEFAULT_FONTPKG': constants.LUALATEX_DEFAULT_FONTPKG, diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index 65b4f1a0e..9c30244e9 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -10,16 +10,14 @@ import re import textwrap -import warnings from os import path -from typing import Any, Callable, Dict, Iterable, Iterator, List, Pattern, Set, Tuple, Union +from typing import Any, Dict, Iterable, Iterator, List, Pattern, Set, Tuple, Union from typing import cast from docutils import nodes, writers from docutils.nodes import Element, Node, Text from sphinx import addnodes, __display_version__ -from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.domains import IndexEntry from sphinx.domains.index import IndexDomain from sphinx.errors import ExtensionError @@ -1528,11 +1526,3 @@ class TexinfoTranslator(SphinxTranslator): self.body.append('\n\n@example\n%s\n@end example\n\n' % self.escape_arg(node.astext())) raise nodes.SkipNode - - def _make_visit_admonition(name: str) -> Callable[["TexinfoTranslator", Element], None]: # type: ignore # NOQA - warnings.warn('TexinfoTranslator._make_visit_admonition() is deprecated.', - RemovedInSphinx30Warning) - - def visit(self: "TexinfoTranslator", node: Element) -> None: - self.visit_admonition(node, admonitionlabels[name]) - return visit diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py index 882007d7e..b2ccd7b89 100644 --- a/sphinx/writers/text.py +++ b/sphinx/writers/text.py @@ -11,9 +11,8 @@ import math import os import re import textwrap -import warnings from itertools import groupby, chain -from typing import Any, Callable, Dict, List, Iterable, Optional, Set, Tuple, Union +from typing import Any, Dict, Generator, List, Iterable, Optional, Set, Tuple, Union from typing import cast from docutils import nodes, writers @@ -21,7 +20,6 @@ from docutils.nodes import Element, Node, Text from docutils.utils import column_width from sphinx import addnodes -from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.locale import admonitionlabels, _ from sphinx.util.docutils import SphinxTranslator @@ -34,23 +32,23 @@ class Cell: """Represents a cell in a table. It can span on multiple columns or on multiple lines. """ - def __init__(self, text="", rowspan=1, colspan=1): + def __init__(self, text: str = "", rowspan: int = 1, colspan: int = 1) -> None: self.text = text self.wrapped = [] # type: List[str] self.rowspan = rowspan self.colspan = colspan - self.col = None - self.row = None + self.col = None # type: Optional[int] + self.row = None # type: Optional[int] - def __repr__(self): + def __repr__(self) -> str: return "<Cell {!r} {}v{}/{}>{}>".format( self.text, self.row, self.rowspan, self.col, self.colspan ) - def __hash__(self): + def __hash__(self) -> int: return hash((self.col, self.row)) - def wrap(self, width): + def wrap(self, width: int) -> None: self.wrapped = my_wrap(self.text, width) @@ -100,7 +98,7 @@ class Table: +--------+--------+ """ - def __init__(self, colwidth=None): + def __init__(self, colwidth: List[int] = None) -> None: self.lines = [] # type: List[List[Cell]] self.separator = 0 self.colwidth = (colwidth if colwidth is not None @@ -108,19 +106,19 @@ class Table: self.current_line = 0 self.current_col = 0 - def add_row(self): + def add_row(self) -> None: """Add a row to the table, to use with ``add_cell()``. It is not needed to call ``add_row()`` before the first ``add_cell()``. """ self.current_line += 1 self.current_col = 0 - def set_separator(self): + def set_separator(self) -> None: """Sets the separator below the current line. """ self.separator = len(self.lines) - def add_cell(self, cell): + def add_cell(self, cell: Cell) -> None: """Add a cell to the current line, to use with ``add_row()``. To add a cell spanning on multiple lines or rows, simply set the ``cell.colspan`` or ``cell.rowspan`` BEFORE inserting it to @@ -131,13 +129,13 @@ class Table: self[self.current_line, self.current_col] = cell self.current_col += cell.colspan - def __getitem__(self, pos): + def __getitem__(self, pos: Tuple[int, int]) -> Cell: line, col = pos self._ensure_has_line(line + 1) self._ensure_has_column(col + 1) return self.lines[line][col] - def __setitem__(self, pos, cell): + def __setitem__(self, pos: Tuple[int, int], cell: Cell) -> None: line, col = pos self._ensure_has_line(line + cell.rowspan) self._ensure_has_column(col + cell.colspan) @@ -147,19 +145,19 @@ class Table: cell.row = line cell.col = col - def _ensure_has_line(self, line): + def _ensure_has_line(self, line: int) -> None: while len(self.lines) < line: self.lines.append([]) - def _ensure_has_column(self, col): + def _ensure_has_column(self, col: int) -> None: for line in self.lines: while len(line) < col: line.append(None) - def __repr__(self): + def __repr__(self) -> str: return "\n".join(repr(line) for line in self.lines) - def cell_width(self, cell, source): + def cell_width(self, cell: Cell, source: List[int]) -> int: """Give the cell width, according to the given source (either ``self.colwidth`` or ``self.measured_widths``). This take into account cells spanning on multiple columns. @@ -170,7 +168,7 @@ class Table: return width + (cell.colspan - 1) * 3 @property - def cells(self): + def cells(self) -> Generator[Cell, None, None]: seen = set() # type: Set[Cell] for lineno, line in enumerate(self.lines): for colno, cell in enumerate(line): @@ -178,7 +176,7 @@ class Table: yield cell seen.add(cell) - def rewrap(self): + def rewrap(self) -> None: """Call ``cell.wrap()`` on all cells, and measure each column width after wrapping (result written in ``self.measured_widths``). """ @@ -191,7 +189,7 @@ class Table: for col in range(cell.col, cell.col + cell.colspan): self.measured_widths[col] = max(self.measured_widths[col], width) - def physical_lines_for_line(self, line): + def physical_lines_for_line(self, line: List[Cell]) -> int: """From a given line, compute the number of physical lines it spans due to text wrapping. """ @@ -200,7 +198,7 @@ class Table: physical_lines = max(physical_lines, len(cell.wrapped)) return physical_lines - def __str__(self): + def __str__(self) -> str: out = [] self.rewrap() @@ -1176,11 +1174,3 @@ class TextTranslator(SphinxTranslator): def unknown_visit(self, node: Node) -> None: raise NotImplementedError('Unknown node: ' + node.__class__.__name__) - - def _make_depart_admonition(name: str) -> Callable[["TextTranslator", Element], None]: # type: ignore # NOQA - warnings.warn('TextTranslator._make_depart_admonition() is deprecated.', - RemovedInSphinx30Warning) - - def depart_admonition(self: "TextTranslator", node: Element) -> None: - self.end_state(first=admonitionlabels[name] + ': ') - return depart_admonition |
