Merge branch '2.0' into 5592_cmdoption_registers_multiple_indices

This commit is contained in:
Takeshi KOMIYA 2019-07-07 16:40:17 +09:00 committed by GitHub
commit fde3d2a1ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
94 changed files with 1886 additions and 2447 deletions

32
CHANGES
View File

@ -7,6 +7,8 @@ Dependencies
Incompatible changes
--------------------
* apidoc: template files are renamed to ``.rst_t``
Deprecated
----------
@ -25,22 +27,37 @@ Features added
--------------
* #5124: graphviz: ``:graphviz_dot:`` option is renamed to ``:layout:``
* #1464: html: emit a warning if :confval:`html_static_path` and
:confval:`html_extra_path` directories are inside output directory
* #6514: html: Add a label to search input for accessability purposes
* #5602: apidoc: Add ``--templatedir`` option
* #6475: Add ``override`` argument to ``app.add_autodocumenter()``
* #6533: LaTeX: refactor visit_enumerated_list() to use ``\sphinxsetlistlabels``
Bugs fixed
----------
* py domain: duplicated warning does not point the location of source code
* #5592: std domain: :rst:dir:`option` directive registers an index entry for
each comma separated option
* #6499: html: Sphinx never updates a copy of :confval:`html_logo` even if
original file has changed
* #1125: html theme: scrollbar is hard to see on classic theme and macOS
* #5502: linkcheck: Consider HTTP 503 response as not an error
* #6439: Make generated download links reproducible
* #6486: UnboundLocalError is raised if broken extension installed
* #6498: autosummary: crashed with wrong autosummary_generate setting
* #6507: autosummary: crashes without no autosummary_generate setting
* #6511: LaTeX: autonumbered list can not be customized in LaTeX
since Sphinx 1.8.0 (refs: #6533)
* #6531: Failed to load last environment object when extension added
* #736: Invalid sort in pair index
* #6527: :confval:`last_updated` wrongly assumes timezone as UTC
* #5592: std domain: :rst:dir:`option` directive registers an index entry for
each comma separated option
Testing
--------
Release 2.1.2 (in development)
Release 2.1.3 (in development)
==============================
Dependencies
@ -61,6 +78,15 @@ Bugs fixed
Testing
--------
Release 2.1.2 (released Jun 19, 2019)
=====================================
Bugs fixed
----------
* #6497: custom lexers fails highlighting when syntax error
* #6478, #6488: info field lists are incorrectly recognized
Release 2.1.1 (released Jun 10, 2019)
=====================================

View File

@ -126,6 +126,30 @@ These options are used when :option:`--full` is specified:
Sets the project release to put in generated files (see :confval:`release`).
.. rubric:: Project templating
.. versionadded:: 2.2
Project templating options for sphinx-apidoc
.. option:: -t, --templatedir=TEMPLATEDIR
Template directory for template files. You can modify the templates of
sphinx project files generated by apidoc. Following Jinja2 template
files are allowed:
* ``module.rst_t``
* ``package.rst_t``
* ``toc.rst_t``
* ``master_doc.rst_t``
* ``conf.py_t``
* ``Makefile_t``
* ``Makefile.new_t``
* ``make.bat_t``
* ``make.bat.new_t``
In detail, please refer the system template files Sphinx provides.
(``sphinx/templates/apidoc`` and ``sphinx/templates/quickstart``)
Environment
-----------

View File

@ -243,21 +243,23 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
These work exactly like :rst:dir:`autoclass` etc.,
but do not offer the options used for automatic member documentation.
:rst:dir:`autodata` and :rst:dir:`autoattribute` support
the ``annotation`` option.
Without this option, the representation of the object
will be shown in the documentation.
When the option is given without arguments,
only the name of the object will be printed::
:rst:dir:`autodata` and :rst:dir:`autoattribute` support the ``annotation``
option. The option controls how the value of variable is shown. If specified
without arguments, only the name of the variable will be printed, and its value
is not shown::
.. autodata:: CD_DRIVE
:annotation:
You can tell sphinx what should be printed after the name::
If the option specified with arguments, it is printed after the name as a value
of the variable::
.. autodata:: CD_DRIVE
:annotation: = your CD device name
By default, without ``annotation`` option, Sphinx tries to obtain the value of
the variable and print it after the name.
For module data members and class attributes, documentation can either be put
into a comment with special formatting (using a ``#:`` to start the comment
instead of just ``#``), or in a docstring *after* the definition. Comments

View File

@ -47,7 +47,7 @@ extras_require = {
'html5lib',
'flake8>=3.5.0',
'flake8-import-order',
'mypy>=0.590',
'mypy>=0.711',
'docutils-stubs',
],
}

View File

@ -1054,8 +1054,8 @@ class Sphinx:
else:
lexer_classes[alias] = lexer
def add_autodocumenter(self, cls):
# type: (Any) -> None
def add_autodocumenter(self, cls, override=False):
# type: (Any, bool) -> None
"""Register a new documenter class for the autodoc extension.
Add *cls* as a new documenter class for the :mod:`sphinx.ext.autodoc`
@ -1067,11 +1067,13 @@ class Sphinx:
.. todo:: Add real docs for Documenter and subclassing
.. versionadded:: 0.6
.. versionchanged:: 2.2
Add *override* keyword.
"""
logger.debug('[app] adding autodocumenter: %r', cls)
from sphinx.ext.autodoc.directive import AutodocDirective
self.registry.add_documenter(cls.objtype, cls)
self.add_directive('auto' + cls.objtype, AutodocDirective)
self.add_directive('auto' + cls.objtype, AutodocDirective, override=override)
def add_autodoc_attrgetter(self, typ, getter):
# type: (Type, Callable[[Any, str, Any], Any]) -> None

View File

@ -38,8 +38,7 @@ from sphinx.highlighting import PygmentsBridge
from sphinx.locale import _, __
from sphinx.search import js_index
from sphinx.theming import HTMLThemeFactory
from sphinx.util import logging, status_iterator
from sphinx.util.console import bold # type: ignore
from sphinx.util import logging, progress_message, status_iterator
from sphinx.util.docutils import is_html5_writer_available, new_document
from sphinx.util.fileutil import copy_asset
from sphinx.util.i18n import format_date
@ -626,6 +625,7 @@ class StandaloneHTMLBuilder(Builder):
def finish(self) -> None:
self.finish_tasks.add_task(self.gen_indices)
self.finish_tasks.add_task(self.gen_pages_from_extensions)
self.finish_tasks.add_task(self.gen_additional_pages)
self.finish_tasks.add_task(self.copy_image_files)
self.finish_tasks.add_task(self.copy_download_files)
@ -636,9 +636,8 @@ class StandaloneHTMLBuilder(Builder):
# dump the search index
self.handle_finish()
@progress_message(__('generating indices'))
def gen_indices(self) -> None:
logger.info(bold(__('generating indices...')), nonl=True)
# the global general index
if self.use_index:
self.write_genindex()
@ -646,16 +645,14 @@ class StandaloneHTMLBuilder(Builder):
# the global domain-specific indices
self.write_domain_indices()
logger.info('')
def gen_additional_pages(self) -> None:
def gen_pages_from_extensions(self) -> None:
# pages from extensions
for pagelist in self.events.emit('html-collect-pages'):
for pagename, context, template in pagelist:
self.handle_page(pagename, context, template)
logger.info(bold(__('writing additional pages...')), nonl=True)
@progress_message(__('writing additional pages'))
def gen_additional_pages(self) -> None:
# additional pages from conf.py
for pagename, template in self.config.html_additional_pages.items():
logger.info(' ' + pagename, nonl=True)
@ -672,8 +669,6 @@ class StandaloneHTMLBuilder(Builder):
fn = path.join(self.outdir, '_static', 'opensearch.xml')
self.handle_page('opensearch', {}, 'opensearch.xml', outfilename=fn)
logger.info('')
def write_genindex(self) -> None:
# the total count of lines for each index letter, used to distribute
# the entries into two columns
@ -746,84 +741,77 @@ class StandaloneHTMLBuilder(Builder):
logger.warning(__('cannot copy downloadable file %r: %s'),
path.join(self.srcdir, src), err)
def create_pygments_style_file(self) -> None:
"""create a style file for pygments."""
with open(path.join(self.outdir, '_static', 'pygments.css'), 'w') as f:
f.write(self.highlighter.get_stylesheet())
def copy_translation_js(self) -> None:
"""Copy a JavaScript file for translations."""
if self.config.language is not None:
jsfile = self._get_translations_js()
if jsfile:
copyfile(jsfile, path.join(self.outdir, '_static', 'translations.js'))
def copy_stemmer_js(self) -> None:
"""Copy a JavaScript file for stemmer."""
if self.indexer is not None:
jsfile = self.indexer.get_js_stemmer_rawcode()
if jsfile:
copyfile(jsfile, path.join(self.outdir, '_static', '_stemmer.js'))
def copy_theme_static_files(self, context: Dict) -> None:
if self.theme:
for entry in self.theme.get_theme_dirs()[::-1]:
copy_asset(path.join(entry, 'static'),
path.join(self.outdir, '_static'),
excluded=DOTFILES, context=context, renderer=self.templates)
def copy_html_static_files(self, context: Dict) -> None:
excluded = Matcher(self.config.exclude_patterns + ["**/.*"])
for entry in self.config.html_static_path:
copy_asset(path.join(self.confdir, entry),
path.join(self.outdir, '_static'),
excluded, context=context, renderer=self.templates)
def copy_html_logo(self) -> None:
if self.config.html_logo:
copy_asset(path.join(self.confdir, self.config.html_logo),
path.join(self.outdir, '_static'))
def copy_html_favicon(self) -> None:
if self.config.html_favicon:
copy_asset(path.join(self.confdir, self.config.html_favicon),
path.join(self.outdir, '_static'))
def copy_static_files(self) -> None:
try:
# copy static files
logger.info(bold(__('copying static files... ')), nonl=True)
ensuredir(path.join(self.outdir, '_static'))
# first, create pygments style file
with open(path.join(self.outdir, '_static', 'pygments.css'), 'w') as f:
f.write(self.highlighter.get_stylesheet())
# then, copy translations JavaScript file
if self.config.language is not None:
jsfile = self._get_translations_js()
if jsfile:
copyfile(jsfile, path.join(self.outdir, '_static',
'translations.js'))
with progress_message(__('copying static files... ')):
ensuredir(path.join(self.outdir, '_static'))
# copy non-minified stemmer JavaScript file
if self.indexer is not None:
jsfile = self.indexer.get_js_stemmer_rawcode()
if jsfile:
copyfile(jsfile, path.join(self.outdir, '_static', '_stemmer.js'))
# prepare context for templates
context = self.globalcontext.copy()
if self.indexer is not None:
context.update(self.indexer.context_for_searchtool())
ctx = self.globalcontext.copy()
# add context items for search function used in searchtools.js_t
if self.indexer is not None:
ctx.update(self.indexer.context_for_searchtool())
# then, copy over theme-supplied static files
if self.theme:
for theme_path in self.theme.get_theme_dirs()[::-1]:
entry = path.join(theme_path, 'static')
copy_asset(entry, path.join(self.outdir, '_static'), excluded=DOTFILES,
context=ctx, renderer=self.templates)
# then, copy over all user-supplied static files
excluded = Matcher(self.config.exclude_patterns + ["**/.*"])
for static_path in self.config.html_static_path:
entry = path.join(self.confdir, static_path)
if not path.exists(entry):
logger.warning(__('html_static_path entry %r does not exist'), entry)
continue
copy_asset(entry, path.join(self.outdir, '_static'), excluded,
context=ctx, renderer=self.templates)
# copy logo and favicon files if not already in static path
if self.config.html_logo:
logobase = path.basename(self.config.html_logo)
logotarget = path.join(self.outdir, '_static', logobase)
if not path.isfile(path.join(self.confdir, self.config.html_logo)):
logger.warning(__('logo file %r does not exist'), self.config.html_logo)
elif not path.isfile(logotarget):
copyfile(path.join(self.confdir, self.config.html_logo),
logotarget)
if self.config.html_favicon:
iconbase = path.basename(self.config.html_favicon)
icontarget = path.join(self.outdir, '_static', iconbase)
if not path.isfile(path.join(self.confdir, self.config.html_favicon)):
logger.warning(__('favicon file %r does not exist'),
self.config.html_favicon)
elif not path.isfile(icontarget):
copyfile(path.join(self.confdir, self.config.html_favicon),
icontarget)
logger.info(__('done'))
self.create_pygments_style_file()
self.copy_translation_js()
self.copy_stemmer_js()
self.copy_theme_static_files(context)
self.copy_html_static_files(context)
self.copy_html_logo()
self.copy_html_favicon()
except OSError as err:
logger.warning(__('cannot copy static file %r'), err)
def copy_extra_files(self) -> None:
"""copy html_extra_path files."""
try:
# copy html_extra_path files
logger.info(bold(__('copying extra files... ')), nonl=True)
excluded = Matcher(self.config.exclude_patterns)
for extra_path in self.config.html_extra_path:
entry = path.join(self.confdir, extra_path)
if not path.exists(entry):
logger.warning(__('html_extra_path entry %r does not exist'), entry)
continue
copy_asset(entry, self.outdir, excluded)
logger.info(__('done'))
with progress_message(__('copying extra files')):
excluded = Matcher(self.config.exclude_patterns)
for extra_path in self.config.html_extra_path:
entry = path.join(self.confdir, extra_path)
copy_asset(entry, self.outdir, excluded)
except OSError as err:
logger.warning(__('cannot copy extra file %r'), err)
@ -1067,27 +1055,23 @@ class StandaloneHTMLBuilder(Builder):
self.finish_tasks.add_task(self.dump_search_index)
self.finish_tasks.add_task(self.dump_inventory)
@progress_message(__('dumping object inventory'))
def dump_inventory(self) -> None:
logger.info(bold(__('dumping object inventory... ')), nonl=True)
InventoryFile.dump(path.join(self.outdir, INVENTORY_FILENAME), self.env, self)
logger.info(__('done'))
def dump_search_index(self) -> None:
logger.info(
bold(__('dumping search index in %s ... ') % self.indexer.label()),
nonl=True)
self.indexer.prune(self.env.all_docs)
searchindexfn = path.join(self.outdir, self.searchindex_filename)
# first write to a temporary file, so that if dumping fails,
# the existing index won't be overwritten
if self.indexer_dumps_unicode:
with open(searchindexfn + '.tmp', 'w', encoding='utf-8') as ft:
self.indexer.dump(ft, self.indexer_format)
else:
with open(searchindexfn + '.tmp', 'wb') as fb:
self.indexer.dump(fb, self.indexer_format)
movefile(searchindexfn + '.tmp', searchindexfn)
logger.info(__('done'))
with progress_message(__('dumping search index in %s') % self.indexer.label()):
self.indexer.prune(self.env.all_docs)
searchindexfn = path.join(self.outdir, self.searchindex_filename)
# first write to a temporary file, so that if dumping fails,
# the existing index won't be overwritten
if self.indexer_dumps_unicode:
with open(searchindexfn + '.tmp', 'w', encoding='utf-8') as ft:
self.indexer.dump(ft, self.indexer_format)
else:
with open(searchindexfn + '.tmp', 'wb') as fb:
self.indexer.dump(fb, self.indexer_format)
movefile(searchindexfn + '.tmp', searchindexfn)
def convert_html_css_files(app: Sphinx, config: Config) -> None:
@ -1166,6 +1150,44 @@ def validate_math_renderer(app: Sphinx) -> None:
raise ConfigError(__('Unknown math_renderer %r is given.') % name)
def validate_html_extra_path(app: Sphinx, config: Config) -> None:
"""Check html_extra_paths setting."""
for entry in config.html_extra_path[:]:
extra_path = path.normpath(path.join(app.confdir, entry))
if not path.exists(extra_path):
logger.warning(__('html_extra_path entry %r does not exist'), entry)
config.html_extra_path.remove(entry)
elif path.commonpath([app.outdir, extra_path]) == app.outdir:
logger.warning(__('html_extra_path entry %r is placed inside outdir'), entry)
config.html_extra_path.remove(entry)
def validate_html_static_path(app: Sphinx, config: Config) -> None:
"""Check html_static_paths setting."""
for entry in config.html_static_path[:]:
static_path = path.normpath(path.join(app.confdir, entry))
if not path.exists(static_path):
logger.warning(__('html_static_path entry %r does not exist'), entry)
config.html_static_path.remove(entry)
elif path.commonpath([app.outdir, static_path]) == app.outdir:
logger.warning(__('html_static_path entry %r is placed inside outdir'), entry)
config.html_static_path.remove(entry)
def validate_html_logo(app: Sphinx, config: Config) -> None:
"""Check html_logo setting."""
if config.html_logo and not path.isfile(path.join(app.confdir, config.html_logo)):
logger.warning(__('logo file %r does not exist'), config.html_logo)
config.html_logo = None # type: ignore
def validate_html_favicon(app: Sphinx, config: Config) -> None:
"""Check html_favicon setting."""
if config.html_favicon and not path.isfile(path.join(app.confdir, config.html_favicon)):
logger.warning(__('favicon file %r does not exist'), config.html_favicon)
config.html_favicon = None # type: ignore
# for compatibility
import sphinx.builders.dirhtml # NOQA
import sphinx.builders.singlehtml # NOQA
@ -1221,6 +1243,10 @@ def setup(app: Sphinx) -> Dict[str, Any]:
# event handlers
app.connect('config-inited', convert_html_css_files)
app.connect('config-inited', convert_html_js_files)
app.connect('config-inited', validate_html_extra_path)
app.connect('config-inited', validate_html_static_path)
app.connect('config-inited', validate_html_logo)
app.connect('config-inited', validate_html_favicon)
app.connect('builder-inited', validate_math_renderer)
app.connect('html-page-context', setup_js_tag_helper)

View File

@ -14,6 +14,7 @@ import multiprocessing
import os
import sys
import traceback
from typing import Any, IO, List
from docutils.utils import SystemMessage
@ -26,13 +27,8 @@ from sphinx.util import Tee, format_exception_cut_frames, save_traceback
from sphinx.util.console import red, nocolor, color_terminal, terminal_safe # type: ignore
from sphinx.util.docutils import docutils_namespace, patch_docutils
if False:
# For type annotation
from typing import Any, IO, List, Union # NOQA
def handle_exception(app, args, exception, stderr=sys.stderr):
# type: (Sphinx, Any, Union[Exception, KeyboardInterrupt], IO) -> None
def handle_exception(app: Sphinx, args: Any, exception: BaseException, stderr: IO = sys.stderr) -> None: # NOQA
if args.pdb:
import pdb
print(red(__('Exception occurred while building, starting debugger:')),
@ -82,8 +78,7 @@ def handle_exception(app, args, exception, stderr=sys.stderr):
file=stderr)
def jobs_argument(value):
# type: (str) -> int
def jobs_argument(value: str) -> int:
"""
Special type to handle 'auto' flags passed to 'sphinx-build' via -j flag. Can
be expanded to handle other special scaling requests, such as setting job count
@ -99,8 +94,7 @@ def jobs_argument(value):
return jobs
def get_parser():
# type: () -> argparse.ArgumentParser
def get_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
usage='%(prog)s [OPTIONS] SOURCEDIR OUTPUTDIR [FILENAMES...]',
epilog=__('For more information, visit <http://sphinx-doc.org/>.'),
@ -195,15 +189,13 @@ files can be built by specifying individual filenames.
return parser
def make_main(argv=sys.argv[1:]):
# type: (List[str]) -> int
def make_main(argv: List[str] = sys.argv[1:]) -> int:
"""Sphinx build "make mode" entry."""
from sphinx.cmd import make_mode
return make_mode.run_make_mode(argv[1:])
def build_main(argv=sys.argv[1:]):
# type: (List[str]) -> int
def build_main(argv: List[str] = sys.argv[1:]) -> int:
"""Sphinx build "main" command-line entry."""
parser = get_parser()
@ -288,8 +280,7 @@ def build_main(argv=sys.argv[1:]):
return 2
def main(argv=sys.argv[1:]):
# type: (List[str]) -> int
def main(argv: List[str] = sys.argv[1:]) -> int:
sphinx.locale.setlocale(locale.LC_ALL, '')
sphinx.locale.init_console(os.path.join(package_dir, 'locale'), 'sphinx')

View File

@ -18,16 +18,13 @@ import os
import subprocess
import sys
from os import path
from typing import List
import sphinx
from sphinx.cmd.build import build_main
from sphinx.util.console import color_terminal, nocolor, bold, blue # type: ignore
from sphinx.util.osutil import cd, rmtree
if False:
# For type annotation
from typing import List # NOQA
BUILDERS = [
("", "html", "to make standalone HTML files"),
@ -58,20 +55,16 @@ BUILDERS = [
class Make:
def __init__(self, srcdir, builddir, opts):
# type: (str, str, List[str]) -> None
def __init__(self, srcdir: str, builddir: str, opts: List[str]) -> None:
self.srcdir = srcdir
self.builddir = builddir
self.opts = opts
self.makecmd = os.environ.get('MAKE', 'make') # refer $MAKE to determine make command
def builddir_join(self, *comps):
# type: (str) -> str
def builddir_join(self, *comps: str) -> str:
return path.join(self.builddir, *comps)
def build_clean(self):
# type: () -> int
def build_clean(self) -> int:
srcdir = path.abspath(self.srcdir)
builddir = path.abspath(self.builddir)
if not path.exists(self.builddir):
@ -90,8 +83,7 @@ class Make:
rmtree(self.builddir_join(item))
return 0
def build_help(self):
# type: () -> None
def build_help(self) -> None:
if not color_terminal():
nocolor()
@ -101,8 +93,7 @@ class Make:
if not osname or os.name == osname:
print(' %s %s' % (blue(bname.ljust(10)), description))
def build_latexpdf(self):
# type: () -> int
def build_latexpdf(self) -> int:
if self.run_generic_build('latex') > 0:
return 1
@ -117,8 +108,7 @@ class Make:
print('Error: Failed to run: %s' % makecmd)
return 1
def build_latexpdfja(self):
# type: () -> int
def build_latexpdfja(self) -> int:
if self.run_generic_build('latex') > 0:
return 1
@ -133,8 +123,7 @@ class Make:
print('Error: Failed to run: %s' % makecmd)
return 1
def build_info(self):
# type: () -> int
def build_info(self) -> int:
if self.run_generic_build('texinfo') > 0:
return 1
try:
@ -144,15 +133,13 @@ class Make:
print('Error: Failed to run: %s' % self.makecmd)
return 1
def build_gettext(self):
# type: () -> int
def build_gettext(self) -> int:
dtdir = self.builddir_join('gettext', '.doctrees')
if self.run_generic_build('gettext', doctreedir=dtdir) > 0:
return 1
return 0
def run_generic_build(self, builder, doctreedir=None):
# type: (str, str) -> int
def run_generic_build(self, builder: str, doctreedir: str = None) -> int:
# compatibility with old Makefile
papersize = os.getenv('PAPER', '')
opts = self.opts
@ -168,8 +155,7 @@ class Make:
return build_main(args + opts)
def run_make_mode(args):
# type: (List[str]) -> int
def run_make_mode(args: List[str]) -> int:
if len(args) < 3:
print('Error: at least 3 arguments (builder, source '
'dir, build dir) are required.', file=sys.stderr)

View File

@ -17,6 +17,7 @@ import time
import warnings
from collections import OrderedDict
from os import path
from typing import Any, Callable, Dict, List, Pattern, Union
# try to import readline, unix specific enhancement
try:
@ -42,10 +43,6 @@ from sphinx.util.console import ( # type: ignore
from sphinx.util.osutil import ensuredir
from sphinx.util.template import SphinxRenderer
if False:
# For type annotation
from typing import Any, Callable, Dict, List, Pattern, Union # NOQA
TERM_ENCODING = getattr(sys.stdin, 'encoding', None) # RemovedInSphinx40Warning
EXTENSIONS = OrderedDict([
@ -84,8 +81,7 @@ else:
# function to get input from terminal -- overridden by the test suite
def term_input(prompt):
# type: (str) -> str
def term_input(prompt: str) -> str:
if sys.platform == 'win32':
# Important: On windows, readline is not enabled by default. In these
# environment, escape sequences have been broken. To avoid the
@ -100,58 +96,49 @@ class ValidationError(Exception):
"""Raised for validation errors."""
def is_path(x):
# type: (str) -> str
def is_path(x: str) -> str:
x = path.expanduser(x)
if not path.isdir(x):
raise ValidationError(__("Please enter a valid path name."))
return x
def allow_empty(x):
# type: (str) -> str
def allow_empty(x: str) -> str:
return x
def nonempty(x):
# type: (str) -> str
def nonempty(x: str) -> str:
if not x:
raise ValidationError(__("Please enter some text."))
return x
def choice(*l):
# type: (str) -> Callable[[str], str]
def val(x):
# type: (str) -> str
def choice(*l: str) -> Callable[[str], str]:
def val(x: str) -> str:
if x not in l:
raise ValidationError(__('Please enter one of %s.') % ', '.join(l))
return x
return val
def boolean(x):
# type: (str) -> bool
def boolean(x: str) -> bool:
if x.upper() not in ('Y', 'YES', 'N', 'NO'):
raise ValidationError(__("Please enter either 'y' or 'n'."))
return x.upper() in ('Y', 'YES')
def suffix(x):
# type: (str) -> str
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'."))
return x
def ok(x):
# type: (str) -> str
def ok(x: str) -> str:
return x
def term_decode(text):
# type: (Union[bytes,str]) -> str
def term_decode(text: Union[bytes, str]) -> str:
warnings.warn('term_decode() is deprecated.',
RemovedInSphinx40Warning, stacklevel=2)
@ -175,8 +162,7 @@ def term_decode(text):
return text.decode('latin1')
def do_prompt(text, default=None, validator=nonempty):
# type: (str, str, Callable[[str], Any]) -> Union[str, bool]
def do_prompt(text: str, default: str = None, validator: Callable[[str], Any] = nonempty) -> Union[str, bool]: # NOQA
while True:
if default is not None:
prompt = PROMPT_PREFIX + '%s [%s]: ' % (text, default)
@ -201,8 +187,7 @@ def do_prompt(text, default=None, validator=nonempty):
return x
def convert_python_source(source, rex=re.compile(r"[uU]('.*?')")):
# type: (str, Pattern) -> str
def convert_python_source(source: str, rex: Pattern = re.compile(r"[uU]('.*?')")) -> str:
# remove Unicode literal prefixes
warnings.warn('convert_python_source() is deprecated.',
RemovedInSphinx40Warning)
@ -210,13 +195,11 @@ def convert_python_source(source, rex=re.compile(r"[uU]('.*?')")):
class QuickstartRenderer(SphinxRenderer):
def __init__(self, templatedir):
# type: (str) -> None
def __init__(self, templatedir: str) -> None:
self.templatedir = templatedir or ''
super().__init__()
def render(self, template_name, context):
# type: (str, Dict) -> str
def render(self, template_name: str, context: Dict) -> str:
user_template = path.join(self.templatedir, path.basename(template_name))
if self.templatedir and path.exists(user_template):
return self.render_from_file(user_template, context)
@ -224,8 +207,7 @@ class QuickstartRenderer(SphinxRenderer):
return super().render(template_name, context)
def ask_user(d):
# type: (Dict) -> None
def ask_user(d: Dict) -> None:
"""Ask the user for quickstart values missing from *d*.
Values are:
@ -367,8 +349,8 @@ directly.'''))
print()
def generate(d, overwrite=True, silent=False, templatedir=None):
# type: (Dict, bool, bool, str) -> None
def generate(d: Dict, overwrite: bool = True, silent: bool = False, templatedir: str = None
) -> None:
"""Generate project based on values in *d*."""
template = QuickstartRenderer(templatedir=templatedir)
@ -401,8 +383,7 @@ def generate(d, overwrite=True, silent=False, templatedir=None):
ensuredir(path.join(srcdir, d['dot'] + 'templates'))
ensuredir(path.join(srcdir, d['dot'] + 'static'))
def write_file(fpath, content, newline=None):
# type: (str, str, str) -> None
def write_file(fpath: str, content: str, newline: str = None) -> None:
if overwrite or not path.isfile(fpath):
if 'quiet' not in d:
print(__('Creating file %s.') % fpath)
@ -460,8 +441,7 @@ where "builder" is one of the supported builders, e.g. html, latex or linkcheck.
'''))
def valid_dir(d):
# type: (Dict) -> bool
def valid_dir(d: Dict) -> bool:
dir = d['path']
if not path.exists(dir):
return True
@ -490,8 +470,7 @@ def valid_dir(d):
return True
def get_parser():
# type: () -> argparse.ArgumentParser
def get_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
usage='%(prog)s [OPTIONS] <PROJECT_DIR>',
epilog=__("For more information, visit <http://sphinx-doc.org/>."),
@ -572,8 +551,7 @@ Makefile to be used with sphinx-build.
return parser
def main(argv=sys.argv[1:]):
# type: (List[str]) -> int
def main(argv: List[str] = sys.argv[1:]) -> int:
sphinx.locale.setlocale(locale.LC_ALL, '')
sphinx.locale.init_console(os.path.join(package_dir, 'locale'), 'sphinx')

View File

@ -73,6 +73,7 @@ class ObjectDescription(SphinxDirective):
def get_field_type_map(self) -> Dict[str, Tuple[Field, bool]]:
if self._doc_field_type_map == {}:
self._doc_field_type_map = {}
for field in self.doc_field_types:
for name in field.names:
self._doc_field_type_map[name] = (field, False)

View File

@ -10,21 +10,22 @@
"""
import copy
from typing import NamedTuple
from typing import Any, Callable, Dict, Iterable, List, NamedTuple, Tuple, Type, Union
from docutils import nodes
from docutils.nodes import Element, Node, system_message
from docutils.parsers.rst.states import Inliner
from sphinx.addnodes import pending_xref
from sphinx.errors import SphinxError
from sphinx.locale import _
from sphinx.roles import XRefRole
from sphinx.util.typing import RoleFunction
if False:
# For type annotation
from typing import Any, Callable, Dict, Iterable, List, Tuple, Type, Union # NOQA
from docutils import nodes # NOQA
from docutils.parsers.rst.states import Inliner # NOQA
from sphinx import addnodes # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.roles import XRefRole # NOQA
from sphinx.util.typing import RoleFunction # NOQA
from sphinx.builders import Builder
from sphinx.environment import BuildEnvironment
class ObjType:
@ -46,8 +47,7 @@ class ObjType:
'searchprio': 1,
}
def __init__(self, lname, *roles, **attrs):
# type: (str, Any, Any) -> None
def __init__(self, lname: str, *roles, **attrs) -> None:
self.lname = lname
self.roles = roles # type: Tuple
self.attrs = self.known_attrs.copy() # type: Dict
@ -82,15 +82,14 @@ class Index:
localname = None # type: str
shortname = None # type: str
def __init__(self, domain):
# type: (Domain) -> None
def __init__(self, domain: "Domain") -> None:
if self.name is None or self.localname is None:
raise SphinxError('Index subclass %s has no valid name or localname'
% self.__class__.__name__)
self.domain = domain
def generate(self, docnames=None):
# type: (Iterable[str]) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]
def generate(self, docnames: Iterable[str] = None
) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]:
"""Get entries for the index.
If ``docnames`` is given, restrict to entries referring to these
@ -181,7 +180,7 @@ class Domain:
#: role name -> a warning message if reference is missing
dangling_warnings = {} # type: Dict[str, str]
#: node_class -> (enum_node_type, title_getter)
enumerable_nodes = {} # type: Dict[Type[nodes.Node], Tuple[str, Callable]]
enumerable_nodes = {} # type: Dict[Type[Node], Tuple[str, Callable]]
#: data value for a fresh environment
initial_data = {} # type: Dict
@ -190,8 +189,7 @@ class Domain:
#: data version, bump this when the format of `self.data` changes
data_version = 0
def __init__(self, env):
# type: (BuildEnvironment) -> None
def __init__(self, env: "BuildEnvironment") -> None:
self.env = env # type: BuildEnvironment
self._role_cache = {} # type: Dict[str, Callable]
self._directive_cache = {} # type: Dict[str, Callable]
@ -220,8 +218,7 @@ 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 add_object_type(self, name, objtype):
# type: (str, ObjType) -> None
def add_object_type(self, name: str, objtype: ObjType) -> None:
"""Add an object type."""
self.object_types[name] = objtype
if objtype.roles:
@ -232,8 +229,7 @@ class Domain:
for role in objtype.roles:
self._role2type.setdefault(role, []).append(name)
def role(self, name):
# type: (str) -> RoleFunction
def role(self, name: str) -> RoleFunction:
"""Return a role adapter function that always gives the registered
role its full name ('domain:name') as the first argument.
"""
@ -243,15 +239,15 @@ class Domain:
return None
fullname = '%s:%s' % (self.name, name)
def role_adapter(typ, rawtext, text, lineno, inliner, options={}, content=[]):
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
def role_adapter(typ: str, rawtext: str, text: str, lineno: int,
inliner: Inliner, options: Dict = {}, content: List[str] = []
) -> Tuple[List[Node], List[system_message]]:
return self.roles[name](fullname, rawtext, text, lineno,
inliner, options, content)
self._role_cache[name] = role_adapter
return role_adapter
def directive(self, name):
# type: (str) -> Callable
def directive(self, name: str) -> Callable:
"""Return a directive adapter class that always gives the registered
directive its full name ('domain:name') as ``self.name``.
"""
@ -263,8 +259,7 @@ class Domain:
BaseDirective = self.directives[name]
class DirectiveAdapter(BaseDirective): # type: ignore
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
self.name = fullname
return super().run()
self._directive_cache[name] = DirectiveAdapter
@ -272,13 +267,11 @@ class Domain:
# methods that should be overwritten
def clear_doc(self, docname):
# type: (str) -> None
def clear_doc(self, docname: str) -> None:
"""Remove traces of a document in the domain-specific inventories."""
pass
def merge_domaindata(self, docnames, otherdata):
# type: (List[str], Dict) -> None
def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None:
"""Merge in data regarding *docnames* from a different domaindata
inventory (coming from a subprocess in parallel builds).
"""
@ -286,26 +279,24 @@ class Domain:
'to be able to do parallel builds!' %
self.__class__)
def process_doc(self, env, docname, document):
# type: (BuildEnvironment, str, nodes.document) -> None
def process_doc(self, env: "BuildEnvironment", docname: str,
document: nodes.document) -> None:
"""Process a document after it is read by the environment."""
pass
def check_consistency(self):
# type: () -> None
def check_consistency(self) -> None:
"""Do consistency checks (**experimental**)."""
pass
def process_field_xref(self, pnode):
# type: (addnodes.pending_xref) -> None
def process_field_xref(self, pnode: pending_xref) -> None:
"""Process a pending xref created in a doc field.
For example, attach information about the current scope.
"""
pass
def resolve_xref(self, env, fromdocname, builder,
typ, target, node, contnode):
# type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA
def resolve_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder",
typ: str, target: str, node: pending_xref, contnode: Element
) -> Element:
"""Resolve the pending_xref *node* with the given *typ* and *target*.
This method should return a new node, to replace the xref node,
@ -321,8 +312,9 @@ class Domain:
"""
pass
def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode):
# type: (BuildEnvironment, str, Builder, str, addnodes.pending_xref, nodes.Element) -> List[Tuple[str, nodes.Element]] # NOQA
def resolve_any_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder",
target: str, node: pending_xref, contnode: Element
) -> List[Tuple[str, Element]]:
"""Resolve the pending_xref *node* with the given *target*.
The reference comes from an "any" or similar role, which means that we
@ -338,8 +330,7 @@ class Domain:
"""
raise NotImplementedError
def get_objects(self):
# type: () -> Iterable[Tuple[str, str, str, str, str, int]]
def get_objects(self) -> Iterable[Tuple[str, str, str, str, str, int]]:
"""Return an iterable of "object descriptions".
Object descriptions are tuples with six items:
@ -374,20 +365,17 @@ class Domain:
"""
return []
def get_type_name(self, type, primary=False):
# type: (ObjType, bool) -> str
def get_type_name(self, type: ObjType, primary: bool = False) -> str:
"""Return full name for given ObjType."""
if primary:
return type.lname
return _('%s %s') % (self.label, type.lname)
def get_enumerable_node_type(self, node):
# type: (nodes.Node) -> str
def get_enumerable_node_type(self, node: Node) -> str:
"""Get type of enumerable nodes (experimental)."""
enum_node_type, _ = self.enumerable_nodes.get(node.__class__, (None, None))
return enum_node_type
def get_full_qualified_name(self, node):
# type: (nodes.Element) -> str
def get_full_qualified_name(self, node: Element) -> str:
"""Return full qualified name for given node."""
return None

View File

@ -10,24 +10,27 @@
import re
import string
from typing import Any, Dict, Iterator, List, Tuple
from typing import cast
from docutils import nodes
from docutils.nodes import Element
from sphinx import addnodes
from sphinx.addnodes import pending_xref, desc_signature
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType
from sphinx.locale import _
from sphinx.environment import BuildEnvironment
from sphinx.locale import _, __
from sphinx.roles import XRefRole
from sphinx.util import logging
from sphinx.util.docfields import Field, TypedField
from sphinx.util.nodes import make_refnode
if False:
# For type annotation
from typing import Any, Dict, Iterator, List, Tuple # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.environment import BuildEnvironment # NOQA
logger = logging.getLogger(__name__)
# RE to split at word boundaries
wsplit_re = re.compile(r'(\W+)')
@ -79,23 +82,20 @@ class CObject(ObjectDescription):
'struct', '_Bool',
}
def _parse_type(self, node, ctype):
# type: (nodes.Element, str) -> None
def _parse_type(self, node: Element, ctype: str) -> None:
# add cross-ref nodes for all words
for part in [_f for _f in wsplit_re.split(ctype) if _f]:
tnode = nodes.Text(part, part)
if part[0] in string.ascii_letters + '_' and \
part not in self.stopwords:
pnode = addnodes.pending_xref(
'', refdomain='c', reftype='type', reftarget=part,
modname=None, classname=None)
pnode = pending_xref('', refdomain='c', reftype='type', reftarget=part,
modname=None, classname=None)
pnode += tnode
node += pnode
else:
node += tnode
def _parse_arglist(self, arglist):
# type: (str) -> Iterator[str]
def _parse_arglist(self, arglist: str) -> Iterator[str]:
while True:
m = c_funcptr_arg_sig_re.match(arglist)
if m:
@ -113,8 +113,7 @@ class CObject(ObjectDescription):
yield arglist
break
def handle_signature(self, sig, signode):
# type: (str, addnodes.desc_signature) -> str
def handle_signature(self, sig: str, signode: desc_signature) -> str:
"""Transform a C signature into RST nodes."""
# first try the function pointer signature regex, it's more specific
m = c_funcptr_sig_re.match(sig)
@ -183,8 +182,7 @@ class CObject(ObjectDescription):
signode += addnodes.desc_addname(const, const)
return fullname
def get_index_text(self, name):
# type: (str) -> str
def get_index_text(self, name: str) -> str:
if self.objtype == 'function':
return _('%s (C function)') % name
elif self.objtype == 'member':
@ -198,8 +196,7 @@ class CObject(ObjectDescription):
else:
return ''
def add_target_and_index(self, name, sig, signode):
# type: (str, str, addnodes.desc_signature) -> None
def add_target_and_index(self, name: str, sig: str, signode: desc_signature) -> None:
# for C API items we add a prefix since names are usually not qualified
# by a module name and so easily clash with e.g. section titles
targetname = 'c.' + name
@ -208,36 +205,30 @@ class CObject(ObjectDescription):
signode['ids'].append(targetname)
signode['first'] = (not self.names)
self.state.document.note_explicit_target(signode)
inv = self.env.domaindata['c']['objects']
if name in inv:
self.state_machine.reporter.warning(
'duplicate C object description of %s, ' % name +
'other instance in ' + self.env.doc2path(inv[name][0]),
line=self.lineno)
inv[name] = (self.env.docname, self.objtype)
domain = cast(CDomain, self.env.get_domain('c'))
domain.note_object(name, self.objtype)
indextext = self.get_index_text(name)
if indextext:
self.indexnode['entries'].append(('single', indextext,
targetname, '', None))
def before_content(self):
# type: () -> None
def before_content(self) -> None:
self.typename_set = False
if self.name == 'c:type':
if self.names:
self.env.ref_context['c:type'] = self.names[0]
self.typename_set = True
def after_content(self):
# type: () -> None
def after_content(self) -> None:
if self.typename_set:
self.env.ref_context.pop('c:type', None)
class CXRefRole(XRefRole):
def process_link(self, env, refnode, has_explicit_title, title, target):
# type: (BuildEnvironment, nodes.Element, bool, str, str) -> Tuple[str, str]
def process_link(self, env: BuildEnvironment, refnode: Element,
has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]:
if not has_explicit_title:
target = target.lstrip('~') # only has a meaning for the title
# if the first character is a tilde, don't display the module/class
@ -280,53 +271,61 @@ class CDomain(Domain):
'objects': {}, # fullname -> docname, objtype
} # type: Dict[str, Dict[str, Tuple[str, Any]]]
def clear_doc(self, docname):
# type: (str) -> None
for fullname, (fn, _l) in list(self.data['objects'].items()):
if fn == docname:
del self.data['objects'][fullname]
@property
def objects(self) -> Dict[str, Tuple[str, str]]:
return self.data.setdefault('objects', {}) # fullname -> docname, objtype
def merge_domaindata(self, docnames, otherdata):
# type: (List[str], Dict) -> None
def note_object(self, name: str, objtype: 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, objtype)
def clear_doc(self, docname: str) -> None:
for fullname, (fn, _l) in list(self.objects.items()):
if fn == docname:
del self.objects[fullname]
def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None:
# XXX check duplicates
for fullname, (fn, objtype) in otherdata['objects'].items():
if fn in docnames:
self.data['objects'][fullname] = (fn, objtype)
def resolve_xref(self, env, fromdocname, builder,
typ, target, node, contnode):
# type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA
def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
typ: str, target: str, node: pending_xref, contnode: Element
) -> Element:
# strip pointer asterisk
target = target.rstrip(' *')
# becase TypedField can generate xrefs
if target in CObject.stopwords:
return contnode
if target not in self.data['objects']:
if target not in self.objects:
return None
obj = self.data['objects'][target]
obj = self.objects[target]
return make_refnode(builder, fromdocname, obj[0], 'c.' + target,
contnode, target)
def resolve_any_xref(self, env, fromdocname, builder, target,
node, contnode):
# type: (BuildEnvironment, str, Builder, str, addnodes.pending_xref, nodes.Element) -> List[Tuple[str, nodes.Element]] # NOQA
def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
target: str, node: pending_xref, contnode: Element
) -> List[Tuple[str, Element]]:
# strip pointer asterisk
target = target.rstrip(' *')
if target not in self.data['objects']:
if target not in self.objects:
return []
obj = self.data['objects'][target]
obj = self.objects[target]
return [('c:' + self.role_for_objtype(obj[1]),
make_refnode(builder, fromdocname, obj[0], 'c.' + target,
contnode, target))]
def get_objects(self):
# type: () -> Iterator[Tuple[str, str, str, str, str, int]]
for refname, (docname, type) in list(self.data['objects'].items()):
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, 'c.' + refname, 1)
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_domain(CDomain)
return {

View File

@ -9,9 +9,11 @@
"""
from collections import namedtuple
from typing import Any, Dict, List
from typing import cast
from docutils import nodes
from docutils.nodes import Node
from sphinx import addnodes
from sphinx import locale
@ -23,9 +25,8 @@ from sphinx.util.docutils import SphinxDirective
if False:
# For type annotation
from typing import Any, Dict, List # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
versionlabels = {
@ -63,8 +64,7 @@ class VersionChange(SphinxDirective):
final_argument_whitespace = True
option_spec = {} # type: Dict
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
node = addnodes.versionmodified()
node.document = self.state.document
self.set_source_info(node)
@ -102,7 +102,7 @@ class VersionChange(SphinxDirective):
domain = cast(ChangeSetDomain, self.env.get_domain('changeset'))
domain.note_changeset(node)
ret = [node] # type: List[nodes.Node]
ret = [node] # type: List[Node]
ret += messages
return ret
@ -117,42 +117,40 @@ class ChangeSetDomain(Domain):
'changes': {}, # version -> list of ChangeSet
} # type: Dict
def clear_doc(self, docname):
# type: (str) -> None
for version, changes in self.data['changes'].items():
for changeset in changes[:]:
if changeset.docname == docname:
changes.remove(changeset)
@property
def changesets(self) -> Dict[str, List[ChangeSet]]:
return self.data.setdefault('changes', {}) # version -> list of ChangeSet
def merge_domaindata(self, docnames, otherdata):
# type: (List[str], Dict) -> None
# XXX duplicates?
for version, otherchanges in otherdata['changes'].items():
changes = self.data['changes'].setdefault(version, [])
for changeset in otherchanges:
if changeset.docname in docnames:
changes.append(changeset)
def process_doc(self, env, docname, document):
# type: (BuildEnvironment, str, nodes.document) -> None
pass # nothing to do here. All changesets are registered on calling directive.
def note_changeset(self, node):
# type: (addnodes.versionmodified) -> None
def note_changeset(self, node: addnodes.versionmodified) -> None:
version = node['version']
module = self.env.ref_context.get('py:module')
objname = self.env.temp_data.get('object')
changeset = ChangeSet(node['type'], self.env.docname, node.line,
module, objname, node.astext())
self.data['changes'].setdefault(version, []).append(changeset)
self.changesets.setdefault(version, []).append(changeset)
def get_changesets_for(self, version):
# type: (str) -> List[ChangeSet]
return self.data['changes'].get(version, [])
def clear_doc(self, docname: str) -> None:
for version, changes in self.changesets.items():
for changeset in changes[:]:
if changeset.docname == docname:
changes.remove(changeset)
def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None:
# XXX duplicates?
for version, otherchanges in otherdata['changes'].items():
changes = self.changesets.setdefault(version, [])
for changeset in otherchanges:
if changeset.docname in docnames:
changes.append(changeset)
def process_doc(self, env: "BuildEnvironment", docname: str, document: nodes.document) -> None: # NOQA
pass # nothing to do here. All changesets are registered on calling directive.
def get_changesets_for(self, version: str) -> List[ChangeSet]:
return self.changesets.get(version, [])
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_domain(ChangeSetDomain)
app.add_directive('deprecated', VersionChange)
app.add_directive('versionadded', VersionChange)

View File

@ -8,11 +8,13 @@
:license: BSD, see LICENSE for details.
"""
from typing import Any, Dict, List, Set, Tuple
from typing import cast
from docutils import nodes
from docutils.nodes import Element
from sphinx import addnodes
from sphinx.addnodes import pending_xref
from sphinx.domains import Domain
from sphinx.locale import __
from sphinx.transforms import SphinxTransform
@ -21,10 +23,10 @@ from sphinx.util.nodes import copy_source_info, make_refnode
if False:
# For type annotation
from typing import Any, Dict, List, Set, Tuple, Union # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.environment import BuildEnvironment
logger = logging.getLogger(__name__)
@ -40,17 +42,14 @@ class CitationDomain(Domain):
}
@property
def citations(self):
# type: () -> Dict[str, Tuple[str, str, int]]
def citations(self) -> Dict[str, Tuple[str, str, int]]:
return self.data.setdefault('citations', {})
@property
def citation_refs(self):
# type: () -> Dict[str, Set[str]]
def citation_refs(self) -> Dict[str, Set[str]]:
return self.data.setdefault('citation_refs', {})
def clear_doc(self, docname):
# type: (str) -> None
def clear_doc(self, docname: str) -> None:
for key, (fn, _l, lineno) in list(self.citations.items()):
if fn == docname:
del self.citations[key]
@ -60,8 +59,7 @@ class CitationDomain(Domain):
elif docname in docnames:
docnames.remove(docname)
def merge_domaindata(self, docnames, otherdata):
# type: (List[str], Dict) -> None
def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None:
# XXX duplicates?
for key, data in otherdata['citations'].items():
if data[0] in docnames:
@ -72,8 +70,7 @@ class CitationDomain(Domain):
if docname in docnames:
citation_refs.add(docname)
def note_citation(self, node):
# type: (nodes.citation) -> None
def note_citation(self, node: nodes.citation) -> None:
label = node[0].astext()
if label in self.citations:
path = self.env.doc2path(self.citations[label][0])
@ -81,20 +78,19 @@ class CitationDomain(Domain):
location=node, type='ref', subtype='citation')
self.citations[label] = (node['docname'], node['ids'][0], node.line)
def note_citation_reference(self, node):
# type: (addnodes.pending_xref) -> None
def note_citation_reference(self, node: pending_xref) -> None:
docnames = self.citation_refs.setdefault(node['reftarget'], set())
docnames.add(self.env.docname)
def check_consistency(self):
# type: () -> None
def check_consistency(self) -> None:
for name, (docname, labelid, lineno) in self.citations.items():
if name not in self.citation_refs:
logger.warning(__('Citation [%s] is not referenced.'), name,
type='ref', subtype='citation', location=(docname, lineno))
def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
# type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA
def resolve_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder",
typ: str, target: str, node: pending_xref, contnode: Element
) -> Element:
docname, labelid, lineno = self.citations.get(target, ('', '', 0))
if not docname:
return None
@ -102,8 +98,9 @@ class CitationDomain(Domain):
return make_refnode(builder, fromdocname, docname,
labelid, contnode)
def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode):
# type: (BuildEnvironment, str, Builder, str, addnodes.pending_xref, nodes.Element) -> List[Tuple[str, nodes.Element]] # NOQA
def resolve_any_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder",
target: str, node: pending_xref, contnode: Element
) -> List[Tuple[str, Element]]:
refnode = self.resolve_xref(env, fromdocname, builder, 'ref', target, node, contnode)
if refnode is None:
return []
@ -115,8 +112,7 @@ class CitationDefinitionTransform(SphinxTransform):
"""Mark citation definition labels as not smartquoted."""
default_priority = 619
def apply(self, **kwargs):
# type: (Any) -> None
def apply(self, **kwargs) -> None:
domain = cast(CitationDomain, self.env.get_domain('citation'))
for node in self.document.traverse(nodes.citation):
# register citation node to domain
@ -135,16 +131,15 @@ class CitationReferenceTransform(SphinxTransform):
"""
default_priority = 619
def apply(self, **kwargs):
# type: (Any) -> None
def apply(self, **kwargs) -> None:
domain = cast(CitationDomain, self.env.get_domain('citation'))
for node in self.document.traverse(nodes.citation_reference):
target = node.astext()
ref = addnodes.pending_xref(target, refdomain='citation', reftype='ref',
reftarget=target, refwarn=True,
support_smartquotes=False,
ids=node["ids"],
classes=node.get('classes', []))
ref = pending_xref(target, refdomain='citation', reftype='ref',
reftarget=target, refwarn=True,
support_smartquotes=False,
ids=node["ids"],
classes=node.get('classes', []))
ref += nodes.inline(target, '[%s]' % target)
copy_source_info(node, ref)
node.replace_self(ref)
@ -153,8 +148,7 @@ class CitationReferenceTransform(SphinxTransform):
domain.note_citation_reference(ref)
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_domain(CitationDomain)
app.add_transform(CitationDefinitionTransform)
app.add_transform(CitationReferenceTransform)

View File

@ -8,25 +8,30 @@
:license: BSD, see LICENSE for details.
"""
from typing import Any, Dict, Iterator, List, Tuple
from typing import cast
from docutils import nodes
from docutils.nodes import Element, Node
from docutils.parsers.rst import directives
from sphinx import addnodes
from sphinx.addnodes import desc_signature, pending_xref
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType
from sphinx.domains.python import _pseudo_parse_arglist
from sphinx.locale import _
from sphinx.environment import BuildEnvironment
from sphinx.locale import _, __
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
if False:
# For type annotation
from typing import Any, Dict, Iterator, List, Tuple # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.environment import BuildEnvironment # NOQA
logger = logging.getLogger(__name__)
class JSObject(ObjectDescription):
@ -44,8 +49,7 @@ class JSObject(ObjectDescription):
#: based on directive nesting
allow_nesting = False
def handle_signature(self, sig, signode):
# type: (str, addnodes.desc_signature) -> Tuple[str, str]
def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]:
"""Breaks down construct signatures
Parses out prefix and argument list from construct definition. The
@ -98,8 +102,8 @@ class JSObject(ObjectDescription):
_pseudo_parse_arglist(signode, arglist)
return fullname, prefix
def add_target_and_index(self, name_obj, sig, signode):
# type: (Tuple[str, str], str, addnodes.desc_signature) -> None
def add_target_and_index(self, name_obj: Tuple[str, str], sig: str,
signode: desc_signature) -> None:
mod_name = self.env.ref_context.get('js:module')
fullname = (mod_name and mod_name + '.' or '') + name_obj[0]
if fullname not in self.state.document.ids:
@ -107,14 +111,10 @@ class JSObject(ObjectDescription):
signode['ids'].append(fullname.replace('$', '_S_'))
signode['first'] = not self.names
self.state.document.note_explicit_target(signode)
objects = self.env.domaindata['js']['objects']
if fullname in objects:
self.state_machine.reporter.warning(
'duplicate object description of %s, ' % fullname +
'other instance in ' +
self.env.doc2path(objects[fullname][0]),
line=self.lineno)
objects[fullname] = self.env.docname, self.objtype
domain = cast(JavaScriptDomain, self.env.get_domain('js'))
domain.note_object(fullname, self.objtype,
location=(self.env.docname, self.lineno))
indextext = self.get_index_text(mod_name, name_obj)
if indextext:
@ -122,8 +122,7 @@ class JSObject(ObjectDescription):
fullname.replace('$', '_S_'),
'', None))
def get_index_text(self, objectname, name_obj):
# type: (str, Tuple[str, str]) -> str
def get_index_text(self, objectname: str, name_obj: Tuple[str, str]) -> str:
name, obj = name_obj
if self.objtype == 'function':
if not obj:
@ -137,8 +136,7 @@ class JSObject(ObjectDescription):
return _('%s (%s attribute)') % (name, obj)
return ''
def before_content(self):
# type: () -> None
def before_content(self) -> None:
"""Handle object nesting before content
:py:class:`JSObject` represents JavaScript language constructs. For
@ -174,8 +172,7 @@ class JSObject(ObjectDescription):
objects = self.env.ref_context.setdefault('js:objects', [])
objects.append(prefix)
def after_content(self):
# type: () -> None
def after_content(self) -> None:
"""Handle object de-nesting after content
If this class is a nestable object, removing the last nested class prefix
@ -246,17 +243,19 @@ class JSModule(SphinxDirective):
'noindex': directives.flag
}
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
mod_name = self.arguments[0].strip()
self.env.ref_context['js:module'] = mod_name
noindex = 'noindex' in self.options
ret = [] # type: List[nodes.Node]
ret = [] # type: List[Node]
if not noindex:
self.env.domaindata['js']['modules'][mod_name] = self.env.docname
domain = cast(JavaScriptDomain, self.env.get_domain('js'))
domain.note_module(mod_name)
# Make a duplicate entry in 'objects' to facilitate searching for
# the module in JavaScriptDomain.find_obj()
self.env.domaindata['js']['objects'][mod_name] = (self.env.docname, 'module')
domain.note_object(mod_name, 'module', location=(self.env.docname, self.lineno))
targetnode = nodes.target('', '', ids=['module-' + mod_name],
ismod=True)
self.state.document.note_explicit_target(targetnode)
@ -269,8 +268,8 @@ class JSModule(SphinxDirective):
class JSXRefRole(XRefRole):
def process_link(self, env, refnode, has_explicit_title, title, target):
# type: (BuildEnvironment, nodes.Element, bool, str, str) -> Tuple[str, str]
def process_link(self, env: BuildEnvironment, refnode: Element,
has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]:
# basically what sphinx.domains.python.PyXRefRole does
refnode['js:object'] = env.ref_context.get('js:object')
refnode['js:module'] = env.ref_context.get('js:module')
@ -319,33 +318,48 @@ class JavaScriptDomain(Domain):
}
initial_data = {
'objects': {}, # fullname -> docname, objtype
'modules': {}, # mod_name -> docname
'modules': {}, # modname -> docname
} # type: Dict[str, Dict[str, Tuple[str, str]]]
def clear_doc(self, docname):
# type: (str) -> None
for fullname, (pkg_docname, _l) in list(self.data['objects'].items()):
if pkg_docname == docname:
del self.data['objects'][fullname]
for mod_name, pkg_docname in list(self.data['modules'].items()):
if pkg_docname == docname:
del self.data['modules'][mod_name]
@property
def objects(self) -> Dict[str, Tuple[str, str]]:
return self.data.setdefault('objects', {}) # fullname -> docname, objtype
def merge_domaindata(self, docnames, otherdata):
# type: (List[str], Dict) -> None
def note_object(self, fullname: str, objtype: str, location: Any = None) -> None:
if fullname in self.objects:
docname = self.objects[fullname][0]
logger.warning(__('duplicate object description of %s, other instance in %s'),
fullname, docname, location=location)
self.objects[fullname] = (self.env.docname, objtype)
@property
def modules(self) -> Dict[str, str]:
return self.data.setdefault('modules', {}) # modname -> docname
def note_module(self, modname: str) -> None:
self.modules[modname] = self.env.docname
def clear_doc(self, docname: str) -> None:
for fullname, (pkg_docname, _l) in list(self.objects.items()):
if pkg_docname == docname:
del self.objects[fullname]
for modname, pkg_docname in list(self.modules.items()):
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():
if fn in docnames:
self.data['objects'][fullname] = (fn, objtype)
self.objects[fullname] = (fn, objtype)
for mod_name, pkg_docname in otherdata['modules'].items():
if pkg_docname in docnames:
self.data['modules'][mod_name] = pkg_docname
self.modules[mod_name] = pkg_docname
def find_obj(self, env, mod_name, prefix, name, typ, searchorder=0):
# type: (BuildEnvironment, str, str, str, str, int) -> Tuple[str, Tuple[str, str]]
def find_obj(self, env: BuildEnvironment, mod_name: str, prefix: str, name: str,
typ: str, searchorder: int = 0) -> Tuple[str, Tuple[str, str]]:
if name[-2:] == '()':
name = name[:-2]
objects = self.data['objects']
searches = []
if mod_name and prefix:
@ -361,14 +375,14 @@ class JavaScriptDomain(Domain):
newname = None
for search_name in searches:
if search_name in objects:
if search_name in self.objects:
newname = search_name
return newname, objects.get(newname)
return newname, self.objects.get(newname)
def resolve_xref(self, env, fromdocname, builder, typ, target, node,
contnode):
# type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA
def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
typ: str, target: str, node: pending_xref, contnode: Element
) -> Element:
mod_name = node.get('js:module')
prefix = node.get('js:object')
searchorder = node.hasattr('refspecific') and 1 or 0
@ -378,9 +392,9 @@ class JavaScriptDomain(Domain):
return make_refnode(builder, fromdocname, obj[0],
name.replace('$', '_S_'), contnode, name)
def resolve_any_xref(self, env, fromdocname, builder, target, node,
contnode):
# type: (BuildEnvironment, str, Builder, str, addnodes.pending_xref, nodes.Element) -> List[Tuple[str, nodes.Element]] # NOQA
def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
target: str, node: pending_xref, contnode: Element
) -> List[Tuple[str, Element]]:
mod_name = node.get('js:module')
prefix = node.get('js:object')
name, obj = self.find_obj(env, mod_name, prefix, target, None, 1)
@ -390,14 +404,11 @@ class JavaScriptDomain(Domain):
make_refnode(builder, fromdocname, obj[0],
name.replace('$', '_S_'), contnode, name))]
def get_objects(self):
# type: () -> Iterator[Tuple[str, str, str, str, str, int]]
for refname, (docname, type) in list(self.data['objects'].items()):
yield refname, refname, type, docname, \
refname.replace('$', '_S_'), 1
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
def get_full_qualified_name(self, node):
# type: (nodes.Element) -> str
def get_full_qualified_name(self, node: Element) -> str:
modname = node.get('js:module')
prefix = node.get('js:object')
target = node.get('reftarget')
@ -407,8 +418,7 @@ class JavaScriptDomain(Domain):
return '.'.join(filter(None, [modname, prefix, target]))
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_domain(JavaScriptDomain)
return {

View File

@ -9,13 +9,17 @@
"""
import warnings
from typing import Any, Dict, Iterable, List, Tuple
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
from sphinx.environment import BuildEnvironment
from sphinx.locale import __
from sphinx.roles import XRefRole
from sphinx.util import logging
@ -23,18 +27,16 @@ from sphinx.util.nodes import make_refnode
if False:
# For type annotation
from typing import Any, Dict, Iterable, List, Tuple # NOQA
from sphinx import addnodes # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.application import Sphinx
from sphinx.builders import Builder
logger = logging.getLogger(__name__)
class MathReferenceRole(XRefRole):
def result_nodes(self, document, env, node, is_ref):
# type: (nodes.document, BuildEnvironment, nodes.Element, bool) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
def result_nodes(self, document: nodes.document, env: BuildEnvironment, node: Element,
is_ref: bool) -> Tuple[List[Node], List[system_message]]:
node['refdomain'] = 'math'
return [node], []
@ -60,12 +62,10 @@ class MathDomain(Domain):
}
@property
def equations(self):
# type: () -> Dict[str, Tuple[str, int]]
def equations(self) -> Dict[str, Tuple[str, int]]:
return self.data.setdefault('objects', {}) # labelid -> (docname, eqno)
def note_equation(self, docname, labelid, location=None):
# type: (str, str, Any) -> None
def note_equation(self, docname: str, labelid: str, location: Any = None) -> None:
if labelid in self.equations:
other = self.equations[labelid][0]
logger.warning(__('duplicate label of equation %s, other instance in %s') %
@ -73,31 +73,27 @@ class MathDomain(Domain):
self.equations[labelid] = (docname, self.env.new_serialno('eqno') + 1)
def get_equation_number_for(self, labelid):
# type: (str) -> int
def get_equation_number_for(self, labelid: str) -> int:
if labelid in self.equations:
return self.equations[labelid][1]
else:
return None
def process_doc(self, env, docname, document):
# type: (BuildEnvironment, str, nodes.document) -> None
def math_node(node):
# type: (nodes.Node) -> bool
def process_doc(self, env: BuildEnvironment, docname: str,
document: nodes.document) -> None:
def math_node(node: Node) -> bool:
return isinstance(node, (nodes.math, nodes.math_block))
self.data['has_equations'][docname] = any(document.traverse(math_node))
def clear_doc(self, docname):
# type: (str) -> None
def clear_doc(self, docname: str) -> None:
for equation_id, (doc, eqno) in list(self.equations.items()):
if doc == docname:
del self.equations[equation_id]
self.data['has_equations'].pop(docname, None)
def merge_domaindata(self, docnames, otherdata):
# type: (Iterable[str], Dict) -> None
def merge_domaindata(self, docnames: Iterable[str], otherdata: Dict) -> None:
for labelid, (doc, eqno) in otherdata['objects'].items():
if doc in docnames:
self.equations[labelid] = (doc, eqno)
@ -105,8 +101,9 @@ class MathDomain(Domain):
for docname in docnames:
self.data['has_equations'][docname] = otherdata['has_equations'][docname]
def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
# type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA
def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: "Builder",
typ: str, target: str, node: pending_xref, contnode: Element
) -> Element:
assert typ in ('eq', 'numref')
docname, number = self.equations.get(target, (None, None))
if docname:
@ -133,20 +130,19 @@ class MathDomain(Domain):
else:
return None
def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode):
# type: (BuildEnvironment, str, Builder, str, addnodes.pending_xref, nodes.Element) -> List[Tuple[str, nodes.Element]] # NOQA
def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: "Builder",
target: str, node: pending_xref, contnode: Element
) -> List[Tuple[str, Element]]:
refnode = self.resolve_xref(env, fromdocname, builder, 'eq', target, node, contnode)
if refnode is None:
return []
else:
return [('eq', refnode)]
def get_objects(self):
# type: () -> List
def get_objects(self) -> List:
return []
def add_equation(self, env, docname, labelid):
# type: (BuildEnvironment, str, str) -> int
def add_equation(self, env: BuildEnvironment, docname: str, labelid: str) -> int:
warnings.warn('MathDomain.add_equation() is deprecated.',
RemovedInSphinx40Warning)
if labelid in self.equations:
@ -158,20 +154,17 @@ class MathDomain(Domain):
self.equations[labelid] = (docname, eqno)
return eqno
def get_next_equation_number(self, docname):
# type: (str) -> int
def get_next_equation_number(self, docname: str) -> int:
warnings.warn('MathDomain.get_next_equation_number() is deprecated.',
RemovedInSphinx40Warning)
targets = [eq for eq in self.equations.values() if eq[0] == docname]
return len(targets) + 1
def has_equations(self):
# type: () -> bool
def has_equations(self) -> bool:
return any(self.data['has_equations'].values())
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_domain(MathDomain)
app.add_role('eq', MathReferenceRole(warn_dangling=True))

View File

@ -10,31 +10,31 @@
import re
import warnings
from typing import Any, Dict, Iterable, Iterator, List, Tuple, Type
from typing import cast
from docutils import nodes
from docutils.nodes import Element, Node
from docutils.parsers.rst import directives
from sphinx import addnodes, locale
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.directives import ObjectDescription
from sphinx.domains import Domain, ObjType, Index, IndexEntry
from sphinx.environment import BuildEnvironment
from sphinx.locale import _, __
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.typing import TextlikeNode
if False:
# For type annotation
from typing import Any, Dict, Iterable, Iterator, List, Tuple, Type # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.util.typing import TextlikeNode # NOQA
logger = logging.getLogger(__name__)
@ -67,8 +67,7 @@ locale.pairindextypes = DeprecatedDict(
)
def _pseudo_parse_arglist(signode, arglist):
# type: (addnodes.desc_signature, str) -> None
def _pseudo_parse_arglist(signode: desc_signature, arglist: str) -> None:
""""Parse" a list of arguments separated by commas.
Arguments can have "optional" annotations given by enclosing them in
@ -76,7 +75,7 @@ def _pseudo_parse_arglist(signode, arglist):
string literal (e.g. default argument value).
"""
paramlist = addnodes.desc_parameterlist()
stack = [paramlist] # type: List[nodes.Element]
stack = [paramlist] # type: List[Element]
try:
for argument in arglist.split(','):
argument = argument.strip()
@ -119,15 +118,9 @@ def _pseudo_parse_arglist(signode, arglist):
# This override allows our inline type specifiers to behave like :class: link
# when it comes to handling "." and "~" prefixes.
class PyXrefMixin:
def make_xref(self,
rolename, # type: str
domain, # type: str
target, # type: str
innernode=nodes.emphasis, # type: Type[TextlikeNode]
contnode=None, # type: nodes.Node
env=None, # type: BuildEnvironment
):
# type: (...) -> nodes.Node
def make_xref(self, rolename: str, domain: str, target: str,
innernode: Type[TextlikeNode] = nodes.emphasis,
contnode: Node = None, env: BuildEnvironment = None) -> Node:
result = super().make_xref(rolename, domain, target, # type: ignore
innernode, contnode, env)
result['refspecific'] = True
@ -142,15 +135,9 @@ class PyXrefMixin:
break
return result
def make_xrefs(self,
rolename, # type: str
domain, # type: str
target, # type: str
innernode=nodes.emphasis, # type: Type[TextlikeNode]
contnode=None, # type: nodes.Node
env=None, # type: BuildEnvironment
):
# type: (...) -> List[nodes.Node]
def make_xrefs(self, rolename: str, domain: str, target: str,
innernode: Type[TextlikeNode] = nodes.emphasis,
contnode: Node = None, env: BuildEnvironment = None) -> List[Node]:
delims = r'(\s*[\[\]\(\),](?:\s*or\s)?\s*|\s+or\s+)'
delims_re = re.compile(delims)
sub_targets = re.split(delims, target)
@ -172,9 +159,9 @@ class PyXrefMixin:
class PyField(PyXrefMixin, Field):
def make_xref(self, rolename, domain, target,
innernode=nodes.emphasis, contnode=None, env=None):
# type: (str, str, str, Type[TextlikeNode], nodes.Node, BuildEnvironment) -> nodes.Node # NOQA
def make_xref(self, rolename: str, domain: str, target: str,
innernode: Type[TextlikeNode] = nodes.emphasis,
contnode: Node = None, env: BuildEnvironment = None) -> Node:
if rolename == 'class' and target == 'None':
# None is not a type, so use obj role instead.
rolename = 'obj'
@ -187,9 +174,9 @@ class PyGroupedField(PyXrefMixin, GroupedField):
class PyTypedField(PyXrefMixin, TypedField):
def make_xref(self, rolename, domain, target,
innernode=nodes.emphasis, contnode=None, env=None):
# type: (str, str, str, Type[TextlikeNode], nodes.Node, BuildEnvironment) -> nodes.Node # NOQA
def make_xref(self, rolename: str, domain: str, target: str,
innernode: Type[TextlikeNode] = nodes.emphasis,
contnode: Node = None, env: BuildEnvironment = None) -> Node:
if rolename == 'class' and target == 'None':
# None is not a type, so use obj role instead.
rolename = 'obj'
@ -231,22 +218,19 @@ class PyObject(ObjectDescription):
allow_nesting = False
def get_signature_prefix(self, sig):
# type: (str) -> str
def get_signature_prefix(self, sig: str) -> str:
"""May return a prefix to put before the object name in the
signature.
"""
return ''
def needs_arglist(self):
# type: () -> bool
def needs_arglist(self) -> bool:
"""May return true if an empty argument list is to be generated even if
the document contains none.
"""
return False
def handle_signature(self, sig, signode):
# type: (str, addnodes.desc_signature) -> Tuple[str, str]
def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]:
"""Transform a Python signature into RST nodes.
Return (fully qualified name of the thing, classname if any).
@ -320,13 +304,12 @@ class PyObject(ObjectDescription):
return fullname, prefix
def get_index_text(self, modname, name):
# type: (str, Tuple[str, str]) -> str
def get_index_text(self, modname: str, name: Tuple[str, str]) -> str:
"""Return the text for the index entry of the object."""
raise NotImplementedError('must be implemented in subclasses')
def add_target_and_index(self, name_cls, sig, signode):
# type: (Tuple[str, str], str, addnodes.desc_signature) -> None
def add_target_and_index(self, name_cls: Tuple[str, str], sig: str,
signode: desc_signature) -> None:
modname = self.options.get('module', self.env.ref_context.get('py:module'))
fullname = (modname and modname + '.' or '') + name_cls[0]
# note target
@ -345,8 +328,7 @@ class PyObject(ObjectDescription):
self.indexnode['entries'].append(('single', indextext,
fullname, '', None))
def before_content(self):
# type: () -> None
def before_content(self) -> None:
"""Handle object nesting before content
:py:class:`PyObject` represents Python language constructs. For
@ -379,8 +361,7 @@ class PyObject(ObjectDescription):
modules.append(self.env.ref_context.get('py:module'))
self.env.ref_context['py:module'] = self.options['module']
def after_content(self):
# type: () -> None
def after_content(self) -> None:
"""Handle object de-nesting after content
If this class is a nestable object, removing the last nested class prefix
@ -411,19 +392,16 @@ class PyModulelevel(PyObject):
Description of an object on module level (functions, data).
"""
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
warnings.warn('PyClassmember is deprecated.',
RemovedInSphinx40Warning)
return super().run()
def needs_arglist(self):
# type: () -> bool
def needs_arglist(self) -> bool:
return self.objtype == 'function'
def get_index_text(self, modname, name_cls):
# type: (str, Tuple[str, str]) -> str
def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str:
if self.objtype == 'function':
if not modname:
return _('%s() (built-in function)') % name_cls[0]
@ -444,19 +422,16 @@ class PyFunction(PyObject):
'async': directives.flag,
})
def get_signature_prefix(self, sig):
# type: (str) -> str
def get_signature_prefix(self, sig: str) -> str:
if 'async' in self.options:
return 'async '
else:
return ''
def needs_arglist(self):
# type: () -> bool
def needs_arglist(self) -> bool:
return True
def get_index_text(self, modname, name_cls):
# type: (str, Tuple[str, str]) -> str
def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str:
name, cls = name_cls
if modname:
return _('%s() (in module %s)') % (name, modname)
@ -467,8 +442,7 @@ class PyFunction(PyObject):
class PyVariable(PyObject):
"""Description of a variable."""
def get_index_text(self, modname, name_cls):
# type: (str, Tuple[str, str]) -> str
def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str:
name, cls = name_cls
if modname:
return _('%s (in module %s)') % (name, modname)
@ -483,12 +457,10 @@ class PyClasslike(PyObject):
allow_nesting = True
def get_signature_prefix(self, sig):
# type: (str) -> str
def get_signature_prefix(self, sig: str) -> str:
return self.objtype + ' '
def get_index_text(self, modname, name_cls):
# type: (str, Tuple[str, str]) -> str
def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str:
if self.objtype == 'class':
if not modname:
return _('%s (built-in class)') % name_cls[0]
@ -504,27 +476,23 @@ class PyClassmember(PyObject):
Description of a class member (methods, attributes).
"""
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
warnings.warn('PyClassmember is deprecated.',
RemovedInSphinx40Warning)
return super().run()
def needs_arglist(self):
# type: () -> bool
def needs_arglist(self) -> bool:
return self.objtype.endswith('method')
def get_signature_prefix(self, sig):
# type: (str) -> str
def get_signature_prefix(self, sig: str) -> str:
if self.objtype == 'staticmethod':
return 'static '
elif self.objtype == 'classmethod':
return 'classmethod '
return ''
def get_index_text(self, modname, name_cls):
# type: (str, Tuple[str, str]) -> str
def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str:
name, cls = name_cls
add_modules = self.env.config.add_module_names
if self.objtype == 'method':
@ -593,15 +561,13 @@ class PyMethod(PyObject):
'staticmethod': directives.flag,
})
def needs_arglist(self):
# type: () -> bool
def needs_arglist(self) -> bool:
if 'property' in self.options:
return False
else:
return True
def get_signature_prefix(self, sig):
# type: (str) -> str
def get_signature_prefix(self, sig: str) -> str:
prefix = []
if 'abstractmethod' in self.options:
prefix.append('abstract')
@ -619,8 +585,7 @@ class PyMethod(PyObject):
else:
return ''
def get_index_text(self, modname, name_cls):
# type: (str, Tuple[str, str]) -> str
def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str:
name, cls = name_cls
try:
clsname, methname = name.rsplit('.', 1)
@ -647,8 +612,7 @@ class PyClassMethod(PyMethod):
option_spec = PyObject.option_spec.copy()
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
self.name = 'py:method'
self.options['classmethod'] = True
@ -660,8 +624,7 @@ class PyStaticMethod(PyMethod):
option_spec = PyObject.option_spec.copy()
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
self.name = 'py:method'
self.options['staticmethod'] = True
@ -671,8 +634,7 @@ class PyStaticMethod(PyMethod):
class PyAttribute(PyObject):
"""Description of an attribute."""
def get_index_text(self, modname, name_cls):
# type: (str, Tuple[str, str]) -> str
def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str:
name, cls = name_cls
try:
clsname, attrname = name.rsplit('.', 1)
@ -691,14 +653,12 @@ class PyDecoratorMixin:
"""
Mixin for decorator directives.
"""
def handle_signature(self, sig, signode):
# type: (str, addnodes.desc_signature) -> Tuple[str, str]
def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]:
ret = super().handle_signature(sig, signode) # type: ignore
signode.insert(0, addnodes.desc_addname('@', '@'))
return ret
def needs_arglist(self):
# type: () -> bool
def needs_arglist(self) -> bool:
return False
@ -706,8 +666,7 @@ class PyDecoratorFunction(PyDecoratorMixin, PyModulelevel):
"""
Directive to mark functions meant to be used as decorators.
"""
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
# a decorator function is a function after all
self.name = 'py:function'
return super().run()
@ -717,8 +676,7 @@ class PyDecoratorMethod(PyDecoratorMixin, PyClassmember):
"""
Directive to mark methods meant to be used as decorators.
"""
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
self.name = 'py:method'
return super().run()
@ -739,14 +697,13 @@ class PyModule(SphinxDirective):
'deprecated': directives.flag,
}
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
domain = cast(PythonDomain, self.env.get_domain('py'))
modname = self.arguments[0].strip()
noindex = 'noindex' in self.options
self.env.ref_context['py:module'] = modname
ret = [] # type: List[nodes.Node]
ret = [] # type: List[Node]
if not noindex:
# note module to the domain
domain.note_module(modname,
@ -780,8 +737,7 @@ class PyCurrentModule(SphinxDirective):
final_argument_whitespace = False
option_spec = {} # type: Dict
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
modname = self.arguments[0].strip()
if modname == 'None':
self.env.ref_context.pop('py:module', None)
@ -791,8 +747,8 @@ class PyCurrentModule(SphinxDirective):
class PyXRefRole(XRefRole):
def process_link(self, env, refnode, has_explicit_title, title, target):
# type: (BuildEnvironment, nodes.Element, bool, str, str) -> Tuple[str, str]
def process_link(self, env: BuildEnvironment, refnode: Element,
has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]:
refnode['py:module'] = env.ref_context.get('py:module')
refnode['py:class'] = env.ref_context.get('py:class')
if not has_explicit_title:
@ -822,8 +778,8 @@ class PythonModuleIndex(Index):
localname = _('Python Module Index')
shortname = _('modules')
def generate(self, docnames=None):
# type: (Iterable[str]) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]
def generate(self, docnames: Iterable[str] = None
) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]:
content = {} # type: Dict[str, List[IndexEntry]]
# list of prefixes to ignore
ignores = None # type: List[str]
@ -937,12 +893,10 @@ class PythonDomain(Domain):
]
@property
def objects(self):
# type: () -> Dict[str, Tuple[str, str]]
def objects(self) -> Dict[str, Tuple[str, str]]:
return self.data.setdefault('objects', {}) # fullname -> docname, objtype
def note_object(self, name, objtype, location=None):
# type: (str, str, Any) -> None
def note_object(self, name: str, objtype: str, location: Any = None) -> None:
"""Note a python object for cross reference.
.. versionadded:: 2.1
@ -955,20 +909,17 @@ class PythonDomain(Domain):
self.objects[name] = (self.env.docname, objtype)
@property
def modules(self):
# type: () -> Dict[str, Tuple[str, str, str, bool]]
def modules(self) -> Dict[str, Tuple[str, str, str, bool]]:
return self.data.setdefault('modules', {}) # modname -> docname, synopsis, platform, deprecated # NOQA
def note_module(self, name, synopsis, platform, deprecated):
# type: (str, str, str, bool) -> None
def note_module(self, name: str, synopsis: str, platform: str, deprecated: bool) -> None:
"""Note a python module for cross reference.
.. versionadded:: 2.1
"""
self.modules[name] = (self.env.docname, synopsis, platform, deprecated)
def clear_doc(self, docname):
# type: (str) -> None
def clear_doc(self, docname: str) -> None:
for fullname, (fn, _l) in list(self.objects.items()):
if fn == docname:
del self.objects[fullname]
@ -976,8 +927,7 @@ class PythonDomain(Domain):
if fn == docname:
del self.modules[modname]
def merge_domaindata(self, docnames, otherdata):
# type: (List[str], Dict) -> None
def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None:
# XXX check duplicates?
for fullname, (fn, objtype) in otherdata['objects'].items():
if fn in docnames:
@ -986,8 +936,8 @@ class PythonDomain(Domain):
if data[0] in docnames:
self.modules[modname] = data
def find_obj(self, env, modname, classname, name, type, searchmode=0):
# type: (BuildEnvironment, str, str, str, str, int) -> List[Tuple[str, Any]]
def find_obj(self, env: BuildEnvironment, modname: str, classname: str,
name: str, type: str, searchmode: int = 0) -> List[Tuple[str, Any]]:
"""Find a Python object for "name", perhaps using the given module
and/or classname. Returns a list of (name, object entry) tuples.
"""
@ -1049,9 +999,9 @@ class PythonDomain(Domain):
matches.append((newname, self.objects[newname]))
return matches
def resolve_xref(self, env, fromdocname, builder,
type, target, node, contnode):
# type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA
def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
type: str, target: str, node: pending_xref, contnode: Element
) -> Element:
modname = node.get('py:module')
clsname = node.get('py:class')
searchmode = node.hasattr('refspecific') and 1 or 0
@ -1070,12 +1020,12 @@ class PythonDomain(Domain):
else:
return make_refnode(builder, fromdocname, obj[0], name, contnode, name)
def resolve_any_xref(self, env, fromdocname, builder, target,
node, contnode):
# type: (BuildEnvironment, str, Builder, str, addnodes.pending_xref, nodes.Element) -> List[Tuple[str, nodes.Element]] # NOQA
def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
target: str, node: pending_xref, contnode: Element
) -> List[Tuple[str, Element]]:
modname = node.get('py:module')
clsname = node.get('py:class')
results = [] # type: List[Tuple[str, nodes.Element]]
results = [] # type: List[Tuple[str, Element]]
# always search in "refspecific" mode with the :any: role
matches = self.find_obj(env, modname, clsname, target, None, 1)
@ -1090,8 +1040,8 @@ class PythonDomain(Domain):
contnode, name)))
return results
def _make_module_refnode(self, builder, fromdocname, name, contnode):
# type: (Builder, str, str, nodes.Node) -> nodes.Element
def _make_module_refnode(self, builder: Builder, fromdocname: str, name: str,
contnode: Node) -> Element:
# get additional info for modules
docname, synopsis, platform, deprecated = self.modules[name]
title = name
@ -1104,16 +1054,14 @@ class PythonDomain(Domain):
return make_refnode(builder, fromdocname, docname,
'module-' + name, contnode, title)
def get_objects(self):
# type: () -> Iterator[Tuple[str, str, str, str, str, int]]
def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]:
for modname, info in self.modules.items():
yield (modname, modname, 'module', info[0], 'module-' + modname, 0)
for refname, (docname, type) in self.objects.items():
if type != 'module': # modules are already handled
yield (refname, refname, type, docname, refname, 1)
def get_full_qualified_name(self, node):
# type: (nodes.Element) -> str
def get_full_qualified_name(self, node: Element) -> str:
modname = node.get('py:module')
clsname = node.get('py:class')
target = node.get('reftarget')
@ -1123,8 +1071,7 @@ class PythonDomain(Domain):
return '.'.join(filter(None, [modname, clsname, target]))
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_domain(PythonDomain)
return {

View File

@ -9,26 +9,24 @@
"""
import re
from typing import Any, Dict, Iterator, List, Tuple
from typing import cast
from docutils.nodes import Element
from docutils.parsers.rst import directives
from sphinx import addnodes
from sphinx.addnodes import desc_signature, pending_xref
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType
from sphinx.environment import BuildEnvironment
from sphinx.locale import _, __
from sphinx.roles import XRefRole
from sphinx.util import logging
from sphinx.util.nodes import make_refnode
if False:
# For type annotation
from typing import Any, Dict, Iterator, List, Tuple # NOQA
from docutils import nodes # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.environment import BuildEnvironment # NOQA
logger = logging.getLogger(__name__)
@ -40,8 +38,7 @@ class ReSTMarkup(ObjectDescription):
Description of generic reST markup.
"""
def add_target_and_index(self, name, sig, signode):
# type: (str, str, addnodes.desc_signature) -> None
def add_target_and_index(self, name: str, sig: str, signode: desc_signature) -> None:
targetname = self.objtype + '-' + name
if targetname not in self.state.document.ids:
signode['names'].append(targetname)
@ -57,13 +54,11 @@ class ReSTMarkup(ObjectDescription):
self.indexnode['entries'].append(('single', indextext,
targetname, '', None))
def get_index_text(self, objectname, name):
# type: (str, str) -> str
def get_index_text(self, objectname: str, name: str) -> str:
return ''
def parse_directive(d):
# type: (str) -> Tuple[str, str]
def parse_directive(d: str) -> Tuple[str, str]:
"""Parse a directive signature.
Returns (directive, arguments) string tuple. If no arguments are given,
@ -87,8 +82,7 @@ class ReSTDirective(ReSTMarkup):
"""
Description of a reST directive.
"""
def handle_signature(self, sig, signode):
# type: (str, addnodes.desc_signature) -> str
def handle_signature(self, sig: str, signode: desc_signature) -> str:
name, args = parse_directive(sig)
desc_name = '.. %s::' % name
signode += addnodes.desc_name(desc_name, desc_name)
@ -96,18 +90,15 @@ class ReSTDirective(ReSTMarkup):
signode += addnodes.desc_addname(args, args)
return name
def get_index_text(self, objectname, name):
# type: (str, str) -> str
def get_index_text(self, objectname: str, name: str) -> str:
return _('%s (directive)') % name
def before_content(self):
# type: () -> None
def before_content(self) -> None:
if self.names:
directives = self.env.ref_context.setdefault('rst:directives', [])
directives.append(self.names[0])
def after_content(self):
# type: () -> None
def after_content(self) -> None:
directives = self.env.ref_context.setdefault('rst:directives', [])
if directives:
directives.pop()
@ -122,8 +113,7 @@ class ReSTDirectiveOption(ReSTMarkup):
'type': directives.unchanged,
})
def handle_signature(self, sig, signode):
# type: (str, addnodes.desc_signature) -> str
def handle_signature(self, sig: str, signode: desc_signature) -> str:
try:
name, argument = re.split(r'\s*:\s+', sig.strip(), 1)
except ValueError:
@ -137,8 +127,7 @@ class ReSTDirectiveOption(ReSTMarkup):
signode += addnodes.desc_annotation(text, text)
return name
def add_target_and_index(self, name, sig, signode):
# type: (str, str, addnodes.desc_signature) -> None
def add_target_and_index(self, name: str, sig: str, signode: desc_signature) -> None:
directive_name = self.current_directive
targetname = '-'.join([self.objtype, self.current_directive, name])
if targetname not in self.state.document.ids:
@ -162,8 +151,7 @@ class ReSTDirectiveOption(ReSTMarkup):
self.indexnode['entries'].append(('single', text, targetname, '', key))
@property
def current_directive(self):
# type: () -> str
def current_directive(self) -> str:
directives = self.env.ref_context.get('rst:directives')
if directives:
return directives[-1]
@ -175,13 +163,11 @@ class ReSTRole(ReSTMarkup):
"""
Description of a reST role.
"""
def handle_signature(self, sig, signode):
# type: (str, addnodes.desc_signature) -> str
def handle_signature(self, sig: str, signode: desc_signature) -> str:
signode += addnodes.desc_name(':%s:' % sig, ':%s:' % sig)
return sig
def get_index_text(self, objectname, name):
# type: (str, str) -> str
def get_index_text(self, objectname: str, name: str) -> str:
return _('%s (role)') % name
@ -209,12 +195,10 @@ class ReSTDomain(Domain):
} # type: Dict[str, Dict[Tuple[str, str], str]]
@property
def objects(self):
# type: () -> Dict[Tuple[str, str], str]
def objects(self) -> Dict[Tuple[str, str], str]:
return self.data.setdefault('objects', {}) # (objtype, fullname) -> docname
def note_object(self, objtype, name, location=None):
# type: (str, str, Any) -> None
def note_object(self, objtype: str, name: str, location: Any = None) -> None:
if (objtype, name) in self.objects:
docname = self.objects[objtype, name]
logger.warning(__('duplicate description of %s %s, other instance in %s') %
@ -222,21 +206,20 @@ class ReSTDomain(Domain):
self.objects[objtype, name] = self.env.docname
def clear_doc(self, docname):
# type: (str) -> None
def clear_doc(self, docname: str) -> None:
for (typ, name), doc in list(self.objects.items()):
if doc == docname:
del self.objects[typ, name]
def merge_domaindata(self, docnames, otherdata):
# type: (List[str], Dict) -> None
def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None:
# XXX check duplicates
for (typ, name), doc in otherdata['objects'].items():
if doc in docnames:
self.objects[typ, name] = doc
def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
# type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA
def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
typ: str, target: str, node: pending_xref, contnode: Element
) -> Element:
objtypes = self.objtypes_for_role(typ)
for objtype in objtypes:
todocname = self.objects.get((objtype, target))
@ -246,9 +229,10 @@ class ReSTDomain(Domain):
contnode, target + ' ' + objtype)
return None
def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode):
# type: (BuildEnvironment, str, Builder, str, addnodes.pending_xref, nodes.Element) -> List[Tuple[str, nodes.Element]] # NOQA
results = [] # type: List[Tuple[str, nodes.Element]]
def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
target: str, node: pending_xref, contnode: Element
) -> List[Tuple[str, Element]]:
results = [] # type: List[Tuple[str, Element]]
for objtype in self.object_types:
todocname = self.objects.get((objtype, target))
if todocname:
@ -258,14 +242,12 @@ class ReSTDomain(Domain):
contnode, target + ' ' + objtype)))
return results
def get_objects(self):
# type: () -> Iterator[Tuple[str, str, str, str, str, int]]
def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]:
for (typ, name), docname in self.data['objects'].items():
yield name, name, typ, docname, typ + '-' + name, 1
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_domain(ReSTDomain)
return {

View File

@ -12,13 +12,16 @@ import re
import unicodedata
import warnings
from copy import copy
from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Type, Union
from typing import cast
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.nodes import Element, Node, system_message
from docutils.parsers.rst import Directive, directives
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.directives import ObjectDescription
from sphinx.domains import Domain, ObjType
@ -28,15 +31,13 @@ from sphinx.roles import XRefRole
from sphinx.util import ws_re, logging, docname_join
from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import clean_astext, make_refnode
from sphinx.util.typing import RoleFunction
if False:
# For type annotation
from typing import Any, Callable, Dict, Iterable, Iterator, List, Tuple, Type, Union # NOQA
from docutils.parsers.rst import Directive # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.util.typing import RoleFunction # NOQA
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.environment import BuildEnvironment
logger = logging.getLogger(__name__)
@ -52,10 +53,9 @@ class GenericObject(ObjectDescription):
A generic x-ref directive registered with Sphinx.add_object_type().
"""
indextemplate = ''
parse_node = None # type: Callable[[GenericObject, BuildEnvironment, str, addnodes.desc_signature], str] # NOQA
parse_node = None # type: Callable[[GenericObject, BuildEnvironment, str, desc_signature], str] # NOQA
def handle_signature(self, sig, signode):
# type: (str, addnodes.desc_signature) -> str
def handle_signature(self, sig: str, signode: desc_signature) -> str:
if self.parse_node:
name = self.parse_node(self.env, sig, signode)
else:
@ -65,8 +65,7 @@ class GenericObject(ObjectDescription):
name = ws_re.sub('', sig)
return name
def add_target_and_index(self, name, sig, signode):
# type: (str, str, addnodes.desc_signature) -> None
def add_target_and_index(self, name: str, sig: str, signode: desc_signature) -> None:
targetname = '%s-%s' % (self.objtype, name)
signode['ids'].append(targetname)
self.state.document.note_explicit_target(signode)
@ -94,8 +93,8 @@ class EnvVarXRefRole(XRefRole):
Cross-referencing role for environment variables (adds an index entry).
"""
def result_nodes(self, document, env, node, is_ref):
# type: (nodes.document, BuildEnvironment, nodes.Element, bool) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
def result_nodes(self, document: nodes.document, env: "BuildEnvironment", node: Element,
is_ref: bool) -> Tuple[List[Node], List[system_message]]:
if not is_ref:
return [node], []
varname = node['reftarget']
@ -122,8 +121,7 @@ class Target(SphinxDirective):
final_argument_whitespace = True
option_spec = {} # type: Dict
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
# normalize whitespace in fullname like XRefRole does
fullname = ws_re.sub(' ', self.arguments[0].strip())
targetname = '%s-%s' % (self.name, fullname)
@ -155,8 +153,7 @@ class Cmdoption(ObjectDescription):
Description of a command-line option (.. option).
"""
def handle_signature(self, sig, signode):
# type: (str, addnodes.desc_signature) -> str
def handle_signature(self, sig: str, signode: desc_signature) -> str:
"""Transform an option description into RST nodes."""
count = 0
firstname = ''
@ -184,8 +181,7 @@ class Cmdoption(ObjectDescription):
raise ValueError
return firstname
def add_target_and_index(self, firstname, sig, signode):
# type: (str, str, addnodes.desc_signature) -> None
def add_target_and_index(self, firstname: str, sig: str, signode: desc_signature) -> None:
currprogram = self.env.ref_context.get('std:program')
for optname in signode.get('allnames', []):
targetname = optname.replace('/', '-')
@ -223,8 +219,7 @@ class Program(SphinxDirective):
final_argument_whitespace = True
option_spec = {} # type: Dict
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
program = ws_re.sub('-', self.arguments[0].strip())
if program == 'None':
self.env.ref_context.pop('std:program', None)
@ -234,21 +229,20 @@ class Program(SphinxDirective):
class OptionXRefRole(XRefRole):
def process_link(self, env, refnode, has_explicit_title, title, target):
# type: (BuildEnvironment, nodes.Element, bool, str, str) -> Tuple[str, str]
def process_link(self, env: "BuildEnvironment", refnode: Element, has_explicit_title: bool,
title: str, target: str) -> Tuple[str, str]:
refnode['std:program'] = env.ref_context.get('std:program')
return title, target
def split_term_classifiers(line):
# type: (str) -> List[Union[str, None]]
def split_term_classifiers(line: str) -> List[Optional[str]]:
# split line into a term and classifiers. if no classifier, None is used..
parts = re.split(' +: +', line) + [None]
return parts
def make_glossary_term(env, textnodes, index_key, source, lineno, new_id=None):
# type: (BuildEnvironment, Iterable[nodes.Node], str, str, int, str) -> nodes.term
def make_glossary_term(env: "BuildEnvironment", textnodes: Iterable[Node], index_key: str,
source: str, lineno: int, new_id: str = None) -> nodes.term:
# get a text-only representation of the term and register it
# as a cross-reference target
term = nodes.term('', '', *textnodes)
@ -294,8 +288,7 @@ class Glossary(SphinxDirective):
'sorted': directives.flag,
}
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
node = addnodes.glossary()
node.document = self.state.document
@ -401,16 +394,15 @@ class Glossary(SphinxDirective):
return messages + [node]
def token_xrefs(text):
# type: (str) -> List[nodes.Node]
def token_xrefs(text: str) -> List[Node]:
retnodes = [] # type: List[nodes.Node]
pos = 0
for m in token_re.finditer(text):
if m.start() > pos:
txt = text[pos:m.start()]
retnodes.append(nodes.Text(txt, txt))
refnode = addnodes.pending_xref(
m.group(1), reftype='token', refdomain='std', reftarget=m.group(1))
refnode = pending_xref(m.group(1), reftype='token', refdomain='std',
reftarget=m.group(1))
refnode += nodes.literal(m.group(1), m.group(1), classes=['xref'])
retnodes.append(refnode)
pos = m.end()
@ -430,8 +422,7 @@ class ProductionList(SphinxDirective):
final_argument_whitespace = True
option_spec = {} # type: Dict
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
domain = cast(StandardDomain, self.env.get_domain('std'))
node = addnodes.productionlist() # type: nodes.Element
i = 0
@ -536,8 +527,7 @@ class StandardDomain(Domain):
nodes.container: ('code-block', None),
} # type: Dict[Type[nodes.Node], Tuple[str, Callable]]
def __init__(self, env):
# type: (BuildEnvironment) -> None
def __init__(self, env: "BuildEnvironment") -> None:
super().__init__(env)
# set up enumerable nodes
@ -546,27 +536,22 @@ class StandardDomain(Domain):
self.enumerable_nodes[node] = settings
@property
def objects(self):
# type: () -> Dict[Tuple[str, str], Tuple[str, str]]
def objects(self) -> Dict[Tuple[str, str], Tuple[str, str]]:
return self.data.setdefault('objects', {}) # (objtype, name) -> docname, labelid
@property
def progoptions(self):
# type: () -> Dict[Tuple[str, str], Tuple[str, str]]
def progoptions(self) -> Dict[Tuple[str, str], Tuple[str, str]]:
return self.data.setdefault('progoptions', {}) # (program, name) -> docname, labelid
@property
def labels(self):
# type: () -> Dict[str, Tuple[str, str, str]]
def labels(self) -> Dict[str, Tuple[str, str, str]]:
return self.data.setdefault('labels', {}) # labelname -> docname, labelid, sectionname
@property
def anonlabels(self):
# type: () -> Dict[str, Tuple[str, str]]
def anonlabels(self) -> Dict[str, Tuple[str, str]]:
return self.data.setdefault('anonlabels', {}) # labelname -> docname, labelid
def clear_doc(self, docname):
# type: (str) -> None
def clear_doc(self, docname: str) -> None:
key = None # type: Any
for key, (fn, _l) in list(self.progoptions.items()):
if fn == docname:
@ -581,8 +566,7 @@ class StandardDomain(Domain):
if fn == docname:
del self.anonlabels[key]
def merge_domaindata(self, docnames, otherdata):
# type: (List[str], Dict) -> None
def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None:
# XXX duplicates?
for key, data in otherdata['progoptions'].items():
if data[0] in docnames:
@ -597,8 +581,7 @@ class StandardDomain(Domain):
if data[0] in docnames:
self.anonlabels[key] = data
def process_doc(self, env, docname, document):
# type: (BuildEnvironment, str, nodes.document) -> None
def process_doc(self, env: "BuildEnvironment", docname: str, document: nodes.document) -> None: # NOQA
for name, explicit in document.nametypes.items():
if not explicit:
continue
@ -639,17 +622,15 @@ class StandardDomain(Domain):
continue
self.labels[name] = docname, labelid, sectname
def add_object(self, objtype, name, docname, labelid):
# type: (str, str, str, str) -> None
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, name, docname, labelid):
# type: (str, str, str, str) -> None
def add_program_option(self, program: str, name: str, docname: str, labelid: str) -> None:
self.progoptions[program, name] = (docname, labelid)
def build_reference_node(self, fromdocname, builder, docname, labelid,
sectname, rolename, **options):
# type: (str, Builder, str, str, str, str, Any) -> nodes.Element
def build_reference_node(self, fromdocname: str, builder: "Builder", docname: str,
labelid: str, sectname: str, rolename: str, **options
) -> Element:
nodeclass = options.pop('nodeclass', nodes.reference)
newnode = nodeclass('', '', internal=True, **options)
innernode = nodes.inline(sectname, sectname)
@ -662,7 +643,7 @@ class StandardDomain(Domain):
# set more info in contnode; in case the
# get_relative_uri call raises NoUri,
# the builder will then have to resolve these
contnode = addnodes.pending_xref('')
contnode = pending_xref('')
contnode['refdocname'] = docname
contnode['refsectname'] = sectname
newnode['refuri'] = builder.get_relative_uri(
@ -672,8 +653,8 @@ class StandardDomain(Domain):
newnode.append(innernode)
return newnode
def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
# type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA
def resolve_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder",
typ: str, target: str, node: pending_xref, contnode: Element) -> Element:
if typ == 'ref':
resolver = self._resolve_ref_xref
elif typ == 'numref':
@ -694,8 +675,9 @@ class StandardDomain(Domain):
return resolver(env, fromdocname, builder, typ, target, node, contnode)
def _resolve_ref_xref(self, env, fromdocname, builder, typ, target, node, contnode):
# type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA
def _resolve_ref_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", typ: str, target: str, node: pending_xref,
contnode: Element) -> Element:
if node['refexplicit']:
# reference to anonymous label; the reference uses
# the supplied link caption
@ -711,8 +693,9 @@ class StandardDomain(Domain):
return self.build_reference_node(fromdocname, builder,
docname, labelid, sectname, 'ref')
def _resolve_numref_xref(self, env, fromdocname, builder, typ, target, node, contnode):
# type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA
def _resolve_numref_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", typ: str, target: str,
node: pending_xref, contnode: Element) -> Element:
if target in self.labels:
docname, labelid, figname = self.labels.get(target, ('', '', ''))
else:
@ -772,8 +755,9 @@ class StandardDomain(Domain):
nodeclass=addnodes.number_reference,
title=title)
def _resolve_keyword_xref(self, env, fromdocname, builder, typ, target, node, contnode):
# type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA
def _resolve_keyword_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", typ: str, target: str,
node: pending_xref, contnode: Element) -> Element:
# keywords are oddballs: they are referenced by named labels
docname, labelid, _ = self.labels.get(target, ('', '', ''))
if not docname:
@ -781,8 +765,9 @@ class StandardDomain(Domain):
return make_refnode(builder, fromdocname, docname,
labelid, contnode)
def _resolve_doc_xref(self, env, fromdocname, builder, typ, target, node, contnode):
# type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA
def _resolve_doc_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", typ: str, target: str,
node: pending_xref, contnode: Element) -> Element:
# directly reference to document by source name; can be absolute or relative
refdoc = node.get('refdoc', fromdocname)
docname = docname_join(refdoc, node['reftarget'])
@ -797,8 +782,9 @@ class StandardDomain(Domain):
innernode = nodes.inline(caption, caption, classes=['doc'])
return make_refnode(builder, fromdocname, docname, None, innernode)
def _resolve_option_xref(self, env, fromdocname, builder, typ, target, node, contnode):
# type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA
def _resolve_option_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", typ: str, target: str,
node: pending_xref, contnode: Element) -> Element:
progname = node.get('std:program')
target = target.strip()
docname, labelid = self.progoptions.get((progname, target), ('', ''))
@ -818,8 +804,9 @@ class StandardDomain(Domain):
return make_refnode(builder, fromdocname, docname,
labelid, contnode)
def _resolve_citation_xref(self, env, fromdocname, builder, typ, target, node, contnode):
# type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA
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))
@ -841,8 +828,9 @@ class StandardDomain(Domain):
del node['ids'][:]
raise
def _resolve_obj_xref(self, env, fromdocname, builder, typ, target, node, contnode):
# type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA
def _resolve_obj_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", typ: str, target: str,
node: pending_xref, contnode: Element) -> Element:
objtypes = self.objtypes_for_role(typ) or []
for objtype in objtypes:
if (objtype, target) in self.objects:
@ -855,8 +843,9 @@ class StandardDomain(Domain):
return make_refnode(builder, fromdocname, docname,
labelid, contnode)
def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode):
# type: (BuildEnvironment, str, Builder, str, addnodes.pending_xref, nodes.Element) -> List[Tuple[str, nodes.Element]] # NOQA
def resolve_any_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", target: str, node: pending_xref,
contnode: Element) -> List[Tuple[str, Element]]:
results = [] # type: List[Tuple[str, nodes.Element]]
ltarget = target.lower() # :ref: lowercases its target automatically
for role in ('ref', 'option'): # do not try "keyword"
@ -877,8 +866,7 @@ class StandardDomain(Domain):
labelid, contnode)))
return results
def get_objects(self):
# type: () -> Iterator[Tuple[str, str, str, str, str, int]]
def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]:
# handle the special 'doc' reference here
for doc in self.env.all_docs:
yield (doc, clean_astext(self.env.titles[doc]), 'doc', doc, '', -1)
@ -899,34 +887,30 @@ class StandardDomain(Domain):
if name not in non_anon_labels:
yield (name, name, 'label', docname, labelid, -1)
def get_type_name(self, type, primary=False):
# type: (ObjType, bool) -> str
def get_type_name(self, type: ObjType, primary: bool = False) -> str:
# never prepend "Default"
return type.lname
def is_enumerable_node(self, node):
# type: (nodes.Node) -> bool
def is_enumerable_node(self, node: Node) -> bool:
return node.__class__ in self.enumerable_nodes
def get_numfig_title(self, node):
# type: (nodes.Node) -> str
def get_numfig_title(self, node: Node) -> str:
"""Get the title of enumerable nodes to refer them using its title"""
if self.is_enumerable_node(node):
_, title_getter = self.enumerable_nodes.get(node.__class__, (None, None))
elem = cast(nodes.Element, node)
_, title_getter = self.enumerable_nodes.get(elem.__class__, (None, None))
if title_getter:
return title_getter(node)
return title_getter(elem)
else:
for subnode in node:
if subnode.tagname in ('caption', 'title'):
for subnode in elem:
if isinstance(subnode, (nodes.caption, nodes.title)):
return clean_astext(subnode)
return None
def get_enumerable_node_type(self, node):
# type: (nodes.Node) -> str
def get_enumerable_node_type(self, node: Node) -> str:
"""Get type of enumerable nodes."""
def has_child(node, cls):
# type: (nodes.Element, Type) -> bool
def has_child(node: Element, cls: Type) -> bool:
return any(isinstance(child, cls) for child in node)
if isinstance(node, nodes.section):
@ -940,8 +924,7 @@ class StandardDomain(Domain):
figtype, _ = self.enumerable_nodes.get(node.__class__, (None, None))
return figtype
def get_figtype(self, node):
# type: (nodes.Node) -> str
def get_figtype(self, node: Node) -> str:
"""Get figure type of nodes.
.. deprecated:: 1.8
@ -951,8 +934,8 @@ class StandardDomain(Domain):
RemovedInSphinx30Warning, stacklevel=2)
return self.get_enumerable_node_type(node)
def get_fignumber(self, env, builder, figtype, docname, target_node):
# type: (BuildEnvironment, Builder, str, str, nodes.Element) -> Tuple[int, ...]
def get_fignumber(self, env: "BuildEnvironment", builder: "Builder",
figtype: str, docname: str, target_node: Element) -> Tuple[int, ...]:
if figtype == 'section':
if builder.name == 'latex':
return tuple()
@ -974,8 +957,7 @@ class StandardDomain(Domain):
# Maybe it is defined in orphaned document.
raise ValueError
def get_full_qualified_name(self, node):
# type: (nodes.Element) -> str
def get_full_qualified_name(self, node: Element) -> str:
if node.get('reftype') == 'option':
progname = node.get('std:program')
command = ws_re.split(node.get('reftarget'))
@ -989,24 +971,20 @@ class StandardDomain(Domain):
else:
return None
def note_citations(self, env, docname, document):
# type: (BuildEnvironment, str, nodes.document) -> None
def note_citations(self, env: "BuildEnvironment", docname: str, document: nodes.document) -> None: # NOQA
warnings.warn('StandardDomain.note_citations() is deprecated.',
RemovedInSphinx40Warning)
def note_citation_refs(self, env, docname, document):
# type: (BuildEnvironment, str, nodes.document) -> None
def note_citation_refs(self, env: "BuildEnvironment", docname: str, document: nodes.document) -> None: # NOQA
warnings.warn('StandardDomain.note_citation_refs() is deprecated.',
RemovedInSphinx40Warning)
def note_labels(self, env, docname, document):
# type: (BuildEnvironment, str, nodes.document) -> None
def note_labels(self, env: "BuildEnvironment", docname: str, document: nodes.document) -> None: # NOQA
warnings.warn('StandardDomain.note_labels() is deprecated.',
RemovedInSphinx40Warning)
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_domain(StandardDomain)
return {

View File

@ -15,14 +15,22 @@ 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 docutils import nodes
from docutils.nodes import Node
from sphinx import addnodes
from sphinx.config import Config
from sphinx.deprecation import (
RemovedInSphinx30Warning, RemovedInSphinx40Warning, deprecated_alias
)
from sphinx.domains import Domain
from sphinx.environment.adapters.toctree import TocTree
from sphinx.errors import SphinxError, BuildEnvironmentError, DocumentError, ExtensionError
from sphinx.events import EventManager
from sphinx.locale import __
from sphinx.project import Project
from sphinx.transforms import SphinxTransformer
from sphinx.util import DownloadFiles, FilenameUniqDict
from sphinx.util import logging
@ -32,14 +40,8 @@ from sphinx.util.nodes import is_translatable
if False:
# For type annotation
from typing import Any, Callable, Dict, IO, Iterator, List, Optional, Set, Tuple, Union # NOQA
from docutils import nodes # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.config import Config # NOQA
from sphinx.event import EventManager # NOQA
from sphinx.domains import Domain # NOQA
from sphinx.project import Project # NOQA
from sphinx.application import Sphinx
from sphinx.builders import Builder
logger = logging.getLogger(__name__)
@ -92,8 +94,7 @@ class BuildEnvironment:
# --------- ENVIRONMENT INITIALIZATION -------------------------------------
def __init__(self, app=None):
# type: (Sphinx) -> None
def __init__(self, app: "Sphinx" = None):
self.app = None # type: Sphinx
self.doctreedir = None # type: str
self.srcdir = None # type: str
@ -191,19 +192,16 @@ class BuildEnvironment:
if app:
self.setup(app)
def __getstate__(self):
# type: () -> Dict
def __getstate__(self) -> Dict:
"""Obtains serializable data for pickling."""
__dict__ = self.__dict__.copy()
__dict__.update(app=None, domains={}, events=None) # clear unpickable attributes
return __dict__
def __setstate__(self, state):
# type: (Dict) -> None
def __setstate__(self, state: Dict) -> None:
self.__dict__.update(state)
def setup(self, app):
# type: (Sphinx) -> None
def setup(self, app: "Sphinx") -> None:
"""Set up BuildEnvironment object."""
if self.version and self.version != app.registry.get_envversion(app):
raise BuildEnvironmentError(__('build environment version not current'))
@ -231,12 +229,13 @@ class BuildEnvironment:
# initialie settings
self._update_settings(app.config)
def _update_config(self, config):
# type: (Config) -> None
def _update_config(self, config: Config) -> None:
"""Update configurations by new one."""
self.config_status = CONFIG_OK
if self.config is None:
self.config_status = CONFIG_NEW
elif self.config.extensions != config.extensions:
self.config_status = CONFIG_EXTENSIONS_CHANGED
else:
# check if a config value was changed that affects how
# doctrees are read
@ -245,15 +244,9 @@ class BuildEnvironment:
self.config_status = CONFIG_CHANGED
break
# this value is not covered by the above loop because it is handled
# specially by the config class
if self.config.extensions != config.extensions:
self.config_status = CONFIG_EXTENSIONS_CHANGED
self.config = config
def _update_settings(self, config):
# type: (Config) -> None
def _update_settings(self, config: Config) -> None:
"""Update settings by new config."""
self.settings['input_encoding'] = config.source_encoding
self.settings['trim_footnote_reference_space'] = config.trim_footnote_reference_space
@ -262,8 +255,7 @@ class BuildEnvironment:
# Allow to disable by 3rd party extension (workaround)
self.settings.setdefault('smart_quotes', True)
def set_versioning_method(self, method, compare):
# type: (Union[str, Callable], bool) -> None
def set_versioning_method(self, method: Union[str, Callable], compare: bool) -> None:
"""This sets the doctree versioning method for this environment.
Versioning methods are a builder property; only builders with the same
@ -286,8 +278,7 @@ class BuildEnvironment:
self.versioning_condition = condition
self.versioning_compare = compare
def clear_doc(self, docname):
# type: (str) -> None
def clear_doc(self, docname: str) -> None:
"""Remove all traces of a source file in the inventory."""
if docname in self.all_docs:
self.all_docs.pop(docname, None)
@ -297,8 +288,8 @@ class BuildEnvironment:
for domain in self.domains.values():
domain.clear_doc(docname)
def merge_info_from(self, docnames, other, app):
# type: (List[str], BuildEnvironment, Sphinx) -> None
def merge_info_from(self, docnames: List[str], other: "BuildEnvironment",
app: "Sphinx") -> None:
"""Merge global information gathered about *docnames* while reading them
from the *other* environment.
@ -315,16 +306,14 @@ class BuildEnvironment:
domain.merge_domaindata(docnames, other.domaindata[domainname])
self.events.emit('env-merge-info', self, docnames, other)
def path2doc(self, filename):
# type: (str) -> Optional[str]
def path2doc(self, filename: str) -> str:
"""Return the docname for the filename if the file is document.
*filename* should be absolute or relative to the source directory.
"""
return self.project.path2doc(filename)
def doc2path(self, docname, base=True, suffix=None):
# type: (str, Union[bool, str], str) -> str
def doc2path(self, docname: str, base: Union[bool, str] = True, suffix: str = None) -> str:
"""Return the filename for the document name.
If *base* is True, return absolute path under self.srcdir.
@ -347,8 +336,7 @@ class BuildEnvironment:
pathname = path.join(base, pathname) # type: ignore
return pathname
def relfn2path(self, filename, docname=None):
# type: (str, str) -> Tuple[str, str]
def relfn2path(self, filename: str, docname: str = None) -> Tuple[str, str]:
"""Return paths to a file referenced from a document, relative to
documentation root and absolute.
@ -367,13 +355,11 @@ class BuildEnvironment:
return rel_fn, path.abspath(path.join(self.srcdir, rel_fn))
@property
def found_docs(self):
# type: () -> Set[str]
def found_docs(self) -> Set[str]:
"""contains all existing docnames."""
return self.project.docnames
def find_files(self, config, builder):
# type: (Config, Builder) -> None
def find_files(self, config: Config, builder: "Builder") -> None:
"""Find all source files in the source dir and put them in
self.found_docs.
"""
@ -401,8 +387,7 @@ class BuildEnvironment:
except OSError as exc:
raise DocumentError(__('Failed to scan documents in %s: %r') % (self.srcdir, exc))
def get_outdated_files(self, config_changed):
# type: (bool) -> Tuple[Set[str], Set[str], Set[str]]
def get_outdated_files(self, config_changed: bool) -> Tuple[Set[str], Set[str], Set[str]]:
"""Return (added, changed, removed) sets."""
# clear all files no longer present
removed = set(self.all_docs) - self.found_docs
@ -452,8 +437,7 @@ class BuildEnvironment:
return added, changed, removed
def check_dependents(self, app, already):
# type: (Sphinx, Set[str]) -> Iterator[str]
def check_dependents(self, app: "Sphinx", already: Set[str]) -> Generator[str, None, None]:
to_rewrite = [] # type: List[str]
for docnames in self.events.emit('env-get-updated', self):
to_rewrite.extend(docnames)
@ -463,8 +447,7 @@ class BuildEnvironment:
# --------- SINGLE FILE READING --------------------------------------------
def prepare_settings(self, docname):
# type: (str) -> None
def prepare_settings(self, docname: str) -> None:
"""Prepare to set up environment for reading."""
self.temp_data['docname'] = docname
# defaults to the global default, but can be re-set in a document
@ -475,13 +458,11 @@ class BuildEnvironment:
# utilities to use while reading a document
@property
def docname(self):
# type: () -> str
def docname(self) -> str:
"""Returns the docname of the document currently being parsed."""
return self.temp_data['docname']
def new_serialno(self, category=''):
# type: (str) -> int
def new_serialno(self, category: str = '') -> int:
"""Return a serial number, e.g. for index entry targets.
The number is guaranteed to be unique in the current document.
@ -491,8 +472,7 @@ class BuildEnvironment:
self.temp_data[key] = cur + 1
return cur
def note_dependency(self, filename):
# type: (str) -> None
def note_dependency(self, filename: str) -> None:
"""Add *filename* as a dependency of the current document.
This means that the document will be rebuilt if this file changes.
@ -501,8 +481,7 @@ class BuildEnvironment:
"""
self.dependencies[self.docname].add(filename)
def note_included(self, filename):
# type: (str) -> None
def note_included(self, filename: str) -> None:
"""Add *filename* as a included from other document.
This means the document is not orphaned.
@ -511,15 +490,13 @@ class BuildEnvironment:
"""
self.included[self.docname].add(self.path2doc(filename))
def note_reread(self):
# type: () -> None
def note_reread(self) -> None:
"""Add the current document to the list of documents that will
automatically be re-read at the next build.
"""
self.reread_always.add(self.docname)
def get_domain(self, domainname):
# type: (str) -> Domain
def get_domain(self, domainname: str) -> Domain:
"""Return the domain instance with the specified name.
Raises an ExtensionError if the domain is not registered.
@ -531,8 +508,7 @@ class BuildEnvironment:
# --------- RESOLVING REFERENCES AND TOCTREES ------------------------------
def get_doctree(self, docname):
# type: (str) -> nodes.document
def get_doctree(self, docname: str) -> nodes.document:
"""Read the doctree for a file from the pickle and return it."""
filename = path.join(self.doctreedir, docname + '.doctree')
with open(filename, 'rb') as f:
@ -541,9 +517,9 @@ class BuildEnvironment:
doctree.reporter = LoggingReporter(self.doc2path(docname))
return doctree
def get_and_resolve_doctree(self, docname, builder, doctree=None,
prune_toctrees=True, includehidden=False):
# type: (str, Builder, nodes.document, bool, bool) -> nodes.document
def get_and_resolve_doctree(self, docname: str, builder: "Builder",
doctree: nodes.document = None, prune_toctrees: bool = True,
includehidden: bool = False) -> nodes.document:
"""Read the doctree from the pickle, resolve cross-references and
toctrees and return it.
"""
@ -565,9 +541,9 @@ class BuildEnvironment:
return doctree
def resolve_toctree(self, docname, builder, toctree, prune=True, maxdepth=0,
titles_only=False, collapse=False, includehidden=False):
# type: (str, Builder, addnodes.toctree, bool, int, bool, bool, bool) -> nodes.Node
def resolve_toctree(self, docname: str, builder: "Builder", toctree: addnodes.toctree,
prune: bool = True, maxdepth: int = 0, titles_only: bool = False,
collapse: bool = False, includehidden: bool = False) -> Node:
"""Resolve a *toctree* node into individual bullet lists with titles
as items, returning None (if no containing titles are found) or
a new node.
@ -583,12 +559,11 @@ class BuildEnvironment:
maxdepth, titles_only, collapse,
includehidden)
def resolve_references(self, doctree, fromdocname, builder):
# type: (nodes.document, str, Builder) -> None
def resolve_references(self, doctree: nodes.document, fromdocname: str,
builder: "Builder") -> None:
self.apply_post_transforms(doctree, fromdocname)
def apply_post_transforms(self, doctree, docname):
# type: (nodes.document, str) -> None
def apply_post_transforms(self, doctree: nodes.document, docname: str) -> None:
"""Apply all post-transforms."""
try:
# set env.docname during applying post-transforms
@ -605,12 +580,10 @@ class BuildEnvironment:
# allow custom references to be resolved
self.events.emit('doctree-resolved', doctree, docname)
def collect_relations(self):
# type: () -> Dict[str, List[str]]
def collect_relations(self) -> Dict[str, List[str]]:
traversed = set()
def traverse_toctree(parent, docname):
# type: (str, str) -> Iterator[Tuple[str, str]]
def traverse_toctree(parent: str, docname: str) -> Iterator[Tuple[str, str]]:
if parent == docname:
logger.warning(__('self referenced toctree found. Ignored.'), location=docname)
return
@ -639,8 +612,7 @@ class BuildEnvironment:
return relations
def check_consistency(self):
# type: () -> None
def check_consistency(self) -> None:
"""Do consistency checks."""
included = set().union(*self.included.values()) # type: ignore
for docname in sorted(self.all_docs):
@ -663,48 +635,41 @@ class BuildEnvironment:
# --------- METHODS FOR COMPATIBILITY --------------------------------------
def update(self, config, srcdir, doctreedir):
# type: (Config, str, str) -> List[str]
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, app):
# type: (List[str], Sphinx) -> None
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, app, nproc):
# type: (List[str], Sphinx, int) -> None
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, app=None):
# type: (str, Sphinx) -> None
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, doctree):
# type: (str, nodes.document) -> None
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):
# type: () -> List[str]
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, app=None):
# type: (IO, Sphinx) -> BuildEnvironment
def load(f: IO, app: "Sphinx" = None) -> "BuildEnvironment":
warnings.warn('BuildEnvironment.load() is deprecated. '
'Please use pickle.load() instead.',
RemovedInSphinx30Warning, stacklevel=2)
@ -720,8 +685,7 @@ class BuildEnvironment:
return env
@classmethod
def loads(cls, string, app=None):
# type: (bytes, Sphinx) -> BuildEnvironment
def loads(cls, string: bytes, app: "Sphinx" = None) -> "BuildEnvironment":
warnings.warn('BuildEnvironment.loads() is deprecated. '
'Please use pickle.loads() instead.',
RemovedInSphinx30Warning, stacklevel=2)
@ -729,8 +693,7 @@ class BuildEnvironment:
return cls.load(io, app)
@classmethod
def frompickle(cls, filename, app):
# type: (str, Sphinx) -> BuildEnvironment
def frompickle(cls, filename: str, app: "Sphinx") -> "BuildEnvironment":
warnings.warn('BuildEnvironment.frompickle() is deprecated. '
'Please use pickle.load() instead.',
RemovedInSphinx30Warning, stacklevel=2)
@ -738,16 +701,14 @@ class BuildEnvironment:
return cls.load(f, app)
@staticmethod
def dump(env, f):
# type: (BuildEnvironment, IO) -> None
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):
# type: (BuildEnvironment) -> bytes
def dumps(cls, env: "BuildEnvironment") -> bytes:
warnings.warn('BuildEnvironment.dumps() is deprecated. '
'Please use pickle.dumps() instead.',
RemovedInSphinx30Warning, stacklevel=2)
@ -755,8 +716,7 @@ class BuildEnvironment:
cls.dump(env, io)
return io.getvalue()
def topickle(self, filename):
# type: (str) -> None
def topickle(self, filename: str) -> None:
warnings.warn('env.topickle() is deprecated. '
'Please use pickle.dump() instead.',
RemovedInSphinx30Warning, stacklevel=2)
@ -764,15 +724,14 @@ class BuildEnvironment:
self.dump(self, f)
@property
def versionchanges(self):
# type: () -> Dict[str, List[Tuple[str, str, int, str, str, str]]]
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, version, node, lineno):
# type: (str, str, addnodes.versionmodified, int) -> None
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)

View File

@ -8,18 +8,14 @@
:license: BSD, see LICENSE for details.
"""
if False:
# For type annotation
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.environment import BuildEnvironment
class ImageAdapter:
def __init__(self, env):
# type: (BuildEnvironment) -> None
def __init__(self, env: BuildEnvironment) -> None:
self.env = env
def get_original_image_uri(self, name):
# type: (str) -> str
def get_original_image_uri(self, name: str) -> str:
"""Get the original image URI."""
while name in self.env.original_image_uri:
name = self.env.original_image_uri[name]

View File

@ -11,33 +11,30 @@ import bisect
import re
import unicodedata
from itertools import groupby
from typing import Any, Dict, Pattern, List, Tuple
from sphinx.builders import Builder
from sphinx.environment import BuildEnvironment
from sphinx.errors import NoUri
from sphinx.locale import _, __
from sphinx.util import split_into, logging
if False:
# For type annotation
from typing import Any, Dict, Pattern, List, Tuple # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.environment import BuildEnvironment # NOQA
logger = logging.getLogger(__name__)
class IndexEntries:
def __init__(self, env):
# type: (BuildEnvironment) -> None
def __init__(self, env: BuildEnvironment) -> None:
self.env = env
def create_index(self, builder, group_entries=True,
_fixre=re.compile(r'(.*) ([(][^()]*[)])')):
# type: (Builder, bool, Pattern) -> List[Tuple[str, List[Tuple[str, Any]]]]
def create_index(self, builder: Builder, group_entries: bool = True,
_fixre: Pattern = re.compile(r'(.*) ([(][^()]*[)])')
) -> List[Tuple[str, List[Tuple[str, Any]]]]:
"""Create the real index from the collected index entries."""
new = {} # type: Dict[str, List]
def add_entry(word, subword, main, link=True, dic=new, key=None):
# type: (str, str, str, bool, Dict, str) -> None
def add_entry(word: str, subword: str, main: str, link: bool = True,
dic: Dict = new, key: str = None) -> None:
# Force the word to be unicode if it's a ASCII bytestring.
# This will solve problems with unicode normalization later.
# For instance the RFC role will add bytestrings at the moment
@ -91,8 +88,7 @@ class IndexEntries:
# sort the index entries; put all symbols at the front, even those
# following the letters in ASCII, this is where the chr(127) comes from
def keyfunc(entry):
# type: (Tuple[str, List]) -> Tuple[str, str]
def keyfunc(entry: Tuple[str, List]) -> Tuple[str, str]:
key, (void, void, category_key) = entry
if category_key:
# using specified category key to sort
@ -137,12 +133,21 @@ class IndexEntries:
oldsubitems = subitems
i += 1
# sort the sub-index entries
def keyfunc2(entry: Tuple[str, List]) -> str:
key = unicodedata.normalize('NFD', entry[0].lower())
if key.startswith('\N{RIGHT-TO-LEFT MARK}'):
key = key[1:]
if key[0:1].isalpha() or key.startswith('_'):
key = chr(127) + key
return key
# group the entries by letter
def keyfunc2(item):
# type: (Tuple[str, List]) -> str
def keyfunc3(item: Tuple[str, List]) -> str:
# hack: mutating the subitems dicts to a list in the keyfunc
k, v = item
v[1] = sorted((si, se) for (si, (se, void, void)) in v[1].items())
v[1] = sorted(((si, se) for (si, (se, void, void)) in v[1].items()),
key=keyfunc2)
if v[2] is None:
# now calculate the key
if k.startswith('\N{RIGHT-TO-LEFT MARK}'):
@ -156,4 +161,4 @@ class IndexEntries:
else:
return v[2]
return [(key_, list(group))
for (key_, group) in groupby(newlist, keyfunc2)]
for (key_, group) in groupby(newlist, keyfunc3)]

View File

@ -8,9 +8,11 @@
:license: BSD, see LICENSE for details.
"""
from typing import Iterable, cast
from typing import cast
from typing import Iterable, List
from docutils import nodes
from docutils.nodes import Element, Node
from sphinx import addnodes
from sphinx.locale import __
@ -20,20 +22,18 @@ from sphinx.util.nodes import clean_astext, process_only_nodes
if False:
# For type annotation
from typing import Any, Dict, List # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.builders import Builder
from sphinx.environment import BuildEnvironment
logger = logging.getLogger(__name__)
class TocTree:
def __init__(self, env):
# type: (BuildEnvironment) -> None
def __init__(self, env: "BuildEnvironment") -> None:
self.env = env
def note(self, docname, toctreenode):
# type: (str, addnodes.toctree) -> None
def note(self, docname: str, toctreenode: addnodes.toctree) -> None:
"""Note a TOC tree directive in a document and gather information about
file relations from it.
"""
@ -48,9 +48,9 @@ class TocTree:
self.env.files_to_rebuild.setdefault(includefile, set()).add(docname)
self.env.toctree_includes.setdefault(docname, []).extend(includefiles)
def resolve(self, docname, builder, toctree, prune=True, maxdepth=0,
titles_only=False, collapse=False, includehidden=False):
# type: (str, Builder, addnodes.toctree, bool, int, bool, bool, bool) -> nodes.Element
def resolve(self, docname: str, builder: "Builder", toctree: addnodes.toctree,
prune: bool = True, maxdepth: int = 0, titles_only: bool = False,
collapse: bool = False, includehidden: bool = False) -> Element:
"""Resolve a *toctree* node into individual bullet lists with titles
as items, returning None (if no containing titles are found) or
a new node.
@ -86,8 +86,7 @@ class TocTree:
toctree_ancestors = self.get_toctree_ancestors(docname)
excluded = Matcher(self.env.config.exclude_patterns)
def _toctree_add_classes(node, depth):
# type: (nodes.Element, int) -> None
def _toctree_add_classes(node: Element, depth: int) -> None:
"""Add 'toctree-l%d' and 'current' classes to the toctree."""
for subnode in node.children:
if isinstance(subnode, (addnodes.compact_paragraph,
@ -105,7 +104,7 @@ class TocTree:
if not subnode['anchorname']:
# give the whole branch a 'current' class
# (useful for styling it differently)
branchnode = subnode # type: nodes.Element
branchnode = subnode # type: Element
while branchnode:
branchnode['classes'].append('current')
branchnode = branchnode.parent
@ -117,11 +116,12 @@ class TocTree:
subnode['iscurrent'] = True
subnode = subnode.parent
def _entries_from_toctree(toctreenode, parents, separate=False, subtree=False):
# type: (addnodes.toctree, List[str], bool, bool) -> List[nodes.Element]
def _entries_from_toctree(toctreenode: addnodes.toctree, parents: List[str],
separate: bool = False, subtree: bool = False
) -> List[Element]:
"""Return TOC entries for a toctree node."""
refs = [(e[0], e[1]) for e in toctreenode['entries']]
entries = [] # type: List[nodes.Element]
entries = [] # type: List[Element]
for (title, ref) in refs:
try:
refdoc = None
@ -265,8 +265,7 @@ class TocTree:
docname, refnode['refuri']) + refnode['anchorname']
return newnode
def get_toctree_ancestors(self, docname):
# type: (str) -> List[str]
def get_toctree_ancestors(self, docname: str) -> List[str]:
parent = {}
for p, children in self.env.toctree_includes.items():
for child in children:
@ -278,8 +277,8 @@ class TocTree:
d = parent[d]
return ancestors
def _toctree_prune(self, node, depth, maxdepth, collapse=False):
# type: (nodes.Element, int, int, bool) -> None
def _toctree_prune(self, node: Element, depth: int, maxdepth: int, collapse: bool = False
) -> None:
"""Utility: Cut a TOC at a specified depth."""
for subnode in node.children[:]:
if isinstance(subnode, (addnodes.compact_paragraph,
@ -300,8 +299,7 @@ class TocTree:
# recurse on visible children
self._toctree_prune(subnode, depth + 1, maxdepth, collapse)
def get_toc_for(self, docname, builder):
# type: (str, Builder) -> nodes.Node
def get_toc_for(self, docname: str, builder: "Builder") -> Node:
"""Return a TOC nodetree -- for use on the same page only!"""
tocdepth = self.env.metadata[docname].get('tocdepth', 0)
try:
@ -316,11 +314,11 @@ class TocTree:
node['refuri'] = node['anchorname'] or '#'
return toc
def get_toctree_for(self, docname, builder, collapse, **kwds):
# type: (str, Builder, bool, Any) -> nodes.Element
def get_toctree_for(self, docname: str, builder: "Builder", collapse: bool, **kwds
) -> Element:
"""Return the global TOC nodetree."""
doctree = self.env.get_doctree(self.env.config.master_doc)
toctrees = [] # type: List[nodes.Element]
toctrees = [] # type: List[Element]
if 'includehidden' not in kwds:
kwds['includehidden'] = True
if 'maxdepth' not in kwds:

View File

@ -8,12 +8,12 @@
:license: BSD, see LICENSE for details.
"""
if False:
# For type annotation
from typing import Dict, List, Set # NOQA
from docutils import nodes # NOQA
from sphinx.sphinx import Sphinx # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from typing import Dict, List, Set
from docutils import nodes
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
class EnvironmentCollector:
@ -27,8 +27,7 @@ class EnvironmentCollector:
listener_ids = None # type: Dict[str, int]
def enable(self, app):
# type: (Sphinx) -> None
def enable(self, app: Sphinx) -> None:
assert self.listener_ids is None
self.listener_ids = {
'doctree-read': app.connect('doctree-read', self.process_doc),
@ -38,43 +37,39 @@ class EnvironmentCollector:
'env-get-outdated': app.connect('env-get-outdated', self.get_outdated_docs),
}
def disable(self, app):
# type: (Sphinx) -> None
def disable(self, app: Sphinx) -> None:
assert self.listener_ids is not None
for listener_id in self.listener_ids.values():
app.disconnect(listener_id)
self.listener_ids = None
def clear_doc(self, app, env, docname):
# type: (Sphinx, BuildEnvironment, str) -> None
def clear_doc(self, app: Sphinx, env: BuildEnvironment, docname: str) -> None:
"""Remove specified data of a document.
This method is called on the removal of the document."""
raise NotImplementedError
def merge_other(self, app, env, docnames, other):
# type: (Sphinx, BuildEnvironment, Set[str], BuildEnvironment) -> None
def merge_other(self, app: Sphinx, env: BuildEnvironment,
docnames: Set[str], other: BuildEnvironment) -> None:
"""Merge in specified data regarding docnames from a different `BuildEnvironment`
object which coming from a subprocess in parallel builds."""
raise NotImplementedError
def process_doc(self, app, doctree):
# type: (Sphinx, nodes.document) -> None
def process_doc(self, app: Sphinx, doctree: nodes.document) -> None:
"""Process a document and gather specific data from it.
This method is called after the document is read."""
raise NotImplementedError
def get_updated_docs(self, app, env):
# type: (Sphinx, BuildEnvironment) -> List[str]
def get_updated_docs(self, app: Sphinx, env: BuildEnvironment) -> List[str]:
"""Return a list of docnames to re-read.
This methods is called after reading the whole of documents (experimental).
"""
return []
def get_outdated_docs(self, app, env, added, changed, removed):
# type: (Sphinx, BuildEnvironment, str, Set[str], Set[str], Set[str]) -> List[str]
def get_outdated_docs(self, app: Sphinx, env: BuildEnvironment,
added: Set[str], changed: Set[str], removed: Set[str]) -> List[str]:
"""Return a list of docnames to re-read.
This methods is called before reading the documents.

View File

@ -11,22 +11,21 @@
import os
from glob import glob
from os import path
from typing import Any, Dict, List, Set
from docutils import nodes
from docutils.nodes import Node
from docutils.utils import relative_path
from sphinx import addnodes
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
from sphinx.environment.collectors import EnvironmentCollector
from sphinx.locale import __
from sphinx.util import logging
from sphinx.util.i18n import get_image_filename_for_language, search_image_for_language
from sphinx.util.images import guess_mimetype
if False:
# For type annotation
from typing import Dict, List, Set # NOQA
from sphinx.sphinx import Sphinx # NOQA
from sphinx.environment import BuildEnvironment # NOQA
logger = logging.getLogger(__name__)
@ -34,16 +33,14 @@ logger = logging.getLogger(__name__)
class ImageCollector(EnvironmentCollector):
"""Image files collector for sphinx.environment."""
def clear_doc(self, app, env, docname):
# type: (Sphinx, BuildEnvironment, str) -> None
def clear_doc(self, app: Sphinx, env: BuildEnvironment, docname: str) -> None:
env.images.purge_doc(docname)
def merge_other(self, app, env, docnames, other):
# type: (Sphinx, BuildEnvironment, Set[str], BuildEnvironment) -> None
def merge_other(self, app: Sphinx, env: BuildEnvironment,
docnames: Set[str], other: BuildEnvironment) -> None:
env.images.merge_other(docnames, other.images)
def process_doc(self, app, doctree):
# type: (Sphinx, nodes.document) -> None
def process_doc(self, app: Sphinx, doctree: nodes.document) -> None:
"""Process and rewrite image URIs."""
docname = app.env.docname
@ -92,8 +89,8 @@ class ImageCollector(EnvironmentCollector):
continue
app.env.images.add_file(docname, imgpath)
def collect_candidates(self, env, imgpath, candidates, node):
# type: (BuildEnvironment, str, Dict[str, str], nodes.Node) -> None
def collect_candidates(self, env: BuildEnvironment, imgpath: str,
candidates: Dict[str, str], node: Node) -> None:
globbed = {} # type: Dict[str, List[str]]
for filename in glob(imgpath):
new_imgpath = relative_path(path.join(env.srcdir, 'dummy'),
@ -115,16 +112,14 @@ class ImageCollector(EnvironmentCollector):
class DownloadFileCollector(EnvironmentCollector):
"""Download files collector for sphinx.environment."""
def clear_doc(self, app, env, docname):
# type: (Sphinx, BuildEnvironment, str) -> None
def clear_doc(self, app: Sphinx, env: BuildEnvironment, docname: str) -> None:
env.dlfiles.purge_doc(docname)
def merge_other(self, app, env, docnames, other):
# type: (Sphinx, BuildEnvironment, Set[str], BuildEnvironment) -> None
def merge_other(self, app: Sphinx, env: BuildEnvironment,
docnames: Set[str], other: BuildEnvironment) -> None:
env.dlfiles.merge_other(docnames, other.dlfiles)
def process_doc(self, app, doctree):
# type: (Sphinx, nodes.document) -> None
def process_doc(self, app: Sphinx, doctree: nodes.document) -> None:
"""Process downloadable file paths. """
for node in doctree.traverse(addnodes.download_reference):
targetname = node['reftarget']
@ -140,8 +135,7 @@ class DownloadFileCollector(EnvironmentCollector):
node['filename'] = app.env.dlfiles.add_file(app.env.docname, rel_filename)
def setup(app):
# type: (Sphinx) -> Dict
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_env_collector(ImageCollector)
app.add_env_collector(DownloadFileCollector)

View File

@ -10,35 +10,30 @@
import os
from os import path
from typing import Any, Dict, Set
from docutils import nodes
from docutils.utils import relative_path
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
from sphinx.environment.collectors import EnvironmentCollector
from sphinx.util.osutil import fs_encoding
if False:
# For type annotation
from typing import Dict, Set # NOQA
from docutils import nodes # NOQA
from sphinx.sphinx import Sphinx # NOQA
from sphinx.environment import BuildEnvironment # NOQA
class DependenciesCollector(EnvironmentCollector):
"""dependencies collector for sphinx.environment."""
def clear_doc(self, app, env, docname):
# type: (Sphinx, BuildEnvironment, str) -> None
def clear_doc(self, app: Sphinx, env: BuildEnvironment, docname: str) -> None:
env.dependencies.pop(docname, None)
def merge_other(self, app, env, docnames, other):
# type: (Sphinx, BuildEnvironment, Set[str], BuildEnvironment) -> None
def merge_other(self, app: Sphinx, env: BuildEnvironment,
docnames: Set[str], other: BuildEnvironment) -> None:
for docname in docnames:
if docname in other.dependencies:
env.dependencies[docname] = other.dependencies[docname]
def process_doc(self, app, doctree):
# type: (Sphinx, nodes.document) -> None
def process_doc(self, app: Sphinx, doctree: nodes.document) -> None:
"""Process docutils-generated dependency info."""
cwd = os.getcwd()
frompath = path.join(path.normpath(app.srcdir), 'dummy')
@ -55,8 +50,7 @@ class DependenciesCollector(EnvironmentCollector):
app.env.dependencies[app.env.docname].add(relpath)
def setup(app):
# type: (Sphinx) -> Dict
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_env_collector(DependenciesCollector)
return {

View File

@ -8,34 +8,31 @@
:license: BSD, see LICENSE for details.
"""
from typing import Any, Dict, Set
from docutils import nodes
from sphinx import addnodes
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
from sphinx.environment.collectors import EnvironmentCollector
from sphinx.util import split_index_msg, logging
if False:
# For type annotation
from typing import Dict, Set # NOQA
from docutils import nodes # NOQA
from sphinx.applicatin import Sphinx # NOQA
from sphinx.environment import BuildEnvironment # NOQA
logger = logging.getLogger(__name__)
class IndexEntriesCollector(EnvironmentCollector):
name = 'indices'
def clear_doc(self, app, env, docname):
# type: (Sphinx, BuildEnvironment, str) -> None
def clear_doc(self, app: Sphinx, env: BuildEnvironment, docname: str) -> None:
env.indexentries.pop(docname, None)
def merge_other(self, app, env, docnames, other):
# type: (Sphinx, BuildEnvironment, Set[str], BuildEnvironment) -> None
def merge_other(self, app: Sphinx, env: BuildEnvironment,
docnames: Set[str], other: BuildEnvironment) -> None:
for docname in docnames:
env.indexentries[docname] = other.indexentries[docname]
def process_doc(self, app, doctree):
# type: (Sphinx, nodes.document) -> None
def process_doc(self, app: Sphinx, doctree: nodes.document) -> None:
docname = app.env.docname
entries = app.env.indexentries[docname] = []
for node in doctree.traverse(addnodes.index):
@ -50,8 +47,7 @@ class IndexEntriesCollector(EnvironmentCollector):
entries.append(entry)
def setup(app):
# type: (Sphinx) -> Dict
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_env_collector(IndexEntriesCollector)
return {

View File

@ -8,33 +8,28 @@
:license: BSD, see LICENSE for details.
"""
from typing import List, cast
from typing import Any, Dict, List, Set
from typing import cast
from docutils import nodes
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
from sphinx.environment.collectors import EnvironmentCollector
if False:
# For type annotation
from typing import Dict, Set # NOQA
from sphinx.sphinx import Sphinx # NOQA
from sphinx.environment import BuildEnvironment # NOQA
class MetadataCollector(EnvironmentCollector):
"""metadata collector for sphinx.environment."""
def clear_doc(self, app, env, docname):
# type: (Sphinx, BuildEnvironment, str) -> None
def clear_doc(self, app: Sphinx, env: BuildEnvironment, docname: str) -> None:
env.metadata.pop(docname, None)
def merge_other(self, app, env, docnames, other):
# type: (Sphinx, BuildEnvironment, Set[str], BuildEnvironment) -> None
def merge_other(self, app: Sphinx, env: BuildEnvironment,
docnames: Set[str], other: BuildEnvironment) -> None:
for docname in docnames:
env.metadata[docname] = other.metadata[docname]
def process_doc(self, app, doctree):
# type: (Sphinx, nodes.document) -> None
def process_doc(self, app: Sphinx, doctree: nodes.document) -> None:
"""Process the docinfo part of the doctree as metadata.
Keep processing minimal -- just return what docutils says.
@ -67,8 +62,7 @@ class MetadataCollector(EnvironmentCollector):
doctree.pop(0)
def setup(app):
# type: (Sphinx) -> Dict
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_env_collector(MetadataCollector)
return {

View File

@ -8,34 +8,30 @@
:license: BSD, see LICENSE for details.
"""
from typing import Any, Dict, Set
from docutils import nodes
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
from sphinx.environment.collectors import EnvironmentCollector
from sphinx.transforms import SphinxContentsFilter
if False:
# For type annotation
from typing import Dict, Set # NOQA
from sphinx.sphinx import Sphinx # NOQA
from sphinx.environment import BuildEnvironment # NOQA
class TitleCollector(EnvironmentCollector):
"""title collector for sphinx.environment."""
def clear_doc(self, app, env, docname):
# type: (Sphinx, BuildEnvironment, str) -> None
def clear_doc(self, app: Sphinx, env: BuildEnvironment, docname: str) -> None:
env.titles.pop(docname, None)
env.longtitles.pop(docname, None)
def merge_other(self, app, env, docnames, other):
# type: (Sphinx, BuildEnvironment, Set[str], BuildEnvironment) -> None
def merge_other(self, app: Sphinx, env: BuildEnvironment,
docnames: Set[str], other: BuildEnvironment) -> None:
for docname in docnames:
env.titles[docname] = other.titles[docname]
env.longtitles[docname] = other.longtitles[docname]
def process_doc(self, app, doctree):
# type: (Sphinx, nodes.document) -> None
def process_doc(self, app: Sphinx, doctree: nodes.document) -> None:
"""Add a title node to the document (just copy the first section title),
and store that title in the environment.
"""
@ -59,8 +55,7 @@ class TitleCollector(EnvironmentCollector):
app.env.longtitles[app.env.docname] = longtitlenode
def setup(app):
# type: (Sphinx) -> Dict
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_env_collector(TitleCollector)
return {

View File

@ -8,31 +8,29 @@
:license: BSD, see LICENSE for details.
"""
from typing import Any, Dict, List, Set, Tuple, Type, TypeVar
from typing import cast
from docutils import nodes
from docutils.nodes import Element, Node
from sphinx import addnodes
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
from sphinx.environment.adapters.toctree import TocTree
from sphinx.environment.collectors import EnvironmentCollector
from sphinx.locale import __
from sphinx.transforms import SphinxContentsFilter
from sphinx.util import url_re, logging
if False:
# For type annotation
from typing import Dict, List, Set, Tuple, Type, TypeVar # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.environment import BuildEnvironment # NOQA
N = TypeVar('N')
N = TypeVar('N')
logger = logging.getLogger(__name__)
class TocTreeCollector(EnvironmentCollector):
def clear_doc(self, app, env, docname):
# type: (Sphinx, BuildEnvironment, str) -> None
def clear_doc(self, app: Sphinx, env: BuildEnvironment, docname: str) -> None:
env.tocs.pop(docname, None)
env.toc_secnumbers.pop(docname, None)
env.toc_fignumbers.pop(docname, None)
@ -46,8 +44,8 @@ class TocTreeCollector(EnvironmentCollector):
if not fnset:
del env.files_to_rebuild[subfn]
def merge_other(self, app, env, docnames, other):
# type: (Sphinx, BuildEnvironment, Set[str], BuildEnvironment) -> None
def merge_other(self, app: Sphinx, env: BuildEnvironment, docnames: Set[str],
other: BuildEnvironment) -> None:
for docname in docnames:
env.tocs[docname] = other.tocs[docname]
env.toc_num_entries[docname] = other.toc_num_entries[docname]
@ -61,14 +59,12 @@ class TocTreeCollector(EnvironmentCollector):
for subfn, fnset in other.files_to_rebuild.items():
env.files_to_rebuild.setdefault(subfn, set()).update(fnset & set(docnames))
def process_doc(self, app, doctree):
# type: (Sphinx, nodes.document) -> None
def process_doc(self, app: Sphinx, doctree: nodes.document) -> None:
"""Build a TOC from the doctree and store it in the inventory."""
docname = app.env.docname
numentries = [0] # nonlocal again...
def traverse_in_section(node, cls):
# type: (nodes.Element, Type[N]) -> List[N]
def traverse_in_section(node: Element, cls: Type[N]) -> List[N]:
"""Like traverse(), but stay within the same section."""
result = [] # type: List[N]
if isinstance(node, cls):
@ -80,9 +76,8 @@ class TocTreeCollector(EnvironmentCollector):
result.extend(traverse_in_section(child, cls))
return result
def build_toc(node, depth=1):
# type: (nodes.Element, int) -> nodes.bullet_list
entries = [] # type: List[nodes.Element]
def build_toc(node: Element, depth: int = 1) -> nodes.bullet_list:
entries = [] # type: List[Element]
for sectionnode in node:
# find all toctree nodes in this section and add them
# to the toc (just copying the toctree node which is then
@ -107,7 +102,7 @@ class TocTreeCollector(EnvironmentCollector):
'', '', internal=True, refuri=docname,
anchorname=anchorname, *nodetext)
para = addnodes.compact_paragraph('', '', reference)
item = nodes.list_item('', para) # type: nodes.Element
item = nodes.list_item('', para) # type: Element
sub_item = build_toc(sectionnode, depth + 1)
if sub_item:
item += sub_item
@ -135,12 +130,10 @@ class TocTreeCollector(EnvironmentCollector):
app.env.tocs[docname] = nodes.bullet_list('')
app.env.toc_num_entries[docname] = numentries[0]
def get_updated_docs(self, app, env):
# type: (Sphinx, BuildEnvironment) -> List[str]
def get_updated_docs(self, app: Sphinx, env: BuildEnvironment) -> List[str]:
return self.assign_section_numbers(env) + self.assign_figure_numbers(env)
def assign_section_numbers(self, env):
# type: (BuildEnvironment) -> List[str]
def assign_section_numbers(self, env: BuildEnvironment) -> List[str]:
"""Assign a section number to each heading under a numbered toctree."""
# a list of all docnames whose section numbers changed
rewrite_needed = []
@ -149,8 +142,7 @@ class TocTreeCollector(EnvironmentCollector):
old_secnumbers = env.toc_secnumbers
env.toc_secnumbers = {}
def _walk_toc(node, secnums, depth, titlenode=None):
# type: (nodes.Element, Dict, int, nodes.title) -> None
def _walk_toc(node: Element, secnums: Dict, depth: int, titlenode: nodes.title = None) -> None: # NOQA
# titlenode is the title of the document, it will get assigned a
# secnumber too, so that it shows up in next/prev/parent rellinks
for subnode in node.children:
@ -184,8 +176,7 @@ class TocTreeCollector(EnvironmentCollector):
elif isinstance(subnode, addnodes.toctree):
_walk_toctree(subnode, depth)
def _walk_toctree(toctreenode, depth):
# type: (addnodes.toctree, int) -> None
def _walk_toctree(toctreenode: addnodes.toctree, depth: int) -> None:
if depth == 0:
return
for (title, ref) in toctreenode['entries']:
@ -216,8 +207,7 @@ class TocTreeCollector(EnvironmentCollector):
return rewrite_needed
def assign_figure_numbers(self, env):
# type: (BuildEnvironment) -> List[str]
def assign_figure_numbers(self, env: BuildEnvironment) -> List[str]:
"""Assign a figure number to each figure under a numbered toctree."""
rewrite_needed = []
@ -227,8 +217,7 @@ class TocTreeCollector(EnvironmentCollector):
env.toc_fignumbers = {}
fignum_counter = {} # type: Dict[str, Dict[Tuple[int, ...], int]]
def get_figtype(node):
# type: (nodes.Node) -> str
def get_figtype(node: Node) -> str:
for domain in env.domains.values():
figtype = domain.get_enumerable_node_type(node)
if figtype:
@ -236,8 +225,7 @@ class TocTreeCollector(EnvironmentCollector):
return None
def get_section_number(docname, section):
# type: (str, nodes.section) -> Tuple[int, ...]
def get_section_number(docname: str, section: nodes.section) -> Tuple[int, ...]:
anchorname = '#' + section['ids'][0]
secnumbers = env.toc_secnumbers.get(docname, {})
if anchorname in secnumbers:
@ -247,24 +235,22 @@ class TocTreeCollector(EnvironmentCollector):
return secnum or tuple()
def get_next_fignumber(figtype, secnum):
# type: (str, Tuple[int, ...]) -> Tuple[int, ...]
def get_next_fignumber(figtype: str, secnum: Tuple[int, ...]) -> Tuple[int, ...]:
counter = fignum_counter.setdefault(figtype, {})
secnum = secnum[:env.config.numfig_secnum_depth]
counter[secnum] = counter.get(secnum, 0) + 1
return secnum + (counter[secnum],)
def register_fignumber(docname, secnum, figtype, fignode):
# type: (str, Tuple[int, ...], str, nodes.Element) -> None
def register_fignumber(docname: str, secnum: Tuple[int, ...],
figtype: str, fignode: Element) -> None:
env.toc_fignumbers.setdefault(docname, {})
fignumbers = env.toc_fignumbers[docname].setdefault(figtype, {})
figure_id = fignode['ids'][0]
fignumbers[figure_id] = get_next_fignumber(figtype, secnum)
def _walk_doctree(docname, doctree, secnum):
# type: (str, nodes.Element, Tuple[int, ...]) -> None
def _walk_doctree(docname: str, doctree: Element, secnum: Tuple[int, ...]) -> None:
for subnode in doctree.children:
if isinstance(subnode, nodes.section):
next_secnum = get_section_number(docname, subnode)
@ -286,8 +272,7 @@ class TocTreeCollector(EnvironmentCollector):
_walk_doctree(docname, subnode, secnum)
def _walk_doc(docname, secnum):
# type: (str, Tuple[int, ...]) -> None
def _walk_doc(docname: str, secnum: Tuple[int, ...]) -> None:
if docname not in assigned:
assigned.add(docname)
doctree = env.get_doctree(docname)
@ -302,8 +287,7 @@ class TocTreeCollector(EnvironmentCollector):
return rewrite_needed
def setup(app):
# type: (Sphinx) -> Dict
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_env_collector(TocTreeCollector)
return {

View File

@ -22,6 +22,7 @@ import sys
import warnings
from fnmatch import fnmatch
from os import path
from typing import Any, List, Tuple
import sphinx.locale
from sphinx import __display_version__, package_dir
@ -32,10 +33,6 @@ from sphinx.util import rst
from sphinx.util.osutil import FileAvoidWrite, ensuredir
from sphinx.util.template import ReSTRenderer
if False:
# For type annotation
from typing import Any, List, Tuple # NOQA
# automodule options
if 'SPHINX_APIDOC_OPTIONS' in os.environ:
OPTIONS = os.environ['SPHINX_APIDOC_OPTIONS'].split(',')
@ -53,8 +50,7 @@ PY_SUFFIXES = {'.py', '.pyx'}
template_dir = path.join(package_dir, 'templates', 'apidoc')
def makename(package, module):
# type: (str, str) -> str
def makename(package: str, module: str) -> str:
"""Join package and module with a dot."""
warnings.warn('makename() is deprecated.',
RemovedInSphinx40Warning)
@ -68,14 +64,12 @@ def makename(package, module):
return name
def module_join(*modnames):
# type: (*str) -> str
def module_join(*modnames: str) -> str:
"""Join module names with dots."""
return '.'.join(filter(None, modnames))
def write_file(name, text, opts):
# type: (str, str, Any) -> None
def write_file(name: str, text: str, opts: Any) -> None:
"""Write the output file for module/package <name>."""
fname = path.join(opts.destdir, '%s.%s' % (name, opts.suffix))
if opts.dryrun:
@ -89,8 +83,7 @@ def write_file(name, text, opts):
f.write(text)
def format_heading(level, text, escape=True):
# type: (int, str, bool) -> str
def format_heading(level: int, text: str, escape: bool = True) -> str:
"""Create a heading of <level> [1, 2 or 3 supported]."""
warnings.warn('format_warning() is deprecated.',
RemovedInSphinx40Warning)
@ -100,8 +93,7 @@ def format_heading(level, text, escape=True):
return '%s\n%s\n\n' % (text, underlining)
def format_directive(module, package=None):
# type: (str, str) -> str
def format_directive(module: str, package: str = None) -> str:
"""Create the automodule directive and add the options."""
warnings.warn('format_directive() is deprecated.',
RemovedInSphinx40Warning)
@ -111,8 +103,8 @@ def format_directive(module, package=None):
return directive
def create_module_file(package, basename, opts):
# type: (str, str, Any) -> None
def create_module_file(package: str, basename: str, opts: Any,
user_template_dir: str = None) -> None:
"""Build the text of the file and write the file."""
qualname = module_join(package, basename)
context = {
@ -121,12 +113,13 @@ def create_module_file(package, basename, opts):
'qualname': qualname,
'automodule_options': OPTIONS,
}
text = ReSTRenderer(template_dir).render('module.rst', context)
text = ReSTRenderer([user_template_dir, template_dir]).render('module.rst_t', context)
write_file(qualname, text, opts)
def create_package_file(root, master_package, subroot, py_files, opts, subs, is_namespace, excludes=[]): # NOQA
# type: (str, str, str, List[str], Any, List[str], bool, List[str]) -> None
def create_package_file(root: str, master_package: str, subroot: str, py_files: List[str],
opts: Any, subs: List[str], is_namespace: bool,
excludes: List[str] = [], user_template_dir: str = None) -> None:
"""Build the text of the file and write the file."""
# build a list of sub packages (directories containing an INITPY file)
subpackages = [sub for sub in subs if not
@ -151,16 +144,16 @@ def create_package_file(root, master_package, subroot, py_files, opts, subs, is_
'automodule_options': OPTIONS,
'show_headings': not opts.noheadings,
}
text = ReSTRenderer(template_dir).render('package.rst', context)
text = ReSTRenderer([user_template_dir, template_dir]).render('package.rst_t', context)
write_file(pkgname, text, opts)
if submodules and opts.separatemodules:
for submodule in submodules:
create_module_file(None, submodule, opts)
create_module_file(None, submodule, opts, user_template_dir)
def create_modules_toc_file(modules, opts, name='modules'):
# type: (List[str], Any, str) -> None
def create_modules_toc_file(modules: List[str], opts: Any, name: str = 'modules',
user_template_dir: str = None) -> None:
"""Create the module's index."""
modules.sort()
prev_module = ''
@ -176,12 +169,11 @@ def create_modules_toc_file(modules, opts, name='modules'):
'maxdepth': opts.maxdepth,
'docnames': modules,
}
text = ReSTRenderer(template_dir).render('toc.rst', context)
text = ReSTRenderer([user_template_dir, template_dir]).render('toc.rst_t', context)
write_file(name, text, opts)
def shall_skip(module, opts, excludes=[]):
# type: (str, Any, List[str]) -> bool
def shall_skip(module: str, opts: Any, excludes: List[str] = []) -> bool:
"""Check if we want to skip this module."""
# skip if the file doesn't exist and not using implicit namespaces
if not opts.implicit_namespaces and not path.exists(module):
@ -207,8 +199,7 @@ def shall_skip(module, opts, excludes=[]):
return False
def is_skipped_module(filename, opts, excludes):
# type: (str, Any, List[str]) -> bool
def is_skipped_module(filename: str, opts: Any, excludes: List[str]) -> bool:
"""Check if we want to skip this module."""
if not path.exists(filename):
# skip if the file doesn't exist
@ -220,8 +211,8 @@ def is_skipped_module(filename, opts, excludes):
return False
def recurse_tree(rootpath, excludes, opts):
# type: (str, List[str], Any) -> List[str]
def recurse_tree(rootpath: str, excludes: List[str], opts: Any,
user_template_dir: str = None) -> List[str]:
"""
Look for every file in the directory tree and create the corresponding
ReST files.
@ -271,7 +262,8 @@ def recurse_tree(rootpath, excludes, opts):
# a namespace and there is something there to document
if not is_namespace or len(py_files) > 0:
create_package_file(root, root_package, subpackage,
py_files, opts, subs, is_namespace, excludes)
py_files, opts, subs, is_namespace, excludes,
user_template_dir)
toplevels.append(module_join(root_package, subpackage))
else:
# if we are at the root level, we don't require it to be a package
@ -279,14 +271,13 @@ def recurse_tree(rootpath, excludes, opts):
for py_file in py_files:
if not is_skipped_module(path.join(rootpath, py_file), opts, excludes):
module = path.splitext(py_file)[0]
create_module_file(root_package, module, opts)
create_module_file(root_package, module, opts, user_template_dir)
toplevels.append(module)
return toplevels
def is_excluded(root, excludes):
# type: (str, List[str]) -> bool
def is_excluded(root: str, excludes: List[str]) -> bool:
"""Check if the directory is in the exclude list.
Note: by having trailing slashes, we avoid common prefix issues, like
@ -298,8 +289,7 @@ def is_excluded(root, excludes):
return False
def get_parser():
# type: () -> argparse.ArgumentParser
def get_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
usage='%(prog)s [OPTIONS] -o <OUTPUT_PATH> <MODULE_PATH> '
'[EXCLUDE_PATTERN, ...]',
@ -386,11 +376,15 @@ Note: By default this script will not overwrite already created files."""))
const='sphinx.ext.%s' % ext, dest='extensions',
help=__('enable %s extension') % ext)
group = parser.add_argument_group(__('Project templating'))
group.add_argument('-t', '--templatedir', metavar='TEMPLATEDIR',
dest='templatedir',
help=__('template directory for template files'))
return parser
def main(argv=sys.argv[1:]):
# type: (List[str]) -> int
def main(argv: List[str] = sys.argv[1:]) -> int:
"""Parse and check the command line arguments."""
sphinx.locale.setlocale(locale.LC_ALL, '')
sphinx.locale.init_console(os.path.join(package_dir, 'locale'), 'sphinx')
@ -412,7 +406,7 @@ def main(argv=sys.argv[1:]):
if not args.dryrun:
ensuredir(args.destdir)
excludes = [path.abspath(exclude) for exclude in args.exclude_pattern]
modules = recurse_tree(rootpath, excludes, args)
modules = recurse_tree(rootpath, excludes, args, args.templatedir)
if args.full:
from sphinx.cmd import quickstart as qs
@ -455,9 +449,10 @@ def main(argv=sys.argv[1:]):
d['extensions'].extend(ext.split(','))
if not args.dryrun:
qs.generate(d, silent=True, overwrite=args.force)
qs.generate(d, silent=True, overwrite=args.force,
templatedir=args.templatedir)
elif args.tocfile:
create_modules_toc_file(modules, args, args.tocfile)
create_modules_toc_file(modules, args, args.tocfile, args.templatedir)
return 0

View File

@ -12,15 +12,18 @@
import re
import warnings
from typing import Any
from types import ModuleType
from typing import Any, Callable, Dict, Iterator, List, Sequence, Set, Tuple, Type, Union
from docutils.statemachine import StringList
import sphinx
from sphinx.config import ENUM
from sphinx.application import Sphinx
from sphinx.config import Config, ENUM
from sphinx.deprecation import (
RemovedInSphinx30Warning, RemovedInSphinx40Warning, deprecated_alias
)
from sphinx.environment import BuildEnvironment
from sphinx.ext.autodoc.importer import import_object, get_object_members
from sphinx.ext.autodoc.mock import mock
from sphinx.locale import _, __
@ -35,12 +38,8 @@ from sphinx.util.inspect import (
if False:
# For type annotation
from types import ModuleType # NOQA
from typing import Callable, Dict, Iterator, List, Sequence, Set, Tuple, Type, Union # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.config import Config # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.ext.autodoc.directive import DocumenterBridge # NOQA
from sphinx.ext.autodoc.directive import DocumenterBridge
logger = logging.getLogger(__name__)
@ -61,8 +60,7 @@ py_ext_sig_re = re.compile(
''', re.VERBOSE)
def identity(x):
# type: (Any) -> Any
def identity(x: Any) -> Any:
return x
@ -71,16 +69,14 @@ INSTANCEATTR = object()
SLOTSATTR = object()
def members_option(arg):
# type: (Any) -> Union[object, List[str]]
def members_option(arg: Any) -> Union[object, List[str]]:
"""Used to convert the :members: option to auto directives."""
if arg is None or arg is True:
return ALL
return [x.strip() for x in arg.split(',')]
def members_set_option(arg):
# type: (Any) -> Union[object, Set[str]]
def members_set_option(arg: Any) -> Union[object, Set[str]]:
"""Used to convert the :members: option to auto directives."""
if arg is None:
return ALL
@ -90,8 +86,7 @@ def members_set_option(arg):
SUPPRESS = object()
def annotation_option(arg):
# type: (Any) -> Any
def annotation_option(arg: Any) -> Any:
if arg is None:
# suppress showing the representation of the object
return SUPPRESS
@ -99,16 +94,14 @@ def annotation_option(arg):
return arg
def bool_option(arg):
# type: (Any) -> bool
def bool_option(arg: Any) -> bool:
"""Used to convert flag options to auto directives. (Instead of
directives.flag(), which returns None).
"""
return True
def merge_special_members_option(options):
# type: (Dict) -> None
def merge_special_members_option(options: Dict) -> None:
"""Merge :special-members: option to :members: option."""
if 'special-members' in options and options['special-members'] is not ALL:
if options.get('members') is ALL:
@ -123,8 +116,7 @@ def merge_special_members_option(options):
# Some useful event listener factories for autodoc-process-docstring.
def cut_lines(pre, post=0, what=None):
# type: (int, int, str) -> Callable
def cut_lines(pre: int, post: int = 0, what: str = None) -> Callable:
"""Return a listener that removes the first *pre* and last *post*
lines of every docstring. If *what* is a sequence of strings,
only docstrings of a type in *what* will be processed.
@ -136,8 +128,8 @@ def cut_lines(pre, post=0, what=None):
This can (and should) be used in place of :confval:`automodule_skip_lines`.
"""
def process(app, what_, name, obj, options, lines):
# type: (Sphinx, str, str, Any, Any, List[str]) -> None
def process(app: Sphinx, what_: str, name: str, obj: Any, options: Any, lines: List[str]
) -> None:
if what and what_ not in what:
return
del lines[:pre]
@ -152,8 +144,8 @@ def cut_lines(pre, post=0, what=None):
return process
def between(marker, what=None, keepempty=False, exclude=False):
# type: (str, Sequence[str], bool, bool) -> Callable
def between(marker: str, what: Sequence[str] = None, keepempty: bool = False,
exclude: bool = False) -> Callable:
"""Return a listener that either keeps, or if *exclude* is True excludes,
lines between lines that match the *marker* regular expression. If no line
matches, the resulting docstring would be empty, so no change will be made
@ -164,8 +156,8 @@ def between(marker, what=None, keepempty=False, exclude=False):
"""
marker_re = re.compile(marker)
def process(app, what_, name, obj, options, lines):
# type: (Sphinx, str, str, Any, Any, List[str]) -> None
def process(app: Sphinx, what_: str, name: str, obj: Any, options: Any, lines: List[str]
) -> None:
if what and what_ not in what:
return
deleted = 0
@ -192,8 +184,7 @@ def between(marker, what=None, keepempty=False, exclude=False):
# But we define this class here to keep compatibility (see #4538)
class Options(dict):
"""A dict/attribute hybrid that returns None on nonexisting keys."""
def __getattr__(self, name):
# type: (str) -> Any
def __getattr__(self, name: str) -> Any:
try:
return self[name.replace('_', '-')]
except KeyError:
@ -229,19 +220,17 @@ class Documenter:
option_spec = {'noindex': bool_option} # type: Dict[str, Callable]
def get_attr(self, obj, name, *defargs):
# type: (Any, str, Any) -> Any
def get_attr(self, obj: Any, name: str, *defargs) -> Any:
"""getattr() override for types such as Zope interfaces."""
return autodoc_attrgetter(self.env.app, obj, name, *defargs)
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
# type: (Any, str, bool, Any) -> bool
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
) -> bool:
"""Called to see if a member can be documented by this documenter."""
raise NotImplementedError('must be implemented in subclasses')
def __init__(self, directive, name, indent=''):
# type: (DocumenterBridge, str, str) -> None
def __init__(self, directive: "DocumenterBridge", name: str, indent: str = '') -> None:
self.directive = directive
self.env = directive.env # type: BuildEnvironment
self.options = directive.genopt
@ -266,18 +255,16 @@ class Documenter:
self.analyzer = None # type: ModuleAnalyzer
@property
def documenters(self):
# type: () -> Dict[str, Type[Documenter]]
def documenters(self) -> Dict[str, Type["Documenter"]]:
"""Returns registered Documenter classes"""
return get_documenters(self.env.app)
def add_line(self, line, source, *lineno):
# type: (str, str, int) -> None
def add_line(self, line: str, source: str, *lineno: int) -> None:
"""Append one line of generated reST to the output."""
self.directive.result.append(self.indent + line, source, *lineno)
def resolve_name(self, modname, parents, path, base):
# type: (str, Any, str, Any) -> Tuple[str, List[str]]
def resolve_name(self, modname: str, parents: Any, path: str, base: Any
) -> Tuple[str, List[str]]:
"""Resolve the module and name of the object to document given by the
arguments and the current module/class.
@ -287,8 +274,7 @@ class Documenter:
"""
raise NotImplementedError('must be implemented in subclasses')
def parse_name(self):
# type: () -> bool
def parse_name(self) -> bool:
"""Determine what module to import and what attribute to document.
Returns True and sets *self.modname*, *self.objpath*, *self.fullname*,
@ -324,8 +310,7 @@ class Documenter:
(self.objpath and '.' + '.'.join(self.objpath) or '')
return True
def import_object(self):
# type: () -> bool
def import_object(self) -> bool:
"""Import the object given by *self.modname* and *self.objpath* and set
it as *self.object*.
@ -343,8 +328,7 @@ class Documenter:
self.env.note_reread()
return False
def get_real_modname(self):
# type: () -> str
def get_real_modname(self) -> str:
"""Get the real module name of an object to document.
It can differ from the name of the module through which the object was
@ -352,8 +336,7 @@ class Documenter:
"""
return self.get_attr(self.object, '__module__', None) or self.modname
def check_module(self):
# type: () -> bool
def check_module(self) -> bool:
"""Check if *self.object* is really defined in the module given by
*self.modname*.
"""
@ -367,16 +350,14 @@ class Documenter:
return False
return True
def format_args(self, **kwargs):
# type: (Any) -> str
def format_args(self, **kwargs) -> str:
"""Format the argument signature of *self.object*.
Should return None if the object does not have a signature.
"""
return None
def format_name(self):
# type: () -> str
def format_name(self) -> str:
"""Format the name of *self.object*.
This normally should be something that can be parsed by the generated
@ -387,8 +368,7 @@ class Documenter:
# directives of course)
return '.'.join(self.objpath) or self.modname
def format_signature(self, **kwargs):
# type: (Any) -> str
def format_signature(self, **kwargs) -> str:
"""Format the signature (arguments and return annotation) of the object.
Let the user process it via the ``autodoc-process-signature`` event.
@ -422,8 +402,7 @@ class Documenter:
else:
return ''
def add_directive_header(self, sig):
# type: (str) -> None
def add_directive_header(self, sig: str) -> None:
"""Add the directive header and options to the generated content."""
domain = getattr(self, 'domain', 'py')
directive = getattr(self, 'directivetype', self.objtype)
@ -438,8 +417,7 @@ class Documenter:
# etc. don't support a prepended module name
self.add_line(' :module: %s' % self.modname, sourcename)
def get_doc(self, encoding=None, ignore=1):
# type: (str, int) -> List[List[str]]
def get_doc(self, encoding: str = None, ignore: int = 1) -> List[List[str]]:
"""Decode and return lines of the docstring(s) for the object."""
if encoding is not None:
warnings.warn("The 'encoding' argument to autodoc.%s.get_doc() is deprecated."
@ -452,8 +430,7 @@ class Documenter:
return [prepare_docstring(docstring, ignore, tab_width)]
return []
def process_doc(self, docstrings):
# type: (List[List[str]]) -> Iterator[str]
def process_doc(self, docstrings: List[List[str]]) -> Iterator[str]:
"""Let the user process the docstrings before adding them."""
for docstringlines in docstrings:
if self.env.app:
@ -463,14 +440,12 @@ class Documenter:
self.options, docstringlines)
yield from docstringlines
def get_sourcename(self):
# type: () -> str
def get_sourcename(self) -> str:
if self.analyzer:
return '%s:docstring of %s' % (self.analyzer.srcname, self.fullname)
return 'docstring of %s' % self.fullname
def add_content(self, more_content, no_docstring=False):
# type: (Any, bool) -> None
def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
"""Add content from docstrings, attribute documentation and user."""
# set sourcename and add content from attribute documentation
sourcename = self.get_sourcename()
@ -500,8 +475,7 @@ class Documenter:
for line, src in zip(more_content.data, more_content.items):
self.add_line(line, src[0], src[1])
def get_object_members(self, want_all):
# type: (bool) -> Tuple[bool, List[Tuple[str, Any]]]
def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, Any]]]:
"""Return `(members_check_module, members)` where `members` is a
list of `(membername, member)` pairs of the members of *self.object*.
@ -527,8 +501,8 @@ class Documenter:
return False, sorted((m.name, m.value) for m in members.values()
if m.directly_defined)
def filter_members(self, members, want_all):
# type: (List[Tuple[str, Any]], bool) -> List[Tuple[str, Any, bool]]
def filter_members(self, members: List[Tuple[str, Any]], want_all: bool
) -> List[Tuple[str, Any, bool]]:
"""Filter the given member list.
Members are skipped if
@ -616,8 +590,7 @@ class Documenter:
return ret
def document_members(self, all_members=False):
# type: (bool) -> None
def document_members(self, all_members: bool = False) -> None:
"""Generate reST for member documentation.
If *all_members* is True, do all members, else those given by
@ -669,8 +642,7 @@ class Documenter:
# sort by source order, by virtue of the module analyzer
tagorder = self.analyzer.tagorder
def keyfunc(entry):
# type: (Tuple[Documenter, bool]) -> int
def keyfunc(entry: Tuple[Documenter, bool]) -> int:
fullname = entry[0].name.split('::')[1]
return tagorder.get(fullname, len(tagorder))
memberdocumenters.sort(key=keyfunc)
@ -684,9 +656,8 @@ class Documenter:
self.env.temp_data['autodoc:module'] = None
self.env.temp_data['autodoc:class'] = None
def generate(self, more_content=None, real_modname=None,
check_module=False, all_members=False):
# type: (Any, str, bool, bool) -> None
def generate(self, more_content: Any = None, real_modname: str = None,
check_module: bool = False, all_members: bool = False) -> None:
"""Generate reST for the object given by *self.name*, and possibly for
its members.
@ -778,26 +749,24 @@ class ModuleDocumenter(Documenter):
'imported-members': bool_option, 'ignore-module-all': bool_option
} # type: Dict[str, Callable]
def __init__(self, *args):
# type: (Any) -> None
def __init__(self, *args) -> None:
super().__init__(*args)
merge_special_members_option(self.options)
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
# type: (Any, str, bool, Any) -> bool
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
) -> bool:
# don't document submodules automatically
return False
def resolve_name(self, modname, parents, path, base):
# type: (str, Any, str, Any) -> Tuple[str, List[str]]
def resolve_name(self, modname: str, parents: Any, path: str, base: Any
) -> Tuple[str, List[str]]:
if modname is not None:
logger.warning(__('"::" in automodule name doesn\'t make sense'),
type='autodoc')
return (path or '') + base, []
def parse_name(self):
# type: () -> bool
def parse_name(self) -> bool:
ret = super().parse_name()
if self.args or self.retann:
logger.warning(__('signature arguments or return annotation '
@ -805,8 +774,7 @@ class ModuleDocumenter(Documenter):
type='autodoc')
return ret
def add_directive_header(self, sig):
# type: (str) -> None
def add_directive_header(self, sig: str) -> None:
Documenter.add_directive_header(self, sig)
sourcename = self.get_sourcename()
@ -819,8 +787,7 @@ class ModuleDocumenter(Documenter):
if self.options.deprecated:
self.add_line(' :deprecated:', sourcename)
def get_object_members(self, want_all):
# type: (bool) -> Tuple[bool, List[Tuple[str, object]]]
def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, object]]]:
if want_all:
if (self.options.ignore_module_all or not
hasattr(self.object, '__all__')):
@ -861,8 +828,8 @@ class ModuleLevelDocumenter(Documenter):
Specialized Documenter subclass for objects on module level (functions,
classes, data/constants).
"""
def resolve_name(self, modname, parents, path, base):
# type: (str, Any, str, Any) -> Tuple[str, List[str]]
def resolve_name(self, modname: str, parents: Any, path: str, base: Any
) -> Tuple[str, List[str]]:
if modname is None:
if path:
modname = path.rstrip('.')
@ -882,8 +849,8 @@ class ClassLevelDocumenter(Documenter):
Specialized Documenter subclass for objects on class level (methods,
attributes).
"""
def resolve_name(self, modname, parents, path, base):
# type: (str, Any, str, Any) -> Tuple[str, List[str]]
def resolve_name(self, modname: str, parents: Any, path: str, base: Any
) -> Tuple[str, List[str]]:
if modname is None:
if path:
mod_cls = path.rstrip('.')
@ -916,8 +883,7 @@ class DocstringSignatureMixin:
feature of reading the signature from the docstring.
"""
def _find_signature(self, encoding=None):
# type: (str) -> Tuple[str, str]
def _find_signature(self, encoding: str = None) -> Tuple[str, str]:
if encoding is not None:
warnings.warn("The 'encoding' argument to autodoc.%s._find_signature() is "
"deprecated." % self.__class__.__name__,
@ -951,8 +917,7 @@ class DocstringSignatureMixin:
break
return result
def get_doc(self, encoding=None, ignore=1):
# type: (str, int) -> List[List[str]]
def get_doc(self, encoding: str = None, ignore: int = 1) -> List[List[str]]:
if encoding is not None:
warnings.warn("The 'encoding' argument to autodoc.%s.get_doc() is deprecated."
% self.__class__.__name__,
@ -962,8 +927,7 @@ class DocstringSignatureMixin:
return lines
return super().get_doc(None, ignore) # type: ignore
def format_signature(self, **kwargs):
# type: (Any) -> str
def format_signature(self, **kwargs) -> str:
if self.args is None and self.env.config.autodoc_docstring_signature: # type: ignore
# only act if a signature is not explicitly given already, and if
# the feature is enabled
@ -978,8 +942,7 @@ class DocstringStripSignatureMixin(DocstringSignatureMixin):
Mixin for AttributeDocumenter to provide the
feature of stripping any function signature from the docstring.
"""
def format_signature(self, **kwargs):
# type: (Any) -> str
def format_signature(self, **kwargs) -> str:
if self.args is None and self.env.config.autodoc_docstring_signature: # type: ignore
# only act if a signature is not explicitly given already, and if
# the feature is enabled
@ -1000,14 +963,13 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
member_order = 30
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
# type: (Any, str, bool, Any) -> bool
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
) -> bool:
# supports functions, builtins and bound methods exported at the module level
return (inspect.isfunction(member) or inspect.isbuiltin(member) or
(inspect.isroutine(member) and isinstance(parent, ModuleDocumenter)))
def format_args(self, **kwargs):
# type: (Any) -> str
def format_args(self, **kwargs) -> str:
if self.env.config.autodoc_typehints == 'none':
kwargs.setdefault('show_annotation', False)
@ -1042,12 +1004,10 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
args = args.replace('\\', '\\\\')
return args
def document_members(self, all_members=False):
# type: (bool) -> None
def document_members(self, all_members: bool = False) -> None:
pass
def add_directive_header(self, sig):
# type: (str) -> None
def add_directive_header(self, sig: str) -> None:
sourcename = self.get_sourcename()
super().add_directive_header(sig)
@ -1086,18 +1046,16 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
'private-members': bool_option, 'special-members': members_option,
} # type: Dict[str, Callable]
def __init__(self, *args):
# type: (Any) -> None
def __init__(self, *args) -> None:
super().__init__(*args)
merge_special_members_option(self.options)
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
# type: (Any, str, bool, Any) -> bool
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
) -> bool:
return isinstance(member, type)
def import_object(self):
# type: () -> Any
def import_object(self) -> Any:
ret = super().import_object()
# if the class is documented under another name, document it
# as data/attribute
@ -1108,8 +1066,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
self.doc_as_attr = True
return ret
def format_args(self, **kwargs):
# type: (Any) -> str
def format_args(self, **kwargs) -> str:
if self.env.config.autodoc_typehints == 'none':
kwargs.setdefault('show_annotation', False)
@ -1129,15 +1086,13 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
# with __init__ in C
return None
def format_signature(self, **kwargs):
# type: (Any) -> str
def format_signature(self, **kwargs) -> str:
if self.doc_as_attr:
return ''
return super().format_signature(**kwargs)
def add_directive_header(self, sig):
# type: (str) -> None
def add_directive_header(self, sig: str) -> None:
if self.doc_as_attr:
self.directivetype = 'attribute'
super().add_directive_header(sig)
@ -1154,8 +1109,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
self.add_line(' ' + _('Bases: %s') % ', '.join(bases),
sourcename)
def get_doc(self, encoding=None, ignore=1):
# type: (str, int) -> List[List[str]]
def get_doc(self, encoding: str = None, ignore: int = 1) -> List[List[str]]:
if encoding is not None:
warnings.warn("The 'encoding' argument to autodoc.%s.get_doc() is deprecated."
% self.__class__.__name__,
@ -1199,8 +1153,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
tab_width = self.directive.state.document.settings.tab_width
return [prepare_docstring(docstring, ignore, tab_width) for docstring in docstrings]
def add_content(self, more_content, no_docstring=False):
# type: (Any, bool) -> None
def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
if self.doc_as_attr:
classname = safe_getattr(self.object, '__qualname__', None)
if not classname:
@ -1215,15 +1168,13 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
else:
super().add_content(more_content)
def document_members(self, all_members=False):
# type: (bool) -> None
def document_members(self, all_members: bool = False) -> None:
if self.doc_as_attr:
return
super().document_members(all_members)
def generate(self, more_content=None, real_modname=None,
check_module=False, all_members=False):
# type: (Any, str, bool, bool) -> None
def generate(self, more_content: Any = None, real_modname: str = None,
check_module: bool = False, all_members: bool = False) -> None:
# Do not pass real_modname and use the name from the __module__
# attribute of the class.
# If a class gets imported into the module real_modname
@ -1245,8 +1196,8 @@ class ExceptionDocumenter(ClassDocumenter):
priority = 10
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
# type: (Any, str, bool, Any) -> bool
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
) -> bool:
return isinstance(member, type) and issubclass(member, BaseException)
@ -1261,12 +1212,11 @@ class DataDocumenter(ModuleLevelDocumenter):
option_spec["annotation"] = annotation_option
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
# type: (Any, str, bool, Any) -> bool
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
) -> bool:
return isinstance(parent, ModuleDocumenter) and isattr
def add_directive_header(self, sig):
# type: (str) -> None
def add_directive_header(self, sig: str) -> None:
super().add_directive_header(sig)
sourcename = self.get_sourcename()
if not self.options.annotation:
@ -1282,12 +1232,10 @@ class DataDocumenter(ModuleLevelDocumenter):
self.add_line(' :annotation: %s' % self.options.annotation,
sourcename)
def document_members(self, all_members=False):
# type: (bool) -> None
def document_members(self, all_members: bool = False) -> None:
pass
def get_real_modname(self):
# type: () -> str
def get_real_modname(self) -> str:
return self.get_attr(self.parent or self.object, '__module__', None) \
or self.modname
@ -1302,13 +1250,12 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
priority = 1 # must be more than FunctionDocumenter
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
# type: (Any, str, bool, Any) -> bool
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
) -> bool:
return inspect.isroutine(member) and \
not isinstance(parent, ModuleDocumenter)
def import_object(self):
# type: () -> Any
def import_object(self) -> Any:
ret = super().import_object()
if not ret:
return ret
@ -1325,8 +1272,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
return ret
def format_args(self, **kwargs):
# type: (Any) -> str
def format_args(self, **kwargs) -> str:
if self.env.config.autodoc_typehints == 'none':
kwargs.setdefault('show_annotation', False)
@ -1341,8 +1287,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
args = args.replace('\\', '\\\\')
return args
def add_directive_header(self, sig):
# type: (str) -> None
def add_directive_header(self, sig) -> None:
super().add_directive_header(sig)
sourcename = self.get_sourcename()
@ -1356,8 +1301,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
if inspect.isstaticmethod(obj, cls=self.parent, name=self.object_name):
self.add_line(' :staticmethod:', sourcename)
def document_members(self, all_members=False):
# type: (bool) -> None
def document_members(self, all_members: bool = False) -> None:
pass
@ -1375,13 +1319,12 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
priority = 10
@staticmethod
def is_function_or_method(obj):
# type: (Any) -> bool
def is_function_or_method(obj: Any) -> bool:
return inspect.isfunction(obj) or inspect.isbuiltin(obj) or inspect.ismethod(obj)
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
# type: (Any, str, bool, Any) -> bool
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
) -> bool:
if inspect.isattributedescriptor(member):
return True
elif (not isinstance(parent, ModuleDocumenter) and
@ -1391,12 +1334,10 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
else:
return False
def document_members(self, all_members=False):
# type: (bool) -> None
def document_members(self, all_members: bool = False) -> None:
pass
def import_object(self):
# type: () -> Any
def import_object(self) -> Any:
ret = super().import_object()
if inspect.isenumattribute(self.object):
self.object = self.object.value
@ -1407,13 +1348,11 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
self._datadescriptor = False
return ret
def get_real_modname(self):
# type: () -> str
def get_real_modname(self) -> str:
return self.get_attr(self.parent or self.object, '__module__', None) \
or self.modname
def add_directive_header(self, sig):
# type: (str) -> None
def add_directive_header(self, sig: str) -> None:
super().add_directive_header(sig)
sourcename = self.get_sourcename()
if not self.options.annotation:
@ -1429,8 +1368,7 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
else:
self.add_line(' :annotation: %s' % self.options.annotation, sourcename)
def add_content(self, more_content, no_docstring=False):
# type: (Any, bool) -> None
def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
if not self._datadescriptor:
# if it's not a data descriptor, its docstring is very probably the
# wrong thing to display
@ -1450,21 +1388,18 @@ class PropertyDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): #
priority = AttributeDocumenter.priority + 1
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
# type: (Any, str, bool, Any) -> bool
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
) -> bool:
return inspect.isproperty(member) and isinstance(parent, ClassDocumenter)
def document_members(self, all_members=False):
# type: (bool) -> None
def document_members(self, all_members: bool = False) -> None:
pass
def get_real_modname(self):
# type: () -> str
def get_real_modname(self) -> str:
return self.get_attr(self.parent or self.object, '__module__', None) \
or self.modname
def add_directive_header(self, sig):
# type: (str) -> None
def add_directive_header(self, sig: str) -> None:
super().add_directive_header(sig)
sourcename = self.get_sourcename()
if inspect.isabstractmethod(self.object):
@ -1485,21 +1420,19 @@ class InstanceAttributeDocumenter(AttributeDocumenter):
priority = 11
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
# type: (Any, str, bool, Any) -> bool
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
) -> bool:
"""This documents only INSTANCEATTR members."""
return isattr and (member is INSTANCEATTR)
def import_object(self):
# type: () -> bool
def import_object(self) -> bool:
"""Never import anything."""
# disguise as an attribute
self.objtype = 'attribute'
self._datadescriptor = False
return True
def add_content(self, more_content, no_docstring=False):
# type: (Any, bool) -> None
def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
"""Never try to get a docstring from the object."""
super().add_content(more_content, no_docstring=True)
@ -1517,13 +1450,12 @@ class SlotsAttributeDocumenter(AttributeDocumenter):
priority = 11
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
# type: (Any, str, bool, Any) -> bool
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
) -> bool:
"""This documents only SLOTSATTR members."""
return member is SLOTSATTR
def import_object(self):
# type: () -> bool
def import_object(self) -> Any:
"""Never import anything."""
# disguise as an attribute
self.objtype = 'attribute'
@ -1541,8 +1473,7 @@ class SlotsAttributeDocumenter(AttributeDocumenter):
self.env.note_reread()
return False
def get_doc(self, encoding=None, ignore=1):
# type: (str, int) -> List[List[str]]
def get_doc(self, encoding: str = None, ignore: int = 1) -> List[List[str]]:
"""Decode and return lines of the docstring(s) for the object."""
name = self.objpath[-1]
__slots__ = safe_getattr(self.parent, '__slots__', [])
@ -1553,14 +1484,12 @@ class SlotsAttributeDocumenter(AttributeDocumenter):
return []
def get_documenters(app):
# type: (Sphinx) -> Dict[str, Type[Documenter]]
def get_documenters(app: Sphinx) -> Dict[str, Type[Documenter]]:
"""Returns registered Documenter classes"""
return app.registry.documenters
def autodoc_attrgetter(app, obj, name, *defargs):
# type: (Sphinx, Any, str, Any) -> Any
def autodoc_attrgetter(app: Sphinx, obj: Any, name: str, *defargs) -> Any:
"""Alternative getattr() for types"""
for typ, func in app.registry.autodoc_attrgettrs.items():
if isinstance(obj, typ):
@ -1569,8 +1498,7 @@ def autodoc_attrgetter(app, obj, name, *defargs):
return safe_getattr(obj, name, *defargs)
def merge_autodoc_default_flags(app, config):
# type: (Sphinx, Config) -> None
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
@ -1602,8 +1530,7 @@ deprecated_alias('sphinx.ext.autodoc',
RemovedInSphinx40Warning)
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_autodocumenter(ModuleDocumenter)
app.add_autodocumenter(ClassDocumenter)
app.add_autodocumenter(ExceptionDocumenter)

View File

@ -7,27 +7,22 @@
"""
import warnings
from typing import Any, Callable, Dict, List, Set, Type
from docutils import nodes
from docutils.parsers.rst.states import Struct
from docutils.nodes import Element, Node
from docutils.parsers.rst.states import RSTState, Struct
from docutils.statemachine import StringList
from docutils.utils import assemble_option_dict
from docutils.utils import Reporter, assemble_option_dict
from sphinx.config import Config
from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.ext.autodoc import Options, get_documenters
from sphinx.environment import BuildEnvironment
from sphinx.ext.autodoc import Documenter, Options, get_documenters
from sphinx.util import logging
from sphinx.util.docutils import SphinxDirective, switch_source_input
from sphinx.util.nodes import nested_parse_with_titles
if False:
# For type annotation
from typing import Any, Callable, Dict, List, Set, Type # NOQA
from docutils.parsers.rst.state import RSTState # NOQA
from docutils.utils import Reporter # NOQA
from sphinx.config import Config # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.ext.autodoc import Documenter # NOQA
logger = logging.getLogger(__name__)
@ -41,21 +36,19 @@ AUTODOC_DEFAULT_OPTIONS = ['members', 'undoc-members', 'inherited-members',
class DummyOptionSpec(dict):
"""An option_spec allows any options."""
def __bool__(self):
# type: () -> bool
def __bool__(self) -> bool:
"""Behaves like some options are defined."""
return True
def __getitem__(self, key):
# type: (str) -> Callable[[str], str]
def __getitem__(self, key: str) -> Callable[[str], str]:
return lambda x: x
class DocumenterBridge:
"""A parameters container for Documenters."""
def __init__(self, env, reporter, options, lineno, state=None):
# type: (BuildEnvironment, Reporter, Options, int, Any) -> None
def __init__(self, env: BuildEnvironment, reporter: Reporter, options: Options,
lineno: int, state: Any = None) -> None:
self.env = env
self.reporter = reporter
self.genopt = options
@ -73,13 +66,12 @@ class DocumenterBridge:
document = Struct(settings=settings)
self.state = Struct(document=document)
def warn(self, msg):
# type: (str) -> None
def warn(self, msg: str) -> None:
logger.warning(msg, location=(self.env.docname, self.lineno))
def process_documenter_options(documenter, config, options):
# type: (Type[Documenter], Config, Dict) -> Options
def process_documenter_options(documenter: Type[Documenter], config: Config, options: Dict
) -> Options:
"""Recognize options of Documenter from user input."""
for name in AUTODOC_DEFAULT_OPTIONS:
if name not in documenter.option_spec:
@ -92,12 +84,12 @@ def process_documenter_options(documenter, config, options):
return Options(assemble_option_dict(options.items(), documenter.option_spec))
def parse_generated_content(state, content, documenter):
# type: (RSTState, StringList, Documenter) -> List[nodes.Node]
def parse_generated_content(state: RSTState, content: StringList, documenter: Documenter
) -> List[Node]:
"""Parse a generated content by Documenter."""
with switch_source_input(state, content):
if documenter.titles_allowed:
node = nodes.section() # type: nodes.Element
node = nodes.section() # type: Element
# necessary so that the child nodes get the right source/line set
node.document = state.document
nested_parse_with_titles(state, content, node)
@ -121,8 +113,7 @@ class AutodocDirective(SphinxDirective):
optional_arguments = 0
final_argument_whitespace = True
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
reporter = self.state.document.reporter
try:

View File

@ -12,20 +12,16 @@ import sys
import traceback
import warnings
from collections import namedtuple
from typing import Any, Callable, Dict, List
from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
from sphinx.util import logging
from sphinx.util.inspect import isclass, isenumclass, safe_getattr
if False:
# For type annotation
from typing import Any, Callable, Dict, List # NOQA
logger = logging.getLogger(__name__)
def import_module(modname, warningiserror=False):
# type: (str, bool) -> Any
def import_module(modname: str, warningiserror: bool = False) -> Any:
"""
Call __import__(modname), convert exceptions to ImportError
"""
@ -41,8 +37,9 @@ def import_module(modname, warningiserror=False):
raise ImportError(exc, traceback.format_exc())
def import_object(modname, objpath, objtype='', attrgetter=safe_getattr, warningiserror=False):
# type: (str, List[str], str, Callable[[Any, str], Any], bool) -> Any
def import_object(modname: str, objpath: List[str], objtype: str = '',
attrgetter: Callable[[Any, str], Any] = safe_getattr,
warningiserror: bool = False) -> Any:
if objpath:
logger.debug('[autodoc] from %s import %s', modname, '.'.join(objpath))
else:
@ -108,8 +105,8 @@ def import_object(modname, objpath, objtype='', attrgetter=safe_getattr, warning
Attribute = namedtuple('Attribute', ['name', 'directly_defined', 'value'])
def get_object_members(subject, objpath, attrgetter, analyzer=None):
# type: (Any, List[str], Callable, Any) -> Dict[str, Attribute] # NOQA
def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
analyzer: Any = None) -> Dict[str, Attribute]:
"""Get members and attributes of target object."""
# the members directly defined in the class
obj_dict = attrgetter(subject, '__dict__', {})

View File

@ -15,14 +15,11 @@ 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
if False:
# For type annotation
from typing import Any, Generator, Iterator, List, Sequence, Tuple, Union # NOQA
logger = logging.getLogger(__name__)
@ -31,8 +28,7 @@ class _MockObject:
__display_name__ = '_MockObject'
def __new__(cls, *args, **kwargs):
# type: (Any, Any) -> Any
def __new__(cls, *args, **kwargs) -> Any:
if len(args) == 3 and isinstance(args[1], tuple):
superclass = args[1][-1].__class__
if superclass is cls:
@ -42,48 +38,39 @@ class _MockObject:
return super().__new__(cls)
def __init__(self, *args, **kwargs):
# type: (Any, Any) -> None
def __init__(self, *args, **kwargs) -> None:
self.__qualname__ = ''
def __len__(self):
# type: () -> int
def __len__(self) -> int:
return 0
def __contains__(self, key):
# type: (str) -> bool
def __contains__(self, key: str) -> bool:
return False
def __iter__(self):
# type: () -> Iterator
def __iter__(self) -> Iterator:
return iter([])
def __mro_entries__(self, bases):
# type: (Tuple) -> Tuple
def __mro_entries__(self, bases: Tuple) -> Tuple:
return (self.__class__,)
def __getitem__(self, key):
# type: (str) -> _MockObject
def __getitem__(self, key: str) -> "_MockObject":
return _make_subclass(key, self.__display_name__, self.__class__)()
def __getattr__(self, key):
# type: (str) -> _MockObject
def __getattr__(self, key: str) -> "_MockObject":
return _make_subclass(key, self.__display_name__, self.__class__)()
def __call__(self, *args, **kw):
# type: (Any, Any) -> Any
def __call__(self, *args, **kw) -> Any:
if args and type(args[0]) in [FunctionType, MethodType]:
# Appears to be a decorator, pass through unchanged
return args[0]
return self
def __repr__(self):
# type: () -> str
def __repr__(self) -> str:
return self.__display_name__
def _make_subclass(name, module, superclass=_MockObject, attributes=None):
# type: (str, str, Any, dict) -> Any
def _make_subclass(name: str, module: str, superclass: Any = _MockObject,
attributes: Any = None) -> Any:
attrs = {'__module__': module, '__display_name__': module + '.' + name}
attrs.update(attributes or {})
@ -94,8 +81,7 @@ class _MockModule(ModuleType):
"""Used by autodoc_mock_imports."""
__file__ = os.devnull
def __init__(self, name, loader=None):
# type: (str, _MockImporter) -> None
def __init__(self, name: str, loader: "_MockImporter" = None) -> None:
super().__init__(name)
self.__all__ = [] # type: List[str]
self.__path__ = [] # type: List[str]
@ -104,18 +90,15 @@ class _MockModule(ModuleType):
warnings.warn('The loader argument for _MockModule is deprecated.',
RemovedInSphinx30Warning)
def __getattr__(self, name):
# type: (str) -> _MockObject
def __getattr__(self, name: str) -> _MockObject:
return _make_subclass(name, self.__name__)()
def __repr__(self):
# type: () -> str
def __repr__(self) -> str:
return self.__name__
class _MockImporter(MetaPathFinder):
def __init__(self, names):
# type: (List[str]) -> None
def __init__(self, names: List[str]) -> None:
self.names = names
self.mocked_modules = [] # type: List[str]
# enable hook by adding itself to meta_path
@ -124,8 +107,7 @@ class _MockImporter(MetaPathFinder):
warnings.warn('_MockImporter is now deprecated.',
RemovedInSphinx30Warning)
def disable(self):
# type: () -> None
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
@ -134,16 +116,14 @@ class _MockImporter(MetaPathFinder):
if m in sys.modules:
del sys.modules[m]
def find_module(self, name, path=None):
# type: (str, Sequence[Union[bytes, str]]) -> Any
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):
# type: (str) -> ModuleType
def load_module(self, name: str) -> ModuleType:
if name in sys.modules:
# module has already been imported, return it
return sys.modules[name]
@ -157,34 +137,30 @@ class _MockImporter(MetaPathFinder):
class MockLoader(Loader):
"""A loader for mocking."""
def __init__(self, finder):
# type: (MockFinder) -> None
def __init__(self, finder: "MockFinder") -> None:
super().__init__()
self.finder = finder
def create_module(self, spec):
# type: (ModuleSpec) -> ModuleType
def create_module(self, spec: ModuleSpec) -> ModuleType:
logger.debug('[autodoc] adding a mock module as %s!', spec.name)
self.finder.mocked_modules.append(spec.name)
return _MockModule(spec.name)
def exec_module(self, module):
# type: (ModuleType) -> None
def exec_module(self, module: ModuleType) -> None:
pass # nothing to do
class MockFinder(MetaPathFinder):
"""A finder for mocking."""
def __init__(self, modnames):
# type: (List[str]) -> None
def __init__(self, modnames: List[str]) -> None:
super().__init__()
self.modnames = modnames
self.loader = MockLoader(self)
self.mocked_modules = [] # type: List[str]
def find_spec(self, fullname, path, target=None):
# type: (str, Sequence[Union[bytes, str]], ModuleType) -> ModuleSpec
def find_spec(self, fullname: str, path: Sequence[Union[bytes, str]],
target: ModuleType = None) -> ModuleSpec:
for modname in self.modnames:
# check if fullname is (or is a descendant of) one of our targets
if modname == fullname or fullname.startswith(modname + '.'):
@ -192,16 +168,14 @@ class MockFinder(MetaPathFinder):
return None
def invalidate_caches(self):
# type: () -> None
def invalidate_caches(self) -> None:
"""Invalidate mocked modules on sys.modules."""
for modname in self.mocked_modules:
sys.modules.pop(modname, None)
@contextlib.contextmanager
def mock(modnames):
# type: (List[str]) -> Generator[None, None, None]
def mock(modnames: List[str]) -> Generator[None, None, None]:
"""Insert mock modules during context::
with mock(['target.module.name']):

View File

@ -8,24 +8,22 @@
:license: BSD, see LICENSE for details.
"""
from typing import Any, Dict
from typing import cast
from docutils import nodes
from docutils.nodes import Node
from sphinx.application import Sphinx
from sphinx.locale import __
from sphinx.util import logging
from sphinx.util.nodes import clean_astext
if False:
# For type annotation
from typing import Any, Dict # NOQA
from sphinx.application import Sphinx # NOQA
logger = logging.getLogger(__name__)
def get_node_depth(node):
def get_node_depth(node: Node) -> int:
i = 0
cur_node = node
while cur_node.parent != node.document:
@ -34,8 +32,7 @@ def get_node_depth(node):
return i
def register_sections_as_label(app, document):
# type: (Sphinx, nodes.Node) -> None
def register_sections_as_label(app: Sphinx, document: Node) -> None:
labels = app.env.domaindata['std']['labels']
anonlabels = app.env.domaindata['std']['anonlabels']
for node in document.traverse(nodes.section):
@ -61,8 +58,7 @@ def register_sections_as_label(app, document):
labels[name] = docname, labelid, sectname
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('autosectionlabel_prefix_document', False, 'env')
app.add_config_value('autosectionlabel_maxdepth', None, 'env')
app.connect('doctree-read', register_sections_as_label)

View File

@ -58,19 +58,24 @@ import posixpath
import re
import sys
import warnings
from os import path
from types import ModuleType
from typing import List, cast
from typing import Any, Dict, List, Tuple, Type
from typing import cast
from docutils import nodes
from docutils.nodes import Element, Node, system_message
from docutils.parsers.rst import directives
from docutils.parsers.rst.states import RSTStateMachine, Struct, state_classes
from docutils.parsers.rst.states import Inliner, RSTStateMachine, Struct, state_classes
from docutils.statemachine import StringList
import sphinx
from sphinx import addnodes
from sphinx.application import Sphinx
from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.environment import BuildEnvironment
from sphinx.environment.adapters.toctree import TocTree
from sphinx.ext.autodoc import get_documenters
from sphinx.ext.autodoc import Documenter, get_documenters
from sphinx.ext.autodoc.directive import DocumenterBridge, Options
from sphinx.ext.autodoc.importer import import_module
from sphinx.ext.autodoc.mock import mock
@ -81,15 +86,8 @@ from sphinx.util.docutils import (
NullReporter, SphinxDirective, SphinxRole, new_document, switch_source_input
)
from sphinx.util.matching import Matcher
from sphinx.writers.html import HTMLTranslator
if False:
# For type annotation
from typing import Any, Dict, Tuple, Type # NOQA
from docutils.parsers.rst.states import Inliner # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.ext.autodoc import Documenter # NOQA
from sphinx.writers.html import HTMLTranslator # NOQA
logger = logging.getLogger(__name__)
@ -104,16 +102,14 @@ class autosummary_toc(nodes.comment):
pass
def process_autosummary_toc(app, doctree):
# type: (Sphinx, nodes.document) -> None
def process_autosummary_toc(app: Sphinx, doctree: nodes.document) -> None:
"""Insert items described in autosummary:: to the TOC tree, but do
not generate the toctree:: list.
"""
env = app.builder.env
crawled = {}
def crawl_toc(node, depth=1):
# type: (nodes.Element, int) -> None
def crawl_toc(node: Element, depth: int = 1) -> None:
crawled[node] = True
for j, subnode in enumerate(node):
try:
@ -130,14 +126,12 @@ def process_autosummary_toc(app, doctree):
crawl_toc(doctree)
def autosummary_toc_visit_html(self, node):
# type: (nodes.NodeVisitor, autosummary_toc) -> None
def autosummary_toc_visit_html(self: nodes.NodeVisitor, node: autosummary_toc) -> None:
"""Hide autosummary toctree list in HTML output."""
raise nodes.SkipNode
def autosummary_noop(self, node):
# type: (nodes.NodeVisitor, nodes.Node) -> None
def autosummary_noop(self: nodes.NodeVisitor, node: Node) -> None:
pass
@ -147,8 +141,7 @@ class autosummary_table(nodes.comment):
pass
def autosummary_table_visit_html(self, node):
# type: (HTMLTranslator, autosummary_table) -> None
def autosummary_table_visit_html(self: HTMLTranslator, node: autosummary_table) -> None:
"""Make the first column of the table non-breaking."""
try:
table = cast(nodes.table, node[0])
@ -173,16 +166,14 @@ _app = None # type: Sphinx
class FakeDirective(DocumenterBridge):
def __init__(self):
# type: () -> None
def __init__(self) -> None:
settings = Struct(tab_width=8)
document = Struct(settings=settings)
state = Struct(document=document)
super().__init__({}, None, Options(), 0, state) # type: ignore
def get_documenter(app, obj, parent):
# type: (Sphinx, Any, Any) -> Type[Documenter]
def get_documenter(app: Sphinx, obj: Any, parent: Any) -> Type[Documenter]:
"""Get an autodoc.Documenter class suitable for documenting the given
object.
@ -236,8 +227,7 @@ class Autosummary(SphinxDirective):
'template': directives.unchanged,
}
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
self.bridge = DocumenterBridge(self.env, self.state.document.reporter,
Options(), self.lineno, self.state)
@ -256,24 +246,30 @@ class Autosummary(SphinxDirective):
docname = posixpath.join(tree_prefix, real_name)
docname = posixpath.normpath(posixpath.join(dirname, docname))
if docname not in self.env.found_docs:
location = self.state_machine.get_source_and_line(self.lineno)
if excluded(self.env.doc2path(docname, None)):
logger.warning(__('toctree references excluded document %r'), docname)
msg = __('autosummary references excluded document %r. Ignored.')
else:
logger.warning(__('toctree references unknown document %r'), docname)
msg = __('autosummary: stub file not found %r. '
'Check your autosummary_generate setting.')
logger.warning(msg, real_name, location=location)
continue
docnames.append(docname)
tocnode = addnodes.toctree()
tocnode['includefiles'] = docnames
tocnode['entries'] = [(None, docn) for docn in docnames]
tocnode['maxdepth'] = -1
tocnode['glob'] = None
if docnames:
tocnode = addnodes.toctree()
tocnode['includefiles'] = docnames
tocnode['entries'] = [(None, docn) for docn in docnames]
tocnode['maxdepth'] = -1
tocnode['glob'] = None
nodes.append(autosummary_toc('', '', tocnode))
nodes.append(autosummary_toc('', '', tocnode))
return nodes
def get_items(self, names):
# type: (List[str]) -> List[Tuple[str, str, str, str]]
def get_items(self, names: List[str]) -> List[Tuple[str, str, str, str]]:
"""Try to import the given names, and return a list of
``[(name, signature, summary_string, real_name), ...]``.
"""
@ -353,8 +349,7 @@ class Autosummary(SphinxDirective):
return items
def get_table(self, items):
# type: (List[Tuple[str, str, str, str]]) -> List[nodes.Node]
def get_table(self, items: List[Tuple[str, str, str, str]]) -> List[Node]:
"""Generate a proper list of table nodes for autosummary:: directive.
*items* is a list produced by :meth:`get_items`.
@ -372,8 +367,7 @@ class Autosummary(SphinxDirective):
body = nodes.tbody('')
group.append(body)
def append_row(*column_texts):
# type: (str) -> None
def append_row(*column_texts: str) -> None:
row = nodes.row('')
source, line = self.state_machine.get_source_and_line()
for text in column_texts:
@ -401,42 +395,36 @@ class Autosummary(SphinxDirective):
return [table_spec, table]
def warn(self, msg):
# type: (str) -> None
def warn(self, msg: str) -> None:
warnings.warn('Autosummary.warn() is deprecated',
RemovedInSphinx40Warning, stacklevel=2)
logger.warning(msg)
@property
def genopt(self):
# type: () -> Options
def genopt(self) -> Options:
warnings.warn('Autosummary.genopt is deprecated',
RemovedInSphinx40Warning, stacklevel=2)
return self.bridge.genopt
@property
def warnings(self):
# type: () -> List[nodes.Node]
def warnings(self) -> List[Node]:
warnings.warn('Autosummary.warnings is deprecated',
RemovedInSphinx40Warning, stacklevel=2)
return []
@property
def result(self):
# type: () -> StringList
def result(self) -> StringList:
warnings.warn('Autosummary.result is deprecated',
RemovedInSphinx40Warning, stacklevel=2)
return self.bridge.result
def strip_arg_typehint(s):
# type: (str) -> str
def strip_arg_typehint(s: str) -> str:
"""Strip a type hint from argument definition."""
return s.split(':')[0].strip()
def mangle_signature(sig, max_chars=30):
# type: (str, int) -> str
def mangle_signature(sig: str, max_chars: int = 30) -> str:
"""Reformat a function signature to a more compact form."""
# Strip return type annotation
s = re.sub(r"\)\s*->\s.*$", ")", sig)
@ -493,8 +481,7 @@ def mangle_signature(sig, max_chars=30):
return "(%s)" % sig
def extract_summary(doc, document):
# type: (List[str], Any) -> str
def extract_summary(doc: List[str], document: Any) -> str:
"""Extract summary from docstring."""
# Skip a blank lines at the top
@ -542,8 +529,8 @@ def extract_summary(doc, document):
return summary
def limited_join(sep, items, max_chars=30, overflow_marker="..."):
# type: (str, List[str], int, str) -> str
def limited_join(sep: str, items: List[str], max_chars: int = 30,
overflow_marker: str = "...") -> str:
"""Join a number of strings to one, limiting the length to *max_chars*.
If the string overflows this limit, replace the last fitting item by
@ -569,8 +556,7 @@ def limited_join(sep, items, max_chars=30, overflow_marker="..."):
# -- Importing items -----------------------------------------------------------
def get_import_prefixes_from_env(env):
# type: (BuildEnvironment) -> List[str]
def get_import_prefixes_from_env(env: BuildEnvironment) -> List[str]:
"""
Obtain current Python import prefixes (for `import_by_name`)
from ``document.env``
@ -591,8 +577,7 @@ def get_import_prefixes_from_env(env):
return prefixes
def import_by_name(name, prefixes=[None]):
# type: (str, List[str]) -> Tuple[str, Any, Any, str]
def import_by_name(name: str, prefixes: List[str] = [None]) -> Tuple[str, Any, Any, str]:
"""Import a Python object that has the given *name*, under one of the
*prefixes*. The first name that succeeds is used.
"""
@ -610,8 +595,7 @@ def import_by_name(name, prefixes=[None]):
raise ImportError('no module named %s' % ' or '.join(tried))
def _import_by_name(name):
# type: (str) -> Tuple[Any, Any, str]
def _import_by_name(name: str) -> Tuple[Any, Any, str]:
"""Import a Python object given its full name."""
try:
name_parts = name.split('.')
@ -654,8 +638,9 @@ def _import_by_name(name):
# -- :autolink: (smart default role) -------------------------------------------
def autolink_role(typ, rawtext, etext, lineno, inliner, options={}, content=[]):
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
def autolink_role(typ: str, rawtext: str, etext: str, lineno: int, inliner: Inliner,
options: Dict = {}, content: List[str] = []
) -> Tuple[List[Node], List[system_message]]:
"""Smart linking role.
Expands to ':obj:`text`' if `text` is an object that can be imported;
@ -686,8 +671,7 @@ class AutoLink(SphinxRole):
Expands to ':obj:`text`' if `text` is an object that can be imported;
otherwise expands to '*text*'.
"""
def run(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
def run(self) -> Tuple[List[Node], List[system_message]]:
pyobj_role = self.env.get_domain('py').role('obj')
objects, errors = pyobj_role('obj', self.rawtext, self.text, self.lineno,
self.inliner, self.options, self.content)
@ -708,10 +692,8 @@ class AutoLink(SphinxRole):
return objects, errors
def get_rst_suffix(app):
# type: (Sphinx) -> str
def get_supported_format(suffix):
# type: (str) -> Tuple[str, ...]
def get_rst_suffix(app: Sphinx) -> str:
def get_supported_format(suffix: str) -> Tuple[str, ...]:
parser_class = app.registry.get_source_parsers().get(suffix)
if parser_class is None:
return ('restructuredtext',)
@ -727,30 +709,34 @@ def get_rst_suffix(app):
return None
def process_generate_options(app):
# type: (Sphinx) -> None
def process_generate_options(app: Sphinx) -> None:
genfiles = app.config.autosummary_generate
if genfiles and not hasattr(genfiles, '__len__'):
if genfiles is True:
env = app.builder.env
genfiles = [env.doc2path(x, base=None) for x in env.found_docs
if os.path.isfile(env.doc2path(x))]
else:
ext = list(app.config.source_suffix)
genfiles = [genfile + (not genfile.endswith(tuple(ext)) and ext[0] or '')
for genfile in genfiles]
for entry in genfiles[:]:
if not path.isfile(path.join(app.srcdir, entry)):
logger.warning(__('autosummary_generate: file not found: %s'), entry)
genfiles.remove(entry)
if not genfiles:
return
from sphinx.ext.autosummary.generate import generate_autosummary_docs
ext = list(app.config.source_suffix)
genfiles = [genfile + (not genfile.endswith(tuple(ext)) and ext[0] or '')
for genfile in genfiles]
suffix = get_rst_suffix(app)
if suffix is None:
logger.warning(__('autosummary generats .rst files internally. '
'But your source_suffix does not contain .rst. Skipped.'))
return
from sphinx.ext.autosummary.generate import generate_autosummary_docs
imported_members = app.config.autosummary_imported_members
with mock(app.config.autosummary_mock_imports):
generate_autosummary_docs(genfiles, builder=app.builder,
@ -758,8 +744,7 @@ def process_generate_options(app):
app=app, imported_members=imported_members)
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: Sphinx) -> Dict[str, Any]:
# I need autodoc
app.setup_extension('sphinx.ext.autodoc')
app.add_node(autosummary_toc,

View File

@ -24,6 +24,7 @@ import pydoc
import re
import sys
import warnings
from typing import Any, Callable, Dict, List, Set, Tuple, Type
from jinja2 import BaseLoader, FileSystemLoader, TemplateNotFound
from jinja2.sandbox import SandboxedEnvironment
@ -31,7 +32,9 @@ from jinja2.sandbox import SandboxedEnvironment
import sphinx.locale
from sphinx import __display_version__
from sphinx import package_dir
from sphinx.builders import Builder
from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.ext.autodoc import Documenter
from sphinx.ext.autosummary import import_by_name, get_documenter
from sphinx.jinja2glue import BuiltinTemplateLoader
from sphinx.locale import __
@ -41,12 +44,6 @@ from sphinx.util import rst
from sphinx.util.inspect import safe_getattr
from sphinx.util.osutil import ensuredir
if False:
# For type annotation
from typing import Any, Callable, Dict, List, Set, Tuple, Type, Union # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.ext.autodoc import Documenter # NOQA
logger = logging.getLogger(__name__)
@ -54,15 +51,13 @@ logger = logging.getLogger(__name__)
class DummyApplication:
"""Dummy Application class for sphinx-autogen command."""
def __init__(self):
# type: () -> None
def __init__(self) -> None:
self.registry = SphinxComponentRegistry()
self.messagelog = [] # type: List[str]
self.verbosity = 0
def setup_documenters(app):
# type: (Any) -> None
def setup_documenters(app: Any) -> None:
from sphinx.ext.autodoc import (
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
@ -79,18 +74,15 @@ def setup_documenters(app):
app.registry.add_documenter(documenter.objtype, documenter)
def _simple_info(msg):
# type: (str) -> None
def _simple_info(msg: str) -> None:
print(msg)
def _simple_warn(msg):
# type: (str) -> None
def _simple_warn(msg: str) -> None:
print('WARNING: ' + msg, file=sys.stderr)
def _underline(title, line='='):
# type: (str, str) -> str
def _underline(title: str, line: str = '=') -> str:
if '\n' in title:
raise ValueError('Can only underline single lines')
return title + '\n' + line * len(title)
@ -99,8 +91,7 @@ def _underline(title, line='='):
class AutosummaryRenderer:
"""A helper class for rendering."""
def __init__(self, builder, template_dir):
# type: (Builder, str) -> None
def __init__(self, builder: Builder, template_dir: str) -> None:
loader = None # type: BaseLoader
template_dirs = [os.path.join(package_dir, 'ext', 'autosummary', 'templates')]
if builder is None:
@ -117,8 +108,7 @@ class AutosummaryRenderer:
self.env.filters['e'] = rst.escape
self.env.filters['underline'] = _underline
def exists(self, template_name):
# type: (str) -> bool
def exists(self, template_name: str) -> bool:
"""Check if template file exists."""
try:
self.env.get_template(template_name)
@ -126,38 +116,107 @@ class AutosummaryRenderer:
except TemplateNotFound:
return False
def render(self, template_name, context):
# type: (str, Dict) -> str
def render(self, template_name: str, context: Dict) -> str:
"""Render a template file."""
return self.env.get_template(template_name).render(context)
# -- Generating output ---------------------------------------------------------
def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
warn=None, info=None, base_path=None, builder=None,
template_dir=None, imported_members=False, app=None):
# type: (List[str], str, str, Callable, Callable, str, Builder, str, bool, Any) -> None
def generate_autosummary_content(name: str, obj: Any, parent: Any,
template: AutosummaryRenderer, template_name: str,
imported_members: bool, app: Any) -> str:
doc = get_documenter(app, obj, parent)
if template_name is None:
template_name = 'autosummary/%s.rst' % doc.objtype
if not template.exists(template_name):
template_name = 'autosummary/base.rst'
def get_members(obj: Any, types: Set[str], include_public: List[str] = [],
imported: bool = True) -> Tuple[List[str], List[str]]:
items = [] # type: List[str]
for name in dir(obj):
try:
value = safe_getattr(obj, name)
except AttributeError:
continue
documenter = get_documenter(app, value, obj)
if documenter.objtype in types:
if imported or getattr(value, '__module__', None) == obj.__name__:
# skip imported members if expected
items.append(name)
public = [x for x in items
if x in include_public or not x.startswith('_')]
return public, items
ns = {} # type: Dict[str, Any]
if doc.objtype == 'module':
ns['members'] = dir(obj)
ns['functions'], ns['all_functions'] = \
get_members(obj, {'function'}, imported=imported_members)
ns['classes'], ns['all_classes'] = \
get_members(obj, {'class'}, imported=imported_members)
ns['exceptions'], ns['all_exceptions'] = \
get_members(obj, {'exception'}, imported=imported_members)
elif doc.objtype == 'class':
ns['members'] = dir(obj)
ns['inherited_members'] = \
set(dir(obj)) - set(obj.__dict__.keys())
ns['methods'], ns['all_methods'] = \
get_members(obj, {'method'}, ['__init__'])
ns['attributes'], ns['all_attributes'] = \
get_members(obj, {'attribute', 'property'})
parts = name.split('.')
if doc.objtype in ('method', 'attribute', 'property'):
mod_name = '.'.join(parts[:-2])
cls_name = parts[-2]
obj_name = '.'.join(parts[-2:])
ns['class'] = cls_name
else:
mod_name, obj_name = '.'.join(parts[:-1]), parts[-1]
ns['fullname'] = name
ns['module'] = mod_name
ns['objname'] = obj_name
ns['name'] = parts[-1]
ns['objtype'] = doc.objtype
ns['underline'] = len(name) * '='
return template.render(template_name, ns)
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:
if info:
warnings.warn('info argument for generate_autosummary_docs() is deprecated.',
RemovedInSphinx40Warning)
_info = info
else:
info = logger.info
_info = logger.info
if warn:
warnings.warn('warn argument for generate_autosummary_docs() is deprecated.',
RemovedInSphinx40Warning)
_warn = warn
else:
warn = logger.warning
_warn = logger.warning
showed_sources = list(sorted(sources))
if len(showed_sources) > 20:
showed_sources = showed_sources[:10] + ['...'] + showed_sources[-10:]
info(__('[autosummary] generating autosummary for: %s') %
', '.join(showed_sources))
_info(__('[autosummary] generating autosummary for: %s') %
', '.join(showed_sources))
if output_dir:
info(__('[autosummary] writing to %s') % output_dir)
_info(__('[autosummary] writing to %s') % output_dir)
if base_path is not None:
sources = [os.path.join(base_path, filename) for filename in sources]
@ -183,7 +242,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
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)
@ -195,67 +254,9 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
new_files.append(fn)
with open(fn, 'w') as f:
doc = get_documenter(app, obj, parent)
if template_name is None:
template_name = 'autosummary/%s.rst' % doc.objtype
if not template.exists(template_name):
template_name = 'autosummary/base.rst'
def get_members(obj, types, include_public=[], imported=True):
# type: (Any, Set[str], List[str], bool) -> Tuple[List[str], List[str]] # NOQA
items = [] # type: List[str]
for name in dir(obj):
try:
value = safe_getattr(obj, name)
except AttributeError:
continue
documenter = get_documenter(app, value, obj)
if documenter.objtype in types:
if imported or getattr(value, '__module__', None) == obj.__name__:
# skip imported members if expected
items.append(name)
public = [x for x in items
if x in include_public or not x.startswith('_')]
return public, items
ns = {} # type: Dict[str, Any]
if doc.objtype == 'module':
ns['members'] = dir(obj)
ns['functions'], ns['all_functions'] = \
get_members(obj, {'function'}, imported=imported_members)
ns['classes'], ns['all_classes'] = \
get_members(obj, {'class'}, imported=imported_members)
ns['exceptions'], ns['all_exceptions'] = \
get_members(obj, {'exception'}, imported=imported_members)
elif doc.objtype == 'class':
ns['members'] = dir(obj)
ns['inherited_members'] = \
set(dir(obj)) - set(obj.__dict__.keys())
ns['methods'], ns['all_methods'] = \
get_members(obj, {'method'}, ['__init__'])
ns['attributes'], ns['all_attributes'] = \
get_members(obj, {'attribute', 'property'})
parts = name.split('.')
if doc.objtype in ('method', 'attribute', 'property'):
mod_name = '.'.join(parts[:-2])
cls_name = parts[-2]
obj_name = '.'.join(parts[-2:])
ns['class'] = cls_name
else:
mod_name, obj_name = '.'.join(parts[:-1]), parts[-1]
ns['fullname'] = name
ns['module'] = mod_name
ns['objname'] = obj_name
ns['name'] = parts[-1]
ns['objtype'] = doc.objtype
ns['underline'] = len(name) * '='
rendered = template.render(template_name, ns)
rendered = generate_autosummary_content(name, obj, parent,
template, template_name,
imported_members, app)
f.write(rendered)
# descend recursively to new files
@ -268,8 +269,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
# -- Finding documented entries in files ---------------------------------------
def find_autosummary_in_files(filenames):
# type: (List[str]) -> List[Tuple[str, str, str]]
def find_autosummary_in_files(filenames: List[str]) -> List[Tuple[str, str, str]]:
"""Find out what items are documented in source/*.rst.
See `find_autosummary_in_lines`.
@ -282,8 +282,8 @@ def find_autosummary_in_files(filenames):
return documented
def find_autosummary_in_docstring(name, module=None, filename=None):
# type: (str, Any, str) -> List[Tuple[str, str, str]]
def find_autosummary_in_docstring(name: str, module: Any = None, filename: str = None
) -> List[Tuple[str, str, str]]:
"""Find out what items are documented in the given object's docstring.
See `find_autosummary_in_lines`.
@ -302,8 +302,8 @@ def find_autosummary_in_docstring(name, module=None, filename=None):
return []
def find_autosummary_in_lines(lines, module=None, filename=None):
# type: (List[str], Any, str) -> List[Tuple[str, str, str]]
def find_autosummary_in_lines(lines: List[str], module: Any = None, filename: str = None
) -> List[Tuple[str, str, str]]:
"""Find out what items appear in autosummary:: directives in the
given lines.
@ -389,8 +389,7 @@ def find_autosummary_in_lines(lines, module=None, filename=None):
return documented
def get_parser():
# type: () -> argparse.ArgumentParser
def get_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
usage='%(prog)s [OPTIONS] <SOURCE_FILE>...',
epilog=__('For more information, visit <http://sphinx-doc.org/>.'),
@ -432,8 +431,7 @@ The format of the autosummary directive is documented in the
return parser
def main(argv=sys.argv[1:]):
# type: (List[str]) -> None
def main(argv: List[str] = sys.argv[1:]) -> None:
sphinx.locale.setlocale(locale.LC_ALL, '')
sphinx.locale.init_console(os.path.join(package_dir, 'locale'), 'sphinx')

View File

@ -14,30 +14,25 @@ import inspect
import pickle
import re
from os import path
from typing import Any, Dict, IO, List, Pattern, Set, Tuple
import sphinx
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.locale import __
from sphinx.util import logging
from sphinx.util.inspect import safe_getattr
if False:
# For type annotation
from typing import Any, Dict, IO, List, Pattern, Set, Tuple # NOQA
from sphinx.application import Sphinx # NOQA
logger = logging.getLogger(__name__)
# utility
def write_header(f, text, char='-'):
# type:(IO, str, str) -> None
def write_header(f: IO, text: str, char: str = '-') -> None:
f.write(text + '\n')
f.write(char * len(text) + '\n')
def compile_regex_list(name, exps):
# type: (str, str) -> List[Pattern]
def compile_regex_list(name: str, exps: str) -> List[Pattern]:
lst = []
for exp in exps:
try:
@ -55,8 +50,7 @@ class CoverageBuilder(Builder):
epilog = __('Testing of coverage in the sources finished, look at the '
'results in %(outdir)s' + path.sep + 'python.txt.')
def init(self):
# type: () -> None
def init(self) -> None:
self.c_sourcefiles = [] # type: List[str]
for pattern in self.config.coverage_c_path:
pattern = path.join(self.srcdir, pattern)
@ -82,12 +76,10 @@ class CoverageBuilder(Builder):
self.py_ignorexps = compile_regex_list('coverage_ignore_pyobjects',
self.config.coverage_ignore_pyobjects)
def get_outdated_docs(self):
# type: () -> str
def get_outdated_docs(self) -> str:
return 'coverage overview'
def write(self, *ignored):
# type: (Any) -> None
def write(self, *ignored) -> None:
self.py_undoc = {} # type: Dict[str, Dict[str, Any]]
self.build_py_coverage()
self.write_py_coverage()
@ -96,8 +88,7 @@ class CoverageBuilder(Builder):
self.build_c_coverage()
self.write_c_coverage()
def build_c_coverage(self):
# type: () -> None
def build_c_coverage(self) -> None:
# Fetch all the info from the header files
c_objects = self.env.domaindata['c']['objects']
for filename in self.c_sourcefiles:
@ -118,8 +109,7 @@ class CoverageBuilder(Builder):
if undoc:
self.c_undoc[filename] = undoc
def write_c_coverage(self):
# type: () -> None
def write_c_coverage(self) -> None:
output_file = path.join(self.outdir, 'c.txt')
with open(output_file, 'w') as op:
if self.config.coverage_write_headline:
@ -138,8 +128,7 @@ class CoverageBuilder(Builder):
return True
return False
def build_py_coverage(self):
# type: () -> None
def build_py_coverage(self) -> None:
objects = self.env.domaindata['py']['objects']
modules = self.env.domaindata['py']['modules']
@ -230,8 +219,7 @@ class CoverageBuilder(Builder):
self.py_undoc[mod_name] = {'funcs': funcs, 'classes': classes}
def write_py_coverage(self):
# type: () -> None
def write_py_coverage(self) -> None:
output_file = path.join(self.outdir, 'python.txt')
failed = []
with open(output_file, 'w') as op:
@ -266,16 +254,14 @@ class CoverageBuilder(Builder):
write_header(op, 'Modules that failed to import')
op.writelines(' * %s -- %s\n' % x for x in failed)
def finish(self):
# type: () -> None
def finish(self) -> None:
# dump the coverage data to a pickle file too
picklepath = path.join(self.outdir, 'undoc.pickle')
with open(picklepath, 'wb') as dumpfile:
pickle.dump((self.py_undoc, self.c_undoc), dumpfile)
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_builder(CoverageBuilder)
app.add_config_value('coverage_ignore_modules', [], False)
app.add_config_value('coverage_ignore_functions', [], False)

View File

@ -16,8 +16,10 @@ import time
import warnings
from io import StringIO
from os import path
from typing import Any, Callable, Dict, Iterable, List, Sequence, Set, Tuple, Type
from docutils import nodes
from docutils.nodes import Element, Node, TextElement
from docutils.parsers.rst import directives
from packaging.specifiers import SpecifierSet, InvalidSpecifier
from packaging.version import Version
@ -33,8 +35,8 @@ from sphinx.util.osutil import relpath
if False:
# For type annotation
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Set, Tuple, Type # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.application import Sphinx
logger = logging.getLogger(__name__)
@ -42,15 +44,13 @@ blankline_re = re.compile(r'^\s*<BLANKLINE>', re.MULTILINE)
doctestopt_re = re.compile(r'#\s*doctest:.+$', re.MULTILINE)
def doctest_encode(text, encoding):
# type: (str, str) -> str
def doctest_encode(text: str, encoding: str) -> str:
warnings.warn('doctest_encode() is deprecated.',
RemovedInSphinx40Warning)
return text
def is_allowed_version(spec, version):
# type: (str, str) -> bool
def is_allowed_version(spec: str, version: str) -> bool:
"""Check `spec` satisfies `version` or not.
This obeys PEP-440 specifiers:
@ -80,8 +80,7 @@ class TestDirective(SphinxDirective):
optional_arguments = 1
final_argument_whitespace = True
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
# use ordinary docutils nodes for test code: they get special attributes
# so that our builder recognizes them, and the other builders are happy.
code = '\n'.join(self.content)
@ -95,7 +94,7 @@ class TestDirective(SphinxDirective):
if not test:
test = code
code = doctestopt_re.sub('', code)
nodetype = nodes.literal_block # type: Type[nodes.TextElement]
nodetype = nodes.literal_block # type: Type[TextElement]
if self.name in ('testsetup', 'testcleanup') or 'hide' in self.options:
nodetype = nodes.comment
if self.arguments:
@ -194,15 +193,13 @@ parser = doctest.DocTestParser()
# helper classes
class TestGroup:
def __init__(self, name):
# type: (str) -> None
def __init__(self, name: str) -> None:
self.name = name
self.setup = [] # type: List[TestCode]
self.tests = [] # type: List[List[TestCode]]
self.cleanup = [] # type: List[TestCode]
def add_code(self, code, prepend=False):
# type: (TestCode, bool) -> None
def add_code(self, code: "TestCode", prepend: bool = False) -> None:
if code.type == 'testsetup':
if prepend:
self.setup.insert(0, code)
@ -220,30 +217,28 @@ class TestGroup:
else:
raise RuntimeError(__('invalid TestCode type'))
def __repr__(self):
# type: () -> str
def __repr__(self) -> str:
return 'TestGroup(name=%r, setup=%r, cleanup=%r, tests=%r)' % (
self.name, self.setup, self.cleanup, self.tests)
class TestCode:
def __init__(self, code, type, filename, lineno, options=None):
# type: (str, str, Optional[str], int, Optional[Dict]) -> None
def __init__(self, code: str, type: str, filename: str,
lineno: int, options: Dict = None) -> None:
self.code = code
self.type = type
self.filename = filename
self.lineno = lineno
self.options = options or {}
def __repr__(self):
# type: () -> str
def __repr__(self) -> str:
return 'TestCode(%r, %r, filename=%r, lineno=%r, options=%r)' % (
self.code, self.type, self.filename, self.lineno, self.options)
class SphinxDocTestRunner(doctest.DocTestRunner):
def summarize(self, out, verbose=None): # type: ignore
# type: (Callable, bool) -> Tuple[int, int]
def summarize(self, out: Callable, verbose: bool = None # type: ignore
) -> Tuple[int, int]:
string_io = StringIO()
old_stdout = sys.stdout
sys.stdout = string_io
@ -254,9 +249,8 @@ class SphinxDocTestRunner(doctest.DocTestRunner):
out(string_io.getvalue())
return res
def _DocTestRunner__patched_linecache_getlines(self, filename,
module_globals=None):
# type: (str, Any) -> Any
def _DocTestRunner__patched_linecache_getlines(self, filename: str,
module_globals: Any = None) -> Any:
# this is overridden from DocTestRunner adding the try-except below
m = self._DocTestRunner__LINECACHE_FILENAME_RE.match(filename) # type: ignore
if m and m.group('name') == self.test.name:
@ -282,8 +276,7 @@ class DocTestBuilder(Builder):
epilog = __('Testing of doctests in the sources finished, look at the '
'results in %(outdir)s/output.txt.')
def init(self):
# type: () -> None
def init(self) -> None:
# default options
self.opt = self.config.doctest_default_flags
@ -312,32 +305,26 @@ class DocTestBuilder(Builder):
'==================================%s\n') %
(date, '=' * len(date)))
def _out(self, text):
# type: (str) -> None
def _out(self, text: str) -> None:
logger.info(text, nonl=True)
self.outfile.write(text)
def _warn_out(self, text):
# type: (str) -> None
def _warn_out(self, text: str) -> None:
if self.app.quiet or self.app.warningiserror:
logger.warning(text)
else:
logger.info(text, nonl=True)
self.outfile.write(text)
def get_target_uri(self, docname, typ=None):
# type: (str, str) -> str
def get_target_uri(self, docname: str, typ: str = None) -> str:
return ''
def get_outdated_docs(self):
# type: () -> Set[str]
def get_outdated_docs(self) -> Set[str]:
return self.env.found_docs
def finish(self):
# type: () -> None
def finish(self) -> None:
# write executive summary
def s(v):
# type: (int) -> str
def s(v: int) -> str:
return v != 1 and 's' or ''
repl = (self.total_tries, s(self.total_tries),
self.total_failures, s(self.total_failures),
@ -356,8 +343,8 @@ Doctest summary
if self.total_failures or self.setup_failures or self.cleanup_failures:
self.app.statuscode = 1
def write(self, build_docnames, updated_docnames, method='update'):
# type: (Iterable[str], Sequence[str], str) -> None
def write(self, build_docnames: Iterable[str], updated_docnames: Sequence[str],
method: str = 'update') -> None:
if build_docnames is None:
build_docnames = sorted(self.env.all_docs)
@ -367,8 +354,7 @@ Doctest summary
doctree = self.env.get_doctree(docname)
self.test_doc(docname, doctree)
def get_filename_for_node(self, node, docname):
# type: (nodes.Node, str) -> str
def get_filename_for_node(self, node: Node, docname: str) -> str:
"""Try to get the file which actually contains the doctest, not the
filename of the document it's included in."""
try:
@ -379,8 +365,7 @@ Doctest summary
return filename
@staticmethod
def get_line_number(node):
# type: (nodes.Node) -> Optional[int]
def get_line_number(node: Node) -> int:
"""Get the real line number or admit we don't know."""
# TODO: Work out how to store or calculate real (file-relative)
# line numbers for doctest blocks in docstrings.
@ -395,8 +380,7 @@ Doctest summary
return node.line - 1
return None
def skipped(self, node):
# type: (nodes.Element) -> bool
def skipped(self, node: Element) -> bool:
if 'skipif' not in node:
return False
else:
@ -409,8 +393,7 @@ Doctest summary
exec(self.config.doctest_global_cleanup, context)
return should_skip
def test_doc(self, docname, doctree):
# type: (str, nodes.Node) -> None
def test_doc(self, docname: str, doctree: Node) -> None:
groups = {} # type: Dict[str, TestGroup]
add_to_all_groups = []
self.setup_runner = SphinxDocTestRunner(verbose=False,
@ -424,17 +407,15 @@ Doctest summary
self.cleanup_runner._fakeout = self.setup_runner._fakeout # type: ignore
if self.config.doctest_test_doctest_blocks:
def condition(node):
# type: (nodes.Node) -> bool
def condition(node: Node) -> bool:
return (isinstance(node, (nodes.literal_block, nodes.comment)) and
'testnodetype' in node) or \
isinstance(node, nodes.doctest_block)
else:
def condition(node):
# type: (nodes.Node) -> bool
def condition(node: Node) -> bool:
return isinstance(node, (nodes.literal_block, nodes.comment)) \
and 'testnodetype' in node
for node in doctree.traverse(condition): # type: nodes.Element
for node in doctree.traverse(condition): # type: Element
if self.skipped(node):
continue
@ -490,16 +471,13 @@ Doctest summary
self.cleanup_failures += res_f
self.cleanup_tries += res_t
def compile(self, code, name, type, flags, dont_inherit):
# type: (str, str, str, Any, bool) -> Any
def compile(self, code: str, name: str, type: str, flags: Any, dont_inherit: bool) -> Any:
return compile(code, name, self.type, flags, dont_inherit)
def test_group(self, group):
# type: (TestGroup) -> None
def test_group(self, group: TestGroup) -> None:
ns = {} # type: Dict
def run_setup_cleanup(runner, testcodes, what):
# type: (Any, List[TestCode], Any) -> bool
def run_setup_cleanup(runner: Any, testcodes: List[TestCode], what: Any) -> bool:
examples = []
for testcode in testcodes:
example = doctest.Example(testcode.code, '', lineno=testcode.lineno)
@ -568,8 +546,7 @@ Doctest summary
run_setup_cleanup(self.cleanup_runner, group.cleanup, 'cleanup')
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_directive('testsetup', TestsetupDirective)
app.add_directive('testcleanup', TestcleanupDirective)
app.add_directive('doctest', DoctestDirective)

View File

@ -23,23 +23,22 @@
:license: BSD, see LICENSE for details.
"""
from typing import Any, Dict, List, Tuple
from docutils import nodes, utils
from docutils.nodes import Node, system_message
from docutils.parsers.rst.states import Inliner
import sphinx
from sphinx.application import Sphinx
from sphinx.util.nodes import split_explicit_title
if False:
# For type annotation
from typing import Any, Dict, List, Tuple # NOQA
from docutils.parsers.rst.states import Inliner # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.util.typing import RoleFunction # NOQA
from sphinx.util.typing import RoleFunction
def make_link_role(base_url, prefix):
# type: (str, str) -> RoleFunction
def role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
def make_link_role(base_url: str, prefix: str) -> RoleFunction:
def role(typ: str, rawtext: str, text: str, lineno: int,
inliner: Inliner, options: Dict = {}, content: List[str] = []
) -> Tuple[List[Node], List[system_message]]:
text = utils.unescape(text)
has_explicit_title, title, part = split_explicit_title(text)
try:
@ -60,14 +59,12 @@ def make_link_role(base_url, prefix):
return role
def setup_link_roles(app):
# type: (Sphinx) -> None
def setup_link_roles(app: Sphinx) -> None:
for name, (base_url, prefix) in app.config.extlinks.items():
app.add_role(name, make_link_role(base_url, prefix))
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('extlinks', {}, 'env')
app.connect('builder-inited', setup_link_roles)
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}

View File

@ -10,18 +10,14 @@
import os
import urllib
from typing import Any, Dict
import sphinx
if False:
# For type annotation
from typing import Any, Dict # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
def create_nojekyll_and_cname(app, env):
# type: (Sphinx, BuildEnvironment) -> None
def create_nojekyll_and_cname(app: Sphinx, env: BuildEnvironment) -> None:
if app.builder.format == 'html':
open(os.path.join(app.builder.outdir, '.nojekyll'), 'wt').close()
@ -35,7 +31,6 @@ def create_nojekyll_and_cname(app, env):
f.write(domain)
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: Sphinx) -> Dict[str, Any]:
app.connect('env-updated', create_nojekyll_and_cname)
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}

View File

@ -15,31 +15,27 @@ import subprocess
from hashlib import sha1
from os import path
from subprocess import CalledProcessError, PIPE
from typing import Any, Dict, List, Tuple
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.nodes import Node
from docutils.parsers.rst import Directive, directives
import sphinx
from sphinx.application import Sphinx
from sphinx.errors import SphinxError
from sphinx.locale import _, __
from sphinx.util import logging
from sphinx.util.docutils import SphinxDirective
from sphinx.util.docutils import SphinxDirective, SphinxTranslator
from sphinx.util.fileutil import copy_asset
from sphinx.util.i18n import search_image_for_language
from sphinx.util.nodes import set_source_info
from sphinx.util.osutil import ensuredir
if False:
# For type annotation
from docutils.parsers.rst import Directive # NOQA
from typing import Any, Dict, List, Tuple # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.util.docutils import SphinxTranslator # NOQA
from sphinx.writers.html import HTMLTranslator # NOQA
from sphinx.writers.latex import LaTeXTranslator # NOQA
from sphinx.writers.manpage import ManualPageTranslator # NOQA
from sphinx.writers.texinfo import TexinfoTranslator # NOQA
from sphinx.writers.text import TextTranslator # NOQA
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
logger = logging.getLogger(__name__)
@ -53,8 +49,7 @@ class ClickableMapDefinition:
maptag_re = re.compile('<map id="(.*?)"')
href_re = re.compile('href=".*?"')
def __init__(self, filename, content, dot=''):
# type: (str, str, str) -> None
def __init__(self, filename: str, content: str, dot: str = '') -> None:
self.id = None # type: str
self.filename = filename
self.content = content.splitlines()
@ -62,8 +57,7 @@ class ClickableMapDefinition:
self.parse(dot=dot)
def parse(self, dot=None):
# type: (str) -> None
def parse(self, dot: str = None) -> None:
matched = self.maptag_re.match(self.content[0])
if not matched:
raise GraphvizError('Invalid clickable map file found: %s' % self.filename)
@ -80,8 +74,7 @@ class ClickableMapDefinition:
if self.href_re.search(line):
self.clickable.append(line)
def generate_clickable_map(self):
# type: () -> str
def generate_clickable_map(self) -> str:
"""Generate clickable map tags if clickable item exists.
If not exists, this only returns empty string.
@ -96,8 +89,7 @@ class graphviz(nodes.General, nodes.Inline, nodes.Element):
pass
def figure_wrapper(directive, node, caption):
# type: (Directive, graphviz, str) -> nodes.figure
def figure_wrapper(directive: Directive, node: graphviz, caption: str) -> nodes.figure:
figure_node = nodes.figure('', node)
if 'align' in node:
figure_node['align'] = node.attributes.pop('align')
@ -110,8 +102,7 @@ def figure_wrapper(directive, node, caption):
return figure_node
def align_spec(argument):
# type: (Any) -> str
def align_spec(argument: Any) -> str:
return directives.choice(argument, ('left', 'center', 'right'))
@ -132,8 +123,7 @@ class Graphviz(SphinxDirective):
'name': directives.unchanged,
}
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
if self.arguments:
document = self.state.document
if self.content:
@ -194,8 +184,7 @@ class GraphvizSimple(SphinxDirective):
'name': directives.unchanged,
}
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
node = graphviz()
node['code'] = '%s %s {\n%s\n}\n' % \
(self.name, self.arguments[0], '\n'.join(self.content))
@ -216,8 +205,8 @@ class GraphvizSimple(SphinxDirective):
return [figure]
def render_dot(self, code, options, format, prefix='graphviz'):
# type: (SphinxTranslator, str, Dict, str, str) -> Tuple[str, str]
def render_dot(self: SphinxTranslator, code: str, options: Dict,
format: str, prefix: str = 'graphviz') -> Tuple[str, str]:
"""Render graphviz code into a PNG or PDF output file."""
graphviz_dot = options.get('graphviz_dot', self.builder.config.graphviz_dot)
hashkey = (code + str(options) + str(graphviz_dot) +
@ -265,9 +254,9 @@ def render_dot(self, code, options, format, prefix='graphviz'):
'[stdout]\n%r') % (exc.stderr, exc.stdout))
def render_dot_html(self, node, code, options, prefix='graphviz',
imgcls=None, alt=None):
# type: (HTMLTranslator, graphviz, str, Dict, str, str, str) -> Tuple[str, str]
def render_dot_html(self: HTMLTranslator, node: graphviz, code: str, options: Dict,
prefix: str = 'graphviz', imgcls: str = None, alt: str = None
) -> Tuple[str, str]:
format = self.builder.config.graphviz_output_format
try:
if format not in ('png', 'svg'):
@ -319,13 +308,12 @@ def render_dot_html(self, node, code, options, prefix='graphviz',
raise nodes.SkipNode
def html_visit_graphviz(self, node):
# type: (HTMLTranslator, graphviz) -> None
def html_visit_graphviz(self: HTMLTranslator, node: graphviz) -> None:
render_dot_html(self, node, node['code'], node['options'])
def render_dot_latex(self, node, code, options, prefix='graphviz'):
# type: (LaTeXTranslator, graphviz, str, Dict, str) -> None
def render_dot_latex(self: LaTeXTranslator, node: graphviz, code: str,
options: Dict, prefix: str = 'graphviz') -> None:
try:
fname, outfn = render_dot(self, code, options, 'pdf', prefix)
except GraphvizError as exc:
@ -357,13 +345,12 @@ def render_dot_latex(self, node, code, options, prefix='graphviz'):
raise nodes.SkipNode
def latex_visit_graphviz(self, node):
# type: (LaTeXTranslator, graphviz) -> None
def latex_visit_graphviz(self: LaTeXTranslator, node: graphviz) -> None:
render_dot_latex(self, node, node['code'], node['options'])
def render_dot_texinfo(self, node, code, options, prefix='graphviz'):
# type: (TexinfoTranslator, graphviz, str, Dict, str) -> None
def render_dot_texinfo(self: TexinfoTranslator, node: graphviz, code: str,
options: Dict, prefix: str = 'graphviz') -> None:
try:
fname, outfn = render_dot(self, code, options, 'png', prefix)
except GraphvizError as exc:
@ -374,13 +361,11 @@ def render_dot_texinfo(self, node, code, options, prefix='graphviz'):
raise nodes.SkipNode
def texinfo_visit_graphviz(self, node):
# type: (TexinfoTranslator, graphviz) -> None
def texinfo_visit_graphviz(self: TexinfoTranslator, node: graphviz) -> None:
render_dot_texinfo(self, node, node['code'], node['options'])
def text_visit_graphviz(self, node):
# type: (TextTranslator, graphviz) -> None
def text_visit_graphviz(self: TextTranslator, node: graphviz) -> None:
if 'alt' in node.attributes:
self.add_text(_('[graph: %s]') % node['alt'])
else:
@ -388,8 +373,7 @@ def text_visit_graphviz(self, node):
raise nodes.SkipNode
def man_visit_graphviz(self, node):
# type: (ManualPageTranslator, graphviz) -> None
def man_visit_graphviz(self: ManualPageTranslator, node: graphviz) -> None:
if 'alt' in node.attributes:
self.body.append(_('[graph: %s]') % node['alt'])
else:
@ -397,16 +381,14 @@ def man_visit_graphviz(self, node):
raise nodes.SkipNode
def on_build_finished(app, exc):
# type: (Sphinx, Exception) -> None
def on_build_finished(app: Sphinx, exc: Exception) -> None:
if exc is None:
src = path.join(sphinx.package_dir, 'templates', 'graphviz', 'graphviz.css')
dst = path.join(app.outdir, '_static')
copy_asset(src, dst)
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_node(graphviz,
html=(html_visit_graphviz, None),
latex=(latex_visit_graphviz, None),

View File

@ -19,17 +19,16 @@
:license: BSD, see LICENSE for details.
"""
from typing import Any, Dict, List
from docutils import nodes
from docutils.nodes import Node
import sphinx
from sphinx.application import Sphinx
from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import nested_parse_with_titles
if False:
# For type annotation
from typing import Any, Dict, List # NOQA
from sphinx.application import Sphinx # NOQA
class ifconfig(nodes.Element):
pass
@ -43,8 +42,7 @@ class IfConfig(SphinxDirective):
final_argument_whitespace = True
option_spec = {} # type: Dict
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
node = ifconfig()
node.document = self.state.document
self.set_source_info(node)
@ -53,8 +51,7 @@ class IfConfig(SphinxDirective):
return [node]
def process_ifconfig_nodes(app, doctree, docname):
# type: (Sphinx, nodes.document, str) -> None
def process_ifconfig_nodes(app: Sphinx, doctree: nodes.document, docname: str) -> None:
ns = {confval.name: confval.value for confval in app.config}
ns.update(app.config.__dict__.copy())
ns['builder'] = app.builder.name
@ -76,8 +73,7 @@ def process_ifconfig_nodes(app, doctree, docname):
node.replace_self(node.children)
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_node(ifconfig)
app.add_directive('ifconfig', IfConfig)
app.connect('doctree-resolved', process_ifconfig_nodes)

View File

@ -7,19 +7,17 @@
:copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import subprocess
from subprocess import CalledProcessError, PIPE
from typing import Any, Dict
from sphinx.application import Sphinx
from sphinx.errors import ExtensionError
from sphinx.locale import __
from sphinx.transforms.post_transforms.images import ImageConverter
from sphinx.util import logging
if False:
# For type annotation
from typing import Any, Dict # NOQA
from sphinx.application import Sphinx # NOQA
logger = logging.getLogger(__name__)
@ -31,8 +29,7 @@ class ImagemagickConverter(ImageConverter):
('application/pdf', 'image/png'),
]
def is_available(self):
# type: () -> bool
def is_available(self) -> bool:
"""Confirms the converter is available or not."""
try:
args = [self.config.image_converter, '-version']
@ -50,8 +47,7 @@ class ImagemagickConverter(ImageConverter):
exc.stderr, exc.stdout)
return False
def convert(self, _from, _to):
# type: (str, str) -> bool
def convert(self, _from: str, _to: str) -> bool:
"""Converts the image to expected one."""
try:
# append an index 0 to source filename to pick up the first frame
@ -75,8 +71,7 @@ class ImagemagickConverter(ImageConverter):
(exc.stderr, exc.stdout))
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_post_transform(ImagemagickConverter)
app.add_config_value('image_converter', 'convert', 'env')
app.add_config_value('image_converter_args', [], 'env')

View File

@ -17,11 +17,16 @@ import tempfile
from hashlib import sha1
from os import path
from subprocess import CalledProcessError, PIPE
from typing import Any, Dict, List, Tuple
from docutils import nodes
from docutils.nodes import Element
import sphinx
from sphinx import package_dir
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.config import Config
from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
from sphinx.errors import SphinxError
from sphinx.locale import _, __
@ -30,14 +35,7 @@ from sphinx.util.math import get_node_equation_number, wrap_displaymath
from sphinx.util.osutil import ensuredir
from sphinx.util.png import read_png_depth, write_png_depth
from sphinx.util.template import LaTeXRenderer
if False:
# For type annotation
from typing import Any, Dict, List, Tuple, Union # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.config import Config # NOQA
from sphinx.writers.html import HTMLTranslator # NOQA
from sphinx.writers.html import HTMLTranslator
logger = logging.getLogger(__name__)
@ -47,8 +45,7 @@ templates_path = path.join(package_dir, 'templates', 'imgmath')
class MathExtError(SphinxError):
category = 'Math extension error'
def __init__(self, msg, stderr=None, stdout=None):
# type: (str, bytes, bytes) -> None
def __init__(self, msg: str, stderr: bytes = None, stdout: bytes = None) -> None:
if stderr:
msg += '\n[stderr]\n' + stderr.decode(sys.getdefaultencoding(), 'replace')
if stdout:
@ -92,8 +89,7 @@ DOC_BODY_PREVIEW = r'''
depth_re = re.compile(br'\[\d+ depth=(-?\d+)\]')
def generate_latex_macro(math, config, confdir=''):
# type: (str, Config, str) -> str
def generate_latex_macro(math: str, config: Config, confdir: str = '') -> str:
"""Generate LaTeX macro."""
variables = {
'fontsize': config.imgmath_font_size,
@ -115,8 +111,7 @@ def generate_latex_macro(math, config, confdir=''):
return LaTeXRenderer(templates_path).render(template_name, variables)
def ensure_tempdir(builder):
# type: (Builder) -> str
def ensure_tempdir(builder: Builder) -> str:
"""Create temporary directory.
use only one tempdir per build -- the use of a directory is cleaner
@ -129,8 +124,7 @@ def ensure_tempdir(builder):
return builder._imgmath_tempdir # type: ignore
def compile_math(latex, builder):
# type: (str, Builder) -> str
def compile_math(latex: str, builder: Builder) -> str:
"""Compile LaTeX macros for math to DVI."""
tempdir = ensure_tempdir(builder)
filename = path.join(tempdir, 'math.tex')
@ -157,8 +151,7 @@ def compile_math(latex, builder):
raise MathExtError('latex exited with error', exc.stderr, exc.stdout)
def convert_dvi_to_image(command, name):
# type: (List[str], str) -> Tuple[bytes, bytes]
def convert_dvi_to_image(command: List[str], name: str) -> Tuple[bytes, bytes]:
"""Convert DVI file to specific image format."""
try:
ret = subprocess.run(command, stdout=PIPE, stderr=PIPE, check=True)
@ -172,8 +165,7 @@ def convert_dvi_to_image(command, name):
raise MathExtError('%s exited with error' % name, exc.stderr, exc.stdout)
def convert_dvi_to_png(dvipath, builder):
# type: (str, Builder) -> Tuple[str, int]
def convert_dvi_to_png(dvipath: str, builder: Builder) -> Tuple[str, int]:
"""Convert DVI file to PNG image."""
tempdir = ensure_tempdir(builder)
filename = path.join(tempdir, 'math.png')
@ -199,8 +191,7 @@ def convert_dvi_to_png(dvipath, builder):
return filename, depth
def convert_dvi_to_svg(dvipath, builder):
# type: (str, Builder) -> Tuple[str, int]
def convert_dvi_to_svg(dvipath: str, builder: Builder) -> Tuple[str, int]:
"""Convert DVI file to SVG image."""
tempdir = ensure_tempdir(builder)
filename = path.join(tempdir, 'math.svg')
@ -214,8 +205,7 @@ def convert_dvi_to_svg(dvipath, builder):
return filename, None
def render_math(self, math):
# type: (HTMLTranslator, str) -> Tuple[str, int]
def render_math(self: HTMLTranslator, math: str) -> Tuple[str, int]:
"""Render the LaTeX math expression *math* using latex and dvipng or
dvisvgm.
@ -271,8 +261,7 @@ def render_math(self, math):
return relfn, depth
def cleanup_tempdir(app, exc):
# type: (Sphinx, Exception) -> None
def cleanup_tempdir(app: Sphinx, exc: Exception) -> None:
if exc:
return
if not hasattr(app.builder, '_imgmath_tempdir'):
@ -283,15 +272,13 @@ def cleanup_tempdir(app, exc):
pass
def get_tooltip(self, node):
# type: (HTMLTranslator, Union[nodes.math, nodes.math_block]) -> str
def get_tooltip(self: HTMLTranslator, node: Element) -> str:
if self.builder.config.imgmath_add_tooltips:
return ' alt="%s"' % self.encode(node.astext()).strip()
return ''
def html_visit_math(self, node):
# type: (HTMLTranslator, nodes.math) -> None
def html_visit_math(self: HTMLTranslator, node: nodes.math) -> None:
try:
fname, depth = render_math(self, '$' + node.astext() + '$')
except MathExtError as exc:
@ -313,8 +300,7 @@ def html_visit_math(self, node):
raise nodes.SkipNode
def html_visit_displaymath(self, node):
# type: (HTMLTranslator, nodes.math_block) -> None
def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None:
if node['nowrap']:
latex = node.astext()
else:
@ -354,8 +340,7 @@ deprecated_alias('sphinx.ext.imgmath',
RemovedInSphinx40Warning)
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_html_math_renderer('imgmath',
(html_visit_math, None),
(html_visit_displaymath, None))

View File

@ -40,27 +40,25 @@ import inspect
import re
import sys
from hashlib import md5
from typing import Iterable, cast
from typing import Any, Dict, Iterable, List, Tuple
from typing import cast
from docutils import nodes
from docutils.nodes import Node
from docutils.parsers.rst import directives
import sphinx
from sphinx import addnodes
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
from sphinx.ext.graphviz import (
graphviz, figure_wrapper,
render_dot_html, render_dot_latex, render_dot_texinfo
)
from sphinx.util.docutils import SphinxDirective
if False:
# For type annotation
from typing import Any, Dict, List, Optional, Tuple # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.writers.html import HTMLTranslator # NOQA
from sphinx.writers.latex import LaTeXTranslator # NOQA
from sphinx.writers.texinfo import TexinfoTranslator # NOQA
from sphinx.writers.html import HTMLTranslator
from sphinx.writers.latex import LaTeXTranslator
from sphinx.writers.texinfo import TexinfoTranslator
module_sig_re = re.compile(r'''^(?:([\w.]*)\.)? # module names
@ -68,8 +66,7 @@ module_sig_re = re.compile(r'''^(?:([\w.]*)\.)? # module names
''', re.VERBOSE)
def try_import(objname):
# type: (str) -> Any
def try_import(objname: str) -> Any:
"""Import a object or module using *name* and *currentmodule*.
*name* should be a relative name from *currentmodule* or
a fully-qualified name.
@ -96,8 +93,7 @@ def try_import(objname):
return None
def import_classes(name, currmodule):
# type: (str, str) -> Any
def import_classes(name: str, currmodule: str) -> Any:
"""Import a class using its fully-qualified *name*."""
target = None
@ -138,9 +134,9 @@ class InheritanceGraph:
from all the way to the root "object", and then is able to generate a
graphviz dot graph from them.
"""
def __init__(self, class_names, currmodule, show_builtins=False,
private_bases=False, parts=0, aliases=None, top_classes=[]):
# type: (List[str], str, bool, bool, int, Optional[Dict[str, str]], List[Any]) -> None
def __init__(self, class_names: List[str], currmodule: str, show_builtins: bool = False,
private_bases: bool = False, parts: int = 0, aliases: Dict[str, str] = None,
top_classes: List[Any] = []) -> None:
"""*class_names* is a list of child classes to show bases from.
If *show_builtins* is True, then Python builtins will be shown
@ -154,16 +150,16 @@ class InheritanceGraph:
raise InheritanceException('No classes found for '
'inheritance diagram')
def _import_classes(self, class_names, currmodule):
# type: (List[str], str) -> List[Any]
def _import_classes(self, class_names: List[str], currmodule: str) -> List[Any]:
"""Import a list of classes."""
classes = [] # type: List[Any]
for name in class_names:
classes.extend(import_classes(name, currmodule))
return classes
def _class_info(self, classes, show_builtins, private_bases, parts, aliases, top_classes):
# type: (List[Any], bool, bool, int, Optional[Dict[str, str]], List[Any]) -> List[Tuple[str, str, List[str], str]] # NOQA
def _class_info(self, classes: List[Any], show_builtins: bool, private_bases: bool,
parts: int, aliases: Dict[str, str], top_classes: List[Any]
) -> List[Tuple[str, str, List[str], str]]:
"""Return name and bases for all classes that are ancestors of
*classes*.
@ -182,8 +178,7 @@ class InheritanceGraph:
all_classes = {}
py_builtins = vars(builtins).values()
def recurse(cls):
# type: (Any) -> None
def recurse(cls: Any) -> None:
if not show_builtins and cls in py_builtins:
return
if not private_bases and cls.__name__.startswith('_'):
@ -222,8 +217,7 @@ class InheritanceGraph:
return list(all_classes.values())
def class_name(self, cls, parts=0, aliases=None):
# type: (Any, int, Optional[Dict[str, str]]) -> str
def class_name(self, cls: Any, parts: int = 0, aliases: Dict[str, str] = None) -> str:
"""Given a class object, return a fully-qualified name.
This works for things I've tested in matplotlib so far, but may not be
@ -243,8 +237,7 @@ class InheritanceGraph:
return aliases[result]
return result
def get_all_class_names(self):
# type: () -> List[str]
def get_all_class_names(self) -> List[str]:
"""Get all of the class names involved in the graph."""
return [fullname for (_, fullname, _, _) in self.class_info]
@ -266,17 +259,15 @@ class InheritanceGraph:
'style': '"setlinewidth(0.5)"',
}
def _format_node_attrs(self, attrs):
# type: (Dict) -> str
def _format_node_attrs(self, attrs: Dict) -> str:
return ','.join(['%s=%s' % x for x in sorted(attrs.items())])
def _format_graph_attrs(self, attrs):
# type: (Dict) -> str
def _format_graph_attrs(self, attrs: Dict) -> str:
return ''.join(['%s=%s;\n' % x for x in sorted(attrs.items())])
def generate_dot(self, name, urls={}, env=None,
graph_attrs={}, node_attrs={}, edge_attrs={}):
# type: (str, Dict, BuildEnvironment, Dict, Dict, Dict) -> str
def generate_dot(self, name: str, urls: Dict = {}, env: BuildEnvironment = None,
graph_attrs: Dict = {}, node_attrs: Dict = {}, edge_attrs: Dict = {}
) -> str:
"""Generate a graphviz dot graph from the classes that were passed in
to __init__.
@ -344,8 +335,7 @@ class InheritanceDiagram(SphinxDirective):
'top-classes': directives.unchanged_required,
}
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
node = inheritance_diagram()
node.document = self.state.document
class_names = self.arguments[0].split()
@ -391,14 +381,12 @@ class InheritanceDiagram(SphinxDirective):
return [figure]
def get_graph_hash(node):
# type: (inheritance_diagram) -> str
def get_graph_hash(node: inheritance_diagram) -> str:
encoded = (node['content'] + str(node['parts'])).encode()
return md5(encoded).hexdigest()[-10:]
def html_visit_inheritance_diagram(self, node):
# type: (HTMLTranslator, inheritance_diagram) -> None
def html_visit_inheritance_diagram(self: HTMLTranslator, node: inheritance_diagram) -> None:
"""
Output the graph for HTML. This will insert a PNG with clickable
image map.
@ -431,8 +419,7 @@ def html_visit_inheritance_diagram(self, node):
raise nodes.SkipNode
def latex_visit_inheritance_diagram(self, node):
# type: (LaTeXTranslator, inheritance_diagram) -> None
def latex_visit_inheritance_diagram(self: LaTeXTranslator, node: inheritance_diagram) -> None:
"""
Output the graph for LaTeX. This will insert a PDF.
"""
@ -447,8 +434,8 @@ def latex_visit_inheritance_diagram(self, node):
raise nodes.SkipNode
def texinfo_visit_inheritance_diagram(self, node):
# type: (TexinfoTranslator, inheritance_diagram) -> None
def texinfo_visit_inheritance_diagram(self: TexinfoTranslator, node: inheritance_diagram
) -> None:
"""
Output the graph for Texinfo. This will insert a PNG.
"""
@ -463,13 +450,11 @@ def texinfo_visit_inheritance_diagram(self, node):
raise nodes.SkipNode
def skip(self, node):
# type: (nodes.NodeVisitor, inheritance_diagram) -> None
def skip(self: nodes.NodeVisitor, node: inheritance_diagram) -> None:
raise nodes.SkipNode
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: Sphinx) -> Dict[str, Any]:
app.setup_extension('sphinx.ext.graphviz')
app.add_node(
inheritance_diagram,

View File

@ -28,24 +28,23 @@ import posixpath
import sys
import time
from os import path
from typing import Any, Dict, IO, List, Tuple
from urllib.parse import urlsplit, urlunsplit
from docutils import nodes
from docutils.nodes import Element, TextElement
from docutils.utils import relative_path
import sphinx
from sphinx.application import Sphinx
from sphinx.builders.html import INVENTORY_FILENAME
from sphinx.config import Config
from sphinx.environment import BuildEnvironment
from sphinx.locale import _, __
from sphinx.util import requests, logging
from sphinx.util.inventory import InventoryFile
from sphinx.util.typing import Inventory
if False:
# For type annotation
from typing import Any, Dict, IO, List, Tuple # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.config import Config # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.util.typing import Inventory # NOQA
logger = logging.getLogger(__name__)
@ -53,8 +52,7 @@ logger = logging.getLogger(__name__)
class InventoryAdapter:
"""Inventory adapter for environment"""
def __init__(self, env):
# type: (BuildEnvironment) -> None
def __init__(self, env: BuildEnvironment) -> None:
self.env = env
if not hasattr(env, 'intersphinx_cache'):
@ -63,28 +61,23 @@ class InventoryAdapter:
self.env.intersphinx_named_inventory = {} # type: ignore
@property
def cache(self):
# type: () -> Dict[str, Tuple[str, int, Inventory]]
def cache(self) -> Dict[str, Tuple[str, int, Inventory]]:
return self.env.intersphinx_cache # type: ignore
@property
def main_inventory(self):
# type: () -> Inventory
def main_inventory(self) -> Inventory:
return self.env.intersphinx_inventory # type: ignore
@property
def named_inventory(self):
# type: () -> Dict[str, Inventory]
def named_inventory(self) -> Dict[str, Inventory]:
return self.env.intersphinx_named_inventory # type: ignore
def clear(self):
# type: () -> None
def clear(self) -> None:
self.env.intersphinx_inventory.clear() # type: ignore
self.env.intersphinx_named_inventory.clear() # type: ignore
def _strip_basic_auth(url):
# type: (str) -> str
def _strip_basic_auth(url: str) -> str:
"""Returns *url* with basic auth credentials removed. Also returns the
basic auth username and password if they're present in *url*.
@ -105,8 +98,7 @@ def _strip_basic_auth(url):
return urlunsplit(frags)
def _read_from_url(url, config=None):
# type: (str, Config) -> IO
def _read_from_url(url: str, config: Config = None) -> IO:
"""Reads data from *url* with an HTTP *GET*.
This function supports fetching from resources which use basic HTTP auth as
@ -131,8 +123,7 @@ def _read_from_url(url, config=None):
return r.raw
def _get_safe_url(url):
# type: (str) -> str
def _get_safe_url(url: str) -> str:
"""Gets version of *url* with basic auth passwords obscured. This function
returns results suitable for printing and logging.
@ -157,8 +148,7 @@ def _get_safe_url(url):
return urlunsplit(frags)
def fetch_inventory(app, uri, inv):
# type: (Sphinx, str, Any) -> Any
def fetch_inventory(app: Sphinx, uri: str, inv: Any) -> Any:
"""Fetch, parse and return an intersphinx inventory file."""
# both *uri* (base URI of the links to generate) and *inv* (actual
# location of the inventory file) can be local or remote URIs
@ -197,8 +187,7 @@ def fetch_inventory(app, uri, inv):
return invdata
def load_mappings(app):
# type: (Sphinx) -> None
def load_mappings(app: Sphinx) -> None:
"""Load all intersphinx mappings into the environment."""
now = int(time.time())
cache_time = now - app.config.intersphinx_cache_limit * 86400
@ -258,8 +247,8 @@ def load_mappings(app):
inventories.main_inventory.setdefault(type, {}).update(objects)
def missing_reference(app, env, node, contnode):
# type: (Sphinx, BuildEnvironment, nodes.Element, nodes.TextElement) -> nodes.reference
def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: TextElement
) -> nodes.reference:
"""Attempt to resolve a missing reference via intersphinx references."""
target = node['reftarget']
inventories = InventoryAdapter(env)
@ -336,8 +325,7 @@ def missing_reference(app, env, node, contnode):
return None
def normalize_intersphinx_mapping(app, config):
# type: (Sphinx, Config) -> None
def normalize_intersphinx_mapping(app: Sphinx, config: Config) -> None:
for key, value in config.intersphinx_mapping.copy().items():
try:
if isinstance(value, (list, tuple)):
@ -361,8 +349,7 @@ def normalize_intersphinx_mapping(app, config):
config.intersphinx_mapping.pop(key)
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('intersphinx_mapping', {}, True)
app.add_config_value('intersphinx_cache_limit', 5, False)
app.add_config_value('intersphinx_timeout', None, False)
@ -376,8 +363,7 @@ def setup(app):
}
def inspect_main(argv):
# type: (List[str]) -> None
def inspect_main(argv: List[str]) -> None:
"""Debug functionality to print out an inventory"""
if len(argv) < 1:
print("Print out an inventory file.\n"
@ -393,8 +379,7 @@ def inspect_main(argv):
srcdir = ''
config = MockConfig()
def warn(self, msg):
# type: (str) -> None
def warn(self, msg: str) -> None:
print(msg, file=sys.stderr)
try:

View File

@ -10,6 +10,7 @@
"""
import warnings
from typing import Any, Dict
from sphinxcontrib.jsmath import ( # NOQA
html_visit_math,
@ -18,16 +19,11 @@ from sphinxcontrib.jsmath import ( # NOQA
)
import sphinx
from sphinx.application import Sphinx
from sphinx.deprecation import RemovedInSphinx40Warning
if False:
# For type annotation
from typing import Any, Dict # NOQA
from sphinx.application import Sphinx # NOQA
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: Sphinx) -> Dict[str, Any]:
warnings.warn('sphinx.ext.jsmath has been moved to sphinxcontrib-jsmath.',
RemovedInSphinx40Warning)

View File

@ -8,25 +8,23 @@
:license: BSD, see LICENSE for details.
"""
from typing import Any, Dict, Set
from docutils import nodes
from docutils.nodes import Node
import sphinx
from sphinx import addnodes
from sphinx.application import Sphinx
from sphinx.errors import SphinxError
from sphinx.locale import _
if False:
# For type annotation
from typing import Any, Dict, Set # NOQA
from sphinx.application import Sphinx # NOQA
class LinkcodeError(SphinxError):
category = "linkcode error"
def doctree_read(app, doctree):
# type: (Sphinx, nodes.Node) -> None
def doctree_read(app: Sphinx, doctree: Node) -> None:
env = app.builder.env
resolve_target = getattr(env.config, 'linkcode_resolve', None)
@ -75,8 +73,7 @@ def doctree_read(app, doctree):
signode += onlynode
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: Sphinx) -> Dict[str, Any]:
app.connect('doctree-read', doctree_read)
app.add_config_value('linkcode_resolve', None, '')
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}

View File

@ -9,26 +9,24 @@
"""
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
if False:
# For type annotation
from typing import Callable, Tuple # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.writers.html import HTMLTranslator # NOQA
from sphinx.writers.html import HTMLTranslator
class MathDirective(MathDirectiveBase):
def run(self):
def run(self) -> List[Node]:
warnings.warn('sphinx.ext.mathbase.MathDirective is moved to '
'sphinx.directives.patches package.',
RemovedInSphinx30Warning, stacklevel=2)
@ -42,8 +40,7 @@ def math_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
return math_role_base(role, rawtext, text, lineno, inliner, options, content)
def get_node_equation_number(writer, node):
# type: (HTMLTranslator, nodes.math_block) -> str
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)
@ -51,8 +48,7 @@ def get_node_equation_number(writer, node):
return get_node_equation_number(writer, node)
def wrap_displaymath(text, label, numbering):
# type: (str, str, bool) -> str
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)
@ -60,8 +56,7 @@ def wrap_displaymath(text, label, numbering):
return wrap_displaymath(text, label, numbering)
def is_in_section_title(node):
# type: (nodes.Element) -> bool
def is_in_section_title(node: Element) -> bool:
"""Determine whether the node is in a section title"""
from sphinx.util.nodes import traverse_parent
@ -75,8 +70,9 @@ def is_in_section_title(node):
return False
def setup_math(app, htmlinlinevisitors, htmldisplayvisitors):
# type: (Sphinx, Tuple[Callable, Callable], Tuple[Callable, Callable]) -> None
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)

View File

@ -11,27 +11,23 @@
"""
import json
from typing import Any, Dict
from typing import cast
from docutils import nodes
import sphinx
from sphinx.application import Sphinx
from sphinx.builders.html import StandaloneHTMLBuilder
from sphinx.domains.math import MathDomain
from sphinx.environment import BuildEnvironment
from sphinx.errors import ExtensionError
from sphinx.locale import _
from sphinx.util.math import get_node_equation_number
if False:
# For type annotation
from typing import Any, Dict # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.writers.html import HTMLTranslator # NOQA
from sphinx.writers.html import HTMLTranslator
def html_visit_math(self, node):
# type: (HTMLTranslator, nodes.math) -> None
def html_visit_math(self: HTMLTranslator, node: nodes.math) -> None:
self.body.append(self.starttag(node, 'span', '', CLASS='math notranslate nohighlight'))
self.body.append(self.builder.config.mathjax_inline[0] +
self.encode(node.astext()) +
@ -39,8 +35,7 @@ def html_visit_math(self, node):
raise nodes.SkipNode
def html_visit_displaymath(self, node):
# type: (HTMLTranslator, nodes.math_block) -> None
def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None:
self.body.append(self.starttag(node, 'div', CLASS='math notranslate nohighlight'))
if node['nowrap']:
self.body.append(self.encode(node.astext()))
@ -72,8 +67,7 @@ def html_visit_displaymath(self, node):
raise nodes.SkipNode
def install_mathjax(app, env):
# type: (Sphinx, BuildEnvironment) -> None
def install_mathjax(app: Sphinx, env: BuildEnvironment) -> None:
if app.builder.format != 'html' or app.builder.math_renderer_name != 'mathjax': # type: ignore # NOQA
return
if not app.config.mathjax_path:
@ -94,8 +88,7 @@ def install_mathjax(app, env):
builder.add_js_file(None, type="text/x-mathjax-config", body=body)
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_html_math_renderer('mathjax',
(html_visit_math, None),
(html_visit_displaymath, None))

View File

@ -8,14 +8,12 @@
:license: BSD, see LICENSE for details.
"""
from typing import Any, Dict, List
from sphinx import __display_version__ as __version__
from sphinx.application import Sphinx
from sphinx.ext.napoleon.docstring import GoogleDocstring, NumpyDocstring
if False:
# For type annotation
from typing import Any, Dict, List # NOQA
class Config:
"""Sphinx napoleon extension settings in `conf.py`.
@ -267,16 +265,14 @@ class Config:
'napoleon_custom_sections': (None, 'env')
}
def __init__(self, **settings):
# type: (Any) -> None
def __init__(self, **settings) -> None:
for name, (default, rebuild) in self._config_values.items():
setattr(self, name, default)
for name, value in settings.items():
setattr(self, name, value)
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: Sphinx) -> Dict[str, Any]:
"""Sphinx extension setup function.
When the extension is loaded, Sphinx imports this module and executes
@ -313,8 +309,7 @@ def setup(app):
return {'version': __version__, 'parallel_read_safe': True}
def _patch_python_domain():
# type: () -> None
def _patch_python_domain() -> None:
try:
from sphinx.domains.python import PyTypedField
except ImportError:
@ -333,8 +328,8 @@ def _patch_python_domain():
can_collapse=True))
def _process_docstring(app, what, name, obj, options, lines):
# type: (Sphinx, str, str, Any, Any, List[str]) -> None
def _process_docstring(app: Sphinx, what: str, name: str, obj: Any,
options: Any, lines: List[str]) -> None:
"""Process the docstring for a given python object.
Called when autodoc has read and processed a docstring. `lines` is a list
@ -383,8 +378,8 @@ def _process_docstring(app, what, name, obj, options, lines):
lines[:] = result_lines[:]
def _skip_member(app, what, name, obj, skip, options):
# type: (Sphinx, str, str, Any, bool, Any) -> bool
def _skip_member(app: Sphinx, what: str, name: str, obj: Any,
skip: bool, options: Any) -> bool:
"""Determine if private and special class members are included in docs.
The following settings in conf.py determine if private and special class

View File

@ -13,16 +13,13 @@
import inspect
import re
from functools import partial
from typing import Any, Callable, Dict, List, Tuple, Type, Union
from sphinx.application import Sphinx
from sphinx.config import Config as SphinxConfig
from sphinx.ext.napoleon.iterators import modify_iter
from sphinx.locale import _
if False:
# For type annotation
from typing import Any, Callable, Dict, List, Tuple, Type, Union # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.config import Config as SphinxConfig # NOQA
_directive_regex = re.compile(r'\.\. \S+::')
_google_section_regex = re.compile(r'^(\s|\w)+:\s*$')
@ -103,9 +100,9 @@ class GoogleDocstring:
_name_rgx = re.compile(r"^\s*((?::(?P<role>\S+):)?`(?P<name>[a-zA-Z0-9_.-]+)`|"
r" (?P<name2>[a-zA-Z0-9_.-]+))\s*", re.X)
def __init__(self, docstring, config=None, app=None, what='', name='',
obj=None, options=None):
# type: (Union[str, List[str]], SphinxConfig, Sphinx, str, str, Any, Any) -> None
def __init__(self, docstring: Union[str, List[str]], config: SphinxConfig = None,
app: Sphinx = None, what: str = '', name: str = '',
obj: Any = None, options: Any = None) -> None:
self._config = config
self._app = app
@ -175,8 +172,7 @@ class GoogleDocstring:
self._parse()
def __str__(self):
# type: () -> str
def __str__(self) -> str:
"""Return the parsed docstring in reStructuredText format.
Returns
@ -187,8 +183,7 @@ class GoogleDocstring:
"""
return '\n'.join(self.lines())
def lines(self):
# type: () -> List[str]
def lines(self) -> List[str]:
"""Return the parsed lines of the docstring in reStructuredText format.
Returns
@ -199,8 +194,7 @@ class GoogleDocstring:
"""
return self._parsed_lines
def _consume_indented_block(self, indent=1):
# type: (int) -> List[str]
def _consume_indented_block(self, indent: int = 1) -> List[str]:
lines = []
line = self._line_iter.peek()
while(not self._is_section_break() and
@ -209,8 +203,7 @@ class GoogleDocstring:
line = self._line_iter.peek()
return lines
def _consume_contiguous(self):
# type: () -> List[str]
def _consume_contiguous(self) -> List[str]:
lines = []
while (self._line_iter.has_next() and
self._line_iter.peek() and
@ -218,8 +211,7 @@ class GoogleDocstring:
lines.append(next(self._line_iter))
return lines
def _consume_empty(self):
# type: () -> List[str]
def _consume_empty(self) -> List[str]:
lines = []
line = self._line_iter.peek()
while self._line_iter.has_next() and not line:
@ -227,8 +219,8 @@ class GoogleDocstring:
line = self._line_iter.peek()
return lines
def _consume_field(self, parse_type=True, prefer_type=False):
# type: (bool, bool) -> Tuple[str, str, List[str]]
def _consume_field(self, parse_type: bool = True, prefer_type: bool = False
) -> Tuple[str, str, List[str]]:
line = next(self._line_iter)
before, colon, after = self._partition_field_on_colon(line)
@ -249,8 +241,8 @@ class GoogleDocstring:
_descs = self.__class__(_descs, self._config).lines()
return _name, _type, _descs
def _consume_fields(self, parse_type=True, prefer_type=False):
# type: (bool, bool) -> List[Tuple[str, str, List[str]]]
def _consume_fields(self, parse_type: bool = True, prefer_type: bool = False
) -> List[Tuple[str, str, List[str]]]:
self._consume_empty()
fields = []
while not self._is_section_break():
@ -259,8 +251,7 @@ class GoogleDocstring:
fields.append((_name, _type, _desc,))
return fields
def _consume_inline_attribute(self):
# type: () -> Tuple[str, List[str]]
def _consume_inline_attribute(self) -> Tuple[str, List[str]]:
line = next(self._line_iter)
_type, colon, _desc = self._partition_field_on_colon(line)
if not colon or not _desc:
@ -270,8 +261,7 @@ class GoogleDocstring:
_descs = self.__class__(_descs, self._config).lines()
return _type, _descs
def _consume_returns_section(self):
# type: () -> List[Tuple[str, str, List[str]]]
def _consume_returns_section(self) -> List[Tuple[str, str, List[str]]]:
lines = self._dedent(self._consume_to_next_section())
if lines:
before, colon, after = self._partition_field_on_colon(lines[0])
@ -290,44 +280,38 @@ class GoogleDocstring:
else:
return []
def _consume_usage_section(self):
# type: () -> List[str]
def _consume_usage_section(self) -> List[str]:
lines = self._dedent(self._consume_to_next_section())
return lines
def _consume_section_header(self):
# type: () -> str
def _consume_section_header(self) -> str:
section = next(self._line_iter)
stripped_section = section.strip(':')
if stripped_section.lower() in self._sections:
section = stripped_section
return section
def _consume_to_end(self):
# type: () -> List[str]
def _consume_to_end(self) -> List[str]:
lines = []
while self._line_iter.has_next():
lines.append(next(self._line_iter))
return lines
def _consume_to_next_section(self):
# type: () -> List[str]
def _consume_to_next_section(self) -> List[str]:
self._consume_empty()
lines = []
while not self._is_section_break():
lines.append(next(self._line_iter))
return lines + self._consume_empty()
def _dedent(self, lines, full=False):
# type: (List[str], bool) -> List[str]
def _dedent(self, lines: List[str], full: bool = False) -> List[str]:
if full:
return [line.lstrip() for line in lines]
else:
min_indent = self._get_min_indent(lines)
return [line[min_indent:] for line in lines]
def _escape_args_and_kwargs(self, name):
# type: (str) -> str
def _escape_args_and_kwargs(self, name: str) -> str:
if name.endswith('_'):
name = name[:-1] + r'\_'
@ -338,8 +322,7 @@ class GoogleDocstring:
else:
return name
def _fix_field_desc(self, desc):
# type: (List[str]) -> List[str]
def _fix_field_desc(self, desc: List[str]) -> List[str]:
if self._is_list(desc):
desc = [''] + desc
elif desc[0].endswith('::'):
@ -352,8 +335,7 @@ class GoogleDocstring:
desc = ['', desc[0]] + self._indent(desc_block, 4)
return desc
def _format_admonition(self, admonition, lines):
# type: (str, List[str]) -> List[str]
def _format_admonition(self, admonition: str, lines: List[str]) -> List[str]:
lines = self._strip_empty(lines)
if len(lines) == 1:
return ['.. %s:: %s' % (admonition, lines[0].strip()), '']
@ -363,8 +345,7 @@ class GoogleDocstring:
else:
return ['.. %s::' % admonition, '']
def _format_block(self, prefix, lines, padding=None):
# type: (str, List[str], str) -> List[str]
def _format_block(self, prefix: str, lines: List[str], padding: str = None) -> List[str]:
if lines:
if padding is None:
padding = ' ' * len(prefix)
@ -380,9 +361,9 @@ class GoogleDocstring:
else:
return [prefix]
def _format_docutils_params(self, fields, field_role='param',
type_role='type'):
# type: (List[Tuple[str, str, List[str]]], str, str) -> List[str]
def _format_docutils_params(self, fields: List[Tuple[str, str, List[str]]],
field_role: str = 'param', type_role: str = 'type'
) -> List[str]:
lines = []
for _name, _type, _desc in fields:
_desc = self._strip_empty(_desc)
@ -397,8 +378,7 @@ class GoogleDocstring:
lines.append(':%s %s: %s' % (type_role, _name, _type))
return lines + ['']
def _format_field(self, _name, _type, _desc):
# type: (str, str, List[str]) -> List[str]
def _format_field(self, _name: str, _type: str, _desc: List[str]) -> List[str]:
_desc = self._strip_empty(_desc)
has_desc = any(_desc)
separator = has_desc and ' -- ' or ''
@ -427,8 +407,8 @@ class GoogleDocstring:
else:
return [field]
def _format_fields(self, field_type, fields):
# type: (str, List[Tuple[str, str, List[str]]]) -> List[str]
def _format_fields(self, field_type: str, fields: List[Tuple[str, str, List[str]]]
) -> List[str]:
field_type = ':%s:' % field_type.strip()
padding = ' ' * len(field_type)
multi = len(fields) > 1
@ -446,8 +426,7 @@ class GoogleDocstring:
lines.append('')
return lines
def _get_current_indent(self, peek_ahead=0):
# type: (int) -> int
def _get_current_indent(self, peek_ahead: int = 0) -> int:
line = self._line_iter.peek(peek_ahead + 1)[peek_ahead]
while line != self._line_iter.sentinel:
if line:
@ -456,22 +435,19 @@ class GoogleDocstring:
line = self._line_iter.peek(peek_ahead + 1)[peek_ahead]
return 0
def _get_indent(self, line):
# type: (str) -> int
def _get_indent(self, line: str) -> int:
for i, s in enumerate(line):
if not s.isspace():
return i
return len(line)
def _get_initial_indent(self, lines):
# type: (List[str]) -> int
def _get_initial_indent(self, lines: List[str]) -> int:
for line in lines:
if line:
return self._get_indent(line)
return 0
def _get_min_indent(self, lines):
# type: (List[str]) -> int
def _get_min_indent(self, lines: List[str]) -> int:
min_indent = None
for line in lines:
if line:
@ -482,12 +458,10 @@ class GoogleDocstring:
min_indent = indent
return min_indent or 0
def _indent(self, lines, n=4):
# type: (List[str], int) -> List[str]
def _indent(self, lines: List[str], n: int = 4) -> List[str]:
return [(' ' * n) + line for line in lines]
def _is_indented(self, line, indent=1):
# type: (str, int) -> bool
def _is_indented(self, line: str, indent: int = 1) -> bool:
for i, s in enumerate(line):
if i >= indent:
return True
@ -495,8 +469,7 @@ class GoogleDocstring:
return False
return False
def _is_list(self, lines):
# type: (List[str]) -> bool
def _is_list(self, lines: List[str]) -> bool:
if not lines:
return False
if _bullet_list_regex.match(lines[0]):
@ -513,8 +486,7 @@ class GoogleDocstring:
break
return next_indent > indent
def _is_section_header(self):
# type: () -> bool
def _is_section_header(self) -> bool:
section = self._line_iter.peek().lower()
match = _google_section_regex.match(section)
if match and section.strip(':') in self._sections:
@ -528,8 +500,7 @@ class GoogleDocstring:
return True
return False
def _is_section_break(self):
# type: () -> bool
def _is_section_break(self) -> bool:
line = self._line_iter.peek()
return (not self._line_iter.has_next() or
self._is_section_header() or
@ -537,9 +508,7 @@ class GoogleDocstring:
line and
not self._is_indented(line, self._section_indent)))
def _load_custom_sections(self):
# type: () -> None
def _load_custom_sections(self) -> None:
if self._config.napoleon_custom_sections is not None:
for entry in self._config.napoleon_custom_sections:
if isinstance(entry, str):
@ -554,8 +523,7 @@ class GoogleDocstring:
self._sections.get(entry[1].lower(),
self._parse_custom_generic_section)
def _parse(self):
# type: () -> None
def _parse(self) -> None:
self._parsed_lines = self._consume_empty()
if self._name and self._what in ('attribute', 'data', 'property'):
@ -594,16 +562,14 @@ class GoogleDocstring:
lines = self._consume_to_next_section()
return self._format_admonition(admonition, lines)
def _parse_attribute_docstring(self):
# type: () -> List[str]
def _parse_attribute_docstring(self) -> List[str]:
_type, _desc = self._consume_inline_attribute()
lines = self._format_field('', '', _desc)
if _type:
lines.extend(['', ':type: %s' % _type])
return lines
def _parse_attributes_section(self, section):
# type: (str) -> List[str]
def _parse_attributes_section(self, section: str) -> List[str]:
lines = []
for _name, _type, _desc in self._consume_fields():
if self._config.napoleon_use_ivar:
@ -624,8 +590,7 @@ class GoogleDocstring:
lines.append('')
return lines
def _parse_examples_section(self, section):
# type: (str) -> List[str]
def _parse_examples_section(self, section: str) -> List[str]:
labels = {
'example': _('Example'),
'examples': _('Examples'),
@ -638,16 +603,14 @@ class GoogleDocstring:
# for now, no admonition for simple custom sections
return self._parse_generic_section(section, False)
def _parse_usage_section(self, section):
# type: (str) -> List[str]
def _parse_usage_section(self, section: str) -> List[str]:
header = ['.. rubric:: Usage:', '']
block = ['.. code-block:: python', '']
lines = self._consume_usage_section()
lines = self._indent(lines, 3)
return header + block + lines + ['']
def _parse_generic_section(self, section, use_admonition):
# type: (str, bool) -> List[str]
def _parse_generic_section(self, section: str, use_admonition: bool) -> List[str]:
lines = self._strip_empty(self._consume_to_next_section())
lines = self._dedent(lines)
if use_admonition:
@ -660,8 +623,7 @@ class GoogleDocstring:
else:
return [header, '']
def _parse_keyword_arguments_section(self, section):
# type: (str) -> List[str]
def _parse_keyword_arguments_section(self, section: str) -> List[str]:
fields = self._consume_fields()
if self._config.napoleon_use_keyword:
return self._format_docutils_params(
@ -671,8 +633,7 @@ class GoogleDocstring:
else:
return self._format_fields(_('Keyword Arguments'), fields)
def _parse_methods_section(self, section):
# type: (str) -> List[str]
def _parse_methods_section(self, section: str) -> List[str]:
lines = [] # type: List[str]
for _name, _type, _desc in self._consume_fields(parse_type=False):
lines.append('.. method:: %s' % _name)
@ -681,25 +642,21 @@ class GoogleDocstring:
lines.append('')
return lines
def _parse_notes_section(self, section):
# type: (str) -> List[str]
def _parse_notes_section(self, section: str) -> List[str]:
use_admonition = self._config.napoleon_use_admonition_for_notes
return self._parse_generic_section(_('Notes'), use_admonition)
def _parse_other_parameters_section(self, section):
# type: (str) -> List[str]
def _parse_other_parameters_section(self, section: str) -> List[str]:
return self._format_fields(_('Other Parameters'), self._consume_fields())
def _parse_parameters_section(self, section):
# type: (str) -> List[str]
def _parse_parameters_section(self, section: str) -> List[str]:
fields = self._consume_fields()
if self._config.napoleon_use_param:
return self._format_docutils_params(fields)
else:
return self._format_fields(_('Parameters'), fields)
def _parse_raises_section(self, section):
# type: (str) -> List[str]
def _parse_raises_section(self, section: str) -> List[str]:
fields = self._consume_fields(parse_type=False, prefer_type=True)
lines = [] # type: List[str]
for _name, _type, _desc in fields:
@ -714,13 +671,11 @@ class GoogleDocstring:
lines.append('')
return lines
def _parse_references_section(self, section):
# type: (str) -> List[str]
def _parse_references_section(self, section: str) -> List[str]:
use_admonition = self._config.napoleon_use_admonition_for_references
return self._parse_generic_section(_('References'), use_admonition)
def _parse_returns_section(self, section):
# type: (str) -> List[str]
def _parse_returns_section(self, section: str) -> List[str]:
fields = self._consume_returns_section()
multi = len(fields) > 1
if multi:
@ -748,21 +703,17 @@ class GoogleDocstring:
lines.append('')
return lines
def _parse_see_also_section(self, section):
# type (str) -> List[str]
def _parse_see_also_section(self, section: str) -> List[str]:
return self._parse_admonition('seealso', section)
def _parse_warns_section(self, section):
# type: (str) -> List[str]
def _parse_warns_section(self, section: str) -> List[str]:
return self._format_fields(_('Warns'), self._consume_fields())
def _parse_yields_section(self, section):
# type: (str) -> List[str]
def _parse_yields_section(self, section: str) -> List[str]:
fields = self._consume_returns_section()
return self._format_fields(_('Yields'), fields)
def _partition_field_on_colon(self, line):
# type: (str) -> Tuple[str, str, str]
def _partition_field_on_colon(self, line: str) -> Tuple[str, str, str]:
before_colon = []
after_colon = []
colon = ''
@ -784,8 +735,7 @@ class GoogleDocstring:
colon,
"".join(after_colon).strip())
def _qualify_name(self, attr_name, klass):
# type: (str, Type) -> str
def _qualify_name(self, attr_name: str, klass: Type) -> str:
if klass and '.' not in attr_name:
if attr_name.startswith('~'):
attr_name = attr_name[1:]
@ -796,8 +746,7 @@ class GoogleDocstring:
return '~%s.%s' % (q, attr_name)
return attr_name
def _strip_empty(self, lines):
# type: (List[str]) -> List[str]
def _strip_empty(self, lines: List[str]) -> List[str]:
if lines:
start = -1
for i, line in enumerate(lines):
@ -910,14 +859,14 @@ class NumpyDocstring(GoogleDocstring):
The lines of the docstring in a list.
"""
def __init__(self, docstring, config=None, app=None, what='', name='',
obj=None, options=None):
# type: (Union[str, List[str]], SphinxConfig, Sphinx, str, str, Any, Any) -> None
def __init__(self, docstring: Union[str, List[str]], config: SphinxConfig = None,
app: Sphinx = None, what: str = '', name: str = '',
obj: Any = None, options: Any = None) -> None:
self._directive_sections = ['.. index::']
super().__init__(docstring, config, app, what, name, obj, options)
def _consume_field(self, parse_type=True, prefer_type=False):
# type: (bool, bool) -> Tuple[str, str, List[str]]
def _consume_field(self, parse_type: bool = True, prefer_type: bool = False
) -> Tuple[str, str, List[str]]:
line = next(self._line_iter)
if parse_type:
_name, _, _type = self._partition_field_on_colon(line)
@ -933,20 +882,17 @@ class NumpyDocstring(GoogleDocstring):
_desc = self.__class__(_desc, self._config).lines()
return _name, _type, _desc
def _consume_returns_section(self):
# type: () -> List[Tuple[str, str, List[str]]]
def _consume_returns_section(self) -> List[Tuple[str, str, List[str]]]:
return self._consume_fields(prefer_type=True)
def _consume_section_header(self):
# type: () -> str
def _consume_section_header(self) -> str:
section = next(self._line_iter)
if not _directive_regex.match(section):
# Consume the header underline
next(self._line_iter)
return section
def _is_section_break(self):
# type: () -> bool
def _is_section_break(self) -> bool:
line1, line2 = self._line_iter.peek(2)
return (not self._line_iter.has_next() or
self._is_section_header() or
@ -955,8 +901,7 @@ class NumpyDocstring(GoogleDocstring):
line1 and
not self._is_indented(line1, self._section_indent)))
def _is_section_header(self):
# type: () -> bool
def _is_section_header(self) -> bool:
section, underline = self._line_iter.peek(2)
section = section.lower()
if section in self._sections and isinstance(underline, str):
@ -968,16 +913,14 @@ class NumpyDocstring(GoogleDocstring):
return True
return False
def _parse_see_also_section(self, section):
# type: (str) -> List[str]
def _parse_see_also_section(self, section: str) -> List[str]:
lines = self._consume_to_next_section()
try:
return self._parse_numpydoc_see_also_section(lines)
except ValueError:
return self._format_admonition('seealso', lines)
def _parse_numpydoc_see_also_section(self, content):
# type: (List[str]) -> List[str]
def _parse_numpydoc_see_also_section(self, content: List[str]) -> List[str]:
"""
Derived from the NumpyDoc implementation of _parse_see_also.
@ -991,8 +934,7 @@ class NumpyDocstring(GoogleDocstring):
"""
items = []
def parse_item_name(text):
# type: (str) -> Tuple[str, str]
def parse_item_name(text: str) -> Tuple[str, str]:
"""Match ':role:`name`' or 'name'"""
m = self._name_rgx.match(text)
if m:
@ -1003,8 +945,7 @@ class NumpyDocstring(GoogleDocstring):
return g[2], g[1]
raise ValueError("%s is not a item name" % text)
def push_item(name, rest):
# type: (str, List[str]) -> None
def push_item(name: str, rest: List[str]) -> None:
if not name:
return
name, role = parse_item_name(name)

View File

@ -11,10 +11,7 @@
"""
import collections
if False:
# For type annotation
from typing import Any, Iterable # NOQA
from typing import Any, Iterable
class peek_iter:
@ -50,8 +47,7 @@ class peek_iter:
be set to a new object instance: ``object()``.
"""
def __init__(self, *args):
# type: (Any) -> None
def __init__(self, *args) -> None:
"""__init__(o, sentinel=None)"""
self._iterable = iter(*args) # type: Iterable
self._cache = collections.deque() # type: collections.deque
@ -60,18 +56,15 @@ class peek_iter:
else:
self.sentinel = object()
def __iter__(self):
# type: () -> peek_iter
def __iter__(self) -> "peek_iter":
return self
def __next__(self, n=None):
# type: (int) -> Any
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)
def _fillcache(self, n):
# type: (int) -> None
def _fillcache(self, n: int) -> None:
"""Cache `n` items. If `n` is 0 or None, then 1 item is cached."""
if not n:
n = 1
@ -82,8 +75,7 @@ class peek_iter:
while len(self._cache) < n:
self._cache.append(self.sentinel)
def has_next(self):
# type: () -> bool
def has_next(self) -> bool:
"""Determine if iterator is exhausted.
Returns
@ -98,8 +90,7 @@ class peek_iter:
"""
return self.peek() != self.sentinel
def next(self, n=None):
# type: (int) -> Any
def next(self, n: int = None) -> Any:
"""Get the next item or `n` items of the iterator.
Parameters
@ -134,8 +125,7 @@ class peek_iter:
result = [self._cache.popleft() for i in range(n)]
return result
def peek(self, n=None):
# type: (int) -> Any
def peek(self, n: int = None) -> Any:
"""Preview the next item or `n` items of the iterator.
The iterator is not advanced when peek is called.
@ -218,8 +208,7 @@ class modify_iter(peek_iter):
"whitespace."
"""
def __init__(self, *args, **kwargs):
# type: (Any, Any) -> None
def __init__(self, *args, **kwargs) -> None:
"""__init__(o, sentinel=None, modifier=lambda x: x)"""
if 'modifier' in kwargs:
self.modifier = kwargs['modifier']
@ -233,8 +222,7 @@ class modify_iter(peek_iter):
'modifier must be callable')
super().__init__(*args)
def _fillcache(self, n):
# type: (int) -> None
def _fillcache(self, n: int) -> None:
"""Cache `n` modified items. If `n` is 0 or None, 1 item is cached.
Each item returned by the iterator is passed through the

View File

@ -12,29 +12,27 @@
"""
import warnings
from typing import Any, Dict, Iterable, List, Tuple
from typing import cast
from docutils import nodes
from docutils.nodes import Element, Node
from docutils.parsers.rst import directives
from docutils.parsers.rst.directives.admonitions import BaseAdmonition
import sphinx
from sphinx.application import Sphinx
from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.domains import Domain
from sphinx.environment import BuildEnvironment
from sphinx.errors import NoUri
from sphinx.locale import _, __
from sphinx.util import logging
from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import make_refnode
from sphinx.util.texescape import tex_escape_map
if False:
# For type annotation
from typing import Any, Dict, Iterable, List, Tuple # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.writers.html import HTMLTranslator # NOQA
from sphinx.writers.latex import LaTeXTranslator # NOQA
from sphinx.writers.html import HTMLTranslator
from sphinx.writers.latex import LaTeXTranslator
logger = logging.getLogger(__name__)
@ -62,12 +60,11 @@ class Todo(BaseAdmonition, SphinxDirective):
'name': directives.unchanged,
}
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
if not self.options.get('class'):
self.options['class'] = ['admonition-todo']
(todo,) = super().run() # type: Tuple[nodes.Node]
(todo,) = super().run() # type: Tuple[Node]
if isinstance(todo, nodes.system_message):
return [todo]
elif isinstance(todo, todo_node):
@ -86,21 +83,18 @@ class TodoDomain(Domain):
label = 'todo'
@property
def todos(self):
# type: () -> Dict[str, List[todo_node]]
def todos(self) -> Dict[str, List[todo_node]]:
return self.data.setdefault('todos', {})
def clear_doc(self, docname):
# type: (str) -> None
def clear_doc(self, docname: str) -> None:
self.todos.pop(docname, None)
def merge_domaindata(self, docnames, otherdata):
# type: (List[str], Dict) -> None
def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None:
for docname in docnames:
self.todos[docname] = otherdata['todos'][docname]
def process_doc(self, env, docname, document):
# type: (BuildEnvironment, str, nodes.document) -> None
def process_doc(self, env: BuildEnvironment, docname: str,
document: nodes.document) -> None:
todos = self.todos.setdefault(docname, [])
for todo in document.traverse(todo_node):
env.app.emit('todo-defined', todo)
@ -111,8 +105,7 @@ class TodoDomain(Domain):
location=todo)
def process_todos(app, doctree):
# type: (Sphinx, nodes.document) -> None
def process_todos(app: Sphinx, doctree: nodes.document) -> None:
warnings.warn('process_todos() is deprecated.', RemovedInSphinx40Warning)
# collect all todos in the environment
# this is not done in the directive itself because it some transformations
@ -150,16 +143,14 @@ class TodoList(SphinxDirective):
final_argument_whitespace = False
option_spec = {} # type: Dict
def run(self):
# type: () -> List[nodes.Node]
def run(self) -> List[Node]:
# Simply insert an empty todolist node which will be replaced later
# when process_todo_nodes is called
return [todolist('')]
class TodoListProcessor:
def __init__(self, app, doctree, docname):
# type: (Sphinx, nodes.document, str) -> None
def __init__(self, app: Sphinx, doctree: nodes.document, docname: str) -> None:
self.builder = app.builder
self.config = app.config
self.env = app.env
@ -167,8 +158,7 @@ class TodoListProcessor:
self.process(doctree, docname)
def process(self, doctree, docname):
# type: (nodes.document, str) -> None
def process(self, doctree: nodes.document, docname: str) -> None:
todos = sum(self.domain.todos.values(), [])
for node in doctree.traverse(todolist):
if not self.config.todo_include_todos:
@ -176,7 +166,7 @@ class TodoListProcessor:
continue
if node.get('ids'):
content = [nodes.target()] # type: List[nodes.Element]
content = [nodes.target()] # type: List[Element]
else:
content = []
@ -194,8 +184,7 @@ class TodoListProcessor:
node.replace_self(content)
def create_todo_reference(self, todo, docname):
# type: (todo_node, str) -> nodes.paragraph
def create_todo_reference(self, todo: todo_node, docname: str) -> nodes.paragraph:
if self.config.todo_link_only:
description = _('<<original entry>>')
else:
@ -224,8 +213,7 @@ class TodoListProcessor:
return para
def process_todo_nodes(app, doctree, fromdocname):
# type: (Sphinx, nodes.document, str) -> None
def process_todo_nodes(app: Sphinx, doctree: nodes.document, fromdocname: str) -> None:
"""Replace all todolist nodes with a list of the collected todos.
Augment each todo with a backlink to the original location.
"""
@ -236,7 +224,7 @@ def process_todo_nodes(app, doctree, fromdocname):
for node in doctree.traverse(todolist):
if node.get('ids'):
content = [nodes.target()] # type: List[nodes.Element]
content = [nodes.target()] # type: List[Element]
else:
content = []
@ -280,8 +268,7 @@ def process_todo_nodes(app, doctree, fromdocname):
node.replace_self(content)
def purge_todos(app, env, docname):
# type: (Sphinx, BuildEnvironment, str) -> None
def purge_todos(app: Sphinx, env: BuildEnvironment, docname: str) -> None:
warnings.warn('purge_todos() is deprecated.', RemovedInSphinx40Warning)
if not hasattr(env, 'todo_all_todos'):
return
@ -289,8 +276,8 @@ def purge_todos(app, env, docname):
if todo['docname'] != docname]
def merge_info(app, env, docnames, other):
# type: (Sphinx, BuildEnvironment, Iterable[str], BuildEnvironment) -> None
def merge_info(app: Sphinx, env: BuildEnvironment, docnames: Iterable[str],
other: BuildEnvironment) -> None:
warnings.warn('merge_info() is deprecated.', RemovedInSphinx40Warning)
if not hasattr(other, 'todo_all_todos'):
return
@ -299,21 +286,18 @@ def merge_info(app, env, docnames, other):
env.todo_all_todos.extend(other.todo_all_todos) # type: ignore
def visit_todo_node(self, node):
# type: (HTMLTranslator, todo_node) -> None
def visit_todo_node(self: HTMLTranslator, node: todo_node) -> None:
if self.config.todo_include_todos:
self.visit_admonition(node)
else:
raise nodes.SkipNode
def depart_todo_node(self, node):
# type: (HTMLTranslator, todo_node) -> None
def depart_todo_node(self: HTMLTranslator, node: todo_node) -> None:
self.depart_admonition(node)
def latex_visit_todo_node(self, node):
# type: (LaTeXTranslator, todo_node) -> None
def latex_visit_todo_node(self: LaTeXTranslator, node: todo_node) -> None:
if self.config.todo_include_todos:
self.body.append('\n\\begin{sphinxadmonition}{note}{')
self.body.append(self.hypertarget_to(node))
@ -324,13 +308,11 @@ def latex_visit_todo_node(self, node):
raise nodes.SkipNode
def latex_depart_todo_node(self, node):
# type: (LaTeXTranslator, todo_node) -> None
def latex_depart_todo_node(self: LaTeXTranslator, node: todo_node) -> None:
self.body.append('\\end{sphinxadmonition}\n')
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_event('todo-defined')
app.add_config_value('todo_include_todos', False, 'html')
app.add_config_value('todo_link_only', False, 'html')

View File

@ -10,29 +10,27 @@
import traceback
import warnings
from typing import Any, Dict, Iterable, Iterator, Set, Tuple
from docutils import nodes
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
from sphinx.util import get_full_modname, logging, status_iterator
from sphinx.util.nodes import make_refnode
if False:
# For type annotation
from typing import Any, Dict, Iterable, Iterator, Set, Tuple # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.config import Config # NOQA
from sphinx.environment import BuildEnvironment # NOQA
logger = logging.getLogger(__name__)
def _get_full_modname(app, modname, attribute):
# type: (Sphinx, str, str) -> str
def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> str:
try:
return get_full_modname(modname, attribute)
except AttributeError:
@ -50,8 +48,7 @@ def _get_full_modname(app, modname, attribute):
return None
def doctree_read(app, doctree):
# type: (Sphinx, nodes.Node) -> None
def doctree_read(app: Sphinx, doctree: Node) -> None:
env = app.builder.env
if not hasattr(env, '_viewcode_modules'):
env._viewcode_modules = {} # type: ignore
@ -122,8 +119,8 @@ def doctree_read(app, doctree):
signode += onlynode
def env_merge_info(app, env, docnames, other):
# type: (Sphinx, BuildEnvironment, Iterable[str], BuildEnvironment) -> None
def env_merge_info(app: Sphinx, env: BuildEnvironment, docnames: Iterable[str],
other: BuildEnvironment) -> None:
if not hasattr(other, '_viewcode_modules'):
return
# create a _viewcode_modules dict on the main environment
@ -133,8 +130,8 @@ def env_merge_info(app, env, docnames, other):
env._viewcode_modules.update(other._viewcode_modules) # type: ignore
def missing_reference(app, env, node, contnode):
# type: (Sphinx, BuildEnvironment, nodes.Element, nodes.Node) -> nodes.Node
def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: Node
) -> Node:
# resolve our "viewcode" reference nodes -- they need special treatment
if node['reftype'] == 'viewcode':
return make_refnode(app.builder, node['refdoc'], node['reftarget'],
@ -143,8 +140,7 @@ def missing_reference(app, env, node, contnode):
return None
def collect_pages(app):
# type: (Sphinx) -> Iterator[Tuple[str, Dict[str, Any], str]]
def collect_pages(app: Sphinx) -> Iterator[Tuple[str, Dict[str, Any], str]]:
env = app.builder.env
if not hasattr(env, '_viewcode_modules'):
return
@ -238,16 +234,14 @@ def collect_pages(app):
yield ('_modules/index', context, 'page.html')
def migrate_viewcode_import(app, config):
# type: (Sphinx, Config) -> None
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):
# type: (Sphinx) -> Dict[str, Any]
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)

View File

@ -139,7 +139,8 @@ class PygmentsBridge:
lexer = lexers['none']
if lang in lexers:
lexer = lexers[lang]
# just return custom lexers here (without installing raiseonerror filter)
return lexers[lang]
elif lang in lexer_classes:
lexer = lexer_classes[lang](**opts)
else:

View File

@ -11,29 +11,25 @@
import re
from io import StringIO
from os import path
from typing import Any, Dict, IO, List, Tuple
from zipfile import ZipFile
from sphinx.errors import PycodeError
from sphinx.pycode.parser import Parser
from sphinx.util import get_module_source, detect_encoding
if False:
# For type annotation
from typing import Any, Dict, IO, List, Tuple # NOQA
class ModuleAnalyzer:
# cache for analyzer objects -- caches both by module and file name
cache = {} # type: Dict[Tuple[str, str], Any]
@classmethod
def for_string(cls, string, modname, srcname='<string>'):
# type: (str, str, str) -> ModuleAnalyzer
def for_string(cls, string: str, modname: str, srcname: str = '<string>'
) -> "ModuleAnalyzer":
return cls(StringIO(string), modname, srcname, decoded=True)
@classmethod
def for_file(cls, filename, modname):
# type: (str, str) -> ModuleAnalyzer
def for_file(cls, filename: str, modname: str) -> "ModuleAnalyzer":
if ('file', filename) in cls.cache:
return cls.cache['file', filename]
try:
@ -48,8 +44,7 @@ class ModuleAnalyzer:
return obj
@classmethod
def for_egg(cls, filename, modname):
# type: (str, str) -> ModuleAnalyzer
def for_egg(cls, filename: str, modname: str) -> "ModuleAnalyzer":
SEP = re.escape(path.sep)
eggpath, relpath = re.split('(?<=\\.egg)' + SEP, filename)
try:
@ -60,8 +55,7 @@ class ModuleAnalyzer:
raise PycodeError('error opening %r' % filename, exc)
@classmethod
def for_module(cls, modname):
# type: (str) -> ModuleAnalyzer
def for_module(cls, modname: str) -> "ModuleAnalyzer":
if ('module', modname) in cls.cache:
entry = cls.cache['module', modname]
if isinstance(entry, PycodeError):
@ -80,8 +74,7 @@ class ModuleAnalyzer:
cls.cache['module', modname] = obj
return obj
def __init__(self, source, modname, srcname, decoded=False):
# type: (IO, str, str, bool) -> None
def __init__(self, source: IO, modname: str, srcname: str, decoded: bool = False) -> None:
self.modname = modname # name of the module
self.srcname = srcname # name of the source file
@ -100,8 +93,7 @@ class ModuleAnalyzer:
self.tagorder = None # type: Dict[str, int]
self.tags = None # type: Dict[str, Tuple[str, int, int]]
def parse(self):
# type: () -> None
def parse(self) -> None:
"""Parse the source code."""
try:
parser = Parser(self.code, self.encoding)
@ -119,16 +111,14 @@ class ModuleAnalyzer:
except Exception as exc:
raise PycodeError('parsing %r failed: %r' % (self.srcname, exc))
def find_attr_docs(self):
# type: () -> Dict[Tuple[str, str], List[str]]
def find_attr_docs(self) -> Dict[Tuple[str, str], List[str]]:
"""Find class and module-level attributes and their documentation."""
if self.attr_docs is None:
self.parse()
return self.attr_docs
def find_tags(self):
# type: () -> Dict[str, Tuple[str, int, int]]
def find_tags(self) -> Dict[str, Tuple[str, int, int]]:
"""Find class, function and method definitions and their location."""
if self.tags is None:
self.parse()

View File

@ -15,10 +15,8 @@ import sys
import tokenize
from token import NAME, NEWLINE, INDENT, DEDENT, NUMBER, OP, STRING
from tokenize import COMMENT, NL
from typing import Any, Dict, List, Tuple
if False:
# For type annotation
from typing import Any, Dict, List, Tuple # NOQA
comment_re = re.compile('^\\s*#: ?(.*)\r?\n?$')
indent_re = re.compile('^\\s*$')
@ -31,13 +29,11 @@ else:
ASSIGN_NODES = (ast.Assign)
def filter_whitespace(code):
# type: (str) -> str
def filter_whitespace(code: str) -> str:
return code.replace('\f', ' ') # replace FF (form feed) with whitespace
def get_assign_targets(node):
# type: (ast.AST) -> List[ast.expr]
def get_assign_targets(node: ast.AST) -> List[ast.expr]:
"""Get list of targets from Assign and AnnAssign node."""
if isinstance(node, ast.Assign):
return node.targets
@ -45,8 +41,7 @@ def get_assign_targets(node):
return [node.target] # type: ignore
def get_lvar_names(node, self=None):
# type: (ast.AST, ast.arg) -> List[str]
def get_lvar_names(node: ast.AST, self: ast.arg = None) -> List[str]:
"""Convert assignment-AST to variable names.
This raises `TypeError` if the assignment does not create new variable::
@ -88,11 +83,9 @@ def get_lvar_names(node, self=None):
raise NotImplementedError('Unexpected node name %r' % node_name)
def dedent_docstring(s):
# type: (str) -> str
def dedent_docstring(s: str) -> str:
"""Remove common leading indentation from docstring."""
def dummy():
# type: () -> None
def dummy() -> None:
# dummy function to mock `inspect.getdoc`.
pass
@ -104,16 +97,15 @@ def dedent_docstring(s):
class Token:
"""Better token wrapper for tokenize module."""
def __init__(self, kind, value, start, end, source):
# type: (int, Any, Tuple[int, int], Tuple[int, int], str) -> None
def __init__(self, kind: int, value: Any, start: Tuple[int, int], end: Tuple[int, int],
source: str) -> None:
self.kind = kind
self.value = value
self.start = start
self.end = end
self.source = source
def __eq__(self, other):
# type: (Any) -> bool
def __eq__(self, other: Any) -> bool:
if isinstance(other, int):
return self.kind == other
elif isinstance(other, str):
@ -125,32 +117,27 @@ class Token:
else:
raise ValueError('Unknown value: %r' % other)
def match(self, *conditions):
# type: (Any) -> bool
def match(self, *conditions) -> bool:
return any(self == candidate for candidate in conditions)
def __repr__(self):
# type: () -> str
def __repr__(self) -> str:
return '<Token kind=%r value=%r>' % (tokenize.tok_name[self.kind],
self.value.strip())
class TokenProcessor:
def __init__(self, buffers):
# type: (List[str]) -> None
def __init__(self, buffers: List[str]) -> None:
lines = iter(buffers)
self.buffers = buffers
self.tokens = tokenize.generate_tokens(lambda: next(lines))
self.current = None # type: Token
self.previous = None # type: Token
def get_line(self, lineno):
# type: (int) -> str
def get_line(self, lineno: int) -> str:
"""Returns specified line."""
return self.buffers[lineno - 1]
def fetch_token(self):
# type: () -> Token
def fetch_token(self) -> Token:
"""Fetch a next token from source code.
Returns ``False`` if sequence finished.
@ -163,8 +150,7 @@ class TokenProcessor:
return self.current
def fetch_until(self, condition):
# type: (Any) -> List[Token]
def fetch_until(self, condition: Any) -> List[Token]:
"""Fetch tokens until specified token appeared.
.. note:: This also handles parenthesis well.
@ -191,13 +177,11 @@ class AfterCommentParser(TokenProcessor):
and returns the comments for variable if exists.
"""
def __init__(self, lines):
# type: (List[str]) -> None
def __init__(self, lines: List[str]) -> None:
super().__init__(lines)
self.comment = None # type: str
def fetch_rvalue(self):
# type: () -> List[Token]
def fetch_rvalue(self) -> List[Token]:
"""Fetch right-hand value of assignment."""
tokens = []
while self.fetch_token():
@ -217,8 +201,7 @@ class AfterCommentParser(TokenProcessor):
return tokens
def parse(self):
# type: () -> None
def parse(self) -> None:
"""Parse the code and obtain comment after assignment."""
# skip lvalue (or whole of AnnAssign)
while not self.fetch_token().match([OP, '='], NEWLINE, COMMENT):
@ -235,8 +218,7 @@ class AfterCommentParser(TokenProcessor):
class VariableCommentPicker(ast.NodeVisitor):
"""Python source code parser to pick up variable comments."""
def __init__(self, buffers, encoding):
# type: (List[str], str) -> None
def __init__(self, buffers: List[str], encoding: str) -> None:
self.counter = itertools.count()
self.buffers = buffers
self.encoding = encoding
@ -248,8 +230,7 @@ class VariableCommentPicker(ast.NodeVisitor):
self.deforders = {} # type: Dict[str, int]
super().__init__()
def add_entry(self, name):
# type: (str) -> None
def add_entry(self, name: str) -> None:
if self.current_function:
if self.current_classes and self.context[-1] == "__init__":
# store variable comments inside __init__ method of classes
@ -261,8 +242,7 @@ class VariableCommentPicker(ast.NodeVisitor):
self.deforders[".".join(definition)] = next(self.counter)
def add_variable_comment(self, name, comment):
# type: (str, str) -> None
def add_variable_comment(self, name: str, comment: str) -> None:
if self.current_function:
if self.current_classes and self.context[-1] == "__init__":
# store variable comments inside __init__ method of classes
@ -274,27 +254,23 @@ class VariableCommentPicker(ast.NodeVisitor):
self.comments[(context, name)] = comment
def get_self(self):
# type: () -> ast.arg
def get_self(self) -> ast.arg:
"""Returns the name of first argument if in function."""
if self.current_function and self.current_function.args.args:
return self.current_function.args.args[0]
else:
return None
def get_line(self, lineno):
# type: (int) -> str
def get_line(self, lineno: int) -> str:
"""Returns specified line."""
return self.buffers[lineno - 1]
def visit(self, node):
# type: (ast.AST) -> None
def visit(self, node: ast.AST) -> None:
"""Updates self.previous to ."""
super().visit(node)
self.previous = node
def visit_Assign(self, node):
# type: (ast.Assign) -> None
def visit_Assign(self, node: ast.Assign) -> None:
"""Handles Assign node and pick up a variable comment."""
try:
targets = get_assign_targets(node)
@ -334,13 +310,11 @@ class VariableCommentPicker(ast.NodeVisitor):
for varname in varnames:
self.add_entry(varname)
def visit_AnnAssign(self, node):
# type: (ast.AST) -> None
def visit_AnnAssign(self, node: ast.AST) -> None: # Note: ast.AnnAssign not found in py35
"""Handles AnnAssign node and pick up a variable comment."""
self.visit_Assign(node) # type: ignore
def visit_Expr(self, node):
# type: (ast.Expr) -> None
def visit_Expr(self, node: ast.Expr) -> None:
"""Handles Expr node and pick up a comment if string."""
if (isinstance(self.previous, ASSIGN_NODES) and isinstance(node.value, ast.Str)):
try:
@ -357,8 +331,7 @@ class VariableCommentPicker(ast.NodeVisitor):
except TypeError:
pass # this assignment is not new definition!
def visit_Try(self, node):
# type: (ast.Try) -> None
def visit_Try(self, node: ast.Try) -> None:
"""Handles Try node and processes body and else-clause.
.. note:: pycode parser ignores objects definition in except-clause.
@ -368,8 +341,7 @@ class VariableCommentPicker(ast.NodeVisitor):
for subnode in node.orelse:
self.visit(subnode)
def visit_ClassDef(self, node):
# type: (ast.ClassDef) -> None
def visit_ClassDef(self, node: ast.ClassDef) -> None:
"""Handles ClassDef node and set context."""
self.current_classes.append(node.name)
self.add_entry(node.name)
@ -380,8 +352,7 @@ class VariableCommentPicker(ast.NodeVisitor):
self.context.pop()
self.current_classes.pop()
def visit_FunctionDef(self, node):
# type: (ast.FunctionDef) -> None
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
"""Handles FunctionDef node and set context."""
if self.current_function is None:
self.add_entry(node.name) # should be called before setting self.current_function
@ -392,8 +363,7 @@ class VariableCommentPicker(ast.NodeVisitor):
self.context.pop()
self.current_function = None
def visit_AsyncFunctionDef(self, node):
# type: (ast.AsyncFunctionDef) -> None
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
"""Handles AsyncFunctionDef node and set context."""
self.visit_FunctionDef(node) # type: ignore
@ -403,16 +373,14 @@ class DefinitionFinder(TokenProcessor):
classes and methods.
"""
def __init__(self, lines):
# type: (List[str]) -> None
def __init__(self, lines: List[str]) -> None:
super().__init__(lines)
self.decorator = None # type: Token
self.context = [] # type: List[str]
self.indents = [] # type: List
self.definitions = {} # type: Dict[str, Tuple[str, int, int]]
def add_definition(self, name, entry):
# type: (str, Tuple[str, int, int]) -> None
def add_definition(self, name: str, entry: Tuple[str, int, int]) -> None:
"""Add a location of definition."""
if self.indents and self.indents[-1][0] == 'def' and entry[0] == 'def':
# ignore definition of inner function
@ -420,8 +388,7 @@ class DefinitionFinder(TokenProcessor):
else:
self.definitions[name] = entry
def parse(self):
# type: () -> None
def parse(self) -> None:
"""Parse the code to obtain location of definitions."""
while True:
token = self.fetch_token()
@ -442,8 +409,7 @@ class DefinitionFinder(TokenProcessor):
elif token == DEDENT:
self.finalize_block()
def parse_definition(self, typ):
# type: (str) -> None
def parse_definition(self, typ: str) -> None:
"""Parse AST of definition."""
name = self.fetch_token()
self.context.append(name.value)
@ -464,8 +430,7 @@ class DefinitionFinder(TokenProcessor):
self.add_definition(funcname, (typ, start_pos, name.end[0]))
self.context.pop()
def finalize_block(self):
# type: () -> None
def finalize_block(self) -> None:
"""Finalize definition block."""
definition = self.indents.pop()
if definition[0] != 'other':
@ -484,22 +449,19 @@ class Parser:
This is a better wrapper for ``VariableCommentPicker``.
"""
def __init__(self, code, encoding='utf-8'):
# type: (str, str) -> None
def __init__(self, code: str, encoding: str = 'utf-8') -> None:
self.code = filter_whitespace(code)
self.encoding = encoding
self.comments = {} # type: Dict[Tuple[str, str], str]
self.deforders = {} # type: Dict[str, int]
self.definitions = {} # type: Dict[str, Tuple[str, int, int]]
def parse(self):
# type: () -> None
def parse(self) -> None:
"""Parse the source code."""
self.parse_comments()
self.parse_definition()
def parse_comments(self):
# type: () -> None
def parse_comments(self) -> None:
"""Parse the code and pick up comments."""
tree = ast.parse(self.code.encode())
picker = VariableCommentPicker(self.code.splitlines(True), self.encoding)
@ -507,8 +469,7 @@ class Parser:
self.comments = picker.comments
self.deforders = picker.deforders
def parse_definition(self):
# type: () -> None
def parse_definition(self) -> None:
"""Parse the location of definitions from the code."""
parser = DefinitionFinder(self.code.splitlines(True))
parser.parse()

View File

@ -14,25 +14,20 @@ import sys
from collections import namedtuple
from io import StringIO
from subprocess import PIPE
from typing import Any, Dict
import pytest
from . import util
if False:
# For type annotation
from typing import Any, Dict, Union # NOQA
@pytest.fixture(scope='session')
def rootdir():
# type: () -> None
def rootdir() -> None:
return None
@pytest.fixture
def app_params(request, test_params, shared_result, sphinx_test_tempdir, rootdir):
# type: (Any, Any, Any, Any, Any) -> None
"""
parameters that is specified by 'pytest.mark.sphinx' for
sphinx.application.Sphinx initialization
@ -158,10 +153,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: Union[util.SphinxTestApp, util.SphinxTestAppWrapperForSkipBuilding] # NOQA
app_ = util.SphinxTestApp(*args, **kwargs) # type: Any
apps.append(app_)
if test_params['shared_result']:
app_ = util.SphinxTestAppWrapperForSkipBuilding(app_) # type: ignore
app_ = util.SphinxTestAppWrapperForSkipBuilding(app_)
return app_
yield make

View File

@ -5,14 +5,12 @@
:copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import builtins
import os
import shutil
import sys
if False:
# For type annotation
import builtins # NOQA
from typing import Any, Callable, IO, List # NOQA
from typing import Any, Callable, IO, List
FILESYSTEMENCODING = sys.getfilesystemencoding() or sys.getdefaultencoding()
@ -24,61 +22,52 @@ class path(str):
"""
@property
def parent(self):
# type: () -> path
def parent(self) -> "path":
"""
The name of the directory the file or directory is in.
"""
return self.__class__(os.path.dirname(self))
def basename(self):
# type: () -> str
def basename(self) -> str:
return os.path.basename(self)
def abspath(self):
# type: () -> path
def abspath(self) -> "path":
"""
Returns the absolute path.
"""
return self.__class__(os.path.abspath(self))
def isabs(self):
# type: () -> bool
def isabs(self) -> bool:
"""
Returns ``True`` if the path is absolute.
"""
return os.path.isabs(self)
def isdir(self):
# type: () -> bool
def isdir(self) -> bool:
"""
Returns ``True`` if the path is a directory.
"""
return os.path.isdir(self)
def isfile(self):
# type: () -> bool
def isfile(self) -> bool:
"""
Returns ``True`` if the path is a file.
"""
return os.path.isfile(self)
def islink(self):
# type: () -> bool
def islink(self) -> bool:
"""
Returns ``True`` if the path is a symbolic link.
"""
return os.path.islink(self)
def ismount(self):
# type: () -> bool
def ismount(self) -> bool:
"""
Returns ``True`` if the path is a mount point.
"""
return os.path.ismount(self)
def rmtree(self, ignore_errors=False, onerror=None):
# type: (bool, Callable) -> None
def rmtree(self, ignore_errors: bool = False, onerror: Callable = None) -> None:
"""
Removes the file or directory and any files or directories it may
contain.
@ -96,8 +85,7 @@ class path(str):
"""
shutil.rmtree(self, ignore_errors=ignore_errors, onerror=onerror)
def copytree(self, destination, symlinks=False):
# type: (str, bool) -> None
def copytree(self, destination: str, symlinks: bool = False) -> None:
"""
Recursively copy a directory to the given `destination`. If the given
`destination` does not exist it will be created.
@ -109,8 +97,7 @@ class path(str):
"""
shutil.copytree(self, destination, symlinks=symlinks)
def movetree(self, destination):
# type: (str) -> None
def movetree(self, destination: str) -> None:
"""
Recursively move the file or directory to the given `destination`
similar to the Unix "mv" command.
@ -122,54 +109,46 @@ class path(str):
move = movetree
def unlink(self):
# type: () -> None
def unlink(self) -> None:
"""
Removes a file.
"""
os.unlink(self)
def stat(self):
# type: () -> Any
def stat(self) -> Any:
"""
Returns a stat of the file.
"""
return os.stat(self)
def utime(self, arg):
# type: (Any) -> None
def utime(self, arg: Any) -> None:
os.utime(self, arg)
def open(self, mode='r', **kwargs):
# type: (str, Any) -> IO
def open(self, mode: str = 'r', **kwargs) -> IO:
return open(self, mode, **kwargs)
def write_text(self, text, encoding='utf-8', **kwargs):
# type: (str, str, Any) -> None
def write_text(self, text: str, encoding: str = 'utf-8', **kwargs) -> None:
"""
Writes the given `text` to the file.
"""
with open(self, 'w', encoding=encoding, **kwargs) as f:
f.write(text)
def text(self, encoding='utf-8', **kwargs):
# type: (str, Any) -> str
def text(self, encoding: str = 'utf-8', **kwargs) -> str:
"""
Returns the text in the file.
"""
with open(self, encoding=encoding, **kwargs) as f:
return f.read()
def bytes(self):
# type: () -> builtins.bytes
def bytes(self) -> builtins.bytes:
"""
Returns the bytes in the file.
"""
with open(self, mode='rb') as f:
return f.read()
def write_bytes(self, bytes, append=False):
# type: (str, bool) -> None
def write_bytes(self, bytes: str, append: bool = False) -> None:
"""
Writes the given `bytes` to the file.
@ -183,41 +162,35 @@ class path(str):
with open(self, mode=mode) as f:
f.write(bytes)
def exists(self):
# type: () -> bool
def exists(self) -> bool:
"""
Returns ``True`` if the path exist.
"""
return os.path.exists(self)
def lexists(self):
# type: () -> bool
def lexists(self) -> bool:
"""
Returns ``True`` if the path exists unless it is a broken symbolic
link.
"""
return os.path.lexists(self)
def makedirs(self, mode=0o777, exist_ok=False):
# type: (int, bool) -> None
def makedirs(self, mode: int = 0o777, exist_ok: bool = False) -> None:
"""
Recursively create directories.
"""
os.makedirs(self, mode, exist_ok=exist_ok)
def joinpath(self, *args):
# type: (Any) -> path
def joinpath(self, *args) -> "path":
"""
Joins the path with the argument given and returns the result.
"""
return self.__class__(os.path.join(self, *map(self.__class__, args)))
def listdir(self):
# type: () -> List[str]
def listdir(self) -> List[str]:
return os.listdir(self)
__div__ = __truediv__ = joinpath
def __repr__(self):
# type: () -> str
def __repr__(self) -> str:
return '%s(%s)' % (self.__class__.__name__, super().__repr__())

View File

@ -8,21 +8,16 @@
from os import path
from docutils import nodes
from docutils.core import publish_doctree
from sphinx.application import Sphinx
from sphinx.io import SphinxStandaloneReader
from sphinx.parsers import RSTParser
from sphinx.util.docutils import sphinx_domains
if False:
# For type annotation
from docutils import nodes # NOQA
from sphinx.application import Sphinx # NOQA
def parse(app, text, docname='index'):
# type: (Sphinx, str, str) -> nodes.document
def parse(app: Sphinx, text: str, docname: str = 'index') -> nodes.document:
"""Parse a string as reStructuredText with Sphinx application."""
try:
app.env.temp_data['docname'] = docname

View File

@ -11,6 +11,7 @@ import os
import re
import sys
import warnings
from typing import Any, Dict, Generator, IO, List, Pattern
from xml.etree import ElementTree
from docutils import nodes
@ -23,10 +24,6 @@ from sphinx.pycode import ModuleAnalyzer
from sphinx.testing.path import path
from sphinx.util.osutil import relpath
if False:
# For type annotation
from typing import Any, Dict, Generator, IO, List, Pattern # NOQA
__all__ = [
'Struct',
@ -35,26 +32,22 @@ __all__ = [
]
def assert_re_search(regex, text, flags=0):
# type: (Pattern, str, int) -> None
def assert_re_search(regex: Pattern, text: str, flags: int = 0) -> None:
if not re.search(regex, text, flags):
assert False, '%r did not match %r' % (regex, text)
def assert_not_re_search(regex, text, flags=0):
# type: (Pattern, str, int) -> None
def assert_not_re_search(regex: Pattern, text: str, flags: int = 0) -> None:
if re.search(regex, text, flags):
assert False, '%r did match %r' % (regex, text)
def assert_startswith(thing, prefix):
# type: (str, str) -> None
def assert_startswith(thing: str, prefix: str) -> None:
if not thing.startswith(prefix):
assert False, '%r does not start with %r' % (thing, prefix)
def assert_node(node, cls=None, xpath="", **kwargs):
# type: (nodes.Node, Any, str, Any) -> None
def assert_node(node: nodes.Node, cls: Any = None, xpath: str = "", **kwargs) -> None:
if cls:
if isinstance(cls, list):
assert_node(node, cls[0], xpath=xpath, **kwargs)
@ -92,16 +85,14 @@ def assert_node(node, cls=None, xpath="", **kwargs):
'The node%s[%s] is not %r: %r' % (xpath, key, value, node[key])
def etree_parse(path):
# type: (str) -> Any
def etree_parse(path: str) -> Any:
with warnings.catch_warnings(record=False):
warnings.filterwarnings("ignore", category=DeprecationWarning)
return ElementTree.parse(path)
class Struct:
def __init__(self, **kwds):
# type: (Any) -> None
def __init__(self, **kwds) -> None:
self.__dict__.update(kwds)
@ -111,10 +102,9 @@ class SphinxTestApp(application.Sphinx):
better default values for the initialization parameters.
"""
def __init__(self, buildername='html', srcdir=None,
freshenv=False, confoverrides=None, status=None, warning=None,
tags=None, docutilsconf=None):
# type: (str, path, bool, Dict, IO, IO, List[str], str) -> None
def __init__(self, buildername: str = 'html', srcdir: path = None, freshenv: bool = False,
confoverrides: Dict = None, status: IO = None, warning: IO = None,
tags: List[str] = None, docutilsconf: str = None) -> None:
if docutilsconf is not None:
(srcdir / 'docutils.conf').write_text(docutilsconf)
@ -144,8 +134,7 @@ class SphinxTestApp(application.Sphinx):
self.cleanup()
raise
def cleanup(self, doctrees=False):
# type: (bool) -> None
def cleanup(self, doctrees: bool = False) -> None:
ModuleAnalyzer.cache.clear()
LaTeXBuilder.usepackages = []
locale.translators.clear()
@ -159,8 +148,7 @@ class SphinxTestApp(application.Sphinx):
delattr(nodes.GenericNodeVisitor, 'visit_' + method[6:])
delattr(nodes.GenericNodeVisitor, 'depart_' + method[6:])
def __repr__(self):
# type: () -> str
def __repr__(self) -> str:
return '<%s buildername=%r>' % (self.__class__.__name__, self.builder.name)
@ -171,16 +159,13 @@ class SphinxTestAppWrapperForSkipBuilding:
file.
"""
def __init__(self, app_):
# type: (SphinxTestApp) -> None
def __init__(self, app_: SphinxTestApp) -> None:
self.app = app_
def __getattr__(self, name):
# type: (str) -> Any
def __getattr__(self, name: str) -> Any:
return getattr(self.app, name)
def build(self, *args, **kw):
# type: (Any, Any) -> None
def build(self, *args, **kw) -> None:
if not self.app.outdir.listdir(): # type: ignore
# if listdir is empty, do build.
self.app.build(*args, **kw)
@ -190,15 +175,13 @@ class SphinxTestAppWrapperForSkipBuilding:
_unicode_literals_re = re.compile(r'u(".*?")|u(\'.*?\')')
def remove_unicode_literals(s):
# type: (str) -> str
def remove_unicode_literals(s: str) -> str:
warnings.warn('remove_unicode_literals() is deprecated.',
RemovedInSphinx40Warning, stacklevel=2)
return _unicode_literals_re.sub(lambda x: x.group(1) or x.group(2), s)
def find_files(root, suffix=None):
# type: (str, bool) -> Generator
def find_files(root: str, suffix: bool = None) -> Generator[str, None, None]:
for dirpath, dirs, files in os.walk(root, followlinks=True):
dirpath = path(dirpath)
for f in [f for f in files if not suffix or f.endswith(suffix)]: # type: ignore
@ -206,6 +189,5 @@ def find_files(root, suffix=None):
yield relpath(fpath, root)
def strip_escseq(text):
# type: (str) -> str
def strip_escseq(text: str) -> str:
return re.sub('\x1b.*?m', '', text)

View File

@ -413,6 +413,18 @@
\newcommand\sphinxsetup[1]{\setkeys{sphinx}{#1}}
%% ALPHANUMERIC LIST ITEMS
\newcommand\sphinxsetlistlabels[5]
{% #1 = style, #2 = enum, #3 = enumnext, #4 = prefix, #5 = suffix
% #2 and #3 are counters used by enumerate environement e.g. enumi, enumii.
% #1 is a macro such as \arabic or \alph
% prefix and suffix are strings (by default empty and a dot).
\@namedef{the#2}{#1{#2}}%
\@namedef{label#2}{#4\@nameuse{the#2}#5}%
\@namedef{p@#3}{\@nameuse{p@#2}#4\@nameuse{the#2}#5}%
}%
%% MAXLISTDEPTH
%
% remove LaTeX's cap on nesting depth if 'maxlistdepth' key used.

View File

@ -33,7 +33,7 @@
containing fewer words won't appear in the result list.{% endtrans %}
</p>
<form action="" method="get">
<input type="text" name="q" value="" />
<input type="text" name="q" aria-labelledby="search-documentation" value="" />
<input type="submit" value="{{ _('search') }}" />
<span id="search-progress" style="padding-left: 10px"></span>
</form>

View File

@ -169,7 +169,7 @@ class DownloadFiles(dict):
Hence don't hack this directly.
"""
def add_file(self, docname: str, filename: str) -> None:
def add_file(self, docname: str, filename: str) -> str:
if filename not in self:
digest = md5(filename.encode()).hexdigest()
dest = '%s/%s' % (digest, os.path.basename(filename))

View File

@ -12,7 +12,7 @@ import os
import re
import warnings
from collections import namedtuple
from datetime import datetime
from datetime import datetime, timezone
from os import path
from typing import Callable, Generator, List, Set, Tuple
@ -274,7 +274,7 @@ def format_date(format: str, date: datetime = None, language: str = None) -> str
if source_date_epoch is not None:
date = datetime.utcfromtimestamp(float(source_date_epoch))
else:
date = datetime.now()
date = datetime.now(timezone.utc).astimezone()
result = []
tokens = date_format_re.split(format)

View File

@ -30,7 +30,7 @@ class PyStemmer(BaseStemmer):
return self.stemmer.stemWord(word)
class StandardStemmer(PorterStemmer, BaseStemmer): # type: ignore
class StandardStemmer(PorterStemmer, BaseStemmer):
"""All those porter stemmer implementations look hideous;
make at least the stem method nicer.
"""

View File

@ -9,7 +9,7 @@
"""
import os
from typing import Dict
from typing import Dict, List, Union
from jinja2.loaders import BaseLoader
from jinja2.sandbox import SandboxedEnvironment
@ -34,7 +34,13 @@ class BaseRenderer:
class FileRenderer(BaseRenderer):
def __init__(self, search_path: str) -> None:
def __init__(self, search_path: Union[str, List[str]]) -> None:
if isinstance(search_path, str):
search_path = [search_path]
else:
# filter "None" paths
search_path = list(filter(None, search_path))
loader = SphinxFileSystemLoader(search_path)
super().__init__(loader)
@ -46,7 +52,7 @@ class FileRenderer(BaseRenderer):
class SphinxRenderer(FileRenderer):
def __init__(self, template_path: str = None) -> None:
def __init__(self, template_path: Union[str, List[str]] = None) -> None:
if template_path is None:
template_path = os.path.join(package_dir, 'templates')
super().__init__(template_path)
@ -76,7 +82,7 @@ class LaTeXRenderer(SphinxRenderer):
class ReSTRenderer(SphinxRenderer):
def __init__(self, template_path: str = None, language: str = None) -> None:
def __init__(self, template_path: Union[str, List[str]] = None, language: str = None) -> None: # NOQA
super().__init__(template_path)
# add language to environment

View File

@ -1373,11 +1373,8 @@ class LaTeXTranslator(SphinxTranslator):
suffix = node.get('suffix', '.')
self.body.append('\\begin{enumerate}\n')
self.body.append('\\def\\the%s{%s{%s}}\n' % (enum, style, enum))
self.body.append('\\def\\label%s{%s\\the%s %s}\n' %
(enum, prefix, enum, suffix))
self.body.append('\\makeatletter\\def\\p@%s{\\p@%s %s\\the%s %s}\\makeatother\n' %
(enumnext, enum, prefix, enum, suffix))
self.body.append('\\sphinxsetlistlabels{%s}{%s}{%s}{%s}{%s}%%\n' %
(style, enum, enumnext, prefix, suffix))
if 'start' in node:
self.body.append('\\setcounter{%s}{%d}\n' % (enum, node['start'] - 1))
if self.table:
@ -2598,7 +2595,7 @@ class LaTeXTranslator(SphinxTranslator):
RemovedInSphinx30Warning)
def visit_admonition(self, node):
# type: (nodes.Element) -> None
# type: (LaTeXTranslator, nodes.Element) -> None
self.body.append('\n\\begin{sphinxadmonition}{%s}{%s:}' %
(name, admonitionlabels[name]))
return visit_admonition

View File

@ -1752,6 +1752,6 @@ class TexinfoTranslator(SphinxTranslator):
RemovedInSphinx30Warning)
def visit(self, node):
# type: (nodes.Element) -> None
# type: (TexinfoTranslator, nodes.Element) -> None
self.visit_admonition(node, admonitionlabels[name])
return visit

View File

@ -1375,6 +1375,6 @@ class TextTranslator(SphinxTranslator):
RemovedInSphinx30Warning)
def depart_admonition(self, node):
# type: (nodes.Element) -> None
# type: (TextTranslator, nodes.Element) -> None
self.end_state(first=admonitionlabels[name] + ': ')
return depart_admonition

View File

@ -15,5 +15,3 @@ class Derived(Base):
def inheritedmeth(self):
# no docstring here
pass

View File

@ -26,4 +26,4 @@ test-image
.. image:: https://www.python.org/static/img/python-logo.png
.. non-exist remote image
.. image:: http://example.com/NOT_EXIST.PNG
.. image:: https://www.google.com/NOT_EXIST.PNG

View File

@ -23,8 +23,8 @@ link to external1_ and external2_.
link to `Sphinx Site <http://sphinx-doc.org>`_ and `Python Site <http://python.org>`_.
.. _external1: http://example.com/external1
.. _external2: http://example.com/external2
.. _external1: https://www.google.com/external1
.. _external2: https://www.google.com/external2
Multiple references in the same line

View File

@ -6,11 +6,11 @@ This is from CPython documentation.
Some additional anchors to exercise ignore code
* `Example Bar invalid <http://example.com/#!bar>`_
* `Example Bar invalid <http://example.com#!bar>`_ tests that default ignore anchor of #! does not need to be prefixed with /
* `Example Bar invalid <http://example.com/#top>`_
* `Example Bar invalid <https://www.google.com/#!bar>`_
* `Example Bar invalid <https://www.google.com#!bar>`_ tests that default ignore anchor of #! does not need to be prefixed with /
* `Example Bar invalid <https://www.google.com/#top>`_
* `Example anchor invalid <http://www.sphinx-doc.org/en/1.7/intro.html#does-not-exist>`_
* `Complete nonsense <https://localhost:7777/doesnotexist>`_
.. image:: http://example.com/image.png
.. figure:: http://example.com/image2.png
.. image:: https://www.google.com/image.png
.. figure:: https://www.google.com/image2.png

View File

@ -52,7 +52,8 @@ def test_msgfmt(app):
assert (app.outdir / 'en_US.po').isfile(), 'msginit failed'
try:
args = ['msgfmt', 'en_US.po', '-o', os.path.join('en', 'LC_MESSAGES', 'test_root.mo')]
args = ['msgfmt', 'en_US.po',
'-o', os.path.join('en', 'LC_MESSAGES', 'test_root.mo')]
subprocess.run(args, stdout=PIPE, stderr=PIPE, check=True)
except OSError:
pytest.skip() # most likely msgfmt was not found

View File

@ -16,6 +16,7 @@ from itertools import cycle, chain
import pytest
from html5lib import HTMLParser
from sphinx.builders.html import validate_html_extra_path, validate_html_static_path
from sphinx.errors import ConfigError
from sphinx.testing.util import strip_escseq
from sphinx.util import docutils
@ -1239,25 +1240,25 @@ def test_html_entity(app):
def test_html_inventory(app):
app.builder.build_all()
with open(app.outdir / 'objects.inv', 'rb') as f:
invdata = InventoryFile.load(f, 'http://example.com', os.path.join)
invdata = InventoryFile.load(f, 'https://www.google.com', os.path.join)
assert set(invdata.keys()) == {'std:label', 'std:doc'}
assert set(invdata['std:label'].keys()) == {'modindex', 'genindex', 'search'}
assert invdata['std:label']['modindex'] == ('Python',
'',
'http://example.com/py-modindex.html',
'https://www.google.com/py-modindex.html',
'Module Index')
assert invdata['std:label']['genindex'] == ('Python',
'',
'http://example.com/genindex.html',
'https://www.google.com/genindex.html',
'Index')
assert invdata['std:label']['search'] == ('Python',
'',
'http://example.com/search.html',
'https://www.google.com/search.html',
'Search Page')
assert set(invdata['std:doc'].keys()) == {'index'}
assert invdata['std:doc']['index'] == ('Python',
'',
'http://example.com/index.html',
'https://www.google.com/index.html',
'The basic Sphinx documentation for testing')
@ -1496,3 +1497,29 @@ def test_html_pygments_style_manually(app):
def test_html_pygments_for_classic_theme(app):
style = app.builder.highlighter.formatter_args.get('style')
assert style.__name__ == 'SphinxStyle'
@pytest.mark.sphinx(testroot='basic', srcdir='validate_html_extra_path')
def test_validate_html_extra_path(app):
(app.confdir / '_static').makedirs()
app.config.html_extra_path = [
'/path/to/not_found', # not found
'_static',
app.outdir, # outdir
app.outdir / '_static', # inside outdir
]
validate_html_extra_path(app, app.config)
assert app.config.html_extra_path == ['_static']
@pytest.mark.sphinx(testroot='basic', srcdir='validate_html_static_path')
def test_validate_html_static_path(app):
(app.confdir / '_static').makedirs()
app.config.html_static_path = [
'/path/to/not_found', # not found
'_static',
app.outdir, # outdir
app.outdir / '_static', # inside outdir
]
validate_html_static_path(app, app.config)
assert app.config.html_static_path == ['_static']

View File

@ -1242,7 +1242,7 @@ def test_latex_images(app, status, warning):
# not found images
assert '\\sphinxincludegraphics{{NOT_EXIST}.PNG}' not in result
assert ('WARNING: Could not fetch remote image: '
'http://example.com/NOT_EXIST.PNG [404]' in warning.getvalue())
'https://www.google.com/NOT_EXIST.PNG [404]' in warning.getvalue())
# an image having target
assert ('\\sphinxhref{https://www.sphinx-doc.org/}'
@ -1292,25 +1292,15 @@ def test_latex_nested_enumerated_list(app, status, warning):
app.builder.build_all()
result = (app.outdir / 'python.tex').text(encoding='utf8')
assert ('\\def\\theenumi{\\arabic{enumi}}\n'
'\\def\\labelenumi{\\theenumi .}\n'
'\\makeatletter\\def\\p@enumii{\\p@enumi \\theenumi .}\\makeatother\n'
assert ('\\sphinxsetlistlabels{\\arabic}{enumi}{enumii}{}{.}%\n'
'\\setcounter{enumi}{4}\n' in result)
assert ('\\def\\theenumii{\\alph{enumii}}\n'
'\\def\\labelenumii{\\theenumii .}\n'
'\\makeatletter\\def\\p@enumiii{\\p@enumii \\theenumii .}\\makeatother\n'
assert ('\\sphinxsetlistlabels{\\alph}{enumii}{enumiii}{}{.}%\n'
'\\setcounter{enumii}{3}\n' in result)
assert ('\\def\\theenumiii{\\arabic{enumiii}}\n'
'\\def\\labelenumiii{\\theenumiii )}\n'
'\\makeatletter\\def\\p@enumiv{\\p@enumiii \\theenumiii )}\\makeatother\n'
assert ('\\sphinxsetlistlabels{\\arabic}{enumiii}{enumiv}{}{)}%\n'
'\\setcounter{enumiii}{9}\n' in result)
assert ('\\def\\theenumiv{\\arabic{enumiv}}\n'
'\\def\\labelenumiv{(\\theenumiv )}\n'
'\\makeatletter\\def\\p@enumv{\\p@enumiv (\\theenumiv )}\\makeatother\n'
assert ('\\sphinxsetlistlabels{\\arabic}{enumiv}{enumv}{(}{)}%\n'
'\\setcounter{enumiv}{23}\n' in result)
assert ('\\def\\theenumii{\\roman{enumii}}\n'
'\\def\\labelenumii{\\theenumii .}\n'
'\\makeatletter\\def\\p@enumiii{\\p@enumii \\theenumii .}\\makeatother\n'
assert ('\\sphinxsetlistlabels{\\roman}{enumii}{enumiii}{}{.}%\n'
'\\setcounter{enumii}{2}\n' in result)
@ -1405,6 +1395,7 @@ def test_latex_figure_in_admonition(app, status, warning):
result = (app.outdir / 'python.tex').text(encoding='utf8')
assert(r'\begin{figure}[H]' in result)
def test_default_latex_documents():
from sphinx.util import texescape
texescape.init()

View File

@ -25,8 +25,8 @@ def test_defaults(app, status, warning):
# looking for non-existent URL should fail
assert " Max retries exceeded with url: /doesnotexist" in content
# images should fail
assert "Not Found for url: http://example.com/image.png" in content
assert "Not Found for url: http://example.com/image2.png" in content
assert "Not Found for url: https://www.google.com/image.png" in content
assert "Not Found for url: https://www.google.com/image2.png" in content
assert len(content.splitlines()) == 5
@ -36,8 +36,8 @@ def test_defaults(app, status, warning):
'linkcheck_ignore': [
'https://localhost:7777/doesnotexist',
'http://www.sphinx-doc.org/en/1.7/intro.html#',
'http://example.com/image.png',
'http://example.com/image2.png']
'https://www.google.com/image.png',
'https://www.google.com/image2.png']
})
def test_anchors_ignored(app, status, warning):
app.builder.build_all()

View File

@ -129,14 +129,14 @@ def test_expressions():
'5e42', '5e+42', '5e-42',
'5.', '5.e42', '5.e+42', '5.e-42',
'.5', '.5e42', '.5e+42', '.5e-42',
'5.0', '5.0e42','5.0e+42', '5.0e-42']:
'5.0', '5.0e42', '5.0e+42', '5.0e-42']:
expr = e + suffix
exprCheck(expr, 'L' + expr + 'E')
for e in [
'ApF', 'Ap+F', 'Ap-F',
'A.', 'A.pF', 'A.p+F', 'A.p-F',
'.A', '.ApF', '.Ap+F', '.Ap-F',
'A.B', 'A.BpF','A.Bp+F', 'A.Bp-F']:
'A.B', 'A.BpF', 'A.Bp+F', 'A.Bp-F']:
expr = "0x" + e + suffix
exprCheck(expr, 'L' + expr + 'E')
exprCheck('"abc\\"cba"', 'LA8_KcE') # string
@ -676,7 +676,7 @@ def test_template_args():
def test_initializers():
idsMember = {1: 'v__T', 2:'1v'}
idsMember = {1: 'v__T', 2: '1v'}
idsFunction = {1: 'f__T', 2: '1f1T'}
idsTemplate = {2: 'I_1TE1fv', 4: 'I_1TE1fvv'}
# no init
@ -748,7 +748,7 @@ def test_attributes():
{1: 'f', 2: '1fv'},
output='[[attr1]] [[attr2]] void f()')
# position: declarator
check('member', 'int *[[attr]] i', {1: 'i__iP', 2:'1i'})
check('member', 'int *[[attr]] i', {1: 'i__iP', 2: '1i'})
check('member', 'int *const [[attr]] volatile i', {1: 'i__iPVC', 2: '1i'},
output='int *[[attr]] volatile const i')
check('member', 'int &[[attr]] i', {1: 'i__iR', 2: '1i'})

View File

@ -11,9 +11,32 @@ import pytest
from sphinx.builders.html import StandaloneHTMLBuilder
from sphinx.builders.latex import LaTeXBuilder
from sphinx.environment import CONFIG_OK, CONFIG_CHANGED, CONFIG_EXTENSIONS_CHANGED, CONFIG_NEW
from sphinx.testing.comparer import PathComparer
@pytest.mark.sphinx('dummy', testroot='basic')
def test_config_status(make_app, app_params):
args, kwargs = app_params
# clean build
app1 = make_app(*args, freshenv=True, **kwargs)
assert app1.env.config_status == CONFIG_NEW
app1.build()
# incremental build (no config changed)
app2 = make_app(*args, **kwargs)
assert app2.env.config_status == CONFIG_OK
# incremental build (config entry changed)
app3 = make_app(*args, confoverrides={'master_doc': 'content'}, **kwargs)
assert app3.env.config_status == CONFIG_CHANGED
# incremental build (extension changed)
app4 = make_app(*args, confoverrides={'extensions': ['sphinx.ext.autodoc']}, **kwargs)
assert app4.env.config_status == CONFIG_EXTENSIONS_CHANGED
@pytest.mark.sphinx('dummy')
def test_images(app):
app.build()

View File

@ -47,19 +47,30 @@ def test_create_pair_index(app):
app.env.indexentries.clear()
text = (".. index:: pair: docutils; reStructuredText\n"
".. index:: pair: Python; interpreter\n"
".. index:: pair: Sphinx; documentation tool\n")
".. index:: pair: Sphinx; documentation tool\n"
".. index:: pair: Sphinx; :+1:\n"
".. index:: pair: Sphinx; Ель\n"
".. index:: pair: Sphinx; ёлка\n")
restructuredtext.parse(app, text)
index = IndexEntries(app.env).create_index(app.builder)
assert len(index) == 5
assert index[0] == ('D',
assert len(index) == 7
assert index[0] == ('Symbols', [(':+1:', [[], [('Sphinx', [('', '#index-3')])], None])])
assert index[1] == ('D',
[('documentation tool', [[], [('Sphinx', [('', '#index-2')])], None]),
('docutils', [[], [('reStructuredText', [('', '#index-0')])], None])])
assert index[1] == ('I', [('interpreter', [[], [('Python', [('', '#index-1')])], None])])
assert index[2] == ('P', [('Python', [[], [('interpreter', [('', '#index-1')])], None])])
assert index[3] == ('R',
assert index[2] == ('I', [('interpreter', [[], [('Python', [('', '#index-1')])], None])])
assert index[3] == ('P', [('Python', [[], [('interpreter', [('', '#index-1')])], None])])
assert index[4] == ('R',
[('reStructuredText', [[], [('docutils', [('', '#index-0')])], None])])
assert index[4] == ('S',
[('Sphinx', [[], [('documentation tool', [('', '#index-2')])], None])])
assert index[5] == ('S',
[('Sphinx', [[],
[(':+1:', [('', '#index-3')]),
('documentation tool', [('', '#index-2')]),
('ёлка', [('', '#index-5')]),
('Ель', [('', '#index-4')])],
None])])
assert index[6] == ('Е', [('ёлка', [[], [('Sphinx', [('', '#index-5')])], None]),
('Ель', [[], [('Sphinx', [('', '#index-4')])], None])])
@pytest.mark.sphinx('dummy')

View File

@ -302,3 +302,17 @@ def test_generate_autosummary_docs_property(app):
".. currentmodule:: target.methods\n"
"\n"
".. autoproperty:: Base.prop")
@pytest.mark.sphinx('dummy', testroot='ext-autosummary',
confoverrides={'autosummary_generate': []})
def test_empty_autosummary_generate(app, status, warning):
app.build()
assert ("WARNING: autosummary: stub file not found 'autosummary_importfail'"
in warning.getvalue())
@pytest.mark.sphinx('dummy', testroot='ext-autosummary',
confoverrides={'autosummary_generate': ['unknown']})
def test_invalid_autosummary_generate(app, status, warning):
assert 'WARNING: autosummary_generate: file not found: unknown.rst' in warning.getvalue()

View File

@ -18,14 +18,16 @@ def test_githubpages(app, status, warning):
assert not (app.outdir / 'CNAME').exists()
@pytest.mark.sphinx('html', testroot='ext-githubpages', confoverrides={'html_baseurl': 'https://sphinx-doc.github.io'})
@pytest.mark.sphinx('html', testroot='ext-githubpages',
confoverrides={'html_baseurl': 'https://sphinx-doc.github.io'})
def test_no_cname_for_github_io_domain(app, status, warning):
app.builder.build_all()
assert (app.outdir / '.nojekyll').exists()
assert not (app.outdir / 'CNAME').exists()
@pytest.mark.sphinx('html', testroot='ext-githubpages', confoverrides={'html_baseurl': 'https://sphinx-doc.org'})
@pytest.mark.sphinx('html', testroot='ext-githubpages',
confoverrides={'html_baseurl': 'https://sphinx-doc.org'})
def test_cname_for_custom_domain(app, status, warning):
app.builder.build_all()
assert (app.outdir / '.nojekyll').exists()

View File

@ -107,7 +107,7 @@ def test_text_emit_warnings(app, warning):
# test warnings in translation
warnings = getwarning(warning)
warning_expr = ('.*/warnings.txt:4:<translated>:1: '
'WARNING: Inline literal start-string without end-string.\n')
'WARNING: Inline literal start-string without end-string.\n')
assert_re_search(warning_expr, warnings)
@ -885,8 +885,8 @@ def test_xml_keep_external_links(app):
assert_elem(
para1[0],
['LINK TO', 'external2', 'AND', 'external1', '.'],
['http://example.com/external2',
'http://example.com/external1'])
['https://www.google.com/external2',
'https://www.google.com/external1'])
assert_elem(
para1[1],
['LINK TO', 'THE PYTHON SITE', 'AND', 'THE SPHINX SITE', '.'],

View File

@ -279,9 +279,9 @@ def get_verifier(verify, verify_re):
(
# in URIs
'verify_re',
'`test <http://example.com/~me/>`_',
'`test <https://www.google.com/~me/>`_',
None,
r'\\sphinxhref{http://example.com/~me/}{test}.*',
r'\\sphinxhref{https://www.google.com/~me/}{test}.*',
),
(
# description list: simple

View File

@ -13,7 +13,6 @@ import datetime
import functools
import sys
import types
from textwrap import dedent
import pytest

View File

@ -8,8 +8,6 @@
:license: BSD, see LICENSE for details.
"""
import tempfile
from sphinx.testing.util import strip_escseq
from sphinx.util import logging
from sphinx.util.pycompat import execfile_