mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch '3.x'
This commit is contained in:
commit
ec3754bd94
@ -9,6 +9,6 @@ jobs:
|
||||
- run: /python3.6/bin/pip install -U pip setuptools
|
||||
- run: /python3.6/bin/pip install -U .[test]
|
||||
- run: mkdir -p test-reports/pytest
|
||||
- run: make test PYTHON=/python3.6/bin/python TEST=--junitxml=test-reports/pytest/results.xml
|
||||
- run: make test PYTHON=/python3.6/bin/python TEST="--junitxml=test-reports/pytest/results.xml -vv"
|
||||
- store_test_results:
|
||||
path: test-reports
|
||||
|
21
.github/workflows/builddoc.yml
vendored
Normal file
21
.github/workflows/builddoc.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
name: Build document
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.6
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y graphviz
|
||||
pip install -U tox
|
||||
- name: Run Tox
|
||||
run: tox -e docs
|
22
.github/workflows/lint.yml
vendored
Normal file
22
.github/workflows/lint.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
name: Lint source code
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
tool: [docslint, flake8, mypy, twine]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.6
|
||||
- name: Install dependencies
|
||||
run: pip install -U tox
|
||||
- name: Run Tox
|
||||
run: tox -e ${{ matrix.tool }}
|
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@ -18,4 +18,4 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pip install -U tox
|
||||
- name: Run Tox
|
||||
run: tox -e py
|
||||
run: tox -e py -- -vv
|
||||
|
10
.travis.yml
10
.travis.yml
@ -27,14 +27,6 @@ jobs:
|
||||
- python: 'nightly'
|
||||
env:
|
||||
- TOXENV=py310
|
||||
- python: '3.6'
|
||||
env: TOXENV=docs
|
||||
- python: '3.6'
|
||||
env: TOXENV=docslint
|
||||
- python: '3.6'
|
||||
env: TOXENV=mypy
|
||||
- python: '3.6'
|
||||
env: TOXENV=flake8
|
||||
|
||||
- language: node_js
|
||||
node_js: '10.7'
|
||||
@ -47,7 +39,7 @@ install:
|
||||
- if [ $IS_PYTHON = false ]; then npm install; fi
|
||||
|
||||
script:
|
||||
- if [ $IS_PYTHON = true ]; then tox -- -v; fi
|
||||
- if [ $IS_PYTHON = true ]; then tox -- -vv; fi
|
||||
- if [ $IS_PYTHON = false ]; then npm test; fi
|
||||
|
||||
after_success:
|
||||
|
25
CHANGES
25
CHANGES
@ -51,10 +51,24 @@ Deprecated
|
||||
Features added
|
||||
--------------
|
||||
|
||||
* #7853: C and C++, support parameterized GNU style attributes.
|
||||
* #7888: napoleon: Add aliases Warn and Raise.
|
||||
* C, added :rst:dir:`c:alias` directive for inserting copies
|
||||
of existing declarations.
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
* #7839: autosummary: cannot handle umlauts in function names
|
||||
* #7865: autosummary: Failed to extract summary line when abbreviations found
|
||||
* #7866: autosummary: Failed to extract correct summary line when docstring
|
||||
contains a hyperlink target
|
||||
* #7715: LaTeX: ``numfig_secnum_depth > 1`` leads to wrong figure links
|
||||
* #7846: html theme: XML-invalid files were generated
|
||||
* #7869: :rst:role:`abbr` role without an explanation will show the explanation
|
||||
from the previous abbr role
|
||||
* C and C++, removed ``noindex`` directive option as it did
|
||||
nothing.
|
||||
|
||||
Testing
|
||||
--------
|
||||
@ -77,6 +91,17 @@ Features added
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
* #7844: autodoc: Failed to detect module when relative module name given
|
||||
* #7856: autodoc: AttributeError is raised when non-class object is given to
|
||||
the autoclass directive
|
||||
* #7850: autodoc: KeyError is raised for invalid mark up when autodoc_typehints
|
||||
is 'description'
|
||||
* #7812: autodoc: crashed if the target name matches to both an attribute and
|
||||
module that are same name
|
||||
* #7812: autosummary: generates broken stub files if the target code contains
|
||||
an attribute and module that are same name
|
||||
* #7806: viewcode: Failed to resolve viewcode references on 3rd party builders
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
|
@ -8,10 +8,10 @@ reports/feature requests.
|
||||
|
||||
Our contributing guide can be found online at:
|
||||
|
||||
https://www.sphinx-doc.org/en/master/internals/contributing/
|
||||
https://www.sphinx-doc.org/en/master/internals/contributing.html
|
||||
|
||||
You can also browse it from this repository from
|
||||
``doc/internals/contributing/``
|
||||
``doc/internals/contributing.rst``
|
||||
|
||||
Sphinx uses GitHub to host source code, track patches and bugs, and more.
|
||||
Please make an effort to provide as much possible when filing bugs.
|
||||
|
2
LICENSE
2
LICENSE
@ -1,7 +1,7 @@
|
||||
License for Sphinx
|
||||
==================
|
||||
|
||||
Copyright (c) 2007-2019 by the Sphinx team (see AUTHORS file).
|
||||
Copyright (c) 2007-2020 by the Sphinx team (see AUTHORS file).
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
2
Makefile
2
Makefile
@ -83,6 +83,6 @@ build:
|
||||
.PHONY: docs
|
||||
docs:
|
||||
ifndef target
|
||||
$(info You need to give a provide a target variable, e.g. `make docs target=html`.)
|
||||
$(info You need to provide a target variable, e.g. `make docs target=html`.)
|
||||
endif
|
||||
$(MAKE) -C doc $(target)
|
||||
|
@ -157,6 +157,40 @@ connect handlers to the events. Example:
|
||||
app.connect('source-read', source_read_handler)
|
||||
|
||||
|
||||
Below is an overview of each event that happens during a build. In the list
|
||||
below, we include the event name, its callback parameters, and the input and output
|
||||
type for that event::
|
||||
|
||||
1. event.config-inited(app,config)
|
||||
2. event.builder-inited(app)
|
||||
3. event.env-get-outdated(app, env, added, changed, removed)
|
||||
4. event.env-before-read-docs(app, env, docnames)
|
||||
|
||||
for docname in docnames:
|
||||
5. event.env-purge-doc(app, env, docname)
|
||||
if doc changed and not removed:
|
||||
6. source-read(app, docname, source)
|
||||
7. run source parsers: text -> docutils.document (parsers can be added with the app.add_source_parser() API)
|
||||
8. apply transforms (by priority): docutils.document -> docutils.document
|
||||
- event.doctree-read(app, doctree) is called in the middly of transforms,
|
||||
transforms come before/after this event depending on their priority.
|
||||
9. (if running in parallel mode, for each process) event.env-merged-info(app, env, docnames, other)
|
||||
10. event.env-updated(app, env)
|
||||
11. event.env-get-updated(app, env)
|
||||
11. event.env-check-consistency(app, env)
|
||||
|
||||
# For builders that output a single page, they are first joined into a single doctree before post-transforms/doctree-resolved
|
||||
for docname in docnames:
|
||||
12. apply post-transforms (by priority): docutils.document -> docutils.document
|
||||
13. event.doctree-resolved(app, doctree, docname)
|
||||
- (for any reference node that fails to resolve) event.missing-reference(env, node, contnode)
|
||||
|
||||
14. Generate output files
|
||||
|
||||
15. event.build-finished(app, exception)
|
||||
|
||||
Here is a more detailed list of these events.
|
||||
|
||||
.. event:: builder-inited (app)
|
||||
|
||||
Emitted when the builder object has been created. It is available as
|
||||
|
@ -659,10 +659,11 @@ documentation on :ref:`intl` for details.
|
||||
generated by Sphinx will be in that language. Also, Sphinx will try to
|
||||
substitute individual paragraphs from your documents with the translation
|
||||
sets obtained from :confval:`locale_dirs`. Sphinx will search
|
||||
language-specific figures named by `figure_language_filename` and substitute
|
||||
them for original figures. In the LaTeX builder, a suitable language will
|
||||
be selected as an option for the *Babel* package. Default is ``None``,
|
||||
which means that no translation will be done.
|
||||
language-specific figures named by `figure.language.filename`
|
||||
(e.g. the German version of ``myfigure.png`` will be ``myfigure.de.png``)
|
||||
and substitute them for original figures. In the LaTeX builder, a suitable
|
||||
language will be selected as an option for the *Babel* package. Default is
|
||||
``None``, which means that no translation will be done.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
|
||||
|
@ -280,6 +280,12 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
|
||||
|
||||
.. versionadded:: 1.3
|
||||
|
||||
* As a hint to autodoc extension, you can put a ``::`` separator in between
|
||||
module name and object name to let autodoc know the correct module name if
|
||||
it is ambiguous. ::
|
||||
|
||||
.. autoclass:: module.name::Noodle
|
||||
|
||||
|
||||
.. rst:directive:: autofunction
|
||||
autodecorator
|
||||
|
@ -115,6 +115,7 @@ All of the following section headers are supported:
|
||||
* ``Parameters``
|
||||
* ``Return`` *(alias of Returns)*
|
||||
* ``Returns``
|
||||
* ``Raise`` *(alias of Raises)*
|
||||
* ``Raises``
|
||||
* ``References``
|
||||
* ``See Also``
|
||||
@ -122,6 +123,7 @@ All of the following section headers are supported:
|
||||
* ``Todo``
|
||||
* ``Warning``
|
||||
* ``Warnings`` *(alias of Warning)*
|
||||
* ``Warn`` *(alias of Warns)*
|
||||
* ``Warns``
|
||||
* ``Yield`` *(alias of Yields)*
|
||||
* ``Yields``
|
||||
|
@ -739,6 +739,41 @@ Explicit ref: :c:var:`Data.@data.a`. Short-hand ref: :c:var:`Data.a`.
|
||||
.. versionadded:: 3.0
|
||||
|
||||
|
||||
Aliasing Declarations
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. c:namespace-push:: @alias
|
||||
|
||||
Sometimes it may be helpful list declarations elsewhere than their main
|
||||
documentation, e.g., when creating a synopsis of an interface.
|
||||
The following directive can be used for this purpose.
|
||||
|
||||
.. rst:directive:: .. c:alias:: name
|
||||
|
||||
Insert one or more alias declarations. Each entity can be specified
|
||||
as they can in the :rst:role:`c:any` role.
|
||||
|
||||
For example::
|
||||
|
||||
.. c:var:: int data
|
||||
.. c:function:: int f(double k)
|
||||
|
||||
.. c:alias:: data
|
||||
f
|
||||
|
||||
becomes
|
||||
|
||||
.. c:var:: int data
|
||||
.. c:function:: int f(double k)
|
||||
|
||||
.. c:alias:: data
|
||||
f
|
||||
|
||||
.. versionadded:: 3.2
|
||||
|
||||
.. c:namespace-pop::
|
||||
|
||||
|
||||
Inline Expressions and Types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@ -1078,7 +1113,6 @@ Options
|
||||
|
||||
Some directives support options:
|
||||
|
||||
- ``:noindex:``, see :ref:`basic-domain-markup`.
|
||||
- ``:tparam-line-spec:``, for templated declarations.
|
||||
If specified, each template parameter will be rendered on a separate line.
|
||||
|
||||
|
@ -253,7 +253,7 @@ class MessageCatalogBuilder(I18nBuilder):
|
||||
origin = MsgOrigin(template, line)
|
||||
self.catalogs['sphinx'].add(msg, origin)
|
||||
except Exception as exc:
|
||||
raise ThemeError('%s: %r' % (template, exc))
|
||||
raise ThemeError('%s: %r' % (template, exc)) from exc
|
||||
|
||||
def build(self, docnames: Iterable[str], summary: str = None, method: str = 'update') -> None: # NOQA
|
||||
self._extract_from_template()
|
||||
|
@ -135,7 +135,7 @@ class BuildInfo:
|
||||
build_info.tags_hash = lines[3].split()[1].strip()
|
||||
return build_info
|
||||
except Exception as exc:
|
||||
raise ValueError(__('build info file is broken: %r') % exc)
|
||||
raise ValueError(__('build info file is broken: %r') % exc) from exc
|
||||
|
||||
def __init__(self, config: Config = None, tags: Tags = None, config_categories: List[str] = []) -> None: # NOQA
|
||||
self.config_hash = ''
|
||||
@ -1010,7 +1010,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
return
|
||||
except Exception as exc:
|
||||
raise ThemeError(__("An error happened in rendering the page %s.\nReason: %r") %
|
||||
(pagename, exc))
|
||||
(pagename, exc)) from exc
|
||||
|
||||
if not outfilename:
|
||||
outfilename = self.get_outfilename(pagename)
|
||||
|
@ -87,10 +87,12 @@ class UserTheme(Theme):
|
||||
try:
|
||||
value = self.config.get('theme', key)
|
||||
setattr(self, key, value)
|
||||
except configparser.NoSectionError:
|
||||
raise ThemeError(__('%r doesn\'t have "theme" setting') % filename)
|
||||
except configparser.NoSectionError as exc:
|
||||
raise ThemeError(__('%r doesn\'t have "theme" setting') %
|
||||
filename) from exc
|
||||
except configparser.NoOptionError as exc:
|
||||
raise ThemeError(__('%r doesn\'t have "%s" setting') % (filename, exc.args[0]))
|
||||
raise ThemeError(__('%r doesn\'t have "%s" setting') %
|
||||
(filename, exc.args[0])) from exc
|
||||
|
||||
for key in self.OPTIONAL_CONFIG_KEYS:
|
||||
try:
|
||||
|
@ -191,9 +191,9 @@ class Config:
|
||||
elif isinstance(defvalue, int):
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
except ValueError as exc:
|
||||
raise ValueError(__('invalid number %r for config value %r, ignoring') %
|
||||
(value, name))
|
||||
(value, name)) from exc
|
||||
elif hasattr(defvalue, '__call__'):
|
||||
return value
|
||||
elif defvalue is not None and not isinstance(defvalue, str):
|
||||
@ -316,17 +316,17 @@ def eval_config_file(filename: str, tags: Tags) -> Dict[str, Any]:
|
||||
exec(code, namespace)
|
||||
except SyntaxError as err:
|
||||
msg = __("There is a syntax error in your configuration file: %s\n")
|
||||
raise ConfigError(msg % err)
|
||||
except SystemExit:
|
||||
raise ConfigError(msg % err) from err
|
||||
except SystemExit as exc:
|
||||
msg = __("The configuration file (or one of the modules it imports) "
|
||||
"called sys.exit()")
|
||||
raise ConfigError(msg)
|
||||
raise ConfigError(msg) from exc
|
||||
except ConfigError:
|
||||
# pass through ConfigError from conf.py as is. It will be shown in console.
|
||||
raise
|
||||
except Exception:
|
||||
except Exception as exc:
|
||||
msg = __("There is a programmable error in your configuration file:\n\n%s")
|
||||
raise ConfigError(msg % traceback.format_exc())
|
||||
raise ConfigError(msg % traceback.format_exc()) from exc
|
||||
|
||||
return namespace
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
import re
|
||||
from typing import (
|
||||
Any, Callable, Dict, Generator, Iterator, List, Type, Tuple, Union
|
||||
Any, Callable, Dict, Generator, Iterator, List, Type, TypeVar, Tuple, Union
|
||||
)
|
||||
from typing import cast
|
||||
|
||||
@ -26,9 +26,12 @@ from sphinx.domains import Domain, ObjType
|
||||
from sphinx.environment import BuildEnvironment
|
||||
from sphinx.locale import _, __
|
||||
from sphinx.roles import SphinxRole, XRefRole
|
||||
from sphinx.transforms import SphinxTransform
|
||||
from sphinx.transforms.post_transforms import ReferencesResolver
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.cfamily import (
|
||||
NoOldIdError, ASTBaseBase, verify_description_mode, StringifyTransform,
|
||||
NoOldIdError, ASTBaseBase, ASTBaseParenExprList,
|
||||
verify_description_mode, StringifyTransform,
|
||||
BaseParser, DefinitionError, UnsupportedMultiCharacterCharLiteral,
|
||||
identifier_re, anon_identifier_re, integer_literal_re, octal_literal_re,
|
||||
hex_literal_re, binary_literal_re, integers_literal_suffix_re,
|
||||
@ -40,6 +43,7 @@ from sphinx.util.docutils import SphinxDirective
|
||||
from sphinx.util.nodes import make_refnode
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
T = TypeVar('T')
|
||||
|
||||
# https://en.cppreference.com/w/c/keyword
|
||||
_keywords = [
|
||||
@ -1053,7 +1057,7 @@ class ASTDeclaratorParen(ASTDeclarator):
|
||||
# Initializer
|
||||
################################################################################
|
||||
|
||||
class ASTParenExprList(ASTBase):
|
||||
class ASTParenExprList(ASTBaseParenExprList):
|
||||
def __init__(self, exprs: List[ASTExpression]) -> None:
|
||||
self.exprs = exprs
|
||||
|
||||
@ -1883,7 +1887,7 @@ class Symbol:
|
||||
ourChild._fill_empty(otherChild.declaration, otherChild.docname)
|
||||
elif ourChild.docname != otherChild.docname:
|
||||
name = str(ourChild.declaration)
|
||||
msg = __("Duplicate declaration, also defined in '%s'.\n"
|
||||
msg = __("Duplicate C declaration, also defined in '%s'.\n"
|
||||
"Declaration is '%s'.")
|
||||
msg = msg % (ourChild.docname, name)
|
||||
logger.warning(msg, location=otherChild.docname)
|
||||
@ -2300,7 +2304,8 @@ class DefinitionParser(BaseParser):
|
||||
errs = []
|
||||
errs.append((exCast, "If type cast expression"))
|
||||
errs.append((exUnary, "If unary expression"))
|
||||
raise self._make_multi_error(errs, "Error in cast expression.")
|
||||
raise self._make_multi_error(errs,
|
||||
"Error in cast expression.") from exUnary
|
||||
else:
|
||||
return self._parse_unary_expression()
|
||||
|
||||
@ -2767,7 +2772,7 @@ class DefinitionParser(BaseParser):
|
||||
msg += " (e.g., 'void (*f(int arg))(double)')"
|
||||
prevErrors.append((exNoPtrParen, msg))
|
||||
header = "Error in declarator"
|
||||
raise self._make_multi_error(prevErrors, header)
|
||||
raise self._make_multi_error(prevErrors, header) from exNoPtrParen
|
||||
pos = self.pos
|
||||
try:
|
||||
return self._parse_declarator_name_suffix(named, paramMode, typed)
|
||||
@ -2775,7 +2780,7 @@ class DefinitionParser(BaseParser):
|
||||
self.pos = pos
|
||||
prevErrors.append((e, "If declarator-id"))
|
||||
header = "Error in declarator or parameters"
|
||||
raise self._make_multi_error(prevErrors, header)
|
||||
raise self._make_multi_error(prevErrors, header) from e
|
||||
|
||||
def _parse_initializer(self, outer: str = None, allowFallback: bool = True
|
||||
) -> ASTInitializer:
|
||||
@ -2843,7 +2848,7 @@ class DefinitionParser(BaseParser):
|
||||
if True:
|
||||
header = "Type must be either just a name or a "
|
||||
header += "typedef-like declaration."
|
||||
raise self._make_multi_error(prevErrors, header)
|
||||
raise self._make_multi_error(prevErrors, header) from exTyped
|
||||
else:
|
||||
# For testing purposes.
|
||||
# do it again to get the proper traceback (how do you
|
||||
@ -2994,7 +2999,7 @@ class DefinitionParser(BaseParser):
|
||||
errs = []
|
||||
errs.append((exExpr, "If expression"))
|
||||
errs.append((exType, "If type"))
|
||||
raise self._make_multi_error(errs, header)
|
||||
raise self._make_multi_error(errs, header) from exType
|
||||
return res
|
||||
|
||||
|
||||
@ -3017,6 +3022,13 @@ class CObject(ObjectDescription):
|
||||
names=('rtype',)),
|
||||
]
|
||||
|
||||
option_spec = {
|
||||
# have a dummy option to ensure proper errors on options,
|
||||
# otherwise the option is taken as a continuation of the
|
||||
# argument
|
||||
'dummy': None
|
||||
}
|
||||
|
||||
def _add_enumerator_to_parent(self, ast: ASTDeclaration) -> None:
|
||||
assert ast.objectType == 'enumerator'
|
||||
# find the parent, if it exists && is an enum
|
||||
@ -3083,7 +3095,8 @@ class CObject(ObjectDescription):
|
||||
self.state.document.note_explicit_target(signode)
|
||||
|
||||
domain = cast(CDomain, self.env.get_domain('c'))
|
||||
domain.note_object(name, self.objtype, newestId)
|
||||
if name not in domain.objects:
|
||||
domain.objects[name] = (domain.env.docname, newestId, self.objtype)
|
||||
|
||||
indexText = self.get_index_text(name)
|
||||
self.indexnode['entries'].append(('single', indexText, newestId, '', None))
|
||||
@ -3132,7 +3145,7 @@ class CObject(ObjectDescription):
|
||||
name = _make_phony_error_name()
|
||||
symbol = parentSymbol.add_name(name)
|
||||
self.env.temp_data['c:last_symbol'] = symbol
|
||||
raise ValueError
|
||||
raise ValueError from e
|
||||
|
||||
try:
|
||||
symbol = parentSymbol.add_declaration(ast, docname=self.env.docname)
|
||||
@ -3148,7 +3161,10 @@ class CObject(ObjectDescription):
|
||||
# Assume we are actually in the old symbol,
|
||||
# instead of the newly created duplicate.
|
||||
self.env.temp_data['c:last_symbol'] = e.symbol
|
||||
logger.warning("Duplicate declaration, %s", sig, location=signode)
|
||||
msg = __("Duplicate C declaration, also defined in '%s'.\n"
|
||||
"Declaration is '%s'.")
|
||||
msg = msg % (e.symbol.docname, sig)
|
||||
logger.warning(msg, location=signode)
|
||||
|
||||
if ast.objectType == 'enumerator':
|
||||
self._add_enumerator_to_parent(ast)
|
||||
@ -3309,6 +3325,106 @@ class CNamespacePopObject(SphinxDirective):
|
||||
return []
|
||||
|
||||
|
||||
class AliasNode(nodes.Element):
|
||||
def __init__(self, sig: str, env: "BuildEnvironment" = None,
|
||||
parentKey: LookupKey = None) -> None:
|
||||
super().__init__()
|
||||
self.sig = sig
|
||||
if env is not None:
|
||||
if 'c:parent_symbol' not in env.temp_data:
|
||||
root = env.domaindata['c']['root_symbol']
|
||||
env.temp_data['c:parent_symbol'] = root
|
||||
self.parentKey = env.temp_data['c:parent_symbol'].get_lookup_key()
|
||||
else:
|
||||
assert parentKey is not None
|
||||
self.parentKey = parentKey
|
||||
|
||||
def copy(self: T) -> T:
|
||||
return self.__class__(self.sig, env=None, parentKey=self.parentKey) # type: ignore
|
||||
|
||||
|
||||
class AliasTransform(SphinxTransform):
|
||||
default_priority = ReferencesResolver.default_priority - 1
|
||||
|
||||
def apply(self, **kwargs: Any) -> None:
|
||||
for node in self.document.traverse(AliasNode):
|
||||
sig = node.sig
|
||||
parentKey = node.parentKey
|
||||
try:
|
||||
parser = DefinitionParser(sig, location=node,
|
||||
config=self.env.config)
|
||||
name = parser.parse_xref_object()
|
||||
except DefinitionError as e:
|
||||
logger.warning(e, location=node)
|
||||
name = None
|
||||
|
||||
if name is None:
|
||||
# could not be parsed, so stop here
|
||||
signode = addnodes.desc_signature(sig, '')
|
||||
signode.clear()
|
||||
signode += addnodes.desc_name(sig, sig)
|
||||
node.replace_self(signode)
|
||||
continue
|
||||
|
||||
rootSymbol = self.env.domains['c'].data['root_symbol'] # type: Symbol
|
||||
parentSymbol = rootSymbol.direct_lookup(parentKey) # type: Symbol
|
||||
if not parentSymbol:
|
||||
print("Target: ", sig)
|
||||
print("ParentKey: ", parentKey)
|
||||
print(rootSymbol.dump(1))
|
||||
assert parentSymbol # should be there
|
||||
|
||||
s = parentSymbol.find_declaration(
|
||||
name, 'any',
|
||||
matchSelf=True, recurseInAnon=True)
|
||||
if s is None:
|
||||
signode = addnodes.desc_signature(sig, '')
|
||||
node.append(signode)
|
||||
signode.clear()
|
||||
signode += addnodes.desc_name(sig, sig)
|
||||
|
||||
logger.warning("Could not find C declaration for alias '%s'." % name,
|
||||
location=node)
|
||||
node.replace_self(signode)
|
||||
else:
|
||||
nodes = []
|
||||
options = dict() # type: ignore
|
||||
signode = addnodes.desc_signature(sig, '')
|
||||
nodes.append(signode)
|
||||
s.declaration.describe_signature(signode, 'markName', self.env, options)
|
||||
node.replace_self(nodes)
|
||||
|
||||
|
||||
class CAliasObject(ObjectDescription):
|
||||
option_spec = {} # type: Dict
|
||||
|
||||
def run(self) -> List[Node]:
|
||||
if ':' in self.name:
|
||||
self.domain, self.objtype = self.name.split(':', 1)
|
||||
else:
|
||||
self.domain, self.objtype = '', self.name
|
||||
|
||||
node = addnodes.desc()
|
||||
node.document = self.state.document
|
||||
node['domain'] = self.domain
|
||||
# 'desctype' is a backwards compatible attribute
|
||||
node['objtype'] = node['desctype'] = self.objtype
|
||||
node['noindex'] = True
|
||||
|
||||
self.names = [] # type: List[str]
|
||||
signatures = self.get_signatures()
|
||||
for i, sig in enumerate(signatures):
|
||||
node.append(AliasNode(sig, env=self.env))
|
||||
|
||||
contentnode = addnodes.desc_content()
|
||||
node.append(contentnode)
|
||||
self.before_content()
|
||||
self.state.nested_parse(self.content, self.content_offset, contentnode)
|
||||
self.env.temp_data['object'] = None
|
||||
self.after_content()
|
||||
return [node]
|
||||
|
||||
|
||||
class CXRefRole(XRefRole):
|
||||
def process_link(self, env: BuildEnvironment, refnode: Element,
|
||||
has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]:
|
||||
@ -3392,6 +3508,8 @@ class CDomain(Domain):
|
||||
'namespace': CNamespaceObject,
|
||||
'namespace-push': CNamespacePushObject,
|
||||
'namespace-pop': CNamespacePopObject,
|
||||
# other
|
||||
'alias': CAliasObject
|
||||
}
|
||||
roles = {
|
||||
'member': CXRefRole(),
|
||||
@ -3416,14 +3534,6 @@ class CDomain(Domain):
|
||||
def objects(self) -> Dict[str, Tuple[str, str, str]]:
|
||||
return self.data.setdefault('objects', {}) # fullname -> docname, node_id, objtype
|
||||
|
||||
def note_object(self, name: str, objtype: str, node_id: str, location: Any = None) -> None:
|
||||
if name in self.objects:
|
||||
docname = self.objects[name][0]
|
||||
logger.warning(__('Duplicate C object description of %s, '
|
||||
'other instance in %s, use :noindex: for one of them'),
|
||||
name, docname, location=location)
|
||||
self.objects[name] = (self.env.docname, node_id, objtype)
|
||||
|
||||
def clear_doc(self, docname: str) -> None:
|
||||
if Symbol.debug_show_tree:
|
||||
print("clear_doc:", docname)
|
||||
@ -3469,13 +3579,9 @@ class CDomain(Domain):
|
||||
ourObjects = self.data['objects']
|
||||
for fullname, (fn, id_, objtype) in otherdata['objects'].items():
|
||||
if fn in docnames:
|
||||
if fullname in ourObjects:
|
||||
msg = __("Duplicate declaration, also defined in '%s'.\n"
|
||||
"Name of declaration is '%s'.")
|
||||
msg = msg % (ourObjects[fullname], fullname)
|
||||
logger.warning(msg, location=fn)
|
||||
else:
|
||||
if fullname not in ourObjects:
|
||||
ourObjects[fullname] = (fn, id_, objtype)
|
||||
# no need to warn on duplicates, the symbol merge already does that
|
||||
|
||||
def _resolve_xref_inner(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
|
||||
typ: str, target: str, node: pending_xref,
|
||||
@ -3539,6 +3645,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
app.add_domain(CDomain)
|
||||
app.add_config_value("c_id_attributes", [], 'env')
|
||||
app.add_config_value("c_paren_attributes", [], 'env')
|
||||
app.add_post_transform(AliasTransform)
|
||||
|
||||
return {
|
||||
'version': 'builtin',
|
||||
|
@ -31,7 +31,8 @@ from sphinx.transforms import SphinxTransform
|
||||
from sphinx.transforms.post_transforms import ReferencesResolver
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.cfamily import (
|
||||
NoOldIdError, ASTBaseBase, ASTAttribute, verify_description_mode, StringifyTransform,
|
||||
NoOldIdError, ASTBaseBase, ASTAttribute, ASTBaseParenExprList,
|
||||
verify_description_mode, StringifyTransform,
|
||||
BaseParser, DefinitionError, UnsupportedMultiCharacterCharLiteral,
|
||||
identifier_re, anon_identifier_re, integer_literal_re, octal_literal_re,
|
||||
hex_literal_re, binary_literal_re, integers_literal_suffix_re,
|
||||
@ -2742,7 +2743,7 @@ class ASTPackExpansionExpr(ASTExpression):
|
||||
signode += nodes.Text('...')
|
||||
|
||||
|
||||
class ASTParenExprList(ASTBase):
|
||||
class ASTParenExprList(ASTBaseParenExprList):
|
||||
def __init__(self, exprs: List[Union[ASTExpression, ASTBracedInitList]]) -> None:
|
||||
self.exprs = exprs
|
||||
|
||||
@ -4454,7 +4455,7 @@ class Symbol:
|
||||
ourChild._fill_empty(otherChild.declaration, otherChild.docname)
|
||||
elif ourChild.docname != otherChild.docname:
|
||||
name = str(ourChild.declaration)
|
||||
msg = __("Duplicate declaration, also defined in '%s'.\n"
|
||||
msg = __("Duplicate C++ declaration, also defined in '%s'.\n"
|
||||
"Declaration is '%s'.")
|
||||
msg = msg % (ourChild.docname, name)
|
||||
logger.warning(msg, location=otherChild.docname)
|
||||
@ -4882,7 +4883,7 @@ class DefinitionParser(BaseParser):
|
||||
raise self._make_multi_error([
|
||||
(eFold, "If fold expression"),
|
||||
(eExpr, "If parenthesized expression")
|
||||
], "Error in fold expression or parenthesized expression.")
|
||||
], "Error in fold expression or parenthesized expression.") from eExpr
|
||||
return ASTParenExpr(res)
|
||||
# now it definitely is a fold expression
|
||||
if self.skip_string(')'):
|
||||
@ -5066,7 +5067,7 @@ class DefinitionParser(BaseParser):
|
||||
errors = []
|
||||
errors.append((eType, "If type"))
|
||||
errors.append((eExpr, "If expression"))
|
||||
raise self._make_multi_error(errors, header)
|
||||
raise self._make_multi_error(errors, header) from eExpr
|
||||
else: # a primary expression or a type
|
||||
pos = self.pos
|
||||
try:
|
||||
@ -5093,7 +5094,7 @@ class DefinitionParser(BaseParser):
|
||||
errors = []
|
||||
errors.append((eOuter, "If primary expression"))
|
||||
errors.append((eInner, "If type"))
|
||||
raise self._make_multi_error(errors, header)
|
||||
raise self._make_multi_error(errors, header) from eInner
|
||||
|
||||
# and now parse postfixes
|
||||
postFixes = [] # type: List[ASTPostfixOp]
|
||||
@ -5253,7 +5254,8 @@ class DefinitionParser(BaseParser):
|
||||
errs = []
|
||||
errs.append((exCast, "If type cast expression"))
|
||||
errs.append((exUnary, "If unary expression"))
|
||||
raise self._make_multi_error(errs, "Error in cast expression.")
|
||||
raise self._make_multi_error(errs,
|
||||
"Error in cast expression.") from exUnary
|
||||
else:
|
||||
return self._parse_unary_expression()
|
||||
|
||||
@ -5504,7 +5506,7 @@ class DefinitionParser(BaseParser):
|
||||
self.pos = pos
|
||||
prevErrors.append((e, "If non-type argument"))
|
||||
header = "Error in parsing template argument list."
|
||||
raise self._make_multi_error(prevErrors, header)
|
||||
raise self._make_multi_error(prevErrors, header) from e
|
||||
if parsedEnd:
|
||||
assert not parsedComma
|
||||
break
|
||||
@ -5949,7 +5951,7 @@ class DefinitionParser(BaseParser):
|
||||
self.pos = pos
|
||||
prevErrors.append((exNoPtrParen, "If parenthesis in noptr-declarator"))
|
||||
header = "Error in declarator"
|
||||
raise self._make_multi_error(prevErrors, header)
|
||||
raise self._make_multi_error(prevErrors, header) from exNoPtrParen
|
||||
if typed: # pointer to member
|
||||
pos = self.pos
|
||||
try:
|
||||
@ -5988,7 +5990,7 @@ class DefinitionParser(BaseParser):
|
||||
self.pos = pos
|
||||
prevErrors.append((e, "If declarator-id"))
|
||||
header = "Error in declarator or parameters-and-qualifiers"
|
||||
raise self._make_multi_error(prevErrors, header)
|
||||
raise self._make_multi_error(prevErrors, header) from e
|
||||
|
||||
def _parse_initializer(self, outer: str = None, allowFallback: bool = True
|
||||
) -> ASTInitializer:
|
||||
@ -6096,7 +6098,7 @@ class DefinitionParser(BaseParser):
|
||||
header = "Error when parsing function declaration."
|
||||
else:
|
||||
assert False
|
||||
raise self._make_multi_error(prevErrors, header)
|
||||
raise self._make_multi_error(prevErrors, header) from exTyped
|
||||
else:
|
||||
# For testing purposes.
|
||||
# do it again to get the proper traceback (how do you
|
||||
@ -6163,7 +6165,7 @@ class DefinitionParser(BaseParser):
|
||||
errs.append((eType, "If default template argument is a type"))
|
||||
msg = "Error in non-type template parameter"
|
||||
msg += " or constrained template parameter."
|
||||
raise self._make_multi_error(errs, msg)
|
||||
raise self._make_multi_error(errs, msg) from eType
|
||||
|
||||
def _parse_type_using(self) -> ASTTypeUsing:
|
||||
name = self._parse_nested_name()
|
||||
@ -6510,7 +6512,7 @@ class DefinitionParser(BaseParser):
|
||||
self.pos = pos
|
||||
prevErrors.append((e, "If type alias or template alias"))
|
||||
header = "Error in type declaration."
|
||||
raise self._make_multi_error(prevErrors, header)
|
||||
raise self._make_multi_error(prevErrors, header) from e
|
||||
elif objectType == 'concept':
|
||||
declaration = self._parse_concept()
|
||||
elif objectType == 'member':
|
||||
@ -6576,7 +6578,7 @@ class DefinitionParser(BaseParser):
|
||||
errs.append((e1, "If shorthand ref"))
|
||||
errs.append((e2, "If full function ref"))
|
||||
msg = "Error in cross-reference."
|
||||
raise self._make_multi_error(errs, msg)
|
||||
raise self._make_multi_error(errs, msg) from e2
|
||||
|
||||
def parse_expression(self) -> Union[ASTExpression, ASTType]:
|
||||
pos = self.pos
|
||||
@ -6597,7 +6599,7 @@ class DefinitionParser(BaseParser):
|
||||
errs = []
|
||||
errs.append((exExpr, "If expression"))
|
||||
errs.append((exType, "If type"))
|
||||
raise self._make_multi_error(errs, header)
|
||||
raise self._make_multi_error(errs, header) from exType
|
||||
|
||||
|
||||
def _make_phony_error_name() -> ASTNestedName:
|
||||
@ -6622,8 +6624,9 @@ class CPPObject(ObjectDescription):
|
||||
names=('returns', 'return')),
|
||||
]
|
||||
|
||||
option_spec = dict(ObjectDescription.option_spec)
|
||||
option_spec['tparam-line-spec'] = directives.flag
|
||||
option_spec = {
|
||||
'tparam-line-spec': directives.flag,
|
||||
}
|
||||
|
||||
def _add_enumerator_to_parent(self, ast: ASTDeclaration) -> None:
|
||||
assert ast.objectType == 'enumerator'
|
||||
@ -6790,7 +6793,7 @@ class CPPObject(ObjectDescription):
|
||||
name = _make_phony_error_name()
|
||||
symbol = parentSymbol.add_name(name)
|
||||
self.env.temp_data['cpp:last_symbol'] = symbol
|
||||
raise ValueError
|
||||
raise ValueError from e
|
||||
|
||||
try:
|
||||
symbol = parentSymbol.add_declaration(ast, docname=self.env.docname)
|
||||
@ -6806,7 +6809,10 @@ class CPPObject(ObjectDescription):
|
||||
# Assume we are actually in the old symbol,
|
||||
# instead of the newly created duplicate.
|
||||
self.env.temp_data['cpp:last_symbol'] = e.symbol
|
||||
logger.warning("Duplicate declaration, %s", sig, location=signode)
|
||||
msg = __("Duplicate C++ declaration, also defined in '%s'.\n"
|
||||
"Declaration is '%s'.")
|
||||
msg = msg % (e.symbol.docname, sig)
|
||||
logger.warning(msg, location=signode)
|
||||
|
||||
if ast.objectType == 'enumerator':
|
||||
self._add_enumerator_to_parent(ast)
|
||||
@ -7081,7 +7087,6 @@ class CPPAliasObject(ObjectDescription):
|
||||
node['domain'] = self.domain
|
||||
# 'desctype' is a backwards compatible attribute
|
||||
node['objtype'] = node['desctype'] = self.objtype
|
||||
node['noindex'] = True
|
||||
|
||||
self.names = [] # type: List[str]
|
||||
signatures = self.get_signatures()
|
||||
@ -7273,13 +7278,9 @@ class CPPDomain(Domain):
|
||||
ourNames = self.data['names']
|
||||
for name, docname in otherdata['names'].items():
|
||||
if docname in docnames:
|
||||
if name in ourNames:
|
||||
msg = __("Duplicate declaration, also defined in '%s'.\n"
|
||||
"Name of declaration is '%s'.")
|
||||
msg = msg % (ourNames[name], name)
|
||||
logger.warning(msg, location=docname)
|
||||
else:
|
||||
if name not in ourNames:
|
||||
ourNames[name] = docname
|
||||
# no need to warn on duplicates, the symbol merge already does that
|
||||
if Symbol.debug_show_tree:
|
||||
print("\tresult:")
|
||||
print(self.data['root_symbol'].dump(1))
|
||||
|
@ -1041,10 +1041,10 @@ class StandardDomain(Domain):
|
||||
try:
|
||||
figure_id = target_node['ids'][0]
|
||||
return env.toc_fignumbers[docname][figtype][figure_id]
|
||||
except (KeyError, IndexError):
|
||||
except (KeyError, IndexError) as exc:
|
||||
# target_node is found, but fignumber is not assigned.
|
||||
# Maybe it is defined in orphaned document.
|
||||
raise ValueError
|
||||
raise ValueError from exc
|
||||
|
||||
def get_full_qualified_name(self, node: Element) -> str:
|
||||
if node.get('reftype') == 'option':
|
||||
|
@ -376,7 +376,8 @@ class BuildEnvironment:
|
||||
if catalog.domain == domain:
|
||||
self.dependencies[docname].add(catalog.mo_path)
|
||||
except OSError as exc:
|
||||
raise DocumentError(__('Failed to scan documents in %s: %r') % (self.srcdir, exc))
|
||||
raise DocumentError(__('Failed to scan documents in %s: %r') %
|
||||
(self.srcdir, exc)) from exc
|
||||
|
||||
def get_outdated_files(self, config_changed: bool) -> Tuple[Set[str], Set[str], Set[str]]:
|
||||
"""Return (added, changed, removed) sets."""
|
||||
@ -501,8 +502,8 @@ class BuildEnvironment:
|
||||
"""
|
||||
try:
|
||||
return self.domains[domainname]
|
||||
except KeyError:
|
||||
raise ExtensionError(__('Domain %r is not registered') % domainname)
|
||||
except KeyError as exc:
|
||||
raise ExtensionError(__('Domain %r is not registered') % domainname) from exc
|
||||
|
||||
# --------- RESOLVING REFERENCES AND TOCTREES ------------------------------
|
||||
|
||||
|
@ -33,7 +33,6 @@ from sphinx.locale import _, __
|
||||
from sphinx.pycode import ModuleAnalyzer, PycodeError
|
||||
from sphinx.util import inspect
|
||||
from sphinx.util import logging
|
||||
from sphinx.util import split_full_qualified_name
|
||||
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
|
||||
@ -821,7 +820,12 @@ class Documenter:
|
||||
self.add_line('', sourcename)
|
||||
|
||||
# format the object's signature, if any
|
||||
sig = self.format_signature()
|
||||
try:
|
||||
sig = self.format_signature()
|
||||
except Exception as exc:
|
||||
logger.warning(__('error while formatting signature for %s: %s'),
|
||||
self.fullname, exc, type='autodoc')
|
||||
return
|
||||
|
||||
# generate the directive header and options, if applicable
|
||||
self.add_directive_header(sig)
|
||||
@ -970,14 +974,8 @@ class ModuleLevelDocumenter(Documenter):
|
||||
) -> Tuple[str, List[str]]:
|
||||
if modname is None:
|
||||
if path:
|
||||
stripped = path.rstrip('.')
|
||||
modname, qualname = split_full_qualified_name(stripped)
|
||||
if qualname:
|
||||
parents = qualname.split(".")
|
||||
else:
|
||||
parents = []
|
||||
|
||||
if modname is None:
|
||||
modname = path.rstrip('.')
|
||||
else:
|
||||
# if documenting a toplevel object without explicit module,
|
||||
# it can be contained in another auto directive ...
|
||||
modname = self.env.temp_data.get('autodoc:module')
|
||||
@ -1010,13 +1008,8 @@ class ClassLevelDocumenter(Documenter):
|
||||
# ... if still None, there's no way to know
|
||||
if mod_cls is None:
|
||||
return None, []
|
||||
|
||||
try:
|
||||
modname, qualname = split_full_qualified_name(mod_cls)
|
||||
parents = qualname.split(".") if qualname else []
|
||||
except ImportError:
|
||||
parents = mod_cls.split(".")
|
||||
|
||||
modname, sep, cls = mod_cls.rpartition('.')
|
||||
parents = [cls]
|
||||
# if the module name is still missing, get it like above
|
||||
if not modname:
|
||||
modname = self.env.temp_data.get('autodoc:module')
|
||||
|
@ -32,7 +32,7 @@ def import_module(modname: str, warningiserror: bool = False) -> Any:
|
||||
except BaseException as exc:
|
||||
# Importing modules may cause any side effects, including
|
||||
# SystemExit, so we need to catch all errors.
|
||||
raise ImportError(exc, traceback.format_exc())
|
||||
raise ImportError(exc, traceback.format_exc()) from exc
|
||||
|
||||
|
||||
def import_object(modname: str, objpath: List[str], objtype: str = '',
|
||||
@ -97,7 +97,7 @@ def import_object(modname: str, objpath: List[str], objtype: str = '',
|
||||
errmsg += '; the following exception was raised:\n%s' % traceback.format_exc()
|
||||
|
||||
logger.debug(errmsg)
|
||||
raise ImportError(errmsg)
|
||||
raise ImportError(errmsg) from exc
|
||||
|
||||
|
||||
def get_module_members(module: Any) -> List[Tuple[str, Any]]:
|
||||
|
@ -46,11 +46,16 @@ def merge_typehints(app: Sphinx, domain: str, objtype: str, contentnode: Element
|
||||
if objtype == 'class' and app.config.autoclass_content not in ('init', 'both'):
|
||||
return
|
||||
|
||||
signature = cast(addnodes.desc_signature, contentnode.parent[0])
|
||||
if signature['module']:
|
||||
fullname = '.'.join([signature['module'], signature['fullname']])
|
||||
else:
|
||||
fullname = signature['fullname']
|
||||
try:
|
||||
signature = cast(addnodes.desc_signature, contentnode.parent[0])
|
||||
if signature['module']:
|
||||
fullname = '.'.join([signature['module'], signature['fullname']])
|
||||
else:
|
||||
fullname = signature['fullname']
|
||||
except KeyError:
|
||||
# signature node does not have valid context info for the target object
|
||||
return
|
||||
|
||||
annotations = app.env.temp_data.get('annotations', {})
|
||||
if annotations.get(fullname, {}):
|
||||
field_lists = [n for n in contentnode if isinstance(n, nodes.field_list)]
|
||||
|
@ -95,6 +95,8 @@ logger = logging.getLogger(__name__)
|
||||
periods_re = re.compile(r'\.(?:\s+)')
|
||||
literal_re = re.compile(r'::\s*$')
|
||||
|
||||
WELL_KNOWN_ABBREVIATIONS = (' i.e.',)
|
||||
|
||||
|
||||
# -- autosummary_toc node ------------------------------------------------------
|
||||
|
||||
@ -470,6 +472,13 @@ def mangle_signature(sig: str, max_chars: int = 30) -> str:
|
||||
|
||||
def extract_summary(doc: List[str], document: Any) -> str:
|
||||
"""Extract summary from docstring."""
|
||||
def parse(doc: List[str], settings: Any) -> nodes.document:
|
||||
state_machine = RSTStateMachine(state_classes, 'Body')
|
||||
node = new_document('', settings)
|
||||
node.reporter = NullReporter()
|
||||
state_machine.run(doc, node)
|
||||
|
||||
return node
|
||||
|
||||
# Skip a blank lines at the top
|
||||
while doc and not doc[0].strip():
|
||||
@ -487,11 +496,7 @@ def extract_summary(doc: List[str], document: Any) -> str:
|
||||
return ''
|
||||
|
||||
# parse the docstring
|
||||
state_machine = RSTStateMachine(state_classes, 'Body')
|
||||
node = new_document('', document.settings)
|
||||
node.reporter = NullReporter()
|
||||
state_machine.run(doc, node)
|
||||
|
||||
node = parse(doc, document.settings)
|
||||
if not isinstance(node[0], nodes.paragraph):
|
||||
# document starts with non-paragraph: pick up the first line
|
||||
summary = doc[0].strip()
|
||||
@ -502,11 +507,13 @@ def extract_summary(doc: List[str], document: Any) -> str:
|
||||
summary = sentences[0].strip()
|
||||
else:
|
||||
summary = ''
|
||||
while sentences:
|
||||
summary += sentences.pop(0) + '.'
|
||||
for i in range(len(sentences)):
|
||||
summary = ". ".join(sentences[:i + 1]).rstrip(".") + "."
|
||||
node[:] = []
|
||||
state_machine.run([summary], node)
|
||||
if not node.traverse(nodes.system_message):
|
||||
node = parse(doc, document.settings)
|
||||
if summary.endswith(WELL_KNOWN_ABBREVIATIONS):
|
||||
pass
|
||||
elif not node.traverse(nodes.system_message):
|
||||
# considered as that splitting by period does not break inline markups
|
||||
break
|
||||
|
||||
@ -620,7 +627,7 @@ def _import_by_name(name: str) -> Tuple[Any, Any, str]:
|
||||
else:
|
||||
return sys.modules[modname], None, modname
|
||||
except (ValueError, ImportError, AttributeError, KeyError) as e:
|
||||
raise ImportError(*e.args)
|
||||
raise ImportError(*e.args) from e
|
||||
|
||||
|
||||
# -- :autolink: (smart default role) -------------------------------------------
|
||||
@ -701,7 +708,8 @@ def process_generate_options(app: Sphinx) -> None:
|
||||
with mock(app.config.autosummary_mock_imports):
|
||||
generate_autosummary_docs(genfiles, suffix=suffix, base_path=app.srcdir,
|
||||
app=app, imported_members=imported_members,
|
||||
overwrite=app.config.autosummary_generate_overwrite)
|
||||
overwrite=app.config.autosummary_generate_overwrite,
|
||||
encoding=app.config.source_encoding)
|
||||
|
||||
|
||||
def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
|
@ -226,7 +226,8 @@ class ModuleScanner:
|
||||
def generate_autosummary_content(name: str, obj: Any, parent: Any,
|
||||
template: AutosummaryRenderer, template_name: str,
|
||||
imported_members: bool, app: Any,
|
||||
recursive: bool, context: Dict) -> str:
|
||||
recursive: bool, context: Dict,
|
||||
modname: str = None, qualname: str = None) -> str:
|
||||
doc = get_documenter(app, obj, parent)
|
||||
|
||||
def skip_member(obj: Any, name: str, objtype: str) -> bool:
|
||||
@ -315,7 +316,9 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
|
||||
ns['attributes'], ns['all_attributes'] = \
|
||||
get_members(obj, {'attribute', 'property'})
|
||||
|
||||
modname, qualname = split_full_qualified_name(name)
|
||||
if modname is None or qualname is None:
|
||||
modname, qualname = split_full_qualified_name(name)
|
||||
|
||||
if doc.objtype in ('method', 'attribute', 'property'):
|
||||
ns['class'] = qualname.rsplit(".", 1)[0]
|
||||
|
||||
@ -342,7 +345,7 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None,
|
||||
suffix: str = '.rst', base_path: str = None,
|
||||
builder: Builder = None, template_dir: str = None,
|
||||
imported_members: bool = False, app: Any = None,
|
||||
overwrite: bool = True) -> None:
|
||||
overwrite: bool = True, encoding: str = 'utf-8') -> None:
|
||||
if builder:
|
||||
warnings.warn('builder argument for generate_autosummary_docs() is deprecated.',
|
||||
RemovedInSphinx50Warning, stacklevel=2)
|
||||
@ -382,7 +385,8 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None,
|
||||
ensuredir(path)
|
||||
|
||||
try:
|
||||
name, obj, parent, mod_name = import_by_name(entry.name)
|
||||
name, obj, parent, modname = import_by_name(entry.name)
|
||||
qualname = name.replace(modname + ".", "")
|
||||
except ImportError as e:
|
||||
logger.warning(__('[autosummary] failed to import %r: %s') % (entry.name, e))
|
||||
continue
|
||||
@ -392,21 +396,22 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None,
|
||||
context.update(app.config.autosummary_context)
|
||||
|
||||
content = generate_autosummary_content(name, obj, parent, template, entry.template,
|
||||
imported_members, app, entry.recursive, context)
|
||||
imported_members, app, entry.recursive, context,
|
||||
modname, qualname)
|
||||
|
||||
filename = os.path.join(path, name + suffix)
|
||||
if os.path.isfile(filename):
|
||||
with open(filename) as f:
|
||||
with open(filename, encoding=encoding) as f:
|
||||
old_content = f.read()
|
||||
|
||||
if content == old_content:
|
||||
continue
|
||||
elif overwrite: # content has changed
|
||||
with open(filename, 'w') as f:
|
||||
with open(filename, 'w', encoding=encoding) as f:
|
||||
f.write(content)
|
||||
new_files.append(filename)
|
||||
else:
|
||||
with open(filename, 'w') as f:
|
||||
with open(filename, 'w', encoding=encoding) as f:
|
||||
f.write(content)
|
||||
new_files.append(filename)
|
||||
|
||||
|
@ -256,7 +256,7 @@ def render_dot(self: SphinxTranslator, code: str, options: Dict,
|
||||
return None, None
|
||||
except CalledProcessError as exc:
|
||||
raise GraphvizError(__('dot exited with error:\n[stderr]\n%r\n'
|
||||
'[stdout]\n%r') % (exc.stderr, exc.stdout))
|
||||
'[stdout]\n%r') % (exc.stderr, exc.stdout)) from exc
|
||||
|
||||
|
||||
def render_dot_html(self: HTMLTranslator, node: graphviz, code: str, options: Dict,
|
||||
@ -270,7 +270,7 @@ def render_dot_html(self: HTMLTranslator, node: graphviz, code: str, options: Di
|
||||
fname, outfn = render_dot(self, code, options, format, prefix)
|
||||
except GraphvizError as exc:
|
||||
logger.warning(__('dot code %r: %s'), code, exc)
|
||||
raise nodes.SkipNode
|
||||
raise nodes.SkipNode from exc
|
||||
|
||||
classes = [imgcls, 'graphviz'] + node.get('classes', [])
|
||||
imgcls = ' '.join(filter(None, classes))
|
||||
@ -321,7 +321,7 @@ def render_dot_latex(self: LaTeXTranslator, node: graphviz, code: str,
|
||||
fname, outfn = render_dot(self, code, options, 'pdf', prefix)
|
||||
except GraphvizError as exc:
|
||||
logger.warning(__('dot code %r: %s'), code, exc)
|
||||
raise nodes.SkipNode
|
||||
raise nodes.SkipNode from exc
|
||||
|
||||
is_inline = self.is_inline(node)
|
||||
|
||||
@ -358,7 +358,7 @@ def render_dot_texinfo(self: TexinfoTranslator, node: graphviz, code: str,
|
||||
fname, outfn = render_dot(self, code, options, 'png', prefix)
|
||||
except GraphvizError as exc:
|
||||
logger.warning(__('dot code %r: %s'), code, exc)
|
||||
raise nodes.SkipNode
|
||||
raise nodes.SkipNode from exc
|
||||
if fname is not None:
|
||||
self.body.append('@image{%s,,,[graphviz],png}\n' % fname[:-4])
|
||||
raise nodes.SkipNode
|
||||
|
@ -70,7 +70,7 @@ class ImagemagickConverter(ImageConverter):
|
||||
except CalledProcessError as exc:
|
||||
raise ExtensionError(__('convert exited with error:\n'
|
||||
'[stderr]\n%r\n[stdout]\n%r') %
|
||||
(exc.stderr, exc.stdout))
|
||||
(exc.stderr, exc.stdout)) from exc
|
||||
|
||||
|
||||
def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
|
@ -137,13 +137,13 @@ def compile_math(latex: str, builder: Builder) -> str:
|
||||
try:
|
||||
subprocess.run(command, stdout=PIPE, stderr=PIPE, cwd=tempdir, check=True)
|
||||
return path.join(tempdir, 'math.dvi')
|
||||
except OSError:
|
||||
except OSError as exc:
|
||||
logger.warning(__('LaTeX command %r cannot be run (needed for math '
|
||||
'display), check the imgmath_latex setting'),
|
||||
builder.config.imgmath_latex)
|
||||
raise InvokeError
|
||||
raise InvokeError from exc
|
||||
except CalledProcessError as exc:
|
||||
raise MathExtError('latex exited with error', exc.stderr, exc.stdout)
|
||||
raise MathExtError('latex exited with error', exc.stderr, exc.stdout) from exc
|
||||
|
||||
|
||||
def convert_dvi_to_image(command: List[str], name: str) -> Tuple[str, str]:
|
||||
@ -151,13 +151,13 @@ def convert_dvi_to_image(command: List[str], name: str) -> Tuple[str, str]:
|
||||
try:
|
||||
ret = subprocess.run(command, stdout=PIPE, stderr=PIPE, check=True, encoding='ascii')
|
||||
return ret.stdout, ret.stderr
|
||||
except OSError:
|
||||
except OSError as exc:
|
||||
logger.warning(__('%s command %r cannot be run (needed for math '
|
||||
'display), check the imgmath_%s setting'),
|
||||
name, command[0], name)
|
||||
raise InvokeError
|
||||
raise InvokeError from exc
|
||||
except CalledProcessError as exc:
|
||||
raise MathExtError('%s exited with error' % name, exc.stderr, exc.stdout)
|
||||
raise MathExtError('%s exited with error' % name, exc.stderr, exc.stdout) from exc
|
||||
|
||||
|
||||
def convert_dvi_to_png(dvipath: str, builder: Builder) -> Tuple[str, int]:
|
||||
@ -298,7 +298,7 @@ def html_visit_math(self: HTMLTranslator, node: nodes.math) -> None:
|
||||
backrefs=[], source=node.astext())
|
||||
sm.walkabout(self)
|
||||
logger.warning(__('display latex %r: %s'), node.astext(), msg)
|
||||
raise nodes.SkipNode
|
||||
raise nodes.SkipNode from exc
|
||||
if fname is None:
|
||||
# something failed -- use text-only as a bad substitute
|
||||
self.body.append('<span class="math">%s</span>' %
|
||||
@ -324,7 +324,7 @@ def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None
|
||||
backrefs=[], source=node.astext())
|
||||
sm.walkabout(self)
|
||||
logger.warning(__('inline latex %r: %s'), node.astext(), msg)
|
||||
raise nodes.SkipNode
|
||||
raise nodes.SkipNode from exc
|
||||
self.body.append(self.starttag(node, 'div', CLASS='math'))
|
||||
self.body.append('<p>')
|
||||
if node['number']:
|
||||
|
@ -179,7 +179,7 @@ def fetch_inventory(app: Sphinx, uri: str, inv: Any) -> Any:
|
||||
join = path.join if localuri else posixpath.join
|
||||
invdata = InventoryFile.load(f, uri, join)
|
||||
except ValueError as exc:
|
||||
raise ValueError('unknown or unsupported inventory version: %r' % exc)
|
||||
raise ValueError('unknown or unsupported inventory version: %r' % exc) from exc
|
||||
except Exception as err:
|
||||
err.args = ('intersphinx inventory %r not readable due to %s: %s',
|
||||
inv, err.__class__.__name__, str(err))
|
||||
|
@ -158,6 +158,7 @@ class GoogleDocstring:
|
||||
'parameters': self._parse_parameters_section,
|
||||
'return': self._parse_returns_section,
|
||||
'returns': self._parse_returns_section,
|
||||
'raise': self._parse_raises_section,
|
||||
'raises': self._parse_raises_section,
|
||||
'references': self._parse_references_section,
|
||||
'see also': self._parse_see_also_section,
|
||||
@ -165,6 +166,7 @@ class GoogleDocstring:
|
||||
'todo': partial(self._parse_admonition, 'todo'),
|
||||
'warning': partial(self._parse_admonition, 'warning'),
|
||||
'warnings': partial(self._parse_admonition, 'warning'),
|
||||
'warn': self._parse_warns_section,
|
||||
'warns': self._parse_warns_section,
|
||||
'yield': self._parse_yields_section,
|
||||
'yields': self._parse_yields_section,
|
||||
|
@ -131,10 +131,8 @@ def env_merge_info(app: Sphinx, env: BuildEnvironment, docnames: Iterable[str],
|
||||
|
||||
def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: Node
|
||||
) -> Node:
|
||||
if app.builder.format != 'html':
|
||||
return None
|
||||
elif node['reftype'] == 'viewcode':
|
||||
# resolve our "viewcode" reference nodes -- they need special treatment
|
||||
# resolve our "viewcode" reference nodes -- they need special treatment
|
||||
if node['reftype'] == 'viewcode':
|
||||
return make_refnode(app.builder, node['refdoc'], node['reftarget'],
|
||||
node['refid'], contnode)
|
||||
|
||||
|
@ -35,7 +35,7 @@ class ModuleAnalyzer:
|
||||
try:
|
||||
mod = import_module(modname)
|
||||
except Exception as err:
|
||||
raise PycodeError('error importing %r' % modname, err)
|
||||
raise PycodeError('error importing %r' % modname, err) from err
|
||||
loader = getattr(mod, '__loader__', None)
|
||||
filename = getattr(mod, '__file__', None)
|
||||
if loader and getattr(loader, 'get_source', None):
|
||||
@ -52,7 +52,7 @@ class ModuleAnalyzer:
|
||||
try:
|
||||
filename = loader.get_filename(modname)
|
||||
except ImportError as err:
|
||||
raise PycodeError('error getting filename for %r' % modname, err)
|
||||
raise PycodeError('error getting filename for %r' % modname, err) from err
|
||||
if filename is None:
|
||||
# all methods for getting filename failed, so raise...
|
||||
raise PycodeError('no source found for module %r' % modname)
|
||||
@ -90,7 +90,7 @@ class ModuleAnalyzer:
|
||||
if '.egg' + path.sep in filename:
|
||||
obj = cls.cache['file', filename] = cls.for_egg(filename, modname)
|
||||
else:
|
||||
raise PycodeError('error opening %r' % filename, err)
|
||||
raise PycodeError('error opening %r' % filename, err) from err
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
@ -102,7 +102,7 @@ class ModuleAnalyzer:
|
||||
code = egg.read(relpath).decode()
|
||||
return cls.for_string(code, modname, filename)
|
||||
except Exception as exc:
|
||||
raise PycodeError('error opening %r' % filename, exc)
|
||||
raise PycodeError('error opening %r' % filename, exc) from exc
|
||||
|
||||
@classmethod
|
||||
def for_module(cls, modname: str) -> "ModuleAnalyzer":
|
||||
@ -158,7 +158,7 @@ class ModuleAnalyzer:
|
||||
self.tags = parser.definitions
|
||||
self.tagorder = parser.deforders
|
||||
except Exception as exc:
|
||||
raise PycodeError('parsing %r failed: %r' % (self.srcname, exc))
|
||||
raise PycodeError('parsing %r failed: %r' % (self.srcname, exc)) from exc
|
||||
|
||||
def find_attr_docs(self) -> Dict[Tuple[str, str], List[str]]:
|
||||
"""Find class and module-level attributes and their documentation."""
|
||||
|
@ -138,9 +138,9 @@ class SphinxComponentRegistry:
|
||||
entry_points = iter_entry_points('sphinx.builders', name)
|
||||
try:
|
||||
entry_point = next(entry_points)
|
||||
except StopIteration:
|
||||
except StopIteration as exc:
|
||||
raise SphinxError(__('Builder name %s not registered or available'
|
||||
' through entry point') % name)
|
||||
' through entry point') % name) from exc
|
||||
|
||||
self.load_extension(app, entry_point.module_name)
|
||||
|
||||
@ -272,8 +272,8 @@ class SphinxComponentRegistry:
|
||||
def get_source_parser(self, filetype: str) -> "Type[Parser]":
|
||||
try:
|
||||
return self.source_parsers[filetype]
|
||||
except KeyError:
|
||||
raise SphinxError(__('Source parser for %s not registered') % filetype)
|
||||
except KeyError as exc:
|
||||
raise SphinxError(__('Source parser for %s not registered') % filetype) from exc
|
||||
|
||||
def get_source_parsers(self) -> Dict[str, "Type[Parser]"]:
|
||||
return self.source_parsers
|
||||
@ -310,9 +310,11 @@ class SphinxComponentRegistry:
|
||||
try:
|
||||
visit, depart = handlers # unpack once for assertion
|
||||
translation_handlers[node.__name__] = (visit, depart)
|
||||
except ValueError:
|
||||
raise ExtensionError(__('kwargs for add_node() must be a (visit, depart) '
|
||||
'function tuple: %r=%r') % (builder_name, handlers))
|
||||
except ValueError as exc:
|
||||
raise ExtensionError(
|
||||
__('kwargs for add_node() must be a (visit, depart) '
|
||||
'function tuple: %r=%r') % (builder_name, handlers)
|
||||
) from exc
|
||||
|
||||
def get_translator_class(self, builder: Builder) -> "Type[nodes.NodeVisitor]":
|
||||
return self.translators.get(builder.name,
|
||||
@ -406,7 +408,8 @@ class SphinxComponentRegistry:
|
||||
mod = import_module(extname)
|
||||
except ImportError as err:
|
||||
logger.verbose(__('Original exception:\n') + traceback.format_exc())
|
||||
raise ExtensionError(__('Could not import extension %s') % extname, err)
|
||||
raise ExtensionError(__('Could not import extension %s') % extname,
|
||||
err) from err
|
||||
|
||||
setup = getattr(mod, 'setup', None)
|
||||
if setup is None:
|
||||
@ -422,7 +425,7 @@ class SphinxComponentRegistry:
|
||||
__('The %s extension used by this project needs at least '
|
||||
'Sphinx v%s; it therefore cannot be built with this '
|
||||
'version.') % (extname, err)
|
||||
)
|
||||
) from err
|
||||
|
||||
if metadata is None:
|
||||
metadata = {}
|
||||
|
@ -331,14 +331,15 @@ class Abbreviation(SphinxRole):
|
||||
abbr_re = re.compile(r'\((.*)\)$', re.S)
|
||||
|
||||
def run(self) -> Tuple[List[Node], List[system_message]]:
|
||||
options = self.options.copy()
|
||||
matched = self.abbr_re.search(self.text)
|
||||
if matched:
|
||||
text = self.text[:matched.start()].strip()
|
||||
self.options['explanation'] = matched.group(1)
|
||||
options['explanation'] = matched.group(1)
|
||||
else:
|
||||
text = self.text
|
||||
|
||||
return [nodes.abbreviation(self.rawtext, text, **self.options)], []
|
||||
return [nodes.abbreviation(self.rawtext, text, **options)], []
|
||||
|
||||
|
||||
specific_docroles = {
|
||||
|
@ -528,9 +528,9 @@ class SearchJapanese(SearchLanguage):
|
||||
dotted_path = options.get('type', 'sphinx.search.ja.DefaultSplitter')
|
||||
try:
|
||||
self.splitter = import_object(dotted_path)(options)
|
||||
except ExtensionError:
|
||||
except ExtensionError as exc:
|
||||
raise ExtensionError("Splitter module %r can't be imported" %
|
||||
dotted_path)
|
||||
dotted_path) from exc
|
||||
|
||||
def split(self, input: str) -> List[str]:
|
||||
return self.splitter.split(input)
|
||||
|
@ -198,7 +198,7 @@ class BuildDoc(Command):
|
||||
except Exception as exc:
|
||||
handle_exception(app, self, exc, sys.stderr)
|
||||
if not self.pdb:
|
||||
raise SystemExit(1)
|
||||
raise SystemExit(1) from exc
|
||||
|
||||
if not self.link_index:
|
||||
continue
|
||||
|
@ -120,7 +120,7 @@
|
||||
{%- else %}
|
||||
<meta http-equiv="Content-Type" content="text/html; charset={{ encoding }}" />
|
||||
{%- endif %}
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
{{- metatags }}
|
||||
{%- block htmltitle %}
|
||||
<title>{{ title|striptags|e }}{{ titlesuffix }}</title>
|
||||
|
@ -20,7 +20,7 @@
|
||||
{%- endblock %}
|
||||
|
||||
{%- block extrahead %}
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<!--[if lt IE 9]>
|
||||
<script src="_static/css3-mediaqueries.js"></script>
|
||||
<![endif]-->
|
||||
|
@ -74,17 +74,17 @@ class Theme:
|
||||
|
||||
try:
|
||||
inherit = self.config.get('theme', 'inherit')
|
||||
except configparser.NoSectionError:
|
||||
raise ThemeError(__('theme %r doesn\'t have "theme" setting') % name)
|
||||
except configparser.NoOptionError:
|
||||
raise ThemeError(__('theme %r doesn\'t have "inherit" setting') % name)
|
||||
except configparser.NoSectionError as exc:
|
||||
raise ThemeError(__('theme %r doesn\'t have "theme" setting') % name) from exc
|
||||
except configparser.NoOptionError as exc:
|
||||
raise ThemeError(__('theme %r doesn\'t have "inherit" setting') % name) from exc
|
||||
|
||||
if inherit != 'none':
|
||||
try:
|
||||
self.base = factory.create(inherit)
|
||||
except ThemeError:
|
||||
except ThemeError as exc:
|
||||
raise ThemeError(__('no theme named %r found, inherited by %r') %
|
||||
(inherit, name))
|
||||
(inherit, name)) from exc
|
||||
|
||||
def get_theme_dirs(self) -> List[str]:
|
||||
"""Return a list of theme directories, beginning with this theme's,
|
||||
@ -101,13 +101,13 @@ class Theme:
|
||||
"""
|
||||
try:
|
||||
return self.config.get(section, name)
|
||||
except (configparser.NoOptionError, configparser.NoSectionError):
|
||||
except (configparser.NoOptionError, configparser.NoSectionError) as exc:
|
||||
if self.base:
|
||||
return self.base.get_config(section, name, default)
|
||||
|
||||
if default is NODEFAULT:
|
||||
raise ThemeError(__('setting %s.%s occurs in none of the '
|
||||
'searched theme configs') % (section, name))
|
||||
'searched theme configs') % (section, name)) from exc
|
||||
else:
|
||||
return default
|
||||
|
||||
|
@ -82,8 +82,8 @@ class ReferencesResolver(SphinxPostTransform):
|
||||
# let the domain try to resolve the reference
|
||||
try:
|
||||
domain = self.env.domains[node['refdomain']]
|
||||
except KeyError:
|
||||
raise NoUri(target, typ)
|
||||
except KeyError as exc:
|
||||
raise NoUri(target, typ) from exc
|
||||
newnode = domain.resolve_xref(self.env, refdoc, self.app.builder,
|
||||
typ, target, node, contnode)
|
||||
# really hardwired reference types
|
||||
|
@ -334,8 +334,8 @@ def parselinenos(spec: str, total: int) -> List[int]:
|
||||
items.extend(range(start - 1, end))
|
||||
else:
|
||||
raise ValueError
|
||||
except Exception:
|
||||
raise ValueError('invalid line number spec: %r' % spec)
|
||||
except Exception as exc:
|
||||
raise ValueError('invalid line number spec: %r' % spec) from exc
|
||||
|
||||
return items
|
||||
|
||||
@ -406,9 +406,9 @@ def import_object(objname: str, source: str = None) -> Any:
|
||||
except (AttributeError, ImportError) as exc:
|
||||
if source:
|
||||
raise ExtensionError('Could not import %s (needed for %s)' %
|
||||
(objname, source), exc)
|
||||
(objname, source), exc) from exc
|
||||
else:
|
||||
raise ExtensionError('Could not import %s' % objname, exc)
|
||||
raise ExtensionError('Could not import %s' % objname, exc) from exc
|
||||
|
||||
|
||||
def split_full_qualified_name(name: str) -> Tuple[str, str]:
|
||||
@ -425,26 +425,16 @@ def split_full_qualified_name(name: str) -> Tuple[str, str]:
|
||||
Therefore you need to mock 3rd party modules if needed before
|
||||
calling this function.
|
||||
"""
|
||||
from sphinx.util import inspect
|
||||
|
||||
parts = name.split('.')
|
||||
for i, part in enumerate(parts, 1):
|
||||
try:
|
||||
modname = ".".join(parts[:i])
|
||||
module = import_module(modname)
|
||||
|
||||
# check the module has a member named as attrname
|
||||
#
|
||||
# Note: This is needed to detect the attribute having the same name
|
||||
# as the module.
|
||||
# ref: https://github.com/sphinx-doc/sphinx/issues/7812
|
||||
attrname = parts[i]
|
||||
if hasattr(module, attrname):
|
||||
value = inspect.safe_getattr(module, attrname)
|
||||
if not inspect.ismodule(value):
|
||||
return ".".join(parts[:i]), ".".join(parts[i:])
|
||||
import_module(modname)
|
||||
except ImportError:
|
||||
return ".".join(parts[:i - 1]), ".".join(parts[i - 1:])
|
||||
if parts[:i - 1]:
|
||||
return ".".join(parts[:i - 1]), ".".join(parts[i - 1:])
|
||||
else:
|
||||
return None, ".".join(parts)
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
import re
|
||||
from copy import deepcopy
|
||||
from typing import (
|
||||
Any, Callable, List, Match, Pattern, Tuple, Union
|
||||
Any, Callable, List, Match, Optional, Pattern, Tuple, Union
|
||||
)
|
||||
|
||||
from docutils import nodes
|
||||
@ -141,16 +141,14 @@ class ASTCPPAttribute(ASTAttribute):
|
||||
|
||||
|
||||
class ASTGnuAttribute(ASTBaseBase):
|
||||
def __init__(self, name: str, args: Any) -> None:
|
||||
def __init__(self, name: str, args: Optional["ASTBaseParenExprList"]) -> None:
|
||||
self.name = name
|
||||
self.args = args
|
||||
|
||||
def _stringify(self, transform: StringifyTransform) -> str:
|
||||
res = [self.name]
|
||||
if self.args:
|
||||
res.append('(')
|
||||
res.append(transform(self.args))
|
||||
res.append(')')
|
||||
return ''.join(res)
|
||||
|
||||
|
||||
@ -204,6 +202,11 @@ class ASTParenAttribute(ASTAttribute):
|
||||
|
||||
################################################################################
|
||||
|
||||
class ASTBaseParenExprList(ASTBaseBase):
|
||||
pass
|
||||
|
||||
|
||||
################################################################################
|
||||
|
||||
class UnsupportedMultiCharacterCharLiteral(Exception):
|
||||
pass
|
||||
@ -398,11 +401,8 @@ class BaseParser:
|
||||
while 1:
|
||||
if self.match(identifier_re):
|
||||
name = self.matched_text
|
||||
self.skip_ws()
|
||||
if self.skip_string_and_ws('('):
|
||||
self.fail('Parameterized GNU style attribute not yet supported.')
|
||||
attrs.append(ASTGnuAttribute(name, None))
|
||||
# TODO: parse arguments for the attribute
|
||||
exprs = self._parse_paren_expression_list()
|
||||
attrs.append(ASTGnuAttribute(name, exprs))
|
||||
if self.skip_string_and_ws(','):
|
||||
continue
|
||||
elif self.skip_string_and_ws(')'):
|
||||
@ -430,3 +430,6 @@ class BaseParser:
|
||||
return ASTParenAttribute(id, arg)
|
||||
|
||||
return None
|
||||
|
||||
def _parse_paren_expression_list(self) -> ASTBaseParenExprList:
|
||||
raise NotImplementedError
|
||||
|
@ -238,7 +238,7 @@ def get_image_filename_for_language(filename: str, env: "BuildEnvironment") -> s
|
||||
try:
|
||||
return filename_format.format(**d)
|
||||
except KeyError as exc:
|
||||
raise SphinxError('Invalid figure_language_filename: %r' % exc)
|
||||
raise SphinxError('Invalid figure_language_filename: %r' % exc) from exc
|
||||
|
||||
|
||||
def search_image_for_language(filename: str, env: "BuildEnvironment") -> str:
|
||||
|
@ -322,7 +322,7 @@ def safe_getattr(obj: Any, name: str, *defargs: Any) -> Any:
|
||||
"""A getattr() that turns all exceptions into AttributeErrors."""
|
||||
try:
|
||||
return getattr(obj, name, *defargs)
|
||||
except Exception:
|
||||
except Exception as exc:
|
||||
# sometimes accessing a property raises an exception (e.g.
|
||||
# NotImplementedError), so let's try to read the attribute directly
|
||||
try:
|
||||
@ -337,7 +337,7 @@ def safe_getattr(obj: Any, name: str, *defargs: Any) -> Any:
|
||||
if defargs:
|
||||
return defargs[0]
|
||||
|
||||
raise AttributeError(name)
|
||||
raise AttributeError(name) from exc
|
||||
|
||||
|
||||
def object_description(object: Any) -> str:
|
||||
@ -369,8 +369,8 @@ def object_description(object: Any) -> str:
|
||||
for x in sorted_values)
|
||||
try:
|
||||
s = repr(object)
|
||||
except Exception:
|
||||
raise ValueError
|
||||
except Exception as exc:
|
||||
raise ValueError from exc
|
||||
# Strip non-deterministic memory addresses such as
|
||||
# ``<__main__.A at 0x7f68cb685710>``
|
||||
s = memory_address_re.sub('', s)
|
||||
|
@ -148,10 +148,10 @@ def abspath(pathdir: str) -> str:
|
||||
if isinstance(pathdir, bytes):
|
||||
try:
|
||||
pathdir = pathdir.decode(fs_encoding)
|
||||
except UnicodeDecodeError:
|
||||
except UnicodeDecodeError as exc:
|
||||
raise UnicodeDecodeError('multibyte filename not supported on '
|
||||
'this filesystem encoding '
|
||||
'(%r)' % fs_encoding)
|
||||
'(%r)' % fs_encoding) from exc
|
||||
return pathdir
|
||||
|
||||
|
||||
|
@ -26,11 +26,11 @@ def convert_with_2to3(filepath: str) -> str:
|
||||
try:
|
||||
from lib2to3.refactor import RefactoringTool, get_fixers_from_package
|
||||
from lib2to3.pgen2.parse import ParseError
|
||||
except ImportError:
|
||||
except ImportError as exc:
|
||||
# python 3.9.0a6+ emits PendingDeprecationWarning for lib2to3.
|
||||
# Additionally, removal of the module is still discussed at PEP-594.
|
||||
# To support future python, this catches ImportError for lib2to3.
|
||||
raise SyntaxError
|
||||
raise SyntaxError from exc
|
||||
|
||||
fixers = get_fixers_from_package('lib2to3.fixes')
|
||||
refactoring_tool = RefactoringTool(fixers)
|
||||
@ -41,7 +41,8 @@ def convert_with_2to3(filepath: str) -> str:
|
||||
# do not propagate lib2to3 exceptions
|
||||
lineno, offset = err.context[1]
|
||||
# try to match ParseError details with SyntaxError details
|
||||
raise SyntaxError(err.msg, (filepath, lineno, offset, err.value))
|
||||
|
||||
raise SyntaxError(err.msg, (filepath, lineno, offset, err.value)) from err
|
||||
return str(tree)
|
||||
|
||||
|
||||
|
@ -852,8 +852,8 @@ class TexinfoTranslator(SphinxTranslator):
|
||||
num = node.astext().strip()
|
||||
try:
|
||||
footnode, used = self.footnotestack[-1][num]
|
||||
except (KeyError, IndexError):
|
||||
raise nodes.SkipNode
|
||||
except (KeyError, IndexError) as exc:
|
||||
raise nodes.SkipNode from exc
|
||||
# footnotes are repeated for each reference
|
||||
footnode.walkabout(self) # type: ignore
|
||||
raise nodes.SkipChildren
|
||||
|
@ -222,7 +222,7 @@ class Table:
|
||||
for left, right in zip(out, out[1:])
|
||||
]
|
||||
glue.append(tail)
|
||||
return head + "".join(chain(*zip(out, glue)))
|
||||
return head + "".join(chain.from_iterable(zip(out, glue)))
|
||||
|
||||
for lineno, line in enumerate(self.lines):
|
||||
if self.separator and lineno == self.separator:
|
||||
|
@ -0,0 +1,5 @@
|
||||
from .foo import bar
|
||||
|
||||
class foo:
|
||||
"""docstring of target.name_conflict::foo."""
|
||||
pass
|
2
tests/roots/test-ext-autodoc/target/name_conflict/foo.py
Normal file
2
tests/roots/test-ext-autodoc/target/name_conflict/foo.py
Normal file
@ -0,0 +1,2 @@
|
||||
class bar:
|
||||
"""docstring of target.name_conflict.foo::bar."""
|
@ -63,8 +63,8 @@ def compile_latex_document(app, filename='python.tex'):
|
||||
'-output-directory=%s' % app.config.latex_engine,
|
||||
filename]
|
||||
subprocess.run(args, stdout=PIPE, stderr=PIPE, check=True)
|
||||
except OSError: # most likely the latex executable was not found
|
||||
raise pytest.skip.Exception
|
||||
except OSError as exc: # most likely the latex executable was not found
|
||||
raise pytest.skip.Exception from exc
|
||||
except CalledProcessError as exc:
|
||||
print(exc.stdout)
|
||||
print(exc.stderr)
|
||||
@ -1538,7 +1538,7 @@ def test_texescape_for_unicode_supported_engine(app, status, warning):
|
||||
assert 'superscript: ⁰, ¹' in result
|
||||
assert 'subscript: ₀, ₁' in result
|
||||
|
||||
|
||||
|
||||
@pytest.mark.sphinx('latex', testroot='basic',
|
||||
confoverrides={'latex_elements': {'extrapackages': r'\usepackage{foo}'}})
|
||||
def test_latex_elements_extrapackages(app, status, warning):
|
||||
|
@ -58,8 +58,8 @@ def test_texinfo(app, status, warning):
|
||||
try:
|
||||
args = ['makeinfo', '--no-split', 'sphinxtests.texi']
|
||||
subprocess.run(args, stdout=PIPE, stderr=PIPE, cwd=app.outdir, check=True)
|
||||
except OSError:
|
||||
raise pytest.skip.Exception # most likely makeinfo was not found
|
||||
except OSError as exc:
|
||||
raise pytest.skip.Exception from exc # most likely makeinfo was not found
|
||||
except CalledProcessError as exc:
|
||||
print(exc.stdout)
|
||||
print(exc.stderr)
|
||||
|
@ -469,6 +469,8 @@ def test_attributes():
|
||||
check('member', '__attribute__(()) int f', {1: 'f'})
|
||||
check('member', '__attribute__((a)) int f', {1: 'f'})
|
||||
check('member', '__attribute__((a, b)) int f', {1: 'f'})
|
||||
check('member', '__attribute__((optimize(3))) int f', {1: 'f'})
|
||||
check('member', '__attribute__((format(printf, 1, 2))) int f', {1: 'f'})
|
||||
# style: user-defined id
|
||||
check('member', 'id_attr int f', {1: 'f'})
|
||||
# style: user-defined paren
|
||||
|
@ -896,6 +896,8 @@ def test_attributes():
|
||||
check('member', '__attribute__(()) int f', {1: 'f__i', 2: '1f'})
|
||||
check('member', '__attribute__((a)) int f', {1: 'f__i', 2: '1f'})
|
||||
check('member', '__attribute__((a, b)) int f', {1: 'f__i', 2: '1f'})
|
||||
check('member', '__attribute__((optimize(3))) int f', {1: 'f__i', 2: '1f'})
|
||||
check('member', '__attribute__((format(printf, 1, 2))) int f', {1: 'f__i', 2: '1f'})
|
||||
# style: user-defined id
|
||||
check('member', 'id_attr int f', {1: 'f__i', 2: '1f'})
|
||||
# style: user-defined paren
|
||||
|
@ -121,15 +121,16 @@ def test_parse_name(app):
|
||||
verify('class', 'Base', ('test_ext_autodoc', ['Base'], None, None))
|
||||
|
||||
# for members
|
||||
directive.env.ref_context['py:module'] = 'foo'
|
||||
verify('method', 'util.SphinxTestApp.cleanup',
|
||||
('foo', ['util', 'SphinxTestApp', 'cleanup'], None, None))
|
||||
directive.env.ref_context['py:module'] = 'util'
|
||||
directive.env.ref_context['py:module'] = 'sphinx.testing.util'
|
||||
verify('method', 'SphinxTestApp.cleanup',
|
||||
('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None))
|
||||
directive.env.ref_context['py:module'] = 'sphinx.testing.util'
|
||||
directive.env.ref_context['py:class'] = 'Foo'
|
||||
directive.env.temp_data['autodoc:class'] = 'SphinxTestApp'
|
||||
verify('method', 'cleanup', ('util', ['SphinxTestApp', 'cleanup'], None, None))
|
||||
verify('method', 'cleanup',
|
||||
('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None))
|
||||
verify('method', 'SphinxTestApp.cleanup',
|
||||
('util', ['SphinxTestApp', 'cleanup'], None, None))
|
||||
('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None))
|
||||
|
||||
|
||||
def test_format_signature(app):
|
||||
@ -800,14 +801,14 @@ def test_autodoc_inner_class(app):
|
||||
actual = do_autodoc(app, 'class', 'target.Outer.Inner', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: Outer.Inner()',
|
||||
' :module: target',
|
||||
'.. py:class:: Inner()',
|
||||
' :module: target.Outer',
|
||||
'',
|
||||
' Foo',
|
||||
'',
|
||||
'',
|
||||
' .. py:method:: Outer.Inner.meth()',
|
||||
' :module: target',
|
||||
' .. py:method:: Inner.meth()',
|
||||
' :module: target.Outer',
|
||||
'',
|
||||
' Foo',
|
||||
'',
|
||||
@ -1881,6 +1882,43 @@ def test_overload(app):
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_pymodule_for_ModuleLevelDocumenter(app):
|
||||
app.env.ref_context['py:module'] = 'target.classes'
|
||||
actual = do_autodoc(app, 'class', 'Foo')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: Foo()',
|
||||
' :module: target.classes',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_pymodule_for_ClassLevelDocumenter(app):
|
||||
app.env.ref_context['py:module'] = 'target.methods'
|
||||
actual = do_autodoc(app, 'method', 'Base.meth')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:method:: Base.meth()',
|
||||
' :module: target.methods',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_pyclass_for_ClassLevelDocumenter(app):
|
||||
app.env.ref_context['py:module'] = 'target.methods'
|
||||
app.env.ref_context['py:class'] = 'Base'
|
||||
actual = do_autodoc(app, 'method', 'meth')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:method:: Base.meth()',
|
||||
' :module: target.methods',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('dummy', testroot='ext-autodoc')
|
||||
def test_autodoc(app, status, warning):
|
||||
app.builder.build_all()
|
||||
@ -1899,3 +1937,26 @@ my_name
|
||||
|
||||
alias of bug2437.autodoc_dummy_foo.Foo"""
|
||||
assert warning.getvalue() == ''
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_name_conflict(app):
|
||||
actual = do_autodoc(app, 'class', 'target.name_conflict.foo')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: foo()',
|
||||
' :module: target.name_conflict',
|
||||
'',
|
||||
' docstring of target.name_conflict::foo.',
|
||||
'',
|
||||
]
|
||||
|
||||
actual = do_autodoc(app, 'class', 'target.name_conflict.foo.bar')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: bar()',
|
||||
' :module: target.name_conflict.foo',
|
||||
'',
|
||||
' docstring of target.name_conflict.foo::bar.',
|
||||
'',
|
||||
]
|
||||
|
@ -85,8 +85,8 @@ def test_methoddescriptor(app):
|
||||
actual = do_autodoc(app, 'function', 'builtins.int.__add__')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: int.__add__(self, value, /)',
|
||||
' :module: builtins',
|
||||
'.. py:function:: __add__(self, value, /)',
|
||||
' :module: builtins.int',
|
||||
'',
|
||||
' Return self+value.',
|
||||
'',
|
||||
|
@ -13,6 +13,8 @@ import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from sphinx.testing import restructuredtext
|
||||
|
||||
from test_ext_autodoc import do_autodoc
|
||||
|
||||
IS_PYPY = platform.python_implementation() == 'PyPy'
|
||||
@ -633,6 +635,12 @@ def test_autodoc_typehints_description(app):
|
||||
in context)
|
||||
|
||||
|
||||
@pytest.mark.sphinx('text', testroot='ext-autodoc',
|
||||
confoverrides={'autodoc_typehints': "description"})
|
||||
def test_autodoc_typehints_description_for_invalid_node(app):
|
||||
text = ".. py:function:: hello; world"
|
||||
restructuredtext.parse(app, text) # raises no error
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_default_options(app):
|
||||
|
@ -97,7 +97,7 @@ def test_extract_summary(capsys):
|
||||
|
||||
# abbreviations
|
||||
doc = ['Blabla, i.e. bla.']
|
||||
assert extract_summary(doc, document) == 'Blabla, i.e.'
|
||||
assert extract_summary(doc, document) == ' '.join(doc)
|
||||
|
||||
# literal
|
||||
doc = ['blah blah::']
|
||||
@ -108,6 +108,12 @@ def test_extract_summary(capsys):
|
||||
'=========']
|
||||
assert extract_summary(doc, document) == 'blah blah'
|
||||
|
||||
# hyperlink target
|
||||
doc = ['Do `this <https://www.sphinx-doc.org/>`_ and that. '
|
||||
'blah blah blah.']
|
||||
assert (extract_summary(doc, document) ==
|
||||
'Do `this <https://www.sphinx-doc.org/>`_ and that.')
|
||||
|
||||
_, err = capsys.readouterr()
|
||||
assert err == ''
|
||||
|
||||
|
12
tox.ini
12
tox.ini
@ -1,6 +1,6 @@
|
||||
[tox]
|
||||
minversion = 2.4.0
|
||||
envlist = docs,flake8,mypy,coverage,py{36,37,38,39},du{14,15,16}
|
||||
envlist = docs,flake8,mypy,twine,coverage,py{36,37,38,39},du{14,15,16}
|
||||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
@ -86,6 +86,16 @@ extras =
|
||||
commands =
|
||||
python utils/doclinter.py CHANGES CONTRIBUTING.rst README.rst doc/
|
||||
|
||||
[testenv:twine]
|
||||
basepython = python3
|
||||
description =
|
||||
Lint package.
|
||||
deps =
|
||||
twine
|
||||
commands =
|
||||
python setup.py release bdist_wheel sdist
|
||||
twine check dist/*
|
||||
|
||||
[testenv:bindep]
|
||||
description =
|
||||
Install binary dependencies.
|
||||
|
@ -11,7 +11,6 @@ for stable releases
|
||||
* ``git commit -am 'Bump to X.Y.Z final'``
|
||||
* ``make clean``
|
||||
* ``python setup.py release bdist_wheel sdist``
|
||||
* ``twine check dist/Sphinx-*``
|
||||
* ``twine upload dist/Sphinx-* --sign --identity [your GPG key]``
|
||||
* open https://pypi.org/project/Sphinx/ and check there are no obvious errors
|
||||
* ``sh utils/bump_docker.sh X.Y.Z``
|
||||
@ -38,7 +37,6 @@ for first beta releases
|
||||
* ``git commit -am 'Bump to X.Y.0 beta1'``
|
||||
* ``make clean``
|
||||
* ``python setup.py release bdist_wheel sdist``
|
||||
* ``twine check dist/Sphinx-*``
|
||||
* ``twine upload dist/Sphinx-* --sign --identity [your GPG key]``
|
||||
* open https://pypi.org/project/Sphinx/ and check there are no obvious errors
|
||||
* ``git tag vX.Y.0b1``
|
||||
@ -67,7 +65,6 @@ for other beta releases
|
||||
* ``git commit -am 'Bump to X.Y.0 betaN'``
|
||||
* ``make clean``
|
||||
* ``python setup.py release bdist_wheel sdist``
|
||||
* ``twine check dist/Sphinx-*``
|
||||
* ``twine upload dist/Sphinx-* --sign --identity [your GPG key]``
|
||||
* open https://pypi.org/project/Sphinx/ and check there are no obvious errors
|
||||
* ``git tag vX.Y.0bN``
|
||||
@ -95,7 +92,6 @@ for major releases
|
||||
* ``git commit -am 'Bump to X.Y.0 final'``
|
||||
* ``make clean``
|
||||
* ``python setup.py release bdist_wheel sdist``
|
||||
* ``twine check dist/Sphinx-*``
|
||||
* ``twine upload dist/Sphinx-* --sign --identity [your GPG key]``
|
||||
* open https://pypi.org/project/Sphinx/ and check there are no obvious errors
|
||||
* ``sh utils/bump_docker.sh X.Y.Z``
|
||||
|
Loading…
Reference in New Issue
Block a user